<commit_before>const hbs = require('express-hbs');
const _ = require('lodash');
const debug = require('@tryghost/debug')('error-handler');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const config = require('../../../../shared/config');
const helpers = require('../../../../frontend/services/routing/helpers');
const sentry = require('../../../../shared/sentry');

const messages = {
    oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.',
    encounteredError: 'Encountered the error: ',
    whilstTryingToRender: 'whilst trying to render an error page for the error: ',
    pageNotFound: 'Page not found',
    resourceNotFound: 'Resource not found',
    actions: {
        images: {
            upload: 'upload image'
        }
    },
    userMessages: {
        BookshelfRelationsError: 'Database error, cannot {action}.',
        InternalServerError: 'Internal server error, cannot {action}.',
        IncorrectUsageError: 'Incorrect usage error, cannot {action}.',
        NotFoundError: 'Resource not found error, cannot {action}.',
        BadRequestError: 'Request not understood error, cannot {action}.',
        UnauthorizedError: 'Authorisation error, cannot {action}.',
        NoPermissionError: 'Permission error, cannot {action}.',
        ValidationError: 'Validation error, cannot {action}.',
        UnsupportedMediaTypeError: 'Unsupported media error, cannot {action}.',
        TooManyRequestsError: 'Too many requests error, cannot {action}.',
        MaintenanceError: 'Server down for maintenance, cannot {action}.',
        MethodNotAllowedError: 'Method not allowed, cannot {action}.',
        RequestEntityTooLargeError: 'Request too large, cannot {action}.',
        TokenRevocationError: 'Token is not available, cannot {action}.',
        VersionMismatchError: 'Version mismatch error, cannot {action}.',
        DataExportError: 'Error exporting content.',
        DataImportError: 'Duplicated entry, cannot save {action}.',
        DatabaseVersionError: 'Database version compatibility error, cannot {action}.',
        EmailError: 'Error sending email!',
        ThemeValidationError: 'Theme validation error, cannot {action}.',
        HostLimitError: 'Host Limit error, cannot {action}.',
        DisabledFeatureError: 'Theme validation error, the {{{helperName}}} helper is not available. Cannot {action}.',
        UpdateCollisionError: 'Saving failed! Someone else is editing this post.'
    }
};

const escapeExpression = hbs.Utils.escapeExpression;
const _private = {};
const errorHandler = {};

/**
 * This is a bare minimum setup, which allows us to render the error page
 * It uses the {{asset}} helper, and nothing more
 */
_private.createHbsEngine = () => {
    const engine = hbs.create();
    engine.registerHelper('asset', require('../../../../frontend/helpers/asset'));

    return engine.express4();
};

_private.updateStack = (err) => {
    let stackbits = err.stack.split(/\n/g);

    // We build this up backwards, so we always insert at position 1

    if (process.env.NODE_ENV === 'production' || err.statusCode === 404) {
        // In production mode, remove the stack trace
        stackbits.splice(1, stackbits.length - 1);
    } else {
        // In dev mode, clearly mark the strack trace
        stackbits.splice(1, 0, `Stack Trace:`);
    }

    // Add in our custom cotext and help methods

    if (err.help) {
        stackbits.splice(1, 0, `${err.help}`);
    }

    if (err.context) {
        stackbits.splice(1, 0, `${err.context}`);
    }

    return stackbits.join('\n');
};

/**
 * Get an error ready to be shown the the user
 *
 * @TODO: support multiple errors within one single error, see https://github.com/TryGhost/Ghost/issues/7116#issuecomment-252231809
 */
_private.prepareError = (err, req, res, next) => {
    debug(err);

    if (Array.isArray(err)) {
        err = err[0];
    }

    if (!errors.utils.isIgnitionError(err)) {
        // We need a special case for 404 errors
        // @TODO look at adding this to the GhostError class
        if (err.statusCode && err.statusCode === 404) {
            err = new errors.NotFoundError({
                err: err
            });
        } else {
            err = new errors.GhostError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        }
    }

    // used for express logging middleware see core/server/app.js
    req.err = err;

    // alternative for res.status();
    res.statusCode = err.statusCode;

    err.stack = _private.updateStack(err);

    // never cache errors
    res.set({
        'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
    });

    next(err);
};

_private.JSONErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.json({
        errors: [{
            message: err.message,
            context: err.context,
            help: err.help,
            errorType: err.errorType,
            errorDetails: err.errorDetails,
            ghostErrorCode: err.ghostErrorCode
        }]
    });
};

_private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const userError = _private.prepareUserMessage(err, req);

    res.json({
        errors: [{
            message: userError.message || null,
            context: userError.context || null,
            type: err.errorType || null,
            details: err.errorDetails || null,
            property: err.property || null,
            help: err.help || null,
            code: err.code || null,
            id: err.id || null
        }]
    });
};

_private.prepareUserMessage = (err, res) => {
    const userError = {
        message: err.message,
        context: err.context
    };

    const docName = _.get(res, 'frameOptions.docName');
    const method = _.get(res, 'frameOptions.method');

    if (docName && method) {
        let action;

        const actionMap = {
            browse: 'list',
            read: 'read',
            add: 'save',
            edit: 'edit',
            destroy: 'delete'
        };

        if (_.get(messages.actions, [docName, method])) {
            action = tpl(messages.actions[docName][method]);
        } else if (Object.keys(actionMap).includes(method)) {
            let resource = docName;

            if (method !== 'browse') {
                resource = resource.replace(/s$/, '');
            }

            action = `${actionMap[method]} ${resource}`;
        }

        if (action) {
            if (err.context) {
                userError.context = `${err.message} ${err.context}`;
            } else {
                userError.context = err.message;
            }

            userError.message = tpl(messages.userMessages[err.name], {action: action});
        }
    }

    return userError;
};

_private.ErrorFallbackMessage = err => `
${tpl(messages.oopsErrorTemplateHasError)}

${tpl(messages.encounteredError)}

${escapeExpression(err.message || err)}

${tpl(messages.whilstTryingToRender)}

     ${err.statusCode}
${escapeExpression(err.message || err)}
`;

_private.ThemeErrorRenderer = (err, req, res, next) => {
    // If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
    // Skip trying to render an HTML error, and move on to the basic error renderer
    // We do this because customised 404 templates could reference the image that's missing
    // A better long term solution might be to do this based on extension
    if (err.code === 'STATIC_FILE_NOT_FOUND') {
        return next(err);
    }

    // Renderer begin
    // Format Data
    const data = {
        message: err.message,
        // @deprecated Remove in Ghost 5.0
        code: err.statusCode,
        statusCode: err.statusCode,
        errorDetails: err.errorDetails || []
    };

    // Template
    // @TODO: very dirty !!!!!!
    helpers.templates.setTemplate(req, res);

    // It can be that something went wrong with the theme or otherwise loading handlebars
    // This ensures that no matter what res.render will work here
    // @TODO: split the error handler for assets, admin & theme to refactor this away
    if (_.isEmpty(req.app.engines)) {
        res._template = 'error';
        req.app.engine('hbs', _private.createHbsEngine());
        req.app.set('view engine', 'hbs');
        req.app.set('views', config.get('paths').defaultViews);
    }

    // @TODO use renderer here?!
    // Render Call - featuring an error handler for what happens if rendering fails
    res.render(res._template, data, (_err, html) => {
        if (!_err) {
            return res.send(html);
        }

        // re-attach new error e.g. error template has syntax error or misusage
        req.err = _err;

        // And then try to explain things to the user...
        // Cheat and output the error using handlebars escapeExpression
        return res.status(500).send(_private.ErrorFallbackMessage(_err));
    });
};

/**
 *  Borrowed heavily from finalHandler
 */

const DOUBLE_SPACE_REGEXP = /\x20{2}/g;
const NEWLINE_REGEXP = /\n/g;

function createHtmlDocument(status, message) {
    let body = escapeExpression(message)
        .replace(NEWLINE_REGEXP, '
')
        .replace(DOUBLE_SPACE_REGEXP, '  ');

    return `<!DOCTYPE html>\n
       \n
       \n
       <meta charset="utf-8">\n
       <title>${status} Error</title>\n
       \n
       \n

${status} ${body}
\n
       \n
       \n`;
}

_private.HTMLErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    return res.send(createHtmlDocument(res.statusCode, err.stack));
};

errorHandler.resourceNotFound = (req, res, next) => {
    // TODO, handle unknown resources & methods differently, so that we can also produce
    // 405 Method Not Allowed
    next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
};

errorHandler.pageNotFound = (req, res, next) => {
    next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
};

errorHandler.handleJSONResponse = [
    // Make sure the error can be served
    _private.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using JSON format
    _private.JSONErrorRenderer
];

errorHandler.handleJSONResponseV2 = [
    // Make sure the error can be served
    _private.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using JSON format
    _private.JSONErrorRendererV2
];

errorHandler.handleHTMLResponse = [
    // Make sure the error can be served
    _private.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using HTML format
    _private.HTMLErrorRenderer
];

errorHandler.handleThemeResponse = [
    // Make sure the error can be served
    _private.prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using theme template
    _private.ThemeErrorRenderer,
    // Fall back to basic if HTML is not explicitly accepted
    _private.HTMLErrorRenderer
];

module.exports = errorHandler;
<commit_msg>Cleaned code patterns in error handler<commit_after>const hbs = require('express-hbs');
const _ = require('lodash');
const debug = require('@tryghost/debug')('error-handler');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const config = require('../../../../shared/config');
const helpers = require('../../../../frontend/services/routing/helpers');
const sentry = require('../../../../shared/sentry');

const messages = {
    oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.',
    encounteredError: 'Encountered the error: ',
    whilstTryingToRender: 'whilst trying to render an error page for the error: ',
    pageNotFound: 'Page not found',
    resourceNotFound: 'Resource not found',
    actions: {
        images: {
            upload: 'upload image'
        }
    },
    userMessages: {
        BookshelfRelationsError: 'Database error, cannot {action}.',
        InternalServerError: 'Internal server error, cannot {action}.',
        IncorrectUsageError: 'Incorrect usage error, cannot {action}.',
        NotFoundError: 'Resource not found error, cannot {action}.',
        BadRequestError: 'Request not understood error, cannot {action}.',
        UnauthorizedError: 'Authorisation error, cannot {action}.',
        NoPermissionError: 'Permission error, cannot {action}.',
        ValidationError: 'Validation error, cannot {action}.',
        UnsupportedMediaTypeError: 'Unsupported media error, cannot {action}.',
        TooManyRequestsError: 'Too many requests error, cannot {action}.',
        MaintenanceError: 'Server down for maintenance, cannot {action}.',
        MethodNotAllowedError: 'Method not allowed, cannot {action}.',
        RequestEntityTooLargeError: 'Request too large, cannot {action}.',
        TokenRevocationError: 'Token is not available, cannot {action}.',
        VersionMismatchError: 'Version mismatch error, cannot {action}.',
        DataExportError: 'Error exporting content.',
        DataImportError: 'Duplicated entry, cannot save {action}.',
        DatabaseVersionError: 'Database version compatibility error, cannot {action}.',
        EmailError: 'Error sending email!',
        ThemeValidationError: 'Theme validation error, cannot {action}.',
        HostLimitError: 'Host Limit error, cannot {action}.',
        DisabledFeatureError: 'Theme validation error, the {{{helperName}}} helper is not available. Cannot {action}.',
        UpdateCollisionError: 'Saving failed! Someone else is editing this post.'
    }
};

const escapeExpression = hbs.Utils.escapeExpression;

/**
 * This is a bare minimum setup, which allows us to render the error page
 * It uses the {{asset}} helper, and nothing more
 */
const createHbsEngine = () => {
    const engine = hbs.create();
    engine.registerHelper('asset', require('../../../../frontend/helpers/asset'));

    return engine.express4();
};

const updateStack = (err) => {
    let stackbits = err.stack.split(/\n/g);

    // We build this up backwards, so we always insert at position 1

    if (process.env.NODE_ENV === 'production' || err.statusCode === 404) {
        // In production mode, remove the stack trace
        stackbits.splice(1, stackbits.length - 1);
    } else {
        // In dev mode, clearly mark the strack trace
        stackbits.splice(1, 0, `Stack Trace:`);
    }

    // Add in our custom cotext and help methods

    if (err.help) {
        stackbits.splice(1, 0, `${err.help}`);
    }

    if (err.context) {
        stackbits.splice(1, 0, `${err.context}`);
    }

    return stackbits.join('\n');
};

/**
 * Get an error ready to be shown the the user
 */
const prepareError = (err, req, res, next) => {
    debug(err);

    if (Array.isArray(err)) {
        err = err[0];
    }

    if (!errors.utils.isIgnitionError(err)) {
        // We need a special case for 404 errors
        if (err.statusCode && err.statusCode === 404) {
            err = new errors.NotFoundError({
                err: err
            });
        } else {
            err = new errors.GhostError({
                err: err,
                message: err.message,
                statusCode: err.statusCode
            });
        }
    }

    // used for express logging middleware see core/server/app.js
    req.err = err;

    // alternative for res.status();
    res.statusCode = err.statusCode;

    err.stack = updateStack(err);

    // never cache errors
    res.set({
        'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
    });

    next(err);
};

const jsonErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.json({
        errors: [{
            message: err.message,
            context: err.context,
            help: err.help,
            errorType: err.errorType,
            errorDetails: err.errorDetails,
            ghostErrorCode: err.ghostErrorCode
        }]
    });
};

const jsonErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    const userError = prepareUserMessage(err, req);

    res.json({
        errors: [{
            message: userError.message || null,
            context: userError.context || null,
            type: err.errorType || null,
            details: err.errorDetails || null,
            property: err.property || null,
            help: err.help || null,
            code: err.code || null,
            id: err.id || null
        }]
    });
};

const prepareUserMessage = (err, res) => {
    const userError = {
        message: err.message,
        context: err.context
    };

    const docName = _.get(res, 'frameOptions.docName');
    const method = _.get(res, 'frameOptions.method');

    if (docName && method) {
        let action;

        const actionMap = {
            browse: 'list',
            read: 'read',
            add: 'save',
            edit: 'edit',
            destroy: 'delete'
        };

        if (_.get(messages.actions, [docName, method])) {
            action = tpl(messages.actions[docName][method]);
        } else if (Object.keys(actionMap).includes(method)) {
            let resource = docName;

            if (method !== 'browse') {
                resource = resource.replace(/s$/, '');
            }

            action = `${actionMap[method]} ${resource}`;
        }

        if (action) {
            if (err.context) {
                userError.context = `${err.message} ${err.context}`;
            } else {
                userError.context = err.message;
            }

            userError.message = tpl(messages.userMessages[err.name], {action: action});
        }
    }

    return userError;
};

const errorFallbackMessage = err => `
${tpl(messages.oopsErrorTemplateHasError)}

${tpl(messages.encounteredError)}

${escapeExpression(err.message || err)}

${tpl(messages.whilstTryingToRender)}

     ${err.statusCode}
${escapeExpression(err.message || err)}
`;

const themeErrorRenderer = (err, req, res, next) => {
    // If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
    // Skip trying to render an HTML error, and move on to the basic error renderer
    // We do this because customised 404 templates could reference the image that's missing
    // A better long term solution might be to do this based on extension
    if (err.code === 'STATIC_FILE_NOT_FOUND') {
        return next(err);
    }

    // Renderer begin
    // Format Data
    const data = {
        message: err.message,
        // @deprecated Remove in Ghost 5.0
        code: err.statusCode,
        statusCode: err.statusCode,
        errorDetails: err.errorDetails || []
    };

    // Template
    // @TODO: very dirty !!!!!!
    helpers.templates.setTemplate(req, res);

    // It can be that something went wrong with the theme or otherwise loading handlebars
    // This ensures that no matter what res.render will work here
    // @TODO: split the error handler for assets, admin & theme to refactor this away
    if (_.isEmpty(req.app.engines)) {
        res._template = 'error';
        req.app.engine('hbs', createHbsEngine());
        req.app.set('view engine', 'hbs');
        req.app.set('views', config.get('paths').defaultViews);
    }

    // @TODO use renderer here?!
    // Render Call - featuring an error handler for what happens if rendering fails
    res.render(res._template, data, (_err, html) => {
        if (!_err) {
            return res.send(html);
        }

        // re-attach new error e.g. error template has syntax error or misusage
        req.err = _err;

        // And then try to explain things to the user...
        // Cheat and output the error using handlebars escapeExpression
        return res.status(500).send(errorFallbackMessage(_err));
    });
};

/**
 *  Borrowed heavily from finalHandler
 */

const DOUBLE_SPACE_REGEXP = /\x20{2}/g;
const NEWLINE_REGEXP = /\n/g;

function createHtmlDocument(status, message) {
    let body = escapeExpression(message)
        .replace(NEWLINE_REGEXP, '
')
        .replace(DOUBLE_SPACE_REGEXP, '  ');

    return `<!DOCTYPE html>\n
       \n
       \n
       <meta charset="utf-8">\n
       <title>${status} Error</title>\n
       \n
       \n

${status} ${body}
\n
       \n
       \n`;
}

const htmlErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
    return res.send(createHtmlDocument(res.statusCode, err.stack));
};

module.exports.resourceNotFound = (req, res, next) => {
    next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
};

module.exports.pageNotFound = (req, res, next) => {
    next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
};

module.exports.handleJSONResponse = [
    // Make sure the error can be served
    prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using JSON format
    jsonErrorRenderer
];

module.exports.handleJSONResponseV2 = [
    // Make sure the error can be served
    prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using JSON format
    jsonErrorRendererV2
];

module.exports.handleHTMLResponse = [
    // Make sure the error can be served
    prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using HTML format
    htmlErrorRenderer
];

module.exports.handleThemeResponse = [
    // Make sure the error can be served
    prepareError,
    // Handle the error in Sentry
    sentry.errorHandler,
    // Render the error using theme template
    themeErrorRenderer,
    // Fall back to basic if HTML is not explicitly accepted
    htmlErrorRenderer
];
<|endoftext|>


<commit_before>const subManager = new SubsManager();

BlazeComponent.extendComponent({
  mixins() {
    return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
  },

  calculateNextPeak() {
    const cardElement = this.find('.js-card-details');
    if (cardElement) {
      const altitude = cardElement.scrollHeight;
      this.callFirstWith(this, 'setNextPeak', altitude);
    }
  },

  reachNextPeak() {
    const activitiesComponent = this.childComponents('activities')[0];
    activitiesComponent.loadNextPage();
  },

  onCreated() {
    this.isLoaded = new ReactiveVar(false);
    this.parentComponent().parentComponent().showOverlay.set(true);
    this.parentComponent().parentComponent().mouseHasEnterCardDetails = false;
    this.calculateNextPeak();

    Meteor.subscribe('unsaved-edits');
  },

  isWatching() {
    const card = this.currentData();
    return card.findWatcher(Meteor.userId());
  },

  hiddenSystemMessages() {
    return Meteor.user().hasHiddenSystemMessages();
  },

  canModifyCard() {
    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  },

  scrollParentContainer() {
    const cardPanelWidth = 510;
    const bodyBoardComponent = this.parentComponent().parentComponent();

    const $cardContainer = bodyBoardComponent.$('.js-lists');
    const $cardView = this.$(this.firstNode());
    const cardContainerScroll = $cardContainer.scrollLeft();
    const cardContainerWidth = $cardContainer.width();

    const cardViewStart = $cardView.offset().left;
    const cardViewEnd = cardViewStart + cardPanelWidth;

    let offset = false;
    if (cardViewStart < 0) {
      offset = cardViewStart;
    } else if (cardViewEnd > cardContainerWidth) {
      offset = cardViewEnd - cardContainerWidth;
    }

    if (offset) {
      bodyBoardComponent.scrollLeft(cardContainerScroll + offset);
    }
  },

  onRendered() {
    if (!Utils.isMiniScreen()) this.scrollParentContainer();
  },

  onDestroyed() {
    this.parentComponent().parentComponent().showOverlay.set(false);
  },

  events() {
    const events = {
      [`${CSSEvents.transitionend} .js-card-details`]() {
        this.isLoaded.set(true);
      },
      [`${CSSEvents.animationend} .js-card-details`]() {
        this.isLoaded.set(true);
      },
    };

    return [{
      ...events,
      'click .js-close-card-details' () {
        Utils.goBoardId(this.data().boardId);
      },
      'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
      'submit .js-card-description' (evt) {
        evt.preventDefault();
        const description = this.currentComponent().getValue();
        this.data().setDescription(description);
      },
      'submit .js-card-details-title' (evt) {
        evt.preventDefault();
        const title = this.currentComponent().getValue().trim();
        if (title) {
          this.data().setTitle(title);
        }
      },
      'click .js-member': Popup.open('cardMember'),
      'click .js-add-members': Popup.open('cardMembers'),
      'click .js-add-labels': Popup.open('cardLabels'),
      'mouseenter .js-card-details' () {
        this.parentComponent().parentComponent().showOverlay.set(true);
        this.parentComponent().parentComponent().mouseHasEnterCardDetails = true;
      },
      'click #toggleButton'() {
        Meteor.call('toggleSystemMessages');
      },
    }];
  },
}).register('cardDetails');

// We extends the normal InlinedForm component to support UnsavedEdits draft
// feature.
(class extends InlinedForm {
  _getUnsavedEditKey() {
    return {
      fieldName: 'cardDescription',
      // XXX Recovering the currentCard identifier form a session variable is
      // fragile because this variable may change for instance if the route
      // change. We should use some component props instead.
      docId: Session.get('currentCard'),
    };
  }

  close(isReset = false) {
    if (this.isOpen.get() && !isReset) {
      const draft = this.getValue().trim();
      if (draft !== Cards.findOne(Session.get('currentCard')).description) {
        UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
      }
    }
    super.close();
  }

  reset() {
    UnsavedEdits.reset(this._getUnsavedEditKey());
    this.close(true);
  }

  events() {
    const parentEvents = InlinedForm.prototype.events()[0];
    return [{
      ...parentEvents,
      'click .js-close-inlined-form': this.reset,
    }];
  }
}).register('inlinedCardDescription');

Template.cardDetailsActionsPopup.helpers({
  isWatching() {
    return this.findWatcher(Meteor.userId());
  },

  canModifyCard() {
    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  },
});

Template.cardDetailsActionsPopup.events({
  'click .js-members': Popup.open('cardMembers'),
  'click .js-labels': Popup.open('cardLabels'),
  'click .js-attachments': Popup.open('cardAttachments'),
  'click .js-start-date': Popup.open('editCardStartDate'),
  'click .js-due-date': Popup.open('editCardDueDate'),
  'click .js-spent-time': Popup.open('editCardSpentTime'),
  'click .js-move-card': Popup.open('moveCard'),
  'click .js-copy-card': Popup.open('copyCard'),
  'click .js-move-card-to-top' (evt) {
    evt.preventDefault();
    const minOrder = _.min(this.list().cards().map((c) => c.sort));
    this.move(this.listId, minOrder - 1);
  },
  'click .js-move-card-to-bottom' (evt) {
    evt.preventDefault();
    const maxOrder = _.max(this.list().cards().map((c) => c.sort));
    this.move(this.listId, maxOrder + 1);
  },
  'click .js-archive' (evt) {
    evt.preventDefault();
    this.archive();
    Popup.close();
  },
  'click .js-more': Popup.open('cardMore'),
  'click .js-toggle-watch-card' () {
    const currentCard = this;
    const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
    Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
      if (!err && ret) Popup.close();
    });
  },
});

Template.editCardTitleForm.onRendered(function () {
  autosize(this.$('.js-edit-card-title'));
});

Template.editCardTitleForm.events({
  'keydown .js-edit-card-title' (evt) {
    // If enter key was pressed, submit the data
    // Unless the shift key is also being pressed
    if (evt.keyCode === 13 && !evt.shiftKey) {
      $('.js-submit-edit-card-title-form').click();
    }
  },
});

Template.moveCardPopup.events({
  'click .js-select-list' () {
    // XXX We should *not* get the currentCard from the global state, but
    // instead from a “component” state.
    const card = Cards.findOne(Session.get('currentCard'));
    const newListId = this._id;
    card.move(newListId);
    Popup.close();
  },
});

BlazeComponent.extendComponent({
  onCreated() {
    this.selectedBoard = new ReactiveVar(Session.get('currentBoard'));
  },

  boards() {
    const boards = Boards.find({
      archived: false,
      'members.userId': Meteor.userId(),
    }, {
      sort: ['title'],
    });
    return boards;
  },

  aBoardLists() {
    subManager.subscribe('board', this.selectedBoard.get());
    const board = Boards.findOne(this.selectedBoard.get());
    return board.lists();
  },
  events() {
    return [{
      'change .js-select-boards'(evt) {
        this.selectedBoard.set($(evt.currentTarget).val());
      },
    }];
  },
}).register('boardsAndLists');

Template.copyCardPopup.events({
  'click .js-select-list' (evt) {
    const card = Cards.findOne(Session.get('currentCard'));
    const oldId = card._id;
    card._id = null;
    card.listId = this._id;
    const list = Lists.findOne(card.listId);
    card.boardId = list.boardId;
    const textarea = $(evt.currentTarget).parents('.content').find('textarea');
    const title = textarea.val().trim();
    // insert new card to the bottom of new list
    card.sort = Lists.findOne(this._id).cards().count();

    if (title) {
      card.title = title;
      card.coverId = '';
      const _id = Cards.insert(card);
      // In case the filter is active we need to add the newly inserted card in
      // the list of exceptions -- cards that are not filtered. Otherwise the
      // card will disappear instantly.
      // See https://github.com/wekan/wekan/issues/80
      Filter.addException(_id);

      // copy checklists
      let cursor = Checklists.find({cardId: oldId});
      cursor.forEach(function() {
        'use strict';
        const checklist = arguments[0];
        checklist.cardId = _id;
        checklist._id = null;
        Checklists.insert(checklist);
      });

      // copy card comments
      cursor = CardComments.find({cardId: oldId});
      cursor.forEach(function () {
        'use strict';
        const comment = arguments[0];
        comment.cardId = _id;
        comment._id = null;
        CardComments.insert(comment);
      });
      Popup.close();
    }
  },
});

Template.cardMorePopup.events({
  'click .js-copy-card-link-to-clipboard' () {
    // Clipboard code from:
    // https://stackoverflow.com/questions/6300213/copy-selected-text-to-the-clipboard-without-using-flash-must-be-cross-browser
    const StringToCopyElement = document.getElementById('cardURL');
    StringToCopyElement.select();
    if (document.execCommand('copy')) {
      StringToCopyElement.blur();
    } else {
      document.getElementById('cardURL').selectionStart = 0;
      document.getElementById('cardURL').selectionEnd = 999;
      document.execCommand('copy');
      if (window.getSelection) {
        if (window.getSelection().empty) { // Chrome
          window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) { // Firefox
          window.getSelection().removeAllRanges();
        }
      } else if (document.selection) { // IE?
        document.selection.empty();
      }
    }
  },
  'click .js-delete': Popup.afterConfirm('cardDelete', function () {
    Popup.close();
    Cards.remove(this._id);
    Utils.goBoardId(this.boardId);
  }),
});

// Close the card details pane by pressing escape
EscapeActions.register('detailsPane',
  () => {
    Utils.goBoardId(Session.get('currentBoard'));
  },
  () => {
    return !Session.equals('currentCard', null);
  }, {
    noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
  }
);
<commit_msg>added copyManyCard<commit_after>const subManager = new SubsManager();

BlazeComponent.extendComponent({
  mixins() {
    return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
  },

  calculateNextPeak() {
    const cardElement = this.find('.js-card-details');
    if (cardElement) {
      const altitude = cardElement.scrollHeight;
      this.callFirstWith(this, 'setNextPeak', altitude);
    }
  },

  reachNextPeak() {
    const activitiesComponent = this.childComponents('activities')[0];
    activitiesComponent.loadNextPage();
  },

  onCreated() {
    this.isLoaded = new ReactiveVar(false);
    this.parentComponent().parentComponent().showOverlay.set(true);
    this.parentComponent().parentComponent().mouseHasEnterCardDetails = false;
    this.calculateNextPeak();

    Meteor.subscribe('unsaved-edits');
  },

  isWatching() {
    const card = this.currentData();
    return card.findWatcher(Meteor.userId());
  },

  hiddenSystemMessages() {
    return Meteor.user().hasHiddenSystemMessages();
  },

  canModifyCard() {
    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  },

  scrollParentContainer() {
    const cardPanelWidth = 510;
    const bodyBoardComponent = this.parentComponent().parentComponent();

    const $cardContainer = bodyBoardComponent.$('.js-lists');
    const $cardView = this.$(this.firstNode());
    const cardContainerScroll = $cardContainer.scrollLeft();
    const cardContainerWidth = $cardContainer.width();

    const cardViewStart = $cardView.offset().left;
    const cardViewEnd = cardViewStart + cardPanelWidth;

    let offset = false;
    if (cardViewStart < 0) {
      offset = cardViewStart;
    } else if (cardViewEnd > cardContainerWidth) {
      offset = cardViewEnd - cardContainerWidth;
    }

    if (offset) {
      bodyBoardComponent.scrollLeft(cardContainerScroll + offset);
    }
  },

  onRendered() {
    if (!Utils.isMiniScreen()) this.scrollParentContainer();
  },

  onDestroyed() {
    this.parentComponent().parentComponent().showOverlay.set(false);
  },

  events() {
    const events = {
      [`${CSSEvents.transitionend} .js-card-details`]() {
        this.isLoaded.set(true);
      },
      [`${CSSEvents.animationend} .js-card-details`]() {
        this.isLoaded.set(true);
      },
    };

    return [{
      ...events,
      'click .js-close-card-details' () {
        Utils.goBoardId(this.data().boardId);
      },
      'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
      'submit .js-card-description' (evt) {
        evt.preventDefault();
        const description = this.currentComponent().getValue();
        this.data().setDescription(description);
      },
      'submit .js-card-details-title' (evt) {
        evt.preventDefault();
        const title = this.currentComponent().getValue().trim();
        if (title) {
          this.data().setTitle(title);
        }
      },
      'click .js-member': Popup.open('cardMember'),
      'click .js-add-members': Popup.open('cardMembers'),
      'click .js-add-labels': Popup.open('cardLabels'),
      'mouseenter .js-card-details' () {
        this.parentComponent().parentComponent().showOverlay.set(true);
        this.parentComponent().parentComponent().mouseHasEnterCardDetails = true;
      },
      'click #toggleButton'() {
        Meteor.call('toggleSystemMessages');
      },
    }];
  },
}).register('cardDetails');

// We extends the normal InlinedForm component to support UnsavedEdits draft
// feature.
(class extends InlinedForm {
  _getUnsavedEditKey() {
    return {
      fieldName: 'cardDescription',
      // XXX Recovering the currentCard identifier form a session variable is
      // fragile because this variable may change for instance if the route
      // change. We should use some component props instead.
      docId: Session.get('currentCard'),
    };
  }

  close(isReset = false) {
    if (this.isOpen.get() && !isReset) {
      const draft = this.getValue().trim();
      if (draft !== Cards.findOne(Session.get('currentCard')).description) {
        UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
      }
    }
    super.close();
  }

  reset() {
    UnsavedEdits.reset(this._getUnsavedEditKey());
    this.close(true);
  }

  events() {
    const parentEvents = InlinedForm.prototype.events()[0];
    return [{
      ...parentEvents,
      'click .js-close-inlined-form': this.reset,
    }];
  }
}).register('inlinedCardDescription');

Template.cardDetailsActionsPopup.helpers({
  isWatching() {
    return this.findWatcher(Meteor.userId());
  },

  canModifyCard() {
    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  },
});

Template.cardDetailsActionsPopup.events({
  'click .js-members': Popup.open('cardMembers'),
  'click .js-labels': Popup.open('cardLabels'),
  'click .js-attachments': Popup.open('cardAttachments'),
  'click .js-start-date': Popup.open('editCardStartDate'),
  'click .js-due-date': Popup.open('editCardDueDate'),
  'click .js-spent-time': Popup.open('editCardSpentTime'),
  'click .js-move-card': Popup.open('moveCard'),
  'click .js-copy-card': Popup.open('copyCard'),
  'click .js-copy-many-card': Popup.open('copyManyCard'),
  'click .js-move-card-to-top' (evt) {
    evt.preventDefault();
    const minOrder = _.min(this.list().cards().map((c) => c.sort));
    this.move(this.listId, minOrder - 1);
  },
  'click .js-move-card-to-bottom' (evt) {
    evt.preventDefault();
    const maxOrder = _.max(this.list().cards().map((c) => c.sort));
    this.move(this.listId, maxOrder + 1);
  },
  'click .js-archive' (evt) {
    evt.preventDefault();
    this.archive();
    Popup.close();
  },
  'click .js-more': Popup.open('cardMore'),
  'click .js-toggle-watch-card' () {
    const currentCard = this;
    const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
    Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
      if (!err && ret) Popup.close();
    });
  },
});

Template.editCardTitleForm.onRendered(function () {
  autosize(this.$('.js-edit-card-title'));
});

Template.editCardTitleForm.events({
  'keydown .js-edit-card-title' (evt) {
    // If enter key was pressed, submit the data
    // Unless the shift key is also being pressed
    if (evt.keyCode === 13 && !evt.shiftKey) {
      $('.js-submit-edit-card-title-form').click();
    }
  },
});

Template.moveCardPopup.events({
  'click .js-select-list' () {
    // XXX We should *not* get the currentCard from the global state, but
    // instead from a “component” state.
    const card = Cards.findOne(Session.get('currentCard'));
    const newListId = this._id;
    card.move(newListId);
    Popup.close();
  },
});

BlazeComponent.extendComponent({
  onCreated() {
    this.selectedBoard = new ReactiveVar(Session.get('currentBoard'));
  },

  boards() {
    const boards = Boards.find({
      archived: false,
      'members.userId': Meteor.userId(),
    }, {
      sort: ['title'],
    });
    return boards;
  },

  aBoardLists() {
    subManager.subscribe('board', this.selectedBoard.get());
    const board = Boards.findOne(this.selectedBoard.get());
    return board.lists();
  },
  events() {
    return [{
      'change .js-select-boards'(evt) {
        this.selectedBoard.set($(evt.currentTarget).val());
      },
    }];
  },
}).register('boardsAndLists');

Template.copyCardPopup.events({
  'click .js-select-list' (evt) {
    const card = Cards.findOne(Session.get('currentCard'));
    const oldId = card._id;
    card._id = null;
    card.listId = this._id;
    const list = Lists.findOne(card.listId);
    card.boardId = list.boardId;
    const textarea = $(evt.currentTarget).parents('.content').find('textarea');
    const title = textarea.val().trim();
    // insert new card to the bottom of new list
    card.sort = Lists.findOne(this._id).cards().count();

    if (title) {
      card.title = title;
      card.coverId = '';
      const _id = Cards.insert(card);
      // In case the filter is active we need to add the newly inserted card in
      // the list of exceptions -- cards that are not filtered. Otherwise the
      // card will disappear instantly.
      // See https://github.com/wekan/wekan/issues/80
      Filter.addException(_id);

      // copy checklists
      let cursor = Checklists.find({cardId: oldId});
      cursor.forEach(function() {
        'use strict';
        const checklist = arguments[0];
        checklist.cardId = _id;
        checklist._id = null;
        Checklists.insert(checklist);
      });

      // copy card comments
      cursor = CardComments.find({cardId: oldId});
      cursor.forEach(function () {
        'use strict';
        const comment = arguments[0];
        comment.cardId = _id;
        comment._id = null;
        CardComments.insert(comment);
      });
      Popup.close();
    }
  },
});

Template.cardMorePopup.events({
  'click .js-copy-card-link-to-clipboard' () {
    // Clipboard code from:
    // https://stackoverflow.com/questions/6300213/copy-selected-text-to-the-clipboard-without-using-flash-must-be-cross-browser
    const StringToCopyElement = document.getElementById('cardURL');
    StringToCopyElement.select();
    if (document.execCommand('copy')) {
      StringToCopyElement.blur();
    } else {
      document.getElementById('cardURL').selectionStart = 0;
      document.getElementById('cardURL').selectionEnd = 999;
      document.execCommand('copy');
      if (window.getSelection) {
        if (window.getSelection().empty) { // Chrome
          window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) { // Firefox
          window.getSelection().removeAllRanges();
        }
      } else if (document.selection) { // IE?
        document.selection.empty();
      }
    }
  },
  'click .js-delete': Popup.afterConfirm('cardDelete', function () {
    Popup.close();
    Cards.remove(this._id);
    Utils.goBoardId(this.boardId);
  }),
});

// Close the card details pane by pressing escape
EscapeActions.register('detailsPane',
  () => {
    Utils.goBoardId(Session.get('currentBoard'));
  },
  () => {
    return !Session.equals('currentCard', null);
  }, {
    noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
  }
);
<|endoftext|>


<commit_before>// usage: $(document).lazyload2('img')

(function ($) {
  $.fn.lazyload2 = function(selector, options) {
    if (!selector) selector = 'img';
    var images = this.find(selector).get();
    var margin = 10;
    $(window).on('scroll', function() {
      var notLoaded = [];
      $(images).each(function(i) {
        if (!$(this).data('src')) return;
        var bottom = $(window).scrollTop() + $(window).height();
        if ($(this).offset().top < bottom + margin) {
          if (this.nodeName == 'IMG') {
            $(this).attr('src', $(this).data('src'));
          }
          else {
            $(this).replaceWith($('<img />').attr($(this).data()));
          }
        }
        else {
          notLoaded.push(this);
        }
      });
      images = notLoaded;
    });
    $(window).trigger('scroll');
    return this;
  }
})(jQuery);
<commit_msg>lazyload を 100pxで反応するようにした<commit_after>// usage: $(document).lazyload2('img')

(function ($) {
  $.fn.lazyload2 = function(selector, options) {
    if (!selector) selector = 'img';
    var images = this.find(selector).get();
    var margin = 100;
    $(window).on('scroll', function() {
      var notLoaded = [];
      $(images).each(function(i) {
        if (!$(this).data('src')) return;
        var bottom = $(window).scrollTop() + $(window).height();
        if ($(this).offset().top < bottom + margin) {
          if (this.nodeName == 'IMG') {
            $(this).attr('src', $(this).data('src'));
          }
          else {
            $(this).replaceWith($('<img />').attr($(this).data()));
          }
        }
        else {
          notLoaded.push(this);
        }
      });
      images = notLoaded;
    });
    $(window).trigger('scroll');
    return this;
  }
})(jQuery);
<|endoftext|>


<commit_before>const EventEmitter	= require('events').EventEmitter;
const LFSR		= require('lfsr');
const uuidV4		= require('uuid/v4');

const SemanticSDP	= require("semantic-sdp");

const SDPInfo		= SemanticSDP.SDPInfo;
const Setup		= SemanticSDP.Setup;
const MediaInfo		= SemanticSDP.MediaInfo;
const CandidateInfo	= SemanticSDP.CandidateInfo;
const DTLSInfo		= SemanticSDP.DTLSInfo;
const ICEInfo		= SemanticSDP.ICEInfo;
const StreamInfo	= SemanticSDP.StreamInfo;
const TrackInfo		= SemanticSDP.TrackInfo;
const SourceGroupInfo	= SemanticSDP.SourceGroupInfo;

class PeerConnectionServer
{
	constructor(endpoint,tm,capabilities)
	{
		//Store stuff
		this.count = 0;
		this.endpoint = endpoint;
		this.capabilities = capabilities;
		this.tm = tm;

		//Create event emitter
		this.emitter = new EventEmitter();

		//Create our namespace
		this.ns = tm.namespace("medooze::pc");

		//LIsten for creation events
		this.ns.on("cmd",(cmd)=>{
			//Get command nme
			switch(cmd.name)
			{
				case "create" :
					//Process the sdp
					var offer = SDPInfo.expand(cmd.data);
					//Create an DTLS ICE transport in that enpoint
					const transport = this.endpoint.createTransport(offer);
					//Set RTP remote properties
					transport.setRemoteProperties(offer);

					//Create local SDP info
					const answer = offer.answer({
						dtls		: transport.getLocalDTLSInfo(),
						ice		: transport.getLocalICEInfo(),
						candidates	: this.endpoint.getLocalCandidates(),
						capabilities	: this.capabilities
					});

					//Set RTP local  properties
					transport.setLocalProperties(answer);

					//Get new transport id
					const id = this.count++;

					//Create namespace
					const pcns = this.tm.namespace("medooze::pc::"+id);

					//LIsten local events
					transport.on("outgoingtrack",(track,stream)=>{
						//Send new event
						pcns.event("addedtrack",{
							streamId: stream ? stream.getId() : "-",
							track	: track.getTrackInfo()
						});
						//Listen for close track
						track.once("stopped",()=>{
							//Send ended event
							pcns.event("removedtrack",{
								streamId:  stream ? stream.getId() : "-",
								trackId	: track.getId()
							});
						});
					}).on("outgoingstream",(stream)=>{
						//For each stream
						for (const track of stream.getTracks())
						{
							//Send new event
							pcns.event("addedtrack",{
								streamId:  stream ? stream.getId() : "-",
								track	: track.getTrackInfo()
							});
							//Listen for close track
							track.once("stopped",()=>{
								//Send ended event
								pcns.event("removedtrack",{
									streamId:  stream ? stream.getId() : "-",
									trackId	: track.getId()
								});
							});
						}
					}).on("stopped",()=>{
						//Send enven
						pcns.event("stopped");
						//Close ns
						pcns.close();

					});

					//Listen remote events
					pcns.on("event",(event)=>{
						//Get event data
						const data = event.data;
						//Depending on the event
						switch(event.name)
						{
							case "addedtrack":
							{
								//Get events
								const streamId = data.streamId;
								const trackInfo = TrackInfo.expand(data.track);
								//Get stream
								let stream = transport.getIncomingStream(streamId);
								//If we already have it
								if (!stream)
									//Create empty one
									stream = transport.createIncomingStream(new StreamInfo(streamId));
								//Create incoming track
								const track = stream.createTrack(trackInfo);
								break;
							}
							case "removedtrack":
							{
								//Get events
								const streamId = data.streamId;
								const trackId  = data.trackId;
								//Get stream
								let stream = transport.getIncomingStream(streamId)
								//If we already have it
								if (!stream)
									return;
								//Get track
								const track = stream.getTrack(trackId);
								//If no track
								if (!track)
									return;
								//Stop track
								track.stop();
								//Id stream has no more tracks
								if (!stream.getTracks().length)
									//Stop it too
									stream.stop();
								break;
							}
							case "stop":
								//Stop transport
								this.transport.stop();
						}
					});

					/**
					* New managed transport has been created by a remote peer connection client
					*
					* @event PeerConnectionServert#transport
					* @argument {Transport} transport An initialized transport
					* @type {object}
					*/
					this.emitter.emit("transport",transport);

					//Done
					cmd.accept({
						id		: id,
						dtls		: answer.getDTLS().plain(),
						ice		: answer.getICE().plain(),
						candidates	: this.endpoint.getLocalCandidates(),
						capabilities	: this.capabilities
					});

					break;
				defautl:
					cmd.reject("Command not recognised");
					break;
			}
		});

		//Stop when endpoint stop
		this.endpoint.on("stopped",()=>this.stop());
	}

	/**
	 * Add event listener
	 * @param {String} event	- Event name
	 * @param {function} listeener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	on()
	{
		//Delegate event listeners to event emitter
		this.emitter.on.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Add event listener once
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	once()
	{
		//Delegate event listeners to event emitter
		this.emitter.once.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Remove event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	off()
	{
		//Delegate event listeners to event emitter
		this.emitter.removeListener.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Stop the peerconnection server, will not stop the transport created by it
	 */
	stop()
	{
		//Close ns
		this.ns.close();
		//Null
		this.emitter = null;
		this.ns = null;
		this.endpoint = null;
	}
}

module.exports = PeerConnectionServer;
<commit_msg>Change transport event <-> cmd accept so no track events are sent before setting up the pc on brownser<commit_after>const EventEmitter	= require('events').EventEmitter;
const LFSR		= require('lfsr');
const uuidV4		= require('uuid/v4');

const SemanticSDP	= require("semantic-sdp");

const SDPInfo		= SemanticSDP.SDPInfo;
const Setup		= SemanticSDP.Setup;
const MediaInfo		= SemanticSDP.MediaInfo;
const CandidateInfo	= SemanticSDP.CandidateInfo;
const DTLSInfo		= SemanticSDP.DTLSInfo;
const ICEInfo		= SemanticSDP.ICEInfo;
const StreamInfo	= SemanticSDP.StreamInfo;
const TrackInfo		= SemanticSDP.TrackInfo;
const SourceGroupInfo	= SemanticSDP.SourceGroupInfo;

class PeerConnectionServer
{
	constructor(endpoint,tm,capabilities)
	{
		//Store stuff
		this.count = 0;
		this.endpoint = endpoint;
		this.capabilities = capabilities;
		this.tm = tm;

		//Create event emitter
		this.emitter = new EventEmitter();

		//Create our namespace
		this.ns = tm.namespace("medooze::pc");

		//LIsten for creation events
		this.ns.on("cmd",(cmd)=>{
			//Get command nme
			switch(cmd.name)
			{
				case "create" :
					//Process the sdp
					var offer = SDPInfo.expand(cmd.data);
					//Create an DTLS ICE transport in that enpoint
					const transport = this.endpoint.createTransport(offer);
					//Set RTP remote properties
					transport.setRemoteProperties(offer);

					//Create local SDP info
					const answer = offer.answer({
						dtls		: transport.getLocalDTLSInfo(),
						ice		: transport.getLocalICEInfo(),
						candidates	: this.endpoint.getLocalCandidates(),
						capabilities	: this.capabilities
					});

					//Set RTP local  properties
					transport.setLocalProperties(answer);

					//Get new transport id
					const id = this.count++;

					//Create namespace
					const pcns = this.tm.namespace("medooze::pc::"+id);

					//LIsten local events
					transport.on("outgoingtrack",(track,stream)=>{
						//Send new event
						pcns.event("addedtrack",{
							streamId: stream ? stream.getId() : "-",
							track	: track.getTrackInfo()
						});
						//Listen for close track
						track.once("stopped",()=>{
							//Send ended event
							pcns.event("removedtrack",{
								streamId:  stream ? stream.getId() : "-",
								trackId	: track.getId()
							});
						});
					}).on("outgoingstream",(stream)=>{
						//For each stream
						for (const track of stream.getTracks())
						{
							//Send new event
							pcns.event("addedtrack",{
								streamId:  stream ? stream.getId() : "-",
								track	: track.getTrackInfo()
							});
							//Listen for close track
							track.once("stopped",()=>{
								//Send ended event
								pcns.event("removedtrack",{
									streamId:  stream ? stream.getId() : "-",
									trackId	: track.getId()
								});
							});
						}
					}).on("stopped",()=>{
						//Send enven
						pcns.event("stopped");
						//Close ns
						pcns.close();

					});

					//Listen remote events
					pcns.on("event",(event)=>{
						//Get event data
						const data = event.data;
						//Depending on the event
						switch(event.name)
						{
							case "addedtrack":
							{
								//Get events
								const streamId = data.streamId;
								const trackInfo = TrackInfo.expand(data.track);
								//Get stream
								let stream = transport.getIncomingStream(streamId);
								//If we already have it
								if (!stream)
									//Create empty one
									stream = transport.createIncomingStream(new StreamInfo(streamId));
								//Create incoming track
								const track = stream.createTrack(trackInfo);
								break;
							}
							case "removedtrack":
							{
								//Get events
								const streamId = data.streamId;
								const trackId  = data.trackId;
								//Get stream
								let stream = transport.getIncomingStream(streamId)
								//If we already have it
								if (!stream)
									return;
								//Get track
								const track = stream.getTrack(trackId);
								//If no track
								if (!track)
									return;
								//Stop track
								track.stop();
								//Id stream has no more tracks
								if (!stream.getTracks().length)
									//Stop it too
									stream.stop();
								break;
							}
							case "stop":
								//Stop transport
								this.transport.stop();
						}
					});

					//Done
					cmd.accept({
						id		: id,
						dtls		: answer.getDTLS().plain(),
						ice		: answer.getICE().plain(),
						candidates	: this.endpoint.getLocalCandidates(),
						capabilities	: this.capabilities
					});

					/**
					* New managed transport has been created by a remote peer connection client
					*
					* @event PeerConnectionServert#transport
					* @argument {Transport} transport An initialized transport
					* @type {object}
					*/
					this.emitter.emit("transport",transport);

					break;
				defautl:
					cmd.reject("Command not recognised");
					break;
			}
		});

		//Stop when endpoint stop
		this.endpoint.on("stopped",()=>this.stop());
	}

	/**
	 * Add event listener
	 * @param {String} event	- Event name
	 * @param {function} listeener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	on()
	{
		//Delegate event listeners to event emitter
		this.emitter.on.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Add event listener once
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	once()
	{
		//Delegate event listeners to event emitter
		this.emitter.once.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Remove event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {PeerConnectionServer}
	 */
	off()
	{
		//Delegate event listeners to event emitter
		this.emitter.removeListener.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Stop the peerconnection server, will not stop the transport created by it
	 */
	stop()
	{
		//Close ns
		this.ns.close();
		//Null
		this.emitter = null;
		this.ns = null;
		this.endpoint = null;
	}
}

module.exports = PeerConnectionServer;
<|endoftext|>


<commit_before>
var c = function(m){console.log(m)};

var ZElement = Class.create(Delegatable, {
  initialize: function(element, attrs) {
    this.element = new Element(element, attrs || {});
    this.elements = [];
    this.binded  = [];

    this.delegate("element",
                  "addClassName",
                  "writeAttribute",
                  "readAttribute",
                  "descendants");
  },
  insert: function(z) {
    this.elements.push(z);
    this.element.insert(z.element);
  },
  tagName: function() {
    return this.element.tagName.downcase();
  },
  build: function(original) {
    //pending
    /*
    var original = $(original);

    var self = this.element;
    var descendants = [this.element, this.element.descendants()].flatten();
    var orig_descendants = [original, original.descendants()].flatten();

    data_repeat = descendants.findAll(function(e) {
      return e.hasAttribute("data-repeat");
    });
/*
    var o = [];
    data_repeat.each(function(e) {
      var t0 = $(e.ancestors().first()).getTagName();
      var find = "$0 > $1".exec(t0, e.getTagName());

    });
*/
/*
    var o = [];
    data_repeat.each(function(dr) {
      for(var i = 0, e, l = orig_descendants.size(); i < l; i++) {
        e = orig_descendants[i];

      }
    });
*/
/*
    var i = 0, j = 0, k = [];
    var e0, e1;
    while(true) {
      e1 = descendants[i]; //self
      e0 = orig_descendants[i];//orig

      if (e1.hasAttribute("data-repeat")) {
        k.push([e1, i]);
      }

      break;
    }
    */
  }

});

/** section Modules
 *
 *  Provides base interface for creation elements from String
 *  with given rules.
 *
 *  #### Example
 *
 *  var z = "(div.class1#id1)";
 *  Translator.translate(z);
 *  // ->

 *
**/
var Translator = {

  translate: function(str) {

    var tokens = [];

    str.scan(/[a-zA-Z0-9-]+|./, function(s) {
      var t = s.first().strip();

      if (!t.blank())
        tokens.push(s.first());
    });

    var Z = Class.create({
      initialize: function() {
        this._end_element   = 0;
        this._begin_element = 0;
        this._group_a = [];
        this._group_b = [];
        this._elements = [];
        this._root = null;
        this._element;

        this.delegate("this._elements", "last", "push", "pop");

        var g = {
          star  : "*",
          tilde : "~",
          sharp : "#",
          dot   : ".",
          bang  : "!",
          rb    : ")",
          lb    : "("
        };

        $H(g).eachPair(function(k, v) {
          this[k] = v;
        }.bind(this));
      },
      root: function(root) {
        if (root)
          this._root = root;
        else
          return this._root;
      },
      end: function() {
        this._end_element++;
      },
      begin: function() {
        this._begin_element++;
      },
      get_begin: function() {
        return this._begin_element;
      },
      get_end: function() {
        return this._end_element;
      }
    });

    var z = new Z();

    var group_a = [],
        group_b = [];

    var begin_element = 0,
        end_element = 0,
        elements = [],
        root = null,
        element;

    for(var i = 0, n = i+1, p = i - 1,
            length = tokens.length,
            prev, current, next, preprev;
            i < length;
            i ++, n ++, p ++) {

      next    = tokens[n];
      current = tokens[i];
      prev    = tokens[p];
      preprev = tokens[i-2];

      if (current == z.lb) {
        z.begin();

        if (next.isWorld()) {
          element = new ZElement(next);
          var last = elements.last();

          if (last) {
            elements.last().insert(element);
          }
          else {
            root = element;
          }
          elements.push(element);
        }
        else if (next == z.star) {
          continue;
        }

        else {
          throw "Expected <tag> or `*` but $0 given".exec(next);
        }
      }

      else if (current == z.rb) {
        z.end() ;
        if (preprev != z.lb)
          elements.pop();
      }

      else if (current.isWorld()) {
        if (prev.isWorld()) {
          throw "Unexpected `$0`".exec(current);
        }
        else {
          continue;
        }
      }

      else if (current == z.dot) {
        if (next.isWorld()) {
          element.addClassName(next);
        } else {
          throw "Expected `class name`, but given `$0`".exec(next);
        }
      }

      else if (current == z.tilde) {
        var preprev = tokens[i-2];

        if (prev.isWorld()) {

          var hash = group_a.detect(function(hash) {
            return hash.keys().first() == next;
          }) || $H();
          var new_hash = $H({});
          if (preprev == z.lb) //when tag
            new_hash.set(next, ["$0".exec(prev)]);
          else
            new_hash.set(next, ["$0.$1".exec(element.tagName() ,prev)]);
          hash.updateWith(new_hash, function(v1, v2) {
            var v = v1.concat(v2);
            return v;
          });
          group_a.push(hash)
        } else {
          throw "Unexpected `$0` before bind `$1`".exec(prev, current);
        }
      }

      else if (current == z.bang) {
        var preprev = tokens[i-2];

        if (prev.isWorld()) {
          var hash = group_b.detect(function(hash) {
            return hash.keys().first() == next;
          }) || $H();
          var new_hash = $H({});

          if (preprev == z.lb) //when tag
            new_hash.set(next, ["$0".exec(prev)]);
          else
            new_hash.set(next, ["$0.$1".exec(element.tagName() ,prev)]);

          hash.updateWith(new_hash, function(v1, v2) {
            var v = v1.concat(v2);
            return v;
          });
          group_b.push(hash)
        } else {
          throw "Unexpected `$0` before bind `$1`".exec(prev, current);
        }
      }

      else if (current == z.sharp) {
        if (next.isWorld()) {
          element.writeAttribute({'id': next})
        } else {
          throw "Expected `id attr`, but given `$0`".exec(next);
        }
      }
      // (*) | (tag*) | (tag.className*)
      else if (current == z.star) {
        if (!element)
          throw "Element not found";

        //(*)
        if (prev == z.lb && next == z.rb) {
          element.writeAttribute("data-break", "1");
        }
        //(tag*) | (tag.class*)
        else if (prev.isWorld()) {
          //when prev is tag data-repeat <tag>
          //when prev is class name data-repeat <tag>.className
          //otherwise error
          var repeat = element.readAttribute("data-repeat") || "";

          if (preprev == z.dot)
            if (repeat.blank())
              repeat = "$0.$1".exec(element.tagName(), prev);
            else
              repeat += ".$0".exec(prev);
          else if (preprev == z.lb)
            repeat = prev;
          else
            throw "Unexpected $0".exec(current);

          element.writeAttribute("data-repeat", repeat);
        }
        else {
          throw "Unexpected `$0` between `$1` and `$2`".exec(current, prev, next);
        }

      }

      else {
        throw "Unexpected $0".exec(current);
      }
    }

    if (begin_element != end_element) {
      root = null;
      throw "Not balance `(` `)`";
    } else {
      var _group_a = group_a.uniq(),
          _group_b = group_b.uniq();

      if (!_group_a.isEmpty()){
        if (_group_a.size() != _group_b.size()) {
          throw "Binded groupd have different size. `$0` and `$1`.\
          Inspect: $2, $3".exec(_group_a.size(), _group_b.size(),
            _group_a.inspect(), _group_b.inspect());
        }

        for(var len = _group_a.size(), i = 0,
                hash, f_value, new_hash, key, value;
                i < len; i ++) {
          new_hash = new Hash();

          key   = _group_a[i].keys().first();
          value = _group_a[i].get(key);

          f_value = _group_b.detect(function(hash) {
            return hash.keys().first() == key;
          }).get(key);

          new_hash.set("from", value);
          new_hash.set("to", f_value);

          root.binded.push(new_hash)
        }
      }

      return root;
    }
  }
};
<commit_msg>refactor<commit_after>
var c = function(m){console.log(m)};

var ZElement = Class.create(Delegatable, {
  initialize: function(element, attrs) {
    this.element = new Element(element, attrs || {});
    this.elements = [];
    this.binded  = [];

    this.delegate("element",
                  "addClassName",
                  "writeAttribute",
                  "readAttribute",
                  "descendants");
  },
  insert: function(z) {
    this.elements.push(z);
    this.element.insert(z.element);
  },
  tagName: function() {
    return this.element.tagName.downcase();
  }
});

/** section Modules
 *
 *  Provides base interface for creation elements from String
 *  with given rules.
 *
 *  #### Example
 *
 *  var z = "(div.class1#id1)";
 *  Translator.translate(z);
 *  // ->

 *
**/
var Translator = {

  translate: function(str) {

    var tokens = [];

    str.scan(/[a-zA-Z0-9-]+|./, function(s) {
      var t = s.first().strip();

      if (!t.blank())
        tokens.push(s.first());
    });

    var Z = Class.create(Delegatable, {
      initialize: function() {
        this._end_element   = 0;
        this._begin_element = 0;
        this._elements = [];
        this._root = null;
        this._element;

        this.delegate("_elements", "last", "push", "pop");

        var g = {
          star  : "*",
          tilde : "~",
          sharp : "#",
          dot   : ".",
          bang  : "!",
          rb    : ")",
          lb    : "("
        };

        $H(g).eachPair(function(k, v) {
          this[k] = v;
        }.bind(this));
      },
      root: function(root) {
        if (root)
          this._root = root;
        else
          return this._root;
      },
      element: function(e) {
        if (e)
          this._element = e;
        else
          return this._element;
      },
      end: function() {
        this._end_element++;
      },
      begin: function() {
        this._begin_element++;
      },
      get_begin: function() {
        return this._begin_element;
      },
      get_end: function() {
        return this._end_element;
      }
    });

    var z = new Z();

    var group_a = [],
        group_b = [],
        element;

    for(var i = 0, n = i+1, p = i - 1,
            length = tokens.length,
            prev, current, next, preprev;
            i < length;
            i ++, n ++, p ++) {

      next    = tokens[n];
      current = tokens[i];
      prev    = tokens[p];
      preprev = tokens[i-2];

      if (current == z.lb) {
        z.begin();
        if (next.isWorld()) {
          element = new ZElement(next);
          var last = z.last();

          if (last) {
            z.last().insert(element);
          }
          else {
            z.root(element);
          }
          z.push(element);
        }
        else if (next == z.star) {
          continue;
        }
        else {
          throw "Expected <tag> or `*` but $0 given".exec(next);
        }
      }

      else if (current == z.rb) {
        z.end() ;
        if (preprev != z.lb)
          z.pop();
      }

      else if (current.isWorld()) {
        if (prev.isWorld()) {
          throw "Unexpected `$0`".exec(current);
        }
        else {
          continue;
        }
      }

      else if (current == z.dot) {
        if (next.isWorld()) {
          element.addClassName(next);
        } else {
          throw "Expected `class name`, but given `$0`".exec(next);
        }
      }

      else if (current == z.tilde) {
        if (prev.isWorld()) {

          var hash = group_a.detect(function(hash) {
            return hash.keys().first() == next;
          }) || $H();
          var new_hash = $H({});
          if (preprev == z.lb) //when tag
            new_hash.set(next, ["$0".exec(prev)]);
          else
            new_hash.set(next, ["$0.$1".exec(element.tagName() ,prev)]);
          hash.updateWith(new_hash, function(v1, v2) {
            var v = v1.concat(v2);
            return v;
          });
          group_a.push(hash)
        } else {
          throw "Unexpected `$0` before bind `$1`".exec(prev, current);
        }
      }

      else if (current == z.bang) {

        if (prev.isWorld()) {
          var hash = group_b.detect(function(hash) {
            return hash.keys().first() == next;
          }) || $H();
          var new_hash = $H({});

          if (preprev == z.lb) //when tag
            new_hash.set(next, ["$0".exec(prev)]);
          else
            new_hash.set(next, ["$0.$1".exec(element.tagName() ,prev)]);

          hash.updateWith(new_hash, function(v1, v2) {
            var v = v1.concat(v2);
            return v;
          });
          group_b.push(hash)
        } else {
          throw "Unexpected `$0` before bind `$1`".exec(prev, current);
        }
      }

      else if (current == z.sharp) {
        if (next.isWorld()) {
          element.writeAttribute({'id': next})
        } else {
          throw "Expected `id attr`, but given `$0`".exec(next);
        }
      }
      // (*) | (tag*) | (tag.className*)
      else if (current == z.star) {
        if (!element)
          throw "Element not found";

        //(*)
        if (prev == z.lb && next == z.rb) {
          element.writeAttribute("data-break", "1");
        }
        //(tag*) | (tag.class*)
        else if (prev.isWorld()) {
          //when prev is tag data-repeat <tag>
          //when prev is class name data-repeat <tag>.className
          //otherwise error
          var repeat = element.readAttribute("data-repeat") || "";

          if (preprev == z.dot)
            if (repeat.blank())
              repeat = "$0.$1".exec(element.tagName(), prev);
            else
              repeat += ".$0".exec(prev);
          else if (preprev == z.lb)
            repeat = prev;
          else
            throw "Unexpected $0".exec(current);

          element.writeAttribute("data-repeat", repeat);
        }
        else {
          throw "Unexpected `$0` between `$1` and `$2`".exec(current, prev, next);
        }

      }

      else {
        throw "Unexpected $0".exec(current);
      }
    }

    if (z.get_begin() != z.get_end()) {
      z.root(null);
      throw "Not balance `(` `)`";
    } else {
      var _group_a = group_a.uniq(),
          _group_b = group_b.uniq();

      if (!_group_a.isEmpty()){
        if (_group_a.size() != _group_b.size()) {
          throw "Binded groupd have different size. `$0` and `$1`.\
          Inspect: $2, $3".exec(_group_a.size(), _group_b.size(),
            _group_a.inspect(), _group_b.inspect());
        }

        for(var len = _group_a.size(), i = 0,
                hash, f_value, new_hash, key, value;
                i < len; i ++) {
          new_hash = new Hash();

          key   = _group_a[i].keys().first();
          value = _group_a[i].get(key);

          f_value = _group_b.detect(function(hash) {
            return hash.keys().first() == key;
          }).get(key);

          new_hash.set("from", value);
          new_hash.set("to", f_value);

          z.root().binded.push(new_hash)
        }
      }
      return z.root();
    }
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { Row, Col, Button } from 'react-bootstrap';
import _ from 'lodash';

import UploadFileModal from '../components/ImportSubscribers/UploadFileModal';
import SubscribersTable from '../components/ImportSubscribers/SubscribersTable';
import ErrorsList from '../components/ImportSubscribers/ErrorsList';

import parseSubscriberList from '../utils/subscriberListParsers/parseSubscriberList';
import { addSubscribers } from '../actions/subscribersActions';

@connect(null, { addSubscribers })
export default class ImportSubscribers extends React.Component {
  constructor() {
    super();

    this.state = {
      fields: null,
      subscribers: null,
      errors: null
    };
  }

  handleNewFile(text) {
    const data = parseSubscriberList(text);

    // If errors were encountered don't show the
    // dodgy csv/subscribers list to users
    if (_.isEmpty(data.errors)) {
      this.setState({
        subscribers: data.subscribers,
        fields: data.fields
      });
    } else {
      this.setState({
        errors: data.errors
      });
    }
  }

  handleSubmit(e) {
    e.preventDefault();
    const newSubscribers = {
      subscribers: this.state.subscribers,
      fields: this.state.fields
    };

    this.props.addSubscribers(newSubscribers);
  }

  deleteSubscriber(subscriberId) {
    // Deletes the subscriber at subscriberId then
    // updates state accordingly
    let newSubscribers = this.state.subscribers;

    newSubscribers.splice(subscriberId, 1);

    this.setState({
      subscribers: newSubscribers
    });
  }

  cancelImport() {
    this.setState({
      subscribers: null,
      fields: null,
      errors: null
    });
  }

  render() {
    return (

Import Subscribers Import subscribers from a file

          <Row>
            <Col md={12}>

Subscribers to import

                    {(!this.state.subscribers && !this.state.errors) &&
                      <UploadFileModal handleNewFile={this.handleNewFile.bind(this)} />
                    }

                    <ErrorsList errors={this.state.errors}/>

                    <SubscribersTable
                      fields={this.state.fields}
                      subscribers={this.state.subscribers}
                      deleteSubscriber={this.deleteSubscriber.bind(this)}
                    />

                    {this.state.subscribers || this.state.errors &&
                    <Button onClick={this.cancelImport.bind(this)}>Cancel</Button>
                    }

                    {this.state.subscribers &&
                    <Button className="pull-right" bsStyle="primary" type="submit" onClick={this.handleSubmit.bind(this)}>Submit</Button>
                    }

            </Col>
          </Row>

    );
  }
}

ImportSubscribers.propTypes = {
  addSubscribers: React.PropTypes.func.isRequired
};
<commit_msg>Fix buttons not showing up after csv import<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { Row, Col, Button } from 'react-bootstrap';
import _ from 'lodash';

import UploadFileModal from '../components/ImportSubscribers/UploadFileModal';
import SubscribersTable from '../components/ImportSubscribers/SubscribersTable';
import ErrorsList from '../components/ImportSubscribers/ErrorsList';

import parseSubscriberList from '../utils/subscriberListParsers/parseSubscriberList';
import { addSubscribers } from '../actions/subscribersActions';

@connect(null, { addSubscribers })
export default class ImportSubscribers extends React.Component {
  constructor() {
    super();

    this.state = {
      fields: null,
      subscribers: null,
      errors: null
    };
  }

  handleNewFile(text) {
    const data = parseSubscriberList(text);

    // If errors were encountered don't show the
    // dodgy csv/subscribers list to users
    if (_.isEmpty(data.errors)) {
      this.setState({
        subscribers: data.subscribers,
        fields: data.fields
      });
    } else {
      this.setState({
        errors: data.errors
      });
    }
  }

  handleSubmit(e) {
    e.preventDefault();
    const newSubscribers = {
      subscribers: this.state.subscribers,
      fields: this.state.fields
    };

    this.props.addSubscribers(newSubscribers);
  }

  deleteSubscriber(subscriberId) {
    // Deletes the subscriber at subscriberId then
    // updates state accordingly
    let newSubscribers = this.state.subscribers;

    newSubscribers.splice(subscriberId, 1);

    this.setState({
      subscribers: newSubscribers
    });
  }

  cancelImport() {
    this.setState({
      subscribers: null,
      fields: null,
      errors: null
    });
  }

  render() {
    return (

Import Subscribers Import subscribers from a file

          <Row>
            <Col md={12}>

Subscribers to import

                    {(!this.state.subscribers && !this.state.errors) &&
                      <UploadFileModal handleNewFile={this.handleNewFile.bind(this)} />
                    }

                    <ErrorsList errors={this.state.errors}/>

                    <SubscribersTable
                      fields={this.state.fields}
                      subscribers={this.state.subscribers}
                      deleteSubscriber={this.deleteSubscriber.bind(this)}
                    />

                    {(this.state.subscribers || this.state.errors) &&
                    <Button onClick={this.cancelImport.bind(this)}>Cancel</Button>
                    }

                    {this.state.subscribers &&
                    <Button className="pull-right" bsStyle="primary" type="submit" onClick={this.handleSubmit.bind(this)}>Submit</Button>
                    }

            </Col>
          </Row>

    );
  }
}

ImportSubscribers.propTypes = {
  addSubscribers: React.PropTypes.func.isRequired
};
<|endoftext|>


<commit_before>jQuery.evalLog = function (text) {
  try {
    jQuery.globalEval(text.slice('throw "error";'.length));
  } catch (ex) {
    if (typeof console !== "undefined" && typeof console.error !== "undefined") {
      console.error('exception caught evaling RJS');
      console.error(ex);
      console.debug('script follows:', text);
    }
  }
  return text;
};

jQuery.jsonPayload = function (text, fallback) {
  var parsed_json = jQuery.parseJSON(text);
  if (parsed_json['explorer']) {
    return ManageIQ.explorer.process(parsed_json); // ExplorerPresenter payload
  } else {
    return fallback(text);
  }
};

$.ajaxSetup({
  accepts: {
    json: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
  },
  contents: {
    json: /application\/json/
  },
  converters: {
    "text json": function (text) {
      return jQuery.jsonPayload(text, function (text) { return jQuery.parseJSON(text); });
    },
    "text script": function (text) {
      if (text.match(/^{/)) {
        return jQuery.jsonPayload(text, function (text) { return text; });
      } else { // JavaScript payload
        jQuery.evalLog.call(this, text);
        return text;
      }
    }
  }
});
<commit_msg>jquery_overrides - log exceptions from jsonPayload as well as the old js eval<commit_after>function logError(fn) {
  return function (text) {
    try {
      fn(text);
    } catch (ex) {
      if (typeof console !== "undefined" && typeof console.error !== "undefined") {
        console.error('exception caught evaling RJS');
        console.error(ex);
        console.debug('script follows:', text);
      }
    }
    return text;
  };
}

jQuery.jsonPayload = function (text, fallback) {
  var parsed_json = jQuery.parseJSON(text);
  if (parsed_json['explorer']) {
    return ManageIQ.explorer.process(parsed_json); // ExplorerPresenter payload
  } else {
    return fallback(text);
  }
};

$.ajaxSetup({
  accepts: {
    json: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
  },
  contents: {
    json: /application\/json/
  },
  converters: {
    "text json": logError(function (text) {
      return jQuery.jsonPayload(text, function (text) { return jQuery.parseJSON(text); });
    }),
    "text script": logError(function (text) {
      if (text.match(/^{/)) {
        return jQuery.jsonPayload(text, function (text) { return text; });
      } else { // JavaScript payload
        jQuery.globalEval(text.slice('throw "error";'.length));
        return text;
      }
    }),
  }
});
<|endoftext|>


<commit_before>var OPSkinsAPI = require('../index.js');

OPSkinsAPI.prototype.getSales = function(req, callback) {
	if (typeof req === 'function') {
		callback = req;
		req = {};
	}

	this._requireKey();
	this.get("ISales", "GetSales", 1, req, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, meta.total_pages, res);
		}
	});
};

OPSkinsAPI.prototype.editPrice = function(saleID, price, callback) {
	this._requireKey();
	this.post("ISales", "EditPrice", 1, {"saleid": saleID, "price": price}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, !!res.relisted);
		}
	});
};

OPSkinsAPI.prototype.editPrices = function(sales, callback) {
	this._requireKey();

	if (typeof sales !== 'object') {
		throw new Error("The sales parameter must be an object whose keys are OPSkins sale IDs and values are new prices in USD cents");
	}

	var query = {};

	for (var saleid in sales) {
		if (!sales.hasOwnProperty(saleid)) {
			continue;
		}

		if (isNaN(saleid) || isNaN(sales[saleid]) || sales[saleid] % 1 != 0) {
			throw new Error("The sales parameter must be an object whose keys are OPSkins sale IDs and values are new prices in USD cents");
		}

		query['items[' + saleid + ']'] = sales[saleid];
	}

	this.post("ISales", "EditPriceMulti", 1, query, function(err) {
		callback(err || null);
	});
};

OPSkinsAPI.prototype.getListingLimit = function(callback) {
	this._requireKey();
	this.get("ISales", "GetListingLimit", 1, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.listing_limit);
		}
	});
};

OPSkinsAPI.prototype.listItems = function(items, callback) {
	if (!(items instanceof Array)) {
		items = [items];
	}

	this._requireKey();
	this.post("ISales", "ListItems", 1, {"items": JSON.stringify(items)}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res);
		}
	});
};

OPSkinsAPI.prototype.returnItems = function(saleids, callback) {
	this._requireKey();
	this.post("ISales", "ReturnItems", 1, {"items": saleids.join(',')}, function(err, res, meta) {
		if (err && !res) {
			callback(err);
		} else if (!res.offers) {
			callback(new Error("Malformed response"));
		} else {
			callback(null, res.offers);
		}
	});
};

OPSkinsAPI.prototype.getActiveTradeOffers = function(callback) {
	this._requireKey();
	this.get("ISales", "GetActiveTradeOffers", 1, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.offers);
		}
	});
};

OPSkinsAPI.prototype.search = function(params, callback) {
	this._requireKey();
	this.get("ISales", "Search", 1, params, function(err, res) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.sales);
		}
	});
};

OPSkinsAPI.prototype.buyItems = function(saleids, total, callback) {
	this._requireKey();
	this.post("ISales", "BuyItems", 1, {"saleids": saleids.join(','), "total": total}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.items, meta.balance);
		}
	});
};
<commit_msg>Added bumpItems method<commit_after>var OPSkinsAPI = require('../index.js');

OPSkinsAPI.prototype.getSales = function(req, callback) {
	if (typeof req === 'function') {
		callback = req;
		req = {};
	}

	this._requireKey();
	this.get("ISales", "GetSales", 1, req, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, meta.total_pages, res);
		}
	});
};

OPSkinsAPI.prototype.editPrice = function(saleID, price, callback) {
	this._requireKey();
	this.post("ISales", "EditPrice", 1, {"saleid": saleID, "price": price}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, !!res.relisted);
		}
	});
};

OPSkinsAPI.prototype.editPrices = function(sales, callback) {
	this._requireKey();

	if (typeof sales !== 'object') {
		throw new Error("The sales parameter must be an object whose keys are OPSkins sale IDs and values are new prices in USD cents");
	}

	var query = {};

	for (var saleid in sales) {
		if (!sales.hasOwnProperty(saleid)) {
			continue;
		}

		if (isNaN(saleid) || isNaN(sales[saleid]) || sales[saleid] % 1 != 0) {
			throw new Error("The sales parameter must be an object whose keys are OPSkins sale IDs and values are new prices in USD cents");
		}

		query['items[' + saleid + ']'] = sales[saleid];
	}

	this.post("ISales", "EditPriceMulti", 1, query, function(err) {
		callback(err || null);
	});
};

OPSkinsAPI.prototype.getListingLimit = function(callback) {
	this._requireKey();
	this.get("ISales", "GetListingLimit", 1, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.listing_limit);
		}
	});
};

OPSkinsAPI.prototype.listItems = function(items, callback) {
	if (!Array.isArray(items)) {
		items = [items];
	}

	this._requireKey();
	this.post("ISales", "ListItems", 1, {"items": JSON.stringify(items)}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res);
		}
	});
};

OPSkinsAPI.prototype.bumpItems = function(saleids, callback) {
	if (!Array.isArray(saleids)) {
		saleids = [saleids];
	}

	this._requireKey();
	this.post("ISales", "BumpItems", 1, {"items": saleids.join(',')}, function(err, res, meta) {
		if (err) {
			callback(err);
			return;
		}

		callback(null, meta.balance, res.sales);
	});
};

OPSkinsAPI.prototype.returnItems = function(saleids, callback) {
	if (!Array.isArray(saleids)) {
		saleids = [saleids];
	}

	this._requireKey();
	this.post("ISales", "ReturnItems", 1, {"items": saleids.join(',')}, function(err, res, meta) {
		if (err && !res) {
			callback(err);
		} else if (!res.offers) {
			callback(new Error("Malformed response"));
		} else {
			callback(null, res.offers);
		}
	});
};

OPSkinsAPI.prototype.getActiveTradeOffers = function(callback) {
	this._requireKey();
	this.get("ISales", "GetActiveTradeOffers", 1, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.offers);
		}
	});
};

OPSkinsAPI.prototype.search = function(params, callback) {
	this._requireKey();
	this.get("ISales", "Search", 1, params, function(err, res) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.sales);
		}
	});
};

OPSkinsAPI.prototype.buyItems = function(saleids, total, callback) {
	this._requireKey();
	this.post("ISales", "BuyItems", 1, {"saleids": saleids.join(','), "total": total}, function(err, res, meta) {
		if (err) {
			callback(err);
		} else {
			callback(null, res.items, meta.balance);
		}
	});
};
<|endoftext|>


<commit_before>var rndColor = function() {
   var bg_colour = Math.floor(Math.random() * 16777215).toString(16);
   bg_colour = "#"+("000000" + bg_colour).slice(-6);
   return bg_colour;
};
var sampleNames = [
   'Fe', 'Thomas', 'Kirstie', 'Wynell', 'Mario', 'Aretha', 'Cherryl', 'Ta',
   'Lindy', 'Karina', 'Sacha', 'Latesha', 'Miki', 'Janel', 'Leola', 'Romeo',
   'Roderick', 'Felica', 'Ilona', 'Nila', 'Patrina', 'Wes', 'Henry', 'Elvera',
   'Karrie', 'Jacklyn', 'Alethea', 'Emogene', 'Alphonso', 'Chandra', 'Beryl',
   'Lilly', 'Georgetta', 'Darrin', 'Deane', 'Rocio', 'Charissa', 'Simona',
   'Don', 'Arianne', 'Esther', 'Leonia', 'Karma', 'Rosemarie', 'Carolyn',
   'Miriam', 'Chastity', 'Vesta', 'Christian', 'Lashaun'
].sort();
var name = sampleNames[Math.floor(Math.random() * sampleNames.length)];
var color = rndColor();

function displayScreenMode(data) {
   var screenMode = $('#mode');
   screenMode.text(data.width + 'x' + data.height +
      (data.x >= 0 ? '+' + data.x : "" + data.x) +
      (data.y >= 0 ? '+' + data.y : "" + data.y) + ':' +
      data.scale
   );
}

function encodeScreen(data) {
   return data.width + ':' + data.height + ':' +
      data.x + ':' + data.y  + ':' +
      data.scale;
}

function decodeScreen(str) {
   var screenSplitted = str.split(':');

   return {
      width : +screenSplitted[0],
      height : +screenSplitted[1],
      x : +screenSplitted[2],
      y : +screenSplitted[3],
      scale : +screenSplitted[4]
   }
}

var screen = {
   width: window.innerWidth,
   height: window.innerHeight,
   x: 0,
   y: 0,
   scale: 1
};
var touchPoint = {
   x: 0,
   y: 0
};

var wams = new WAMS({screen: encodeScreen(screen), name: name, color: color});

$(document).ready(function() {
   var userID = $('#userID');
   userID.text(name);
   userID.css({ background: color });

   displayScreenMode(screen);
});

$(window).resize(function() {
   screen.width = window.innerWidth;
   screen.height = window.innerHeight;

   displayScreenMode(screen);
   wams.emit('resize_screen', {screen: encodeScreen(screen)});
});

wams.on('adjust_workspace', function(data) {
   data = decodeScreen(data.screen);
   displayScreenMode(data);
   screen = data;
});

function appendElement(data) {
   var elem = document.createElement(data.tag);
   for (attr in data.attributes) {
      if (data.attributes.hasOwnProperty(attr)) {
         elem.setAttribute(attr, data.attributes[attr]);
      }
   }
   elem.innerHTML = data.innerHTML || "";

   var body = document.getElementsByTagName('body');
   body[0].appendChild(elem);

   wams.addMT(elem);

   wams.on('touch', onTouch);
   wams.on('drag', onDrag);
   wams.on('release', onRelease);
}

function deleteElement(elem) {
   elem = $(elem);
   elem.remove();
}

var messageTypes = {
   new_element: appendElement
};

wams.on(WAMS.when.message_received, function(data) {
   messageTypes[data.action] && messageTypes[data.action](data.element);
});

function liftZindex(elem, num) {
   elem = $(elem);
   elem.css({ zIndex: num || 10 });
}
function lowerZindex(elem, num) {
   elem = $(elem);
   elem.css({ zIndex: num || 1 });
}
function moveElement(elem, x, y) {
   elem = $(elem);
   if (elem.hasClass('ball')) {
      elem.css({
         left: x,
         top: y
      });
   }
}
function fixTouchPoint(elem, x, y) {
   elem = $(elem);
   touchPoint.x = x - elem.offset().left;
   touchPoint.y = y - elem.offset().top;
}
function clearTouchPoint(elem) {
   touchPoint.x = 0;
   touchPoint.y = 0;
}
function lock(elem, uuid) {
   elem = $(elem);
   elem.attr('data-lock', uuid || wams.uuid);
}
function unlock(elem) {
   elem = $(elem);
   elem.attr('data-lock', "");
}
function unlocked(elem, uuid) {
   elem = $(elem);
   return (elem.attr('data-lock') === "") ||
      (elem.attr("data-lock") === uuid || wams.uuid);
}

function onTouch(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      if (unlocked(target, wams.uuid)) {
         lock(target);
         liftZindex(target);
         fixTouchPoint(target, touches[t].pageX, touches[t].pageY);
      }
   }
}

function onDrag(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      moveElement(target, touches[t].pageX - touchPoint.x,
         touches[t].pageY - touchPoint.y);
   }
}

function onRelease(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      if (unlocked(target, wams.uuid)) {
         unlock(target);
         lowerZindex(target);
         clearTouchPoint(target);
      }
   }
}

function onRemoteTouch(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         lock(target, data.source);
         liftZindex(target, 5);
      }
   });
}

function onRemoteDrag(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         moveElement(target, element.x, element.y);
      }
   })
}

function onRemoteRelease(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         unlock(target);
         lowerZindex(target);
      }
   })
}

wams.on('enable_remote', function(data) {
   var elements = data.data.element,
      type = data.data.type,
      i, len;
   for (i = 0, len = elements.length; i < len; i++) {
      var element = elements[i];
      if (!document.getElementById(element.attributes.id)) {
         appendElement(element);
         moveElement('#' + element.attributes.id, element.x, element.y);
      }
      if (type === 'touch') {
         onRemoteTouch(data.data);
      } else if (type === 'drag') {
         onRemoteDrag(data.data);
      } else if (type === 'release') {
         onRemoteRelease(data.data);
      }
   }
});

wams.on('disable_remote', function(data) {
   var elements = data.data.element,
      i, len;
   for (i = 0, len = elements.length; i < len; i++) {
      var element = elements[i];
      deleteElement('#' + element.attributes.id);
   }
});
<commit_msg>feat(remoteMTEvents): inject source of data as in wams library<commit_after>var rndColor = function() {
   var bg_colour = Math.floor(Math.random() * 16777215).toString(16);
   bg_colour = "#"+("000000" + bg_colour).slice(-6);
   return bg_colour;
};
var sampleNames = [
   'Fe', 'Thomas', 'Kirstie', 'Wynell', 'Mario', 'Aretha', 'Cherryl', 'Ta',
   'Lindy', 'Karina', 'Sacha', 'Latesha', 'Miki', 'Janel', 'Leola', 'Romeo',
   'Roderick', 'Felica', 'Ilona', 'Nila', 'Patrina', 'Wes', 'Henry', 'Elvera',
   'Karrie', 'Jacklyn', 'Alethea', 'Emogene', 'Alphonso', 'Chandra', 'Beryl',
   'Lilly', 'Georgetta', 'Darrin', 'Deane', 'Rocio', 'Charissa', 'Simona',
   'Don', 'Arianne', 'Esther', 'Leonia', 'Karma', 'Rosemarie', 'Carolyn',
   'Miriam', 'Chastity', 'Vesta', 'Christian', 'Lashaun'
].sort();
var name = sampleNames[Math.floor(Math.random() * sampleNames.length)];
var color = rndColor();

function displayScreenMode(data) {
   var screenMode = $('#mode');
   screenMode.text(data.width + 'x' + data.height +
      (data.x >= 0 ? '+' + data.x : "" + data.x) +
      (data.y >= 0 ? '+' + data.y : "" + data.y) + ':' +
      data.scale
   );
}

function encodeScreen(data) {
   return data.width + ':' + data.height + ':' +
      data.x + ':' + data.y  + ':' +
      data.scale;
}

function decodeScreen(str) {
   var screenSplitted = str.split(':');

   return {
      width : +screenSplitted[0],
      height : +screenSplitted[1],
      x : +screenSplitted[2],
      y : +screenSplitted[3],
      scale : +screenSplitted[4]
   }
}

var screen = {
   width: window.innerWidth,
   height: window.innerHeight,
   x: 0,
   y: 0,
   scale: 1
};
var touchPoint = {
   x: 0,
   y: 0
};

var wams = new WAMS({screen: encodeScreen(screen), name: name, color: color});

$(document).ready(function() {
   var userID = $('#userID');
   userID.text(name);
   userID.css({ background: color });

   displayScreenMode(screen);
});

$(window).resize(function() {
   screen.width = window.innerWidth;
   screen.height = window.innerHeight;

   displayScreenMode(screen);
   wams.emit('resize_screen', {screen: encodeScreen(screen)});
});

wams.on('adjust_workspace', function(data) {
   data = decodeScreen(data.screen);
   displayScreenMode(data);
   screen = data;
});

function appendElement(data) {
   var elem = document.createElement(data.tag);
   for (attr in data.attributes) {
      if (data.attributes.hasOwnProperty(attr)) {
         elem.setAttribute(attr, data.attributes[attr]);
      }
   }
   elem.innerHTML = data.innerHTML || "";

   var body = document.getElementsByTagName('body');
   body[0].appendChild(elem);

   wams.addMT(elem);

   wams.on('touch', onTouch);
   wams.on('drag', onDrag);
   wams.on('release', onRelease);
}

function deleteElement(elem) {
   elem = $(elem);
   elem.remove();
}

var messageTypes = {
   new_element: appendElement
};

wams.on(WAMS.when.message_received, function(data) {
   messageTypes[data.action] && messageTypes[data.action](data.element);
});

function liftZindex(elem, num) {
   elem = $(elem);
   elem.css({ zIndex: num || 10 });
}
function lowerZindex(elem, num) {
   elem = $(elem);
   elem.css({ zIndex: num || 1 });
}
function moveElement(elem, x, y) {
   elem = $(elem);
   if (elem.hasClass('ball')) {
      elem.css({
         left: x,
         top: y
      });
   }
}
function fixTouchPoint(elem, x, y) {
   elem = $(elem);
   touchPoint.x = x - elem.offset().left;
   touchPoint.y = y - elem.offset().top;
}
function clearTouchPoint(elem) {
   touchPoint.x = 0;
   touchPoint.y = 0;
}
function lock(elem, uuid) {
   elem = $(elem);
   elem.attr('data-lock', uuid || wams.uuid);
}
function unlock(elem) {
   elem = $(elem);
   elem.attr('data-lock', "");
}
function unlocked(elem, uuid) {
   elem = $(elem);
   return (elem.attr('data-lock') === "") ||
      (elem.attr("data-lock") === uuid || wams.uuid);
}

function onTouch(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      if (unlocked(target, wams.uuid)) {
         lock(target);
         liftZindex(target);
         fixTouchPoint(target, touches[t].pageX, touches[t].pageY);
      }
   }
}

function onDrag(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      moveElement(target, touches[t].pageX - touchPoint.x,
         touches[t].pageY - touchPoint.y);
   }
}

function onRelease(ev) {
   var touches = ev.gesture.touches;
   for (var t = 0, len = touches.length; t < len; t++) {
      var target = ev.target;
      if (unlocked(target, wams.uuid)) {
         unlock(target);
         lowerZindex(target);
         clearTouchPoint(target);
      }
   }
}

function onRemoteTouch(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         lock(target, data.source);
         liftZindex(target, 5);
      }
   });
}

function onRemoteDrag(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         moveElement(target, element.x, element.y);
      }
   })
}

function onRemoteRelease(data) {
   data.element.forEach(function(element) {
      var target = document.getElementById(element.attributes.id);
      if (unlocked(target, data.source)) {
         unlock(target);
         lowerZindex(target);
      }
   })
}

wams.on('enable_remote', function(data) {
   var elements = data.data.element,
      type = data.data.type,
      i, len;
   for (i = 0, len = elements.length; i < len; i++) {
      var element = elements[i];
      if (!document.getElementById(element.attributes.id)) {
         appendElement(element);
         moveElement('#' + element.attributes.id, element.x, element.y);
      }
      if (type === 'touch') {
         data.data.source = data.source;
         onRemoteTouch(data.data);
      } else if (type === 'drag') {
         data.data.source = data.source;
         onRemoteDrag(data.data);
      } else if (type === 'release') {
         data.data.source = data.source;
         onRemoteRelease(data.data);
      }
   }
});

wams.on('disable_remote', function(data) {
   var elements = data.data.element,
      i, len;
   for (i = 0, len = elements.length; i < len; i++) {
      var element = elements[i];
      deleteElement('#' + element.attributes.id);
   }
});
<|endoftext|>