<commit_before>const joi = require('joi');
const colorField = joi.string().regex(/^#|(#)\d{6}$/).
  meta({regexHuman: "6 character long hex color string, eg: ``'#000000'``"});

const reverseDnsField = joi.string().regex(/^[a-zA-Z][a-zA-Z0-9\_\.]+$/).
  meta({regexHuman: "Reverse DNS notation unique name for your app. For example, host.exp.exponent, where exp.host is our domain and Exponent is our app."});

// Update the PNG_FIELDS exported value when you use this
const pngField = joi.string().uri();

module.exports = joi.object().keys({
  name: joi.string().required().description(`
    The name of your app as it appears both within Exponent and on your home screen as a standalone app.
  `),
  description: joi.string().description(`
    A short description of what your app is and why it is great.
  `),
  slug: joi.string().regex(/^[a-zA-Z0-9\-]+$/).required().description(`
    The friendly url name for publishing. eg: \`\`exp.host/@your-username/slug\`\`.
  `),
  sdkVersion: joi.string().regex(/^(\d+\.\d+\.\d+)|(UNVERSIONED)$/).required().description(`
    The Exponent sdkVersion to run the project on. This should line up with the version specified in your package.json.
  `),
  version: joi.string().description(`
    Your app version, use whatever versioning scheme that you like.
  `),
  orientation: joi.any().valid('default', 'portrait', 'landscape').description(`
    Lock your app to a specific orientation with \`\`portrait\`\` or \`\`landscape\`\`. Defaults to no lock.
  `),
  primaryColor: colorField.description(`
    On Android, this will determine the color of your app in the multitasker. Currently this is not used on iOS, but it may be used for other purposes in the future.
  `),
  iconUrl: pngField.description(`
    A url that points to your app's icon image. We recommend that you use a 512x512 png file with transparency. This icon will appear on the home screen and within the Exponent app.
  `),
  notification: joi.object().keys({
    iconUrl: pngField.description(
      `Url that points to the icon to use for push notifications. 48x48 png grayscale with transparency.`
    ),
    color: colorField.description(
      `Tint color for the push notification image when it appears in the notification tray.`
    ),
    androidMode: joi.any().valid('default', 'collapse').description(
      "Show each push notification individually (``default``) or collapse into one (``collapse``)."
    ),
    androidCollapsedTitle: joi.string().description(
      "If ``androidMode`` is set to ``collapse``, this title is used for the collapsed notification message. eg: ``'#{unread_notifications} new interactions'``."
    ),
  }).description(`
    Configuration for remote (push) notifications.
  `),
  loading: joi.object().keys({
    iconUrl: pngField.uri().description(
      `Url that points to the icon to display while starting up the app. Image size and aspect ratio are up to you. Must be a .png.`
    ),
    exponentIconColor: joi.any().valid('white', 'blue').description(
      "If no icon is provided, we will show the Exponent logo. You can choose between ``white`` and ``blue``."
    ),
    exponentIconGrayscale: joi.number().min(0).max(1).description(
      "Similar to ``exponentIconColor`` but instead indicate if it should be grayscale (``1``) or not (``0``)."
    ),
    backgroundImageUrl: pngField.uri().description(
      "Url that points to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png."
    ),
    backgroundColor: colorField.description(
      "Color to fill the loading screen background"
    ),
    hideExponentText: joi.boolean().description(
      "By default, Exponent shows some text at the bottom of the loading screen. Set this to ``true`` to disable."
    ),
  }).description(`
    Configuration for the loading screen that users see when opening your app, while fetching & caching bundle and assets.
  `),
  appKey: joi.string().description(
    "By default, Exponent looks for the application registered with the AppRegistry as ``main``. If you would like to change this, you can specify the name in this property."
  ),
  androidStatusBarColor: colorField,
  androidHideExponentNotificationInShellApp: joi.boolean().description(
    "By default, Exponent adds a notification to your app with refresh button and debug info. Set this to ``true`` to disable."
  ),
  scheme: joi.string().alphanum().description(
    "Url scheme to link into your app. For example, if we set this to ``'rnplay'``, then rnplay:// urls would open your app when tapped."
  ).meta({standaloneOnly: true}),

  entryPoint: joi.string().description(`
    The relative path to your main JavaScript file.
  `),

  extra: joi.object().description(`
    Any extra fields you want to pass to your experience.
  `),
  rnCliPath: joi.string(),
  packagerOpts: joi.object(),
  ignoreNodeModulesValidation: joi.boolean(),
  nodeModulesPath: joi.string(),

  ios: joi.object().keys({
    bundleIdentifier: reverseDnsField.description(
      "The bundle identifier for your iOS standalone app. You make it up, but it needs to be unique on the App Store. See `this StackOverflow question <http://stackoverflow.com/questions/11347470/what-does-bundle-identifier-mean-in-the-ios-project>`_."
    ),
  }).description('iOS standalone app specific configuration').meta({standaloneOnly: true}),
  android: joi.object().keys({
    package: reverseDnsField.description(
      "The package name for your Android standalone app. You make it up, but it needs to be unique on the Play Store. See `this StackOverflow question <http://stackoverflow.com/questions/6273892/android-package-name-convention>`_."
    ),
    versionCode: joi.number().description(
      "Version number required by Google Play. Increment by one for each release. https://developer.android.com/studio/publish/versioning.html."
    ),
    config: joi.object().keys({
      fabric: joi.object().keys({
        apiKey: joi.string().alphanum().description('Your Fabric API key'),
        buildSecret: joi.string().alphanum().description('Your Fabric build secret'),
      }).description(
        "`Twitter Fabric <https://get.fabric.io/>`_ keys to hook up Crashlytics and other services."
      ),
      googleMaps: joi.object().keys({
        apiKey: joi.string().alphanum().description('Your Google Maps Android SDK API key'),
      }).description(
        "`Google Maps Android SDK <https://developers.google.com/maps/documentation/android-api/signup>`_ key for your standalone app."
      ),
    }),
  }).description('Android standalone app specific configuration').meta({standaloneOnly: true}),
});

module.exports.PNG_FIELDS = ['iconUrl', 'notification.iconUrl', 'loading.iconUrl', 'loading.backgroundImageUrl'];
<commit_msg>Add ios.config.fabric to ExpSchema<commit_after>const joi = require('joi');
const colorField = joi.string().regex(/^#|(#)\d{6}$/).
  meta({regexHuman: "6 character long hex color string, eg: ``'#000000'``"});

const reverseDnsField = joi.string().regex(/^[a-zA-Z][a-zA-Z0-9\_\.]+$/).
  meta({regexHuman: "Reverse DNS notation unique name for your app. For example, host.exp.exponent, where exp.host is our domain and Exponent is our app."});

// Update the PNG_FIELDS exported value when you use this
const pngField = joi.string().uri();

module.exports = joi.object().keys({
  name: joi.string().required().description(`
    The name of your app as it appears both within Exponent and on your home screen as a standalone app.
  `),
  description: joi.string().description(`
    A short description of what your app is and why it is great.
  `),
  slug: joi.string().regex(/^[a-zA-Z0-9\-]+$/).required().description(`
    The friendly url name for publishing. eg: \`\`exp.host/@your-username/slug\`\`.
  `),
  sdkVersion: joi.string().regex(/^(\d+\.\d+\.\d+)|(UNVERSIONED)$/).required().description(`
    The Exponent sdkVersion to run the project on. This should line up with the version specified in your package.json.
  `),
  version: joi.string().description(`
    Your app version, use whatever versioning scheme that you like.
  `),
  orientation: joi.any().valid('default', 'portrait', 'landscape').description(`
    Lock your app to a specific orientation with \`\`portrait\`\` or \`\`landscape\`\`. Defaults to no lock.
  `),
  primaryColor: colorField.description(`
    On Android, this will determine the color of your app in the multitasker. Currently this is not used on iOS, but it may be used for other purposes in the future.
  `),
  iconUrl: pngField.description(`
    A url that points to your app's icon image. We recommend that you use a 512x512 png file with transparency. This icon will appear on the home screen and within the Exponent app.
  `),
  notification: joi.object().keys({
    iconUrl: pngField.description(
      `Url that points to the icon to use for push notifications. 48x48 png grayscale with transparency.`
    ),
    color: colorField.description(
      `Tint color for the push notification image when it appears in the notification tray.`
    ),
    androidMode: joi.any().valid('default', 'collapse').description(
      "Show each push notification individually (``default``) or collapse into one (``collapse``)."
    ),
    androidCollapsedTitle: joi.string().description(
      "If ``androidMode`` is set to ``collapse``, this title is used for the collapsed notification message. eg: ``'#{unread_notifications} new interactions'``."
    ),
  }).description(`
    Configuration for remote (push) notifications.
  `),
  loading: joi.object().keys({
    iconUrl: pngField.uri().description(
      `Url that points to the icon to display while starting up the app. Image size and aspect ratio are up to you. Must be a .png.`
    ),
    exponentIconColor: joi.any().valid('white', 'blue').description(
      "If no icon is provided, we will show the Exponent logo. You can choose between ``white`` and ``blue``."
    ),
    exponentIconGrayscale: joi.number().min(0).max(1).description(
      "Similar to ``exponentIconColor`` but instead indicate if it should be grayscale (``1``) or not (``0``)."
    ),
    backgroundImageUrl: pngField.uri().description(
      "Url that points to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png."
    ),
    backgroundColor: colorField.description(
      "Color to fill the loading screen background"
    ),
    hideExponentText: joi.boolean().description(
      "By default, Exponent shows some text at the bottom of the loading screen. Set this to ``true`` to disable."
    ),
  }).description(`
    Configuration for the loading screen that users see when opening your app, while fetching & caching bundle and assets.
  `),
  appKey: joi.string().description(
    "By default, Exponent looks for the application registered with the AppRegistry as ``main``. If you would like to change this, you can specify the name in this property."
  ),
  androidStatusBarColor: colorField,
  androidHideExponentNotificationInShellApp: joi.boolean().description(
    "By default, Exponent adds a notification to your app with refresh button and debug info. Set this to ``true`` to disable."
  ),
  scheme: joi.string().alphanum().description(
    "Url scheme to link into your app. For example, if we set this to ``'rnplay'``, then rnplay:// urls would open your app when tapped."
  ).meta({standaloneOnly: true}),

  entryPoint: joi.string().description(`
    The relative path to your main JavaScript file.
  `),

  extra: joi.object().description(`
    Any extra fields you want to pass to your experience.
  `),
  rnCliPath: joi.string(),
  packagerOpts: joi.object(),
  ignoreNodeModulesValidation: joi.boolean(),
  nodeModulesPath: joi.string(),

  ios: joi.object().keys({
    bundleIdentifier: reverseDnsField.description(
      "The bundle identifier for your iOS standalone app. You make it up, but it needs to be unique on the App Store. See `this StackOverflow question <http://stackoverflow.com/questions/11347470/what-does-bundle-identifier-mean-in-the-ios-project>`_."
    ),
    config: joi.object().keys({
      fabric: joi.object().keys({
        apiKey: joi.string().alphanum().description('Your Fabric API key'),
        buildSecret: joi.string().alphanum().description('Your Fabric build secret'),
      }).description(
        "`Twitter Fabric <https://get.fabric.io/>`_ keys to hook up Crashlytics and other services."
      ),
    }),
  }).description('iOS standalone app specific configuration').meta({standaloneOnly: true}),
  android: joi.object().keys({
    package: reverseDnsField.description(
      "The package name for your Android standalone app. You make it up, but it needs to be unique on the Play Store. See `this StackOverflow question <http://stackoverflow.com/questions/6273892/android-package-name-convention>`_."
    ),
    versionCode: joi.number().description(
      "Version number required by Google Play. Increment by one for each release. https://developer.android.com/studio/publish/versioning.html."
    ),
    config: joi.object().keys({
      fabric: joi.object().keys({
        apiKey: joi.string().alphanum().description('Your Fabric API key'),
        buildSecret: joi.string().alphanum().description('Your Fabric build secret'),
      }).description(
        "`Twitter Fabric <https://get.fabric.io/>`_ keys to hook up Crashlytics and other services."
      ),
      googleMaps: joi.object().keys({
        apiKey: joi.string().alphanum().description('Your Google Maps Android SDK API key'),
      }).description(
        "`Google Maps Android SDK <https://developers.google.com/maps/documentation/android-api/signup>`_ key for your standalone app."
      ),
    }),
  }).description('Android standalone app specific configuration').meta({standaloneOnly: true}),
});

module.exports.PNG_FIELDS = ['iconUrl', 'notification.iconUrl', 'loading.iconUrl', 'loading.backgroundImageUrl'];
<|endoftext|>


<commit_before>var EngineTools = require("../lib");

EngineTools.availableModules("service", function (err, mods) {
    console.log(mods);
});
<commit_msg>Removed the test directory<commit_after><|endoftext|>


<commit_before>// @flow
import {
  ACTIONS,
  Lbry,
  selectMyClaimsWithoutChannels,
  doNotify,
  MODALS,
  selectMyChannelClaims,
  STATUSES
} from 'lbry-redux';
import { selectPendingPublishes } from 'redux/selectors/publish';
import type {
  UpdatePublishFormData,
  UpdatePublishFormAction,
  PublishParams,
} from 'redux/reducers/publish';
import fs from 'fs';
import path from 'path';

type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
type PromiseAction = Promise<Action>;
type Dispatch = (action: Action | PromiseAction | Array<Action>) => any;
type GetState = () => {};

export const doClearPublish = () => (dispatch: Dispatch): Action =>
  dispatch({ type: ACTIONS.CLEAR_PUBLISH });

export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => (
  dispatch: Dispatch
): UpdatePublishFormAction =>
  dispatch({
    type: ACTIONS.UPDATE_PUBLISH_FORM,
    data: { ...publishFormValue },
  });

export const doResetThumbnailStatus = () => (dispatch: Dispatch): Action =>
  fetch('https://spee.ch/api/channel/availability/@testing')
    .then(() =>
      dispatch({
        type: ACTIONS.UPDATE_PUBLISH_FORM,
        data: {
          uploadThumbnailStatus: STATUSES.READY,
          thumbnail: '',
          nsfw: false,
        },
      })
    )
    .catch(() =>
      dispatch({
        type: ACTIONS.UPDATE_PUBLISH_FORM,
        data: {
          uploadThumbnailStatus: STATUSES.API_DOWN,
          thumbnail: '',
          nsfw: false,
        },
      })
    );

export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => {
  const thumbnail = fs.readFileSync(filePath);
  const fileExt = path.extname(filePath);

  const makeid = () => {
    let text = '';
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < 24; i += 1) text += possible.charAt(Math.floor(Math.random() * 62));
    return text;
  };

  const uploadError = (error: string = '') =>
    dispatch(
      batchActions(
        {
          type: ACTIONS.UPDATE_PUBLISH_FORM,
          data: { uploadThumbnailStatus: STATUSES.API_DOWN },
        },
        doAlertError(error)
      )
    );

  dispatch({
    type: ACTIONS.UPDATE_PUBLISH_FORM,
    data: { uploadThumbnailStatus: STATUSES.IN_PROGRESS },
  });

  const data = new FormData();
  const name = makeid();
  const blob = new Blob([thumbnail], { type: `image/${fileExt.slice(1)}` });
  data.append('name', name);
  data.append('file', blob);
  data.append('nsfw', nsfw.toString());
  return fetch('https://spee.ch/api/claim/publish', {
    method: 'POST',
    body: data,
  })
    .then(response => response.json())
    .then(
      json =>
        json.success
          ? dispatch({
              type: ACTIONS.UPDATE_PUBLISH_FORM,
              data: {
                uploadThumbnailStatus: STATUSES.COMPLETE,
                thumbnail: `${json.data.url}${fileExt}`,
              },
            })
          : uploadError('Upload failed')
    )
    .catch(err => uploadError(err.message));
};

export const doPrepareEdit = (claim: any) => (dispatch: Dispatch) => {
  const { name, amount, channel_name: channelName, value: { stream: { metadata } } } = claim;

  const {
    author,
    description,
    // use same values as default state
    // fee will be undefined for free content
    fee = {
      amount: 0,
      currency: 'LBC',
    },
    language,
    license,
    licenseUrl,
    nsfw,
    thumbnail,
    title,
  } = metadata;

  const publishData = {
    name,
    channel: channelName,
    bid: amount,
    price: { amount: fee.amount, currency: fee.currency },
    contentIsFree: !fee.amount,
    author,
    description,
    fee,
    language,
    license,
    licenseUrl,
    nsfw,
    thumbnail,
    title,
    uri,
  };

  dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
};

export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getState: () => {}) => {
  const state = getState();
  const myClaims = selectMyClaimsWithoutChannels(state);
  const myChannels = selectMyChannelClaims(state);

  const {
    name,
    bid,
    filePath,
    description,
    language,
    license,
    licenseUrl,
    thumbnail,
    nsfw,
    channel,
    title,
    contentIsFree,
    price,
    uri,
    sources,
  } = params;

  // get the claim id from the channel name, we will use that instead
  const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel);
  const channelId = namedChannelClaim ? namedChannelClaim.claim_id : '';

  let isEdit;
  const newPublishName = channel ? `${channel}/${name}` : name;
  for (let i = 0; i < myClaims.length; i += 1) {
    const { channel_name: claimChannelName, name: claimName } = myClaims[i];
    const contentName = claimChannelName ? `${claimChannelName}/${claimName}` : claimName;
    if (contentName === newPublishName) {
      isEdit = true;
      break;
    }
  }

  const fee = contentIsFree || !price.amount ? undefined : { ...price };

  const metadata = {
    title,
    nsfw,
    license,
    licenseUrl,
    language,
    thumbnail,
  };

  if (fee) {
    metadata.fee = fee;
  }

  if (description) {
    metadata.description = description;
  }

  const publishPayload = {
    name,
    channel_id: channelId,
    bid,
    metadata,
  };

  if (filePath) {
    publishPayload.file_path = filePath;
  } else {
    publishPayload.sources = sources;
  }

  dispatch({ type: ACTIONS.PUBLISH_START });

  const success = () => {
    dispatch({
      type: ACTIONS.PUBLISH_SUCCESS,
      data: { pendingPublish: { ...publishPayload, isEdit } },
    });
    dispatch(doNotify({ id: MODALS.PUBLISH }, { uri }));
  };

  const failure = error => {
    dispatch({ type: ACTIONS.PUBLISH_FAIL });
    dispatch(doNotify({ id: MODALS.ERROR, error: error.message }));
  };

  return Lbry.publish(publishPayload).then(success, failure);
};

// Calls claim_list_mine until any pending publishes are confirmed
export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetState) => {
  const state = getState();
  const pendingPublishes = selectPendingPublishes(state);
  const myClaims = selectMyClaimsWithoutChannels(state);

  let publishCheckInterval;

  const checkFileList = () => {
    Lbry.claim_list_mine().then(claims => {
      const claimsWithoutChannels = claims.filter(claim => !claim.name.match(/^@/));
      if (myClaims.length !== claimsWithoutChannels.length) {
        const pendingPublishMap = {};
        pendingPublishes.forEach(({ name }) => {
          pendingPublishMap[name] = name;
        });

        claims.forEach(claim => {
          if (pendingPublishMap[claim.name]) {
            dispatch({
              type: ACTIONS.REMOVE_PENDING_PUBLISH,
              data: {
                name: claim.name,
              },
            });
            dispatch({
              type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
              data: {
                claims,
              },
            });

            delete pendingPublishMap[claim.name];
          }
        });

        clearInterval(publishCheckInterval);
      }
    });
  };

  if (pendingPublishes.length) {
    checkFileList();
    publishCheckInterval = setInterval(() => {
      checkFileList();
    }, 10000);
  }
};
<commit_msg>fix error messages<commit_after>// @flow
import {
  ACTIONS,
  Lbry,
  selectMyClaimsWithoutChannels,
  doNotify,
  MODALS,
  selectMyChannelClaims,
  STATUSES
} from 'lbry-redux';
import { selectPendingPublishes } from 'redux/selectors/publish';
import type {
  UpdatePublishFormData,
  UpdatePublishFormAction,
  PublishParams,
} from 'redux/reducers/publish';
import fs from 'fs';
import path from 'path';

type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
type PromiseAction = Promise<Action>;
type Dispatch = (action: Action | PromiseAction | Array<Action>) => any;
type GetState = () => {};

export const doClearPublish = () => (dispatch: Dispatch): Action =>
  dispatch({ type: ACTIONS.CLEAR_PUBLISH });

export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => (
  dispatch: Dispatch
): UpdatePublishFormAction =>
  dispatch({
    type: ACTIONS.UPDATE_PUBLISH_FORM,
    data: { ...publishFormValue },
  });

export const doResetThumbnailStatus = () => (dispatch: Dispatch): Action =>
  fetch('https://spee.ch/api/channel/availability/@testing')
    .then(() =>
      dispatch({
        type: ACTIONS.UPDATE_PUBLISH_FORM,
        data: {
          uploadThumbnailStatus: STATUSES.READY,
          thumbnail: '',
          nsfw: false,
        },
      })
    )
    .catch(() =>
      dispatch({
        type: ACTIONS.UPDATE_PUBLISH_FORM,
        data: {
          uploadThumbnailStatus: STATUSES.API_DOWN,
          thumbnail: '',
          nsfw: false,
        },
      })
    );

export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => {
  const thumbnail = fs.readFileSync(filePath);
  const fileExt = path.extname(filePath);

  const makeid = () => {
    let text = '';
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < 24; i += 1) text += possible.charAt(Math.floor(Math.random() * 62));
    return text;
  };

  const uploadError = (error: string = '') =>
    dispatch(
      batchActions(
        {
          type: ACTIONS.UPDATE_PUBLISH_FORM,
          data: { uploadThumbnailStatus: STATUSES.API_DOWN },
        },
        dispatch(doNotify({ id: MODALS.ERROR, error }))
      )
    );

  dispatch({
    type: ACTIONS.UPDATE_PUBLISH_FORM,
    data: { uploadThumbnailStatus: STATUSES.IN_PROGRESS },
  });

  const data = new FormData();
  const name = makeid();
  const blob = new Blob([thumbnail], { type: `image/${fileExt.slice(1)}` });
  data.append('name', name);
  data.append('file', blob);
  data.append('nsfw', nsfw.toString());
  return fetch('https://spee.ch/api/claim/publish', {
    method: 'POST',
    body: data,
  })
    .then(response => response.json())
    .then(
      json =>
        json.success
          ? dispatch({
              type: ACTIONS.UPDATE_PUBLISH_FORM,
              data: {
                uploadThumbnailStatus: STATUSES.COMPLETE,
                thumbnail: `${json.data.url}${fileExt}`,
              },
            })
          : uploadError('Upload failed')
    )
    .catch(err => uploadError(err.message));
};

export const doPrepareEdit = (claim: any) => (dispatch: Dispatch) => {
  const { name, amount, channel_name: channelName, value: { stream: { metadata } } } = claim;

  const {
    author,
    description,
    // use same values as default state
    // fee will be undefined for free content
    fee = {
      amount: 0,
      currency: 'LBC',
    },
    language,
    license,
    licenseUrl,
    nsfw,
    thumbnail,
    title,
  } = metadata;

  const publishData = {
    name,
    channel: channelName,
    bid: amount,
    price: { amount: fee.amount, currency: fee.currency },
    contentIsFree: !fee.amount,
    author,
    description,
    fee,
    language,
    license,
    licenseUrl,
    nsfw,
    thumbnail,
    title,
    uri,
  };

  dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
};

export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getState: () => {}) => {
  const state = getState();
  const myClaims = selectMyClaimsWithoutChannels(state);
  const myChannels = selectMyChannelClaims(state);

  const {
    name,
    bid,
    filePath,
    description,
    language,
    license,
    licenseUrl,
    thumbnail,
    nsfw,
    channel,
    title,
    contentIsFree,
    price,
    uri,
    sources,
  } = params;

  // get the claim id from the channel name, we will use that instead
  const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel);
  const channelId = namedChannelClaim ? namedChannelClaim.claim_id : '';

  let isEdit;
  const newPublishName = channel ? `${channel}/${name}` : name;
  for (let i = 0; i < myClaims.length; i += 1) {
    const { channel_name: claimChannelName, name: claimName } = myClaims[i];
    const contentName = claimChannelName ? `${claimChannelName}/${claimName}` : claimName;
    if (contentName === newPublishName) {
      isEdit = true;
      break;
    }
  }

  const fee = contentIsFree || !price.amount ? undefined : { ...price };

  const metadata = {
    title,
    nsfw,
    license,
    licenseUrl,
    language,
    thumbnail,
  };

  if (fee) {
    metadata.fee = fee;
  }

  if (description) {
    metadata.description = description;
  }

  const publishPayload = {
    name,
    channel_id: channelId,
    bid,
    metadata,
  };

  if (filePath) {
    publishPayload.file_path = filePath;
  } else {
    publishPayload.sources = sources;
  }

  dispatch({ type: ACTIONS.PUBLISH_START });

  const success = () => {
    dispatch({
      type: ACTIONS.PUBLISH_SUCCESS,
      data: { pendingPublish: { ...publishPayload, isEdit } },
    });
    dispatch(doNotify({ id: MODALS.PUBLISH }, { uri }));
  };

  const failure = error => {
    dispatch({ type: ACTIONS.PUBLISH_FAIL });
    dispatch(doNotify({ id: MODALS.ERROR, error: error.message }));
  };

  return Lbry.publish(publishPayload).then(success, failure);
};

// Calls claim_list_mine until any pending publishes are confirmed
export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetState) => {
  const state = getState();
  const pendingPublishes = selectPendingPublishes(state);
  const myClaims = selectMyClaimsWithoutChannels(state);

  let publishCheckInterval;

  const checkFileList = () => {
    Lbry.claim_list_mine().then(claims => {
      const claimsWithoutChannels = claims.filter(claim => !claim.name.match(/^@/));
      if (myClaims.length !== claimsWithoutChannels.length) {
        const pendingPublishMap = {};
        pendingPublishes.forEach(({ name }) => {
          pendingPublishMap[name] = name;
        });

        claims.forEach(claim => {
          if (pendingPublishMap[claim.name]) {
            dispatch({
              type: ACTIONS.REMOVE_PENDING_PUBLISH,
              data: {
                name: claim.name,
              },
            });
            dispatch({
              type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
              data: {
                claims,
              },
            });

            delete pendingPublishMap[claim.name];
          }
        });

        clearInterval(publishCheckInterval);
      }
    });
  };

  if (pendingPublishes.length) {
    checkFileList();
    publishCheckInterval = setInterval(() => {
      checkFileList();
    }, 10000);
  }
};
<|endoftext|>


<commit_before><commit_msg>fix nested wizard fields validation (#4016)<commit_after><|endoftext|>


<commit_before><commit_msg>Remove unused empty server.js file<commit_after><|endoftext|>


<commit_before>var Reporter = require('../../lib/reporter');

describe('result', function () {
    describe('inititial status', function () {
        it('should return empty list', function () {
            var reporter = new Reporter();
            var result = reporter.result();

            expect(Array.isArray(result)).toBe(true);
            expect(result.length).toBe(0);
        });
    });

    describe('after adding a report', function () {
        it('should return list with this report', function () {
            var reporter = new Reporter();
            var report = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].pos.line).toBe(report.pos.line);
            expect(result[0].pos.col).toBe(report.pos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('after adding more reports', function () {
        it('should return list with all reports sorted by pos', function () {
            var reporter = new Reporter();

            var report1 = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                message: 'test report1'
            };
            var report2 = {
                pos: {
                    line: 2,
                    col: 2
                },
                type: 'WARN',
                message: 'test report2'
            };
            var report3 = {
                pos: {
                    line: 3,
                    col: 3
                },
                type: 'ERROR',
                message: 'test report3'
            };
            var report4 = {
                pos: {
                    line: 3,
                    col: 4
                },
                type: 'ERROR',
                message: 'test report4'
            };

            reporter.report(report2);
            reporter.report(report4);
            reporter.report(report1);
            reporter.report(report3);

            var result = reporter.result();

            expect(result.length).toBe(4);

            expect(result[0].pos.line).toBe(report1.pos.line);
            expect(result[0].pos.col).toBe(report1.pos.col);
            expect(result[0].type).toBe(report1.type);
            expect(result[0].message).toBe(report1.message);

            expect(result[1].pos.line).toBe(report2.pos.line);
            expect(result[1].pos.col).toBe(report2.pos.col);
            expect(result[1].type).toBe(report2.type);
            expect(result[1].message).toBe(report2.message);

            expect(result[2].pos.line).toBe(report3.pos.line);
            expect(result[2].pos.col).toBe(report3.pos.col);
            expect(result[2].type).toBe(report3.type);
            expect(result[2].message).toBe(report3.message);

            expect(result[3].pos.line).toBe(report4.pos.line);
            expect(result[3].pos.col).toBe(report4.pos.col);
            expect(result[3].type).toBe(report4.type);
            expect(result[3].message).toBe(report4.message);
        });
    });
});

describe('report', function () {
    describe('with pos', function () {
        it('should record the right item', function () {
            var reporter = new Reporter();
            var report = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].pos.line).toBe(report.pos.line);
            expect(result[0].pos.col).toBe(report.pos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('with element', function () {
        it('should record the right item', function () {
            var reporter = new Reporter();
            var report = {
                elem: {
                    startPos: {
                        line: 1,
                        col: 1
                    }
                },
                type: 'INFO',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].pos.line).toBe(report.elem.startPos.line);
            expect(result[0].pos.col).toBe(report.elem.startPos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('without pos nor element', function () {
        it('should record the right item (pos 0,0)', function () {
            var reporter = new Reporter();
            var report = {
                type: 'INFO',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].pos.line).toBe(0);
            expect(result[0].pos.col).toBe(0);
            expect(result[0].type).toBe(report.type);
            expect(result[0].message).toBe(report.message);
        });
    });
});

describe('info', function () {
    it('should record the right item (type "INFO")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var message = 'test info';
        reporter.info(element, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('INFO');
        expect(result[0].pos.line).toBe(element.startPos.line);
        expect(result[0].pos.col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});

describe('warn', function () {
    it('should record the right item (type "WARN")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var message = 'test warn';
        reporter.warn(element, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('WARN');
        expect(result[0].pos.line).toBe(element.startPos.line);
        expect(result[0].pos.col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});

describe('error', function () {
    it('should record the right item (type "ERROR")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var message = 'test error';
        reporter.error(element, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('ERROR');
        expect(result[0].pos.line).toBe(element.startPos.line);
        expect(result[0].pos.col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});
<commit_msg>update test for reporter<commit_after>var Reporter = require('../../lib/reporter');

describe('result', function () {
    describe('inititial status', function () {
        it('should return empty list', function () {
            var reporter = new Reporter();
            var result = reporter.result();

            expect(Array.isArray(result)).toBe(true);
            expect(result.length).toBe(0);
        });
    });

    describe('after adding a report', function () {
        it('should return list with this report', function () {
            var reporter = new Reporter();
            var report = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                rule: '001',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].line).toBe(report.pos.line);
            expect(result[0].col).toBe(report.pos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].rule).toBe(report.rule);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('after adding more reports', function () {
        it('should return list with all reports sorted by pos', function () {
            var reporter = new Reporter();

            var report1 = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                rule: '001',
                message: 'test report1'
            };
            var report2 = {
                pos: {
                    line: 2,
                    col: 2
                },
                type: 'WARN',
                rule: '002',
                message: 'test report2'
            };
            var report3 = {
                pos: {
                    line: 3,
                    col: 3
                },
                type: 'ERROR',
                rule: '003',
                message: 'test report3'
            };
            var report4 = {
                pos: {
                    line: 3,
                    col: 4
                },
                type: 'ERROR',
                rule: '004',
                message: 'test report4'
            };

            reporter.report(report2);
            reporter.report(report4);
            reporter.report(report1);
            reporter.report(report3);

            var result = reporter.result();

            expect(result.length).toBe(4);

            expect(result[0].line).toBe(report1.pos.line);
            expect(result[0].col).toBe(report1.pos.col);
            expect(result[0].type).toBe(report1.type);
            expect(result[0].rule).toBe(report1.rule);
            expect(result[0].message).toBe(report1.message);

            expect(result[1].line).toBe(report2.pos.line);
            expect(result[1].col).toBe(report2.pos.col);
            expect(result[1].type).toBe(report2.type);
            expect(result[1].rule).toBe(report2.rule);
            expect(result[1].message).toBe(report2.message);

            expect(result[2].line).toBe(report3.pos.line);
            expect(result[2].col).toBe(report3.pos.col);
            expect(result[2].type).toBe(report3.type);
            expect(result[2].rule).toBe(report3.rule);
            expect(result[2].message).toBe(report3.message);

            expect(result[3].line).toBe(report4.pos.line);
            expect(result[3].col).toBe(report4.pos.col);
            expect(result[3].type).toBe(report4.type);
            expect(result[3].rule).toBe(report4.rule);
            expect(result[3].message).toBe(report4.message);
        });
    });
});

describe('report', function () {
    describe('with pos', function () {
        it('should record the right item', function () {
            var reporter = new Reporter();
            var report = {
                pos: {
                    line: 1,
                    col: 1
                },
                type: 'INFO',
                rule: '001',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].line).toBe(report.pos.line);
            expect(result[0].col).toBe(report.pos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].rule).toBe(report.rule);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('with element', function () {
        it('should record the right item', function () {
            var reporter = new Reporter();
            var report = {
                elem: {
                    startPos: {
                        line: 1,
                        col: 1
                    }
                },
                type: 'INFO',
                rule: '001',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].line).toBe(report.elem.startPos.line);
            expect(result[0].col).toBe(report.elem.startPos.col);
            expect(result[0].type).toBe(report.type);
            expect(result[0].rule).toBe(report.rule);
            expect(result[0].message).toBe(report.message);
        });
    });

    describe('without pos nor element', function () {
        it('should record the right item (pos 0,0)', function () {
            var reporter = new Reporter();
            var report = {
                type: 'INFO',
                rule: '001',
                message: 'test report'
            };
            reporter.report(report);
            var result = reporter.result();

            expect(result.length).toBe(1);
            expect(result[0].line).toBe(0);
            expect(result[0].col).toBe(0);
            expect(result[0].type).toBe(report.type);
            expect(result[0].rule).toBe(report.rule);
            expect(result[0].message).toBe(report.message);
        });
    });
});

describe('info', function () {
    it('should record the right item (type "INFO")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var rule = '001';
        var message = 'test info';
        reporter.info(element, rule, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('INFO');
        expect(result[0].rule).toBe(rule);
        expect(result[0].line).toBe(element.startPos.line);
        expect(result[0].col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});

describe('warn', function () {
    it('should record the right item (type "WARN")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var rule = '001';
        var message = 'test warn';
        reporter.warn(element, rule, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('WARN');
        expect(result[0].rule).toBe(rule);
        expect(result[0].line).toBe(element.startPos.line);
        expect(result[0].col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});

describe('error', function () {
    it('should record the right item (type "ERROR")', function () {
        var reporter = new Reporter();
        var element = {
            startPos: {
                line: 1,
                col: 1
            }
        };
        var rule = '001';
        var message = 'test error';
        reporter.error(element, rule, message);
        var result = reporter.result();

        expect(result.length).toBe(1);
        expect(result[0].type).toBe('ERROR');
        expect(result[0].rule).toBe(rule);
        expect(result[0].line).toBe(element.startPos.line);
        expect(result[0].col).toBe(element.startPos.col);
        expect(result[0].message).toBe(message);
    });
});
<|endoftext|>


<commit_before>import {
  isPlainObject,
  supportsPassive
} from 'uni-shared'

import {
  hasLifecycleHook
} from 'uni-helpers/index'

import {
  NAVBAR_HEIGHT,
  TABBAR_HEIGHT
} from 'uni-helpers/constants'

import {
  disableScroll,
  createScrollListener
} from 'uni-core/view/bridge/subscribe/scroll'

const passiveOptions = supportsPassive ? {
  passive: false
} : false

function updateCssVar (vm) {
  if (uni.canIUse('css.var')) {
    const pageVm = vm.$parent.$parent
    const navigationBarType = pageVm.navigationBar.type
    const windowTopValue = navigationBarType === 'default' || navigationBarType === 'float' ? NAVBAR_HEIGHT : 0
    const windowBottomValue = getApp().$children[0].showTabBar ? TABBAR_HEIGHT : 0
    const envMethod = uni.canIUse('css.env') ? 'env' : (uni.canIUse('css.constant') ? 'constant' : '')
    const windowTop = windowTopValue && envMethod ? `calc(${windowTopValue}px + ${envMethod}(safe-area-inset-top))` : `${windowTopValue}0px`
    const windowBottom = windowBottomValue && envMethod ? `calc(${windowBottomValue}px + ${envMethod}(safe-area-inset-bottom))` : `${windowBottomValue}px`
    const style = document.documentElement.style
    style.setProperty('--window-top', windowTop)
    style.setProperty('--window-bottom', windowBottom)
    console.debug(`${vm.$page.route}[${vm.$page.id}]：--window-top=${windowTop}`)
    console.debug(`${vm.$page.route}[${vm.$page.id}]：--window-bottom=${windowBottom}`)
  }
}

export default function initSubscribe (subscribe) {
  let scrollListener = false
  let disableScrollListener = false

  subscribe('onPageLoad', vm => { // 用户 onLoad 之前 update
    updateCssVar(vm)
  })

  subscribe('onPageShow', vm => {
    const pageVm = vm.$parent.$parent

    if (vm._isMounted) { // 非首次 show 才 update（首次 show 的时候在 onPageLoad 中触发了）
      updateCssVar(vm)
    }

    if (disableScrollListener) {
      document.removeEventListener('touchmove', disableScrollListener, passiveOptions)
    }

    if (pageVm.disableScroll) {
      disableScrollListener = disableScroll
      document.addEventListener('touchmove', disableScrollListener, passiveOptions)
    }

    const enablePageScroll = hasLifecycleHook(vm.$options, 'onPageScroll')
    const enablePageReachBottom = hasLifecycleHook(vm.$options, 'onReachBottom')
    const onReachBottomDistance = pageVm.onReachBottomDistance

    const enableTransparentTitleNView = (isPlainObject(pageVm.titleNView) && pageVm.titleNView.type ===
      'transparent') || (isPlainObject(pageVm.navigationBar) && pageVm.navigationBar.type === 'transparent')

    if (scrollListener) {
      document.removeEventListener('scroll', scrollListener)
    }

    if (enableTransparentTitleNView || enablePageScroll || enablePageReachBottom) { // 初始化 scroll 监听
      scrollListener = createScrollListener(vm.$page.id, {
        enablePageScroll,
        enablePageReachBottom,
        onReachBottomDistance,
        enableTransparentTitleNView
      })
      requestAnimationFrame(function () { // 避免监听太早，直接触发了 scroll
        document.addEventListener('scroll', scrollListener)
      })
    }
  })
}
<commit_msg>fix: 修复 H5 部分浏览 windowTop 值不正确的问题 fixed #1356 #1348<commit_after>import {
  isPlainObject,
  supportsPassive
} from 'uni-shared'

import {
  hasLifecycleHook
} from 'uni-helpers/index'

import {
  NAVBAR_HEIGHT,
  TABBAR_HEIGHT
} from 'uni-helpers/constants'

import {
  disableScroll,
  createScrollListener
} from 'uni-core/view/bridge/subscribe/scroll'

const passiveOptions = supportsPassive ? {
  passive: false
} : false

function updateCssVar (vm) {
  if (uni.canIUse('css.var')) {
    const pageVm = vm.$parent.$parent
    const navigationBarType = pageVm.navigationBar.type
    const windowTopValue = navigationBarType === 'default' || navigationBarType === 'float' ? NAVBAR_HEIGHT : 0
    const windowBottomValue = getApp().$children[0].showTabBar ? TABBAR_HEIGHT : 0
    const envMethod = uni.canIUse('css.env') ? 'env' : (uni.canIUse('css.constant') ? 'constant' : '')
    const windowTop = windowTopValue && envMethod ? `calc(${windowTopValue}px + ${envMethod}(safe-area-inset-top))` : `${windowTopValue}px`
    const windowBottom = windowBottomValue && envMethod ? `calc(${windowBottomValue}px + ${envMethod}(safe-area-inset-bottom))` : `${windowBottomValue}px`
    const style = document.documentElement.style
    style.setProperty('--window-top', windowTop)
    style.setProperty('--window-bottom', windowBottom)
    console.debug(`${vm.$page.route}[${vm.$page.id}]：--window-top=${windowTop}`)
    console.debug(`${vm.$page.route}[${vm.$page.id}]：--window-bottom=${windowBottom}`)
  }
}

export default function initSubscribe (subscribe) {
  let scrollListener = false
  let disableScrollListener = false

  subscribe('onPageLoad', vm => { // 用户 onLoad 之前 update
    updateCssVar(vm)
  })

  subscribe('onPageShow', vm => {
    const pageVm = vm.$parent.$parent

    if (vm._isMounted) { // 非首次 show 才 update（首次 show 的时候在 onPageLoad 中触发了）
      updateCssVar(vm)
    }

    if (disableScrollListener) {
      document.removeEventListener('touchmove', disableScrollListener, passiveOptions)
    }

    if (pageVm.disableScroll) {
      disableScrollListener = disableScroll
      document.addEventListener('touchmove', disableScrollListener, passiveOptions)
    }

    const enablePageScroll = hasLifecycleHook(vm.$options, 'onPageScroll')
    const enablePageReachBottom = hasLifecycleHook(vm.$options, 'onReachBottom')
    const onReachBottomDistance = pageVm.onReachBottomDistance

    const enableTransparentTitleNView = (isPlainObject(pageVm.titleNView) && pageVm.titleNView.type ===
      'transparent') || (isPlainObject(pageVm.navigationBar) && pageVm.navigationBar.type === 'transparent')

    if (scrollListener) {
      document.removeEventListener('scroll', scrollListener)
    }

    if (enableTransparentTitleNView || enablePageScroll || enablePageReachBottom) { // 初始化 scroll 监听
      scrollListener = createScrollListener(vm.$page.id, {
        enablePageScroll,
        enablePageReachBottom,
        onReachBottomDistance,
        enableTransparentTitleNView
      })
      requestAnimationFrame(function () { // 避免监听太早，直接触发了 scroll
        document.addEventListener('scroll', scrollListener)
      })
    }
  })
}
<|endoftext|>


<commit_before>/*
 *  Title: Test whether internal MDN links have absolute URLs.
 *
 *  Example 1: An MDN link to the 'display' CSS property should use a the relative URL
 *  /en-US/docs/Web/CSS/display and not the absolute URL
 *  https://developer.mozilla.org/en-US/docs/Web/CSS/display.
 *
 *  Implementation notes: This test checks whether the URL begins with the link
 *  "https://developer.mozilla.org/", this means that allizom.org links are not covered.
 *  window.location cannot be used for this, because that would break the unit test, which uses
 *  about:blank as URL.
 */

const reAbsoluteURL = /^(?:https?:)?\/\/developer\.mozilla\.org(?=\/)/i;

docTests.absoluteURLsForInternalLinks = {
  name: "absolute_urls_for_internal_links",
  desc: "absolute_urls_for_internal_links_desc",
  check: function checkAbsoluteURLsForInternalLinks(rootElement) {
    let links = rootElement.getElementsByTagName("a");
    let matches = [];
    for (let i = 0; i < links.length; i++) {
      let href = links[i].getAttribute("href");
      if (href && href.match(reAbsoluteURL)) {
        matches.push({
          node: links[i],
          msg: links[i].outerHTML,
          type: WARNING
        });
      }
    }

    return matches;
  },

  fix: function fixAbsoluteURLsForInternalLinks(matches) {
    matches.forEach(match => {
      let href = match.node.getAttribute("href");
      match.node.setAttribute("href", href.replace(reAbsoluteURL, ""));
    });
  }
};<commit_msg>Also replace 'data-cke-saved-href' attribute when fixing the matches (issue #4)<commit_after>/*
 *  Title: Test whether internal MDN links have absolute URLs.
 *
 *  Example 1: An MDN link to the 'display' CSS property should use a the relative URL
 *  /en-US/docs/Web/CSS/display and not the absolute URL
 *  https://developer.mozilla.org/en-US/docs/Web/CSS/display.
 *
 *  Implementation notes: This test checks whether the URL begins with the link
 *  "https://developer.mozilla.org/", this means that allizom.org links are not covered.
 *  window.location cannot be used for this, because that would break the unit test, which uses
 *  about:blank as URL.
 */

const reAbsoluteURL = /^(?:https?:)?\/\/developer\.mozilla\.org(?=\/)/i;

docTests.absoluteURLsForInternalLinks = {
  name: "absolute_urls_for_internal_links",
  desc: "absolute_urls_for_internal_links_desc",
  check: function checkAbsoluteURLsForInternalLinks(rootElement) {
    let links = rootElement.getElementsByTagName("a");
    let matches = [];
    for (let i = 0; i < links.length; i++) {
      let href = links[i].getAttribute("href");
      if (href && href.match(reAbsoluteURL)) {
        matches.push({
          node: links[i],
          msg: links[i].outerHTML,
          type: WARNING
        });
      }
    }

    return matches;
  },

  fix: function fixAbsoluteURLsForInternalLinks(matches) {
    matches.forEach(match => {
      let href = match.node.getAttribute("href");
      let relativeURL = href.replace(reAbsoluteURL, "");
      match.node.setAttribute("href", relativeURL);
      match.node.setAttribute("data-cke-saved-href", relativeURL);
    });
  }
};<|endoftext|>


<commit_before>/**
 Copyright 2015 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Graphical debugger that hooks into Espruino's internal debugger
 ------------------------------------------------------------------
**/
"use strict";
(function(){

  var CSS_BG_CLASS = "cm-DebugLineBg";

  var inDebugMode = false;
  var currentDebugLine = undefined;
  var callbackOnEqualsLine = undefined; // fn to call when we get `=xxx` - used for variable tooltips
  var equalsLineContents = ""; // for multiple line '=' data, this is what we have so far
  var knownValues = {};
  var icons = {};

  function init() {

    Espruino.addProcessor("terminalPrompt", onTerminalPrompt);
    Espruino.addProcessor("terminalNewLine", onTerminalNewLine);
    Espruino.addProcessor("editorHover", onEditorHover);
  }

  function debugCmd(cmd) {
    Espruino.Core.Serial.write(cmd+"\n");
  }

  function addIcons() {
    icons.go = Espruino.Core.App.addIcon({
      id: "debug-go",
      icon: "debug-go",
      title : "Continue Running",
      order: 2000,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("continue"); }
    });
    icons.stop = Espruino.Core.App.addIcon({
      id: "debug-stop",
      icon: "debug-stop",
      title : "Stop Debugging",
      order: 2001,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("quit"); }
    });
    icons.into = Espruino.Core.App.addIcon({
      id: "debug-into",
      icon: "debug-into",
      title : "Step Into Statement",
      order: 2002,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("step"); }
    });
    icons.over = Espruino.Core.App.addIcon({
      id: "debug-over",
      icon: "debug-over",
      title : "Step Over (Next Statement)",
      order: 2003,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("next"); }
    });
    icons.out = Espruino.Core.App.addIcon({
      id: "debug-out",
      icon: "debug-out",
      title : "Step Out (finish)",
      order: 2004,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("finish"); }
    });
  }

  function removeIcons() {
    for (var i in icons) {
      if (icons[i]!==undefined) {
        icons[i].remove();
        icons[i] = undefined;
      }
    }
  }

  function setDebugMode(isDbg) {
    if (inDebugMode != isDbg) {
      inDebugMode = isDbg;
      knownValues = {};
      if (inDebugMode) {
        addIcons();
      } else {
        removeIcons();
        setDebugLine(undefined); // remove any marked lines
      }
      Espruino.callProcessor("debugMode", inDebugMode);
    }
  }

  function setDebugLine(line) {
    var cm = Espruino.Core.EditorJavaScript.getCodeMirror();
    if (currentDebugLine!==undefined) {
      knownValues = {};
      cm.removeLineClass(currentDebugLine, "background", CSS_BG_CLASS);
    }
    if (line!==undefined) {
      cm.addLineClass(line, "background", CSS_BG_CLASS);
    }
    currentDebugLine = line;
  }

  function onEditorHover(info, callback) {
    function setTooltip(value) {
      knownValues[name] = value;
      var tip = document.createElement("div");
      tip.className = "CodeMirror-debug-tooltip";
      tip.appendChild(document.createTextNode(name + " = " + value));
      info.showTooltip(tip);
    };

    if (inDebugMode && // in debug mode
        !callbackOnEqualsLine && // we're not trying to do something already
        Espruino.Core.Terminal.getTerminalLine()=="debug>" // user is not currently typing on LHS
        ) {
      var name;
      if (info.node.className=="cm-variable" ||
          info.node.className=="cm-variable-2" ||
          info.node.className=="cm-def") {
        name = info.node.textContent;
      }
      if (info.node.className=="cm-property") {
        // get 'a.b' type stuff. There must be a better way :)
        if (info.node.previousSibling &&
            info.node.previousSibling.textContent=="." &&
            info.node.previousSibling.previousSibling &&
            info.node.previousSibling.previousSibling.className == "cm-variable") {
          name = info.node.previousSibling.previousSibling.textContent + "." + info.node.textContent;
        }
      }
      if (name) {
        if (name in knownValues) {
          setTooltip(knownValues[name]);
        } else {
          equalsLineContents = undefined;
          callbackOnEqualsLine = setTooltip;
          debugCmd("p "+name);
        }
      }
    }
    callback(info);
  }

  function onTerminalPrompt(prompt, callback) {
    if (prompt == "debug>") {
      setDebugMode(true);
      var lastLine = Espruino.Core.Terminal.getTerminalLine(1);
      if (lastLine && lastLine.indexOf("^")>0) {
        var codeLine = Espruino.Core.Terminal.getTerminalLine(2);
        if (codeLine) {
          var lineNumber = parseInt(codeLine.substr(0,8).trim());
          if (lineNumber) setDebugLine(lineNumber-1);
        }
      }
    } else {
      setDebugMode(false);
    }

    callback(prompt);
  }

  function onTerminalNewLine(lastLine, callback) {
    if (inDebugMode) {
      if (callbackOnEqualsLine) {
        /** We might want to get the value of some expression in the debugger,
         * so we write `p myExpr\n` to Espruino, and wait for a result, which
         * should be `=foo`. Of course it might be multi-line */
        if (lastLine) {
          if (lastLine[0]=="=" && callbackOnEqualsLine) {
            equalsLineContents=lastLine.substr(1);
            if (Espruino.Core.Utils.countBrackets(equalsLineContents)<=0) {
              callbackOnEqualsLine(equalsLineContents);
              equalsLineContents = undefined;
              callbackOnEqualsLine = undefined;
            }
          } else if (equalsLineContents!==undefined && callbackOnEqualsLine) {
            equalsLineContents += lastLine;
            if (Espruino.Core.Utils.countBrackets(equalsLineContents)<=0) {
              callbackOnEqualsLine(equalsLineContents);
              equalsLineContents = undefined;
              callbackOnEqualsLine = undefined;
            }
          } else if (lastLine.substr(0,6)!="debug>") {
            // we had some error and couldn't dump the value
            equalsLineContents = undefined;
            callbackOnEqualsLine = undefined;
          }
        }
      } else
        setDebugLine(undefined);
    }
    callback(lastLine);
  }

  Espruino.Plugins.Debugger = {
    init : init,
  };
}());
<commit_msg>keep highlighted debugger line<commit_after>/**
 Copyright 2015 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Graphical debugger that hooks into Espruino's internal debugger
 ------------------------------------------------------------------
**/
"use strict";
(function(){

  var CSS_BG_CLASS = "cm-DebugLineBg";

  var inDebugMode = false;
  var currentDebugLine = undefined;
  var callbackOnEqualsLine = undefined; // fn to call when we get `=xxx` - used for variable tooltips
  var equalsLineContents = ""; // for multiple line '=' data, this is what we have so far
  var knownValues = {};
  var icons = {};

  function init() {

    Espruino.addProcessor("terminalPrompt", onTerminalPrompt);
    Espruino.addProcessor("terminalNewLine", onTerminalNewLine);
    Espruino.addProcessor("editorHover", onEditorHover);
  }

  function debugCmd(cmd) {
    Espruino.Core.Serial.write(cmd+"\n");
  }

  function addIcons() {
    icons.go = Espruino.Core.App.addIcon({
      id: "debug-go",
      icon: "debug-go",
      title : "Continue Running",
      order: 2000,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("continue"); }
    });
    icons.stop = Espruino.Core.App.addIcon({
      id: "debug-stop",
      icon: "debug-stop",
      title : "Stop Debugging",
      order: 2001,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("quit"); }
    });
    icons.into = Espruino.Core.App.addIcon({
      id: "debug-into",
      icon: "debug-into",
      title : "Step Into Statement",
      order: 2002,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("step"); }
    });
    icons.over = Espruino.Core.App.addIcon({
      id: "debug-over",
      icon: "debug-over",
      title : "Step Over (Next Statement)",
      order: 2003,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("next"); }
    });
    icons.out = Espruino.Core.App.addIcon({
      id: "debug-out",
      icon: "debug-out",
      title : "Step Out (finish)",
      order: 2004,
      area: {
        name: "code",
        position: "top"
      },
      click: function() { debugCmd("finish"); }
    });
  }

  function removeIcons() {
    for (var i in icons) {
      if (icons[i]!==undefined) {
        icons[i].remove();
        icons[i] = undefined;
      }
    }
  }

  function setDebugMode(isDbg) {
    if (inDebugMode != isDbg) {
      inDebugMode = isDbg;
      knownValues = {};
      if (inDebugMode) {
        addIcons();
      } else {
        removeIcons();
        setDebugLine(undefined); // remove any marked lines
      }
      Espruino.callProcessor("debugMode", inDebugMode);
    }
  }

  function setDebugLine(line) {
    var cm = Espruino.Core.EditorJavaScript.getCodeMirror();
    if (currentDebugLine!==undefined) {
      knownValues = {};
      cm.removeLineClass(currentDebugLine, "background", CSS_BG_CLASS);
    }
    if (line!==undefined) {
      cm.addLineClass(line, "background", CSS_BG_CLASS);
    }
    currentDebugLine = line;
  }

  function onEditorHover(info, callback) {
    function setTooltip(value) {
      knownValues[name] = value;
      var tip = document.createElement("div");
      tip.className = "CodeMirror-debug-tooltip";
      tip.appendChild(document.createTextNode(name + " = " + value));
      info.showTooltip(tip);
    };

    if (inDebugMode && // in debug mode
        !callbackOnEqualsLine && // we're not trying to do something already
        Espruino.Core.Terminal.getTerminalLine()=="debug>" // user is not currently typing on LHS
        ) {
      var name;
      if (info.node.className=="cm-variable" ||
          info.node.className=="cm-variable-2" ||
          info.node.className=="cm-def") {
        name = info.node.textContent;
      }
      if (info.node.className=="cm-property") {
        // get 'a.b' type stuff. There must be a better way :)
        if (info.node.previousSibling &&
            info.node.previousSibling.textContent=="." &&
            info.node.previousSibling.previousSibling &&
            info.node.previousSibling.previousSibling.className == "cm-variable") {
          name = info.node.previousSibling.previousSibling.textContent + "." + info.node.textContent;
        }
      }
      if (name) {
        if (name in knownValues) {
          setTooltip(knownValues[name]);
        } else {
          equalsLineContents = undefined;
          callbackOnEqualsLine = setTooltip;
          debugCmd("p "+name);
        }
      }
    }
    callback(info);
  }

  function onTerminalPrompt(prompt, callback) {
    if (prompt == "debug>") {
      setDebugMode(true);
      var lastLine = Espruino.Core.Terminal.getTerminalLine(1);
      if (lastLine && lastLine.indexOf("^")>0) {
        var codeLine = Espruino.Core.Terminal.getTerminalLine(2);
        if (codeLine) {
          var lineNumber = parseInt(codeLine.substr(0,8).trim());
          if (lineNumber) setDebugLine(lineNumber-1);
        }
      }
    } else {
      setDebugMode(false);
    }

    callback(prompt);
  }

  function onTerminalNewLine(lastLine, callback) {
    if (inDebugMode) {
      if (callbackOnEqualsLine) {
        /** We might want to get the value of some expression in the debugger,
         * so we write `p myExpr\n` to Espruino, and wait for a result, which
         * should be `=foo`. Of course it might be multi-line */
        if (lastLine) {
          if (lastLine[0]=="=" && callbackOnEqualsLine) {
            equalsLineContents=lastLine.substr(1);
            if (Espruino.Core.Utils.countBrackets(equalsLineContents)<=0) {
              callbackOnEqualsLine(equalsLineContents);
              equalsLineContents = undefined;
              callbackOnEqualsLine = undefined;
            }
          } else if (equalsLineContents!==undefined && callbackOnEqualsLine) {
            equalsLineContents += lastLine;
            if (Espruino.Core.Utils.countBrackets(equalsLineContents)<=0) {
              callbackOnEqualsLine(equalsLineContents);
              equalsLineContents = undefined;
              callbackOnEqualsLine = undefined;
            }
          } else if (lastLine.substr(0,6)!="debug>") {
            // we had some error and couldn't dump the value
            equalsLineContents = undefined;
            callbackOnEqualsLine = undefined;
          }
        }
      } else {
        // we're not sure what's going on (an unexpected print, or an exception)
        // but let's keep the highlighted line anyway
        // setDebugLine(undefined); // remove highlighted line
      }
    }
    callback(lastLine);
  }

  Espruino.Plugins.Debugger = {
    init : init,
  };
}());
<|endoftext|>


<commit_before>/**
 * 图(Graph)
 */
<commit_msg>modified<commit_after>/**
 * 图(Graph)
 *
 * 图(Graph)是一种比线性表和树更为复杂的数据结构。
 *
 * 线性结构：是研究数据元素之间的一对一关系。在这种结构中，除第一个和最后一个元素外，任何一个元素都有唯一的一个直接前驱和直接后继。
 *
 * 树结构：是研究数据元素之间的一对多的关系。在这种结构中，每个元素对下(层)可以有0个或多个元素相联系，对上(层)只有唯一的一个元素相关，数据元素之间有明显的层次关系。
 *
 * 图结构：是研究数据元素之间的多对多的关系。在这种结构中，任意两个元素之间可能存在关系。即结点之间的关系可以是任意的，图中任意元素之间都可能相关。
 *
 * 图的应用极为广泛，已渗入到诸如语言学、逻辑学、物理、化学、电讯、计算机科学以及数学的其它分支。
 *
 * 图的基本概念
 *
 * 一个图(G)定义为一个偶对(V,E) ，记为G=(V,E) 。其中： V是顶点(Vertex)的非空有限集合，记为V(G)；E是无序集V&V的一个子集，记为E(G) ，其元素是图的弧(Arc)。
 * 将顶点集合为空的图称为空图。其形式化定义为：
     G=(V ，E)
     V={v|v∈data object}
     E={<v,w>| v,w∈V∧p(v,w)}
     P(v,w)表示从顶点v到顶点w有一条直接通路。
 *
 * 弧(Arc) ：表示两个顶点v和w之间存在一个关系，用顶点偶对<v,w>表示。通常根据图的顶点偶对将图分为有向图和无向图。
 * 有向图(Digraph)： 若图G的关系集合E(G)中，顶点偶对<v,w>的v和w之间是有序的，称图G是有向图。
 *   在有向图中，若 <v,w>∈E(G) ，表示从顶点v到顶点w有一条弧。 其中：v称为弧尾(tail)或始点(initial node)，w称为弧头(head)或终点(terminal node) 。
 * 无向图(Undigraph)： 若图G的关系集合E(G)中，顶点偶对<v,w>的v和w之间是无序的，称图G是无向图。
 *   在无向图中，若<v,w>∈E(G) ，有<w,v>∈E(G) ，即E(G)是对称，则用无序对(v,w) 表示v和w之间的一条边(Edge)，因此(v,w) 和(w,v)代表的是同一条边。
 *
 * 例1：设有有向图G1和无向图G2，形式化定义分别是：
         G1=(V1 ，E1)
         V1={a,b,c,d,e}
         E1={<a,b>,<a,c>, <a,e>,<c,d>,<c,e> ,<d,a>,<d,b>,<e,d>}
         G2=(V2 ，E2)
         V2={a,b,c,d}
         E2={(a,b), (a,c), (a,d), (b,d), (b,c), (c,d)}
 *
 * 完全无向图：对于无向图，若图中顶点数为n ，用e表示边的数目，则e ∈[0，n(n-1)/2] 。具有n(n-1)/2条边的无向图称为完全无向图。
 完全无向图另外的定义是：
 * 对于无向图G=(V，E)，若vi，vj ∈V ，当vi≠vj时，有(vi ,vj)∈E，即图中任意两个不同的顶点间都有一条无向边，这样的无向图称为完全无向图。
 *
 * 完全有向图：对于有向图，若图中顶点数为n ，用e表示弧的数目，则e∈[0，n(n-1)] 。具有n(n-1)条边的有向图称为完全有向图。
 完全有向图另外的定义是：
 * 对于有向图G=(V，E)，若vi，vj∈V ，当vi ≠vj时，有<vi ,vj>∈E∧<vj , vi >∈E ，即图中任意两个不同的顶点间都有一条弧，这样的有向图称为完全有向图。
 *
 * 有很少边或弧的图（e<n㏒n）的图称为稀疏图，反之称为稠密图。
 * 权(Weight)：与图的边和弧相关的数。权可以表示从一个顶点到另一个顶点的距离或耗费。
 *
 * 子图和生成子图：设有图G=(V，E)和G’=(V’，E’)，若V’∈V且E’∈E ，则称图G’是G的子图；若V’=V且E’∈E，则称图G’是G的一个生成子图。
 * 顶点的邻接(Adjacent)：对于无向图G=(V，E)，若边(v,w)∈E，则称顶点v和w 互为邻接点，即v和w相邻接。边(v,w)依附(incident)与顶点v和w 。
 * 对于有向图G=(V ，E)，若有向弧<v,w>∈E，则称顶点v “邻接到”顶点w，顶点w “邻接自”顶点v ，弧<v,w> 与顶点v和w “相关联” 。
 *
 * 顶点的度、入度、出度：对于无向图G=(V，E)， vi∈V，图G中依附于vi的边的数目称为顶点vi的度(degree)，记为TD(vi)。
 显然，在无向图中，所有顶点度的和是图中边的2倍。 即   ∑TD(vi)=2e      i=1, 2, …, n ，e为图的边数。
 对有向图G=(V，E)，若vi ∈V ，图G中以vi作为起点的有向边(弧)的数目称为顶点vi的出度(Outdegree)，记为OD(vi) ；以vi作为终点的有向边(弧)的数目称为顶点vi的入度(Indegree)，记为ID(vi) 。顶点vi的出度与入度之和称为vi的度，记为TD(vi) 。即
 TD(vi)=OD(vi)+ID(vi)
 *
 * 路径(Path)、路径长度、回路(Cycle) ：对无向图G=(V，E)，若从顶点vi经过若干条边能到达vj，称顶点vi和vj是连通的，又称顶点vi到vj有路径。
 对有向图G=(V，E)，从顶点vi到vj有有向路径，指的是从顶点vi经过若干条有向边(弧)能到达vj。
 或路径是图G中连接两顶点之间所经过的顶点序列。即
     Path=vi0vi1…vim ，vij∈V且(vij-1, vij)∈E   j=1,2, …,m
     或
     Path=vi0vi1 …vim ，vij∈V且<vij-1, vij>∈E  j=1,2, …,m
 路径上边或有向边(弧)的数目称为该路径的长度。
 在一条路径中，若没有重复相同的顶点，该路径称为简单路径；第一个顶点和最后一个顶点相同的路径称为回路(环)；在一个回路中，若除第一个与最后一个顶点外，其余顶点不重复出现的回路称为简单回路(简单环)。
 *
 * 连通图、图的连通分量：对无向图G=(V，E)，若vi ，vj ∈V，vi和vj都是连通的，则称图G是连通图，否则称为非连通图。若G是非连通图，则极大的连通子图称为G的连通分量。
 对有向图G=(V，E)，若vi ，vj ∈V，都有以vi为起点， vj 为终点以及以vj为起点，vi为终点的有向路径，称图G是强连通图，否则称为非强连通图。若G是非强连通图，则极大的强连通子图称为G的强连通分量。
 “极大”的含义：指的是对子图再增加图G中的其它顶点，子图就不再连通。
 生成树、生成森林：一个连通图(无向图)的生成树是一个极小连通子图，它含有图中全部n个顶点和只有足以构成一棵树的n-1条边，称为图的生成树。
 关于无向图的生成树的几个结论：
 ◆ 一棵有n个顶点的生成树有且仅有n-1条边；
 ◆ 如果一个图有n个顶点和小于n-1条边，则是非连通图；
 ◆ 如果多于n-1条边，则一定有环；
 ◆ 有n-1条边的图不一定是生成树。

 有向图的生成森林是这样一个子图，由若干棵有向树组成，含有图中全部顶点。
 有向树是只有一个顶点的入度为0 ，其余顶点的入度均为1的有向图。

 *
 * 网：每个边(或弧)都附加一个权值的图，称为带权图。带权的连通图(包括弱连通的有向图)称为网或网络。网络是工程上常用的一个概念，用来表示一个工程或某种流程
 */
<|endoftext|>


<commit_before>// --------------------------------------------------------------------------------------
function createTableHeader(size, head)
{
  var header = $("#results")[0].createTHead();
  var row = header.insertRow(0);

  for(i = 0; i < size; i++) {
    var cell = row.insertCell(0);
    cell.innerHTML = head[i];
  }
}
// --------------------------------------------------------------------------------------
function createTableBody(json, attributes, size)
{
  var body = $("#results")[0].createTBody();

  var response = JSON.parse(json.responseText);
  var res_size = response.length;

  for(i = 0; i < res_size; i++) {
    var row = body.insertRow(0);

    for(j = size; j > 0; j--) {
      cell = row.insertCell(-1);
      cell.innerHTML = response[i][attributes[j - 1]];
    }

    cell.className = cell.innerHTML;  // set class for last column - for coloring
  }
}
// --------------------------------------------------------------------------------------
function removeValidation()
{
 var username =  {
      validators: {
          username: {
          }
      }
  };

  var mac = {
      validators: {
          mac: {
          }
      }
  };

  var date =  {
      validators: {
          date: {
            format: 'MM\.DD\.YYYY hh:mm',
            message: 'Není platné datum.'
          }
      }
  };

  $('#main_form')
  .formValidation('removeField', 'username', username)
  .formValidation('removeField', 'mac_address', mac)
  .formValidation('removeField', 'date_from', date)
  .formValidation('removeField', 'date_to', date);
}
// --------------------------------------------------------------------------------------
function addValidation()
{
 var username =  {
      validators: {
          username: {
          }
      }
  };

  var mac = {
      validators: {
          mac: {
          }
      }
  };

  var date =  {
      validators: {
          date: {
            format: 'MM\.DD\.YYYY hh:mm',
            message: 'Není platné datum.'
          }
      }
  };

  $('#main_form')
  .formValidation('addField', 'username', username)
  .formValidation('addField', 'mac_address', mac)
  .formValidation('addField', 'date_from', date)
  .formValidation('addField', 'date_to', date);
}
// --------------------------------------------------------------------------------------
function parseMac(mac)
{
  var matcher = new RegExp("^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$", "i");
  var found = matcher.test(mac);

  if(found)
    return mac.replace(/:/g, '');

  matcher = new RegExp("^([0-9A-F]{2}[-]){5}([0-9A-F]{2})$", "i");
  found = matcher.test(mac);

  if(found)
    return mac.replace(/-/g, '');

  matcher = new RegExp("^([0-9A-F]{4}[\\.]){2}([0-9A-F]{4})$", "i");
  found = matcher.test(mac);

  if(found)
    return mac.replace(/\./g, '');

  matcher = new RegExp("^[0-9A-F]{12}$", "i");
  found = matcher.test(mac);

  if(found)
    return mac;

  return "";        // unspecified
}
// --------------------------------------------------------------------------------------
function convertDate(date)
{
  // input - 22.05.2015 19:46
  // output - 2015-05-22T19:45:27+02:00

  if(date == "")
    return;

  var parts = date.split("\.");
  var iso_date = parts[2].substring(0,4) + "-" + parts[1] + "-" + parts[0] + "T" + date.split(" ")[1] + ":00+02:00"; // iso 8601 format

  return Date.parse(iso_date);
}
// --------------------------------------------------------------------------------------
$(document).ready(function(){

// --------------------------------------------------------------------------------------

  $("#results")[0].style.visibility = "hidden";
// --------------------------------------------------------------------------------------
  /* search by enter key */
  $(document).keypress(function(e) {
    if(e.which == 13)    /* enter */
      $("#search").click();
  });

// --------------------------------------------------------------------------------------

  // display datetimepicker on click on date_from
  $("#date_from").datetimepicker({
      locale: 'cs',
      format: 'DD.MM.YYYY HH:mm'
    }).on('changeDate show update click', function(e) {
      $('#main_form').formValidation('revalidateField', 'date_from');
    });

  // display datetimepicker on click on date_to
  $("#date_to").datetimepicker({
      locale: 'cs',
      format: 'DD.MM.YYYY HH:mm'
    }).on('changeDate show update click', function(e) {
      $('#main_form').formValidation('revalidateField', 'date_to');
    });

// --------------------------------------------------------------------------------------
  $("#search").click(function(){
    // TODO - zakaz manualniho vstupu pro date_from, date_to ?
    // TODO - spravna validace pri pridani retezce k datumu a kliknuti mimo -> zjistit, ktery event to je
    // TODO - razeni vyslednych dat podle vysledku autentizace
    // TODO - nastavit maximalni a minimalni data
    // TODO - nastavit implicitni zacatek a konec dne pro vyber datumu
    // TODO - u datumu neni videt cely placeholder

    $("#myModal").modal('show');
    removeValidation();

    var input = {
      "username": $("#username").val(),
      "mac": parseMac($("#mac_address").val()),
      "result": $("#auth_result").val(),
      "from": convertDate($("#date_from").val()),
      "to": convertDate($("#date_to").val())
    };

    $("#results_h")[0].innerHTML = "";
    $("#results")[0].innerHTML = "";
    $("#results")[0].style.visibility = "hidden";

// --------------------------------------------------------------------------------------

    $.ajax({
      type: 'post',
      url: "/search",
      dataType: "json",
      data: input,
      success: function(json){
      },
      complete: function(json){
        $("#myModal").modal('hide');
        //$("#search")[0].disabled = false;
        addValidation();

        if(json.responseText == "[]") {
          $("#results_h")[0].innerHTML = "Zadaným parametrům vyhledávání neodpovídají žádné záznamy";
        }
        else {
          $("#results_h")[0].innerHTML = "Nalezené záznamy";
          document.getElementById("results").style.visibility = "visible";

          // table setup
          // reversed order
          var head = ["výsledek autentizace", "uživatelské jméno", "mac adresa", "navštívená instituce", "navštívená země", "realm", "timestamp"];
          var attributes = ["result", "pn", "csi", "visinst", "viscountry", "realm", "timestamp"];
          var size = attributes.length;

          createTableHeader(size, head);
          createTableBody(json, attributes, size);
        }
      }
    });
  });
// --------------------------------------------------------------------------------------
    // TODO - rozdelit na search.js a roaming.js ?

    //$("#roaming").click(function(){
    //  $.ajax({
    //    url: "/roaming/",
    //    success: function(json){
    //    },
    //    complete: function(json){
    //      //console.log(json);
    //
    //      if(json.responseText == "[]") {
    //        $("#results_h")[0].innerHTML = "Zadaným parametrům vyhledávání neodpovídají žádné záznamy";
    //      }
    //      else {
    //        $("#results_h")[0].innerHTML = "Nalezené záznamy";
    //        $("#results")[0].innerHTML = json.responseText;
    //      }
    //    }
    //  });
    //});

// --------------------------------------------------------------------------------------
// taken from http://formvalidation.io
// --------------------------------------------------------------------------------------
// custom validation

    FormValidation.Validator.username = {
        validate: function(validator, $field, options) {
            var value = $field.val();

            if (value === '')
                return true;

            // TODO - jak muze vypadat username ? specialni znaky ve jmenu - tecky, .... ?

            var matcher = new RegExp("^[a-zA-Z0-9]+@([a-zA-Z]+\\.){1,}[a-zA-Z]+$", "i");
            var found = matcher.test(value);

            if(found)
                return true;

            return {
                valid: false,
                message: 'Není platné uživatelské jméno.'
            }
        }
    };
// --------------------------------------------------------------------------------------

    FormValidation.Validator.mac = {
        validate: function(validator, $field, options) {
            var value = $field.val();

            if(value === '')
              return true;    // empty address allowed as search parameter

            else if(parseMac(value) == "")
              return {
                  valid: false,
                  message: 'Není platná mac adresa.'
              }

            return true;
        }
    };
// --------------------------------------------------------------------------------------

    // TODO - pridat nejakou logiku, ktera neumozni vyhledavat kdyz nebude zadan zadny klicovy vstup - mac/username ?
    // na urovni vyhledavani uz je vyreseno -> prazdne parametry hledani nevraceji nic
    // pripadne jeste zakazat vyhledavani, pokud je nejaky parametr neplatny?
    // ==> automaticky delaji validatory
    // ale je problem zaroven se zakazanou validaci pri submitu

    $('#main_form').formValidation({
        framework: 'bootstrap',
        //button: {
        //    selector: '#none',      // do not validate whole form on submit
        //    disabled: 'disabled'
        //},
        feedbackIcons: {
            valid: 'glyphicon glyphicon-ok',
            invalid: 'glyphicon glyphicon-remove',
            validating: 'glyphicon glyphicon-refresh'
        },
        fields: {
            username: {
                validators: {
                    username: {
                    }
                }
            },
            mac_address: {
                validators: {
                    mac: {
                    }
                }
            },
            date_from: {
                validators: {
                    date: {
                      format: 'DD\.MM\.YYYY HH:mm',
                      message: 'Není platné datum.'
                    }
                }
            },
            date_to: {
                validators: {
                    date: {
                      format: 'DD\.MM\.YYYY HH:mm',
                      message: 'Není platné datum.'
                    }
                }
            }
        }
    });
});

<commit_msg>improve validation logic<commit_after>// --------------------------------------------------------------------------------------
function createTableHeader(size, head)
{
  var header = $("#results")[0].createTHead();
  var row = header.insertRow(0);

  for(i = 0; i < size; i++) {
    var cell = row.insertCell(0);
    cell.innerHTML = head[i];
  }
}
// --------------------------------------------------------------------------------------
function createTableBody(json, attributes, size)
{
  var body = $("#results")[0].createTBody();

  var response = JSON.parse(json.responseText);
  var res_size = response.length;

  for(i = 0; i < res_size; i++) {
    var row = body.insertRow(0);

    for(j = size; j > 0; j--) {
      cell = row.insertCell(-1);
      cell.innerHTML = response[i][attributes[j - 1]];
    }

    cell.className = cell.innerHTML;  // set class for last column - for coloring
  }
}
// --------------------------------------------------------------------------------------
function removeValidation()
{
  var username =  {
      validators: {
          username: {
          }
      }
  };

  var mac = {
      validators: {
          mac: {
          }
      }
  };

  var date =  {
      validators: {
          date: {
            //format: 'MM\.DD\.YYYY hh:mm',
            //message: 'Není platné datum.'
          }
      }
  };

  $('#main_form')
  .formValidation('removeField', 'username', username)
  .formValidation('removeField', 'mac_address', mac)
  .formValidation('removeField', 'date_from', date)
  .formValidation('removeField', 'date_to', date);
}
// --------------------------------------------------------------------------------------
function addValidation()
{
  var username =  {
      validators: {
          username: {
          }
      }
  };

  var mac = {
      validators: {
          mac: {
          }
      }
  };

  var date =  {
      validators: {
          date: {
            format: 'MM\.DD\.YYYY hh:mm',
            message: 'Není platné datum.'
          }
      }
  };

  $('#main_form')
  .formValidation('addField', 'username', username)
  .formValidation('addField', 'mac_address', mac)
  .formValidation('addField', 'date_from', date)
  .formValidation('addField', 'date_to', date);
}
// --------------------------------------------------------------------------------------
function parseMac(mac)
{
  var matcher = new RegExp("^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$", "i");
  var found = matcher.test(mac);

  if(found)
    return mac.replace(/:/g, '');

  matcher = new RegExp("^([0-9A-F]{2}[-]){5}([0-9A-F]{2})$", "i");
  found = matcher.test(mac);

  if(found)
    return mac.replace(/-/g, '');

  matcher = new RegExp("^([0-9A-F]{4}[\\.]){2}([0-9A-F]{4})$", "i");
  found = matcher.test(mac);

  if(found)
    return mac.replace(/\./g, '');

  matcher = new RegExp("^[0-9A-F]{12}$", "i");
  found = matcher.test(mac);

  if(found)
    return mac;

  return "";        // unspecified
}
// --------------------------------------------------------------------------------------
function convertDate(date)
{
  // input - 22.05.2015 19:46
  // output - 2015-05-22T19:45:27+02:00

  if(date == "")
    return;

  var parts = date.split("\.");
  var iso_date = parts[2].substring(0,4) + "-" + parts[1] + "-" + parts[0] + "T" + date.split(" ")[1] + ":00+02:00"; // iso 8601 format

  return Date.parse(iso_date);
}
// --------------------------------------------------------------------------------------
function search()
{
  $("#myModal").modal('show');
  //removeValidation();

  var input = {
    "username": $("#username").val(),
    "mac": parseMac($("#mac_address").val()),
    "result": $("#auth_result").val(),
    "from": convertDate($("#date_from").val()),
    "to": convertDate($("#date_to").val())
  };

  $("#results_h")[0].innerHTML = "";
  $("#results")[0].innerHTML = "";
  $("#results")[0].style.visibility = "hidden";

  $.ajax({
    type: 'post',
    url: "/search",
    dataType: "json",
    data: input,
    success: function(json){
    },
    complete: function(json){
      $("#myModal").modal('hide');
      //$("#search")[0].disabled = false;
      //addValidation();

      if(json.responseText == "[]") {
        $("#results_h")[0].innerHTML = "Zadaným parametrům vyhledávání neodpovídají žádné záznamy";
      }
      else {
        $("#results_h")[0].innerHTML = "Nalezené záznamy";
        document.getElementById("results").style.visibility = "visible";

        // table setup
        // reversed order
        var head = ["výsledek autentizace", "uživatelské jméno", "mac adresa", "navštívená instituce", "navštívená země", "realm", "timestamp"];
        var attributes = ["result", "pn", "csi", "visinst", "viscountry", "realm", "timestamp"];
        var size = attributes.length;

        createTableHeader(size, head);
        createTableBody(json, attributes, size);
      }
    }
  });
}
// --------------------------------------------------------------------------------------
$(document).ready(function(){

// --------------------------------------------------------------------------------------

  $("#results")[0].style.visibility = "hidden";
// --------------------------------------------------------------------------------------
  /* search by enter key */
  $(document).keypress(function(e) {
    if(e.which == 13)    /* enter */
      $("#search").click();
  });

// --------------------------------------------------------------------------------------

  // display datetimepicker on click on date_from
  $("#date_from").datetimepicker({
      locale: 'cs',
      format: 'DD.MM.YYYY HH:mm'
    }).on('changeDate show update click', function(e) {
      $('#main_form').formValidation('revalidateField', 'date_from');
    });

  // display datetimepicker on click on date_to
  $("#date_to").datetimepicker({
      locale: 'cs',
      format: 'DD.MM.YYYY HH:mm'
    }).on('changeDate show update click', function(e) {
      $('#main_form').formValidation('revalidateField', 'date_to');
    });

// --------------------------------------------------------------------------------------
  $("#search").click(function(){
    // TODO - zakaz manualniho vstupu pro date_from, date_to ?
    // TODO - spravna validace pri pridani retezce k datumu a kliknuti mimo -> zjistit, ktery event to je
    // TODO - razeni vyslednych dat podle vysledku autentizace
    // TODO - nastavit maximalni a minimalni data
    // TODO - nastavit implicitni zacatek a konec dne pro vyber datumu
    // TODO - u datumu neni videt cely placeholder
    search();
  });

// --------------------------------------------------------------------------------------
    // TODO - rozdelit na search.js a roaming.js ?

    //$("#roaming").click(function(){
    //  $.ajax({
    //    url: "/roaming/",
    //    success: function(json){
    //    },
    //    complete: function(json){
    //      //console.log(json);
    //
    //      if(json.responseText == "[]") {
    //        $("#results_h")[0].innerHTML = "Zadaným parametrům vyhledávání neodpovídají žádné záznamy";
    //      }
    //      else {
    //        $("#results_h")[0].innerHTML = "Nalezené záznamy";
    //        $("#results")[0].innerHTML = json.responseText;
    //      }
    //    }
    //  });
    //});

// --------------------------------------------------------------------------------------
// taken from http://formvalidation.io
// --------------------------------------------------------------------------------------
// custom validation

    FormValidation.Validator.username = {
        validate: function(validator, $field, options) {
            var value = $field.val();

            if (value === '')
                return true;

            // TODO - jak muze vypadat username ? specialni znaky ve jmenu - tecky, .... ?

            var matcher = new RegExp("^[a-zA-Z0-9]+@([a-zA-Z]+\\.){1,}[a-zA-Z]+$", "i");
            var found = matcher.test(value);

            if(found)
                return true;

            return {
                valid: false,
                message: 'Není platné uživatelské jméno.'
            }
        }
    };
// --------------------------------------------------------------------------------------

    FormValidation.Validator.mac = {
        validate: function(validator, $field, options) {
            var value = $field.val();

            if(value === '')
              return true;    // empty address allowed as search parameter

            else if(parseMac(value) == "")
              return {
                  valid: false,
                  message: 'Není platná mac adresa.'
              }

            return true;
        }
    };
// --------------------------------------------------------------------------------------

    // TODO - pridat nejakou logiku, ktera neumozni vyhledavat kdyz nebude zadan zadny klicovy vstup - mac/username ?
    // na urovni vyhledavani uz je vyreseno -> prazdne parametry hledani nevraceji nic
    // pripadne jeste zakazat vyhledavani, pokud je nejaky parametr neplatny?
    // ==> automaticky delaji validatory
    // ale je problem zaroven se zakazanou validaci pri submitu

    $('#main_form').formValidation({
        framework: 'bootstrap',
        //button: {
        //    selector: '#none',      // do not validate whole form on submit
        //    disabled: 'disabled'
        //},
        feedbackIcons: {
            valid: 'glyphicon glyphicon-ok',
            invalid: 'glyphicon glyphicon-remove',
            validating: 'glyphicon glyphicon-refresh'
        },
        fields: {
            username: {
                validators: {
                    username: {
                    }
                }
            },
            mac_address: {
                validators: {
                    mac: {
                    }
                }
            },
            date_from: {
                validators: {
                    date: {
                      format: 'DD\.MM\.YYYY HH:mm',
                      message: 'Není platné datum.'
                    }
                }
            },
            date_to: {
                validators: {
                    date: {
                      format: 'DD\.MM\.YYYY HH:mm',
                      message: 'Není platné datum.'
                    }
                }
            }
        }
    });
});

<|endoftext|>