<commit_before>define([
  'summernote/core/agent',
  'summernote/core/list',
  'summernote/core/dom',
  'summernote/core/range',
  'summernote/defaults',
  'summernote/EventHandler',
  'summernote/Renderer'
], function (agent, list, dom, range,
             defaults, EventHandler, Renderer) {

  // jQuery namespace for summernote
  /**
   * @class $.summernote
   *
   * summernote attribute
   *
   * @mixin defaults
   * @singleton
   *
   */
  $.summernote = $.summernote || {};

  // extends default settings
  //  - $.summernote.version
  //  - $.summernote.options
  //  - $.summernote.lang
  $.extend($.summernote, defaults);

  var renderer = new Renderer();
  var eventHandler = new EventHandler();

  $.extend($.summernote, {
    /** @property {Renderer} */
    renderer: renderer,
    /** @property {EventHandler} */
    eventHandler: eventHandler,
    /**
     * @property {Object} core
     * @property {core.agent} core.agent
     * @property {core.dom} core.dom
     * @property {core.range} core.range
     */
    core: {
      agent: agent,
      list : list,
      dom: dom,
      range: range
    },
    /**
     * @property {Object}
     * pluginEvents event list for plugins
     * event has name and callback function.
     *
     * ```
     * $.summernote.addPlugin({
     *     events : {
     *          'hello' : function(layoutInfo, value, $target) {
     *              console.log('event name is hello, value is ' + value );
     *          }
     *     }
     * })
     * ```
     *
     * * event name is data-event property.
     * * layoutInfo is a summernote layout information.
     * * value is data-value property.
     */
    pluginEvents: {},

    plugins : []
  });

  /**
   * @method addPlugin
   *
   * add Plugin in Summernote
   *
   * Summernote can make a own plugin.
   *
   * ### Define plugin
   * ```
   * // get template function
   * var tmpl = $.summernote.renderer.getTemplate();
   *
   * // add a button
   * $.summernote.addPlugin({
   *     buttons : {
   *        // "hello"  is button's namespace.
   *        "hello" : function(lang, options) {
   *            // make icon button by template function
   *            return tmpl.iconButton('fa fa-header', {
   *                // callback function name when button clicked
   *                event : 'hello',
   *                // set data-value property
   *                value : 'hello',
   *                hide : true
   *            });
   *        }
   *
   *     },
   *
   *     events : {
   *        "hello" : function(layoutInfo, value) {
   *            // here is event code
   *        }
   *     }
   * });
   * ```
   * ### Use a plugin in toolbar
   *
   * ```
   *    $("#editor").summernote({
   *    ...
   *    toolbar : [
   *        // display hello plugin in toolbar
   *        ['group', [ 'hello' ]]
   *    ]
   *    ...
   *    });
   * ```
   *
   *
   * @param {Object} plugin
   * @param {Object} [plugin.buttons] define plugin button. for detail, see to Renderer.addButtonInfo
   * @param {Object} [plugin.dialogs] define plugin dialog. for detail, see to Renderer.addDialogInfo
   * @param {Object} [plugin.events] add event in $.summernote.pluginEvents
   * @param {Object} [plugin.langs] update $.summernote.lang
   * @param {Object} [plugin.options] update $.summernote.options
   */
  $.summernote.addPlugin = function (plugin) {

    // save plugin list
    $.summernote.plugins.push(plugin);

    if (plugin.buttons) {
      $.each(plugin.buttons, function (name, button) {
        renderer.addButtonInfo(name, button);
      });
    }

    if (plugin.dialogs) {
      $.each(plugin.dialogs, function (name, dialog) {
        renderer.addDialogInfo(name, dialog);
      });
    }

    if (plugin.events) {
      $.each(plugin.events, function (name, event) {
        $.summernote.pluginEvents[name] = event;
      });
    }

    if (plugin.langs) {
      $.each(plugin.langs, function (locale, lang) {
        if ($.summernote.lang[locale]) {
          $.extend($.summernote.lang[locale], lang);
        }
      });
    }

    if (plugin.options) {
      $.extend($.summernote.options, plugin.options);
    }
  };

  /*
   * extend $.fn
   */
  $.fn.extend({
    /**
     * @method
     * Initialize summernote
     *  - create editor layout and attach Mouse and keyboard events.
     *
     * ```
     * $("#summernote").summernote( { options ..} );
     * ```
     *
     * @member $.fn
     * @param {Object|String} options reference to $.summernote.options
     * @return {this}
     */
    summernote: function () {
      // check first argument's type
      //  - {String}: External API call {{module}}.{{method}}
      //  - {Object}: init options
      var type = $.type(list.head(arguments));
      var isExternalAPICalled = type === 'string';
      var isInitOptions = type === 'object';

      // extend default options with custom user options
      var options = isInitOptions ? list.head(arguments) : {};

      options = $.extend({}, $.summernote.options, options);

      // Include langInfo in options for later use, e.g. for image drag-n-drop
      // Setup language info with en-US as default
      options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);

      if (!isExternalAPICalled && isInitOptions) {
        // override plugin
        for (var i = 0, len = $.summernote.plugins.length; i < len; i++) {
          var plugin = $.summernote.plugins[i];

          if (options.plugin[plugin.name]) {
            $.summernote.plugins[i] = $.extend(true, plugin, options.plugin[plugin.name]);
          }
        }

      }

      this.each(function (idx, holder) {
        var $holder = $(holder);

        // if layout isn't created yet, createLayout and attach events
        if (!renderer.hasNoteEditor($holder)) {
          renderer.createLayout($holder, options);

          var layoutInfo = renderer.layoutInfoFromHolder($holder);
          $holder.data('layoutInfo', layoutInfo);

          eventHandler.attach(layoutInfo, options);
          eventHandler.attachCustomEvent(layoutInfo, options);

        }
      });

      // callback on init
      if (!isExternalAPICalled && this.length && options.oninit) {
        options.oninit();
      }

      var $first = this.first();
      if ($first.length) {
        var layoutInfo = renderer.layoutInfoFromHolder($first);

        // external API
        if (isExternalAPICalled) {
          var moduleAndMethod = list.head(list.from(arguments));
          var args = list.tail(list.from(arguments));

          // TODO now external API only works for editor
          var params = [moduleAndMethod, layoutInfo.editable()].concat(args);
          return eventHandler.invoke.apply(eventHandler, params);
        } else if (options.focus) {
          // focus on first editable element for initialize editor
          layoutInfo.editable().focus();
        }
      }

      return this;
    },

    /**
     * @method
     *
     * get the HTML contents of note or set the HTML contents of note.
     *
     * * get contents
     * ```
     * var content = $("#summernote").code();
     * ```
     * * set contents
     *
     * ```
     * $("#summernote").code(html);
     * ```
     *
     * @member $.fn
     * @param {String} [html] - HTML contents(optional, set)
     * @return {this|String} - context(set) or HTML contents of note(get).
     */
    code: function (html) {
      // get the HTML contents of note
      if (html === undefined) {
        var $holder = this.first();
        if (!$holder.length) {
          return;
        }

        var layoutInfo = renderer.layoutInfoFromHolder($holder);
        var $editable = layoutInfo && layoutInfo.editable();

        if ($editable && $editable.length) {
          var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo);
          eventHandler.invoke('codeview.sync', layoutInfo);
          return isCodeview ? layoutInfo.codable().val() :
                              layoutInfo.editable().html();
        }
        return dom.value($holder);
      }

      // set the HTML contents of note
      this.each(function (i, holder) {
        var layoutInfo = renderer.layoutInfoFromHolder($(holder));
        var $editable = layoutInfo && layoutInfo.editable();
        if ($editable) {
          $editable.html(html);
        }
      });

      return this;
    },

    /**
     * @method
     *
     * destroy Editor Layout and detach Key and Mouse Event
     *
     * @member $.fn
     * @return {this}
     */
    destroy: function () {
      this.each(function (idx, holder) {
        var $holder = $(holder);

        if (!renderer.hasNoteEditor($holder)) {
          return;
        }

        var info = renderer.layoutInfoFromHolder($holder);
        var options = info.editor().data('options');

        eventHandler.detach(info, options);
        renderer.removeLayout($holder, info, options);
      });

      return this;
    }
  });
});
<commit_msg>Rename isInitOptions to hasInitOptions.<commit_after>define([
  'summernote/core/agent',
  'summernote/core/list',
  'summernote/core/dom',
  'summernote/core/range',
  'summernote/defaults',
  'summernote/EventHandler',
  'summernote/Renderer'
], function (agent, list, dom, range,
             defaults, EventHandler, Renderer) {

  // jQuery namespace for summernote
  /**
   * @class $.summernote
   *
   * summernote attribute
   *
   * @mixin defaults
   * @singleton
   *
   */
  $.summernote = $.summernote || {};

  // extends default settings
  //  - $.summernote.version
  //  - $.summernote.options
  //  - $.summernote.lang
  $.extend($.summernote, defaults);

  var renderer = new Renderer();
  var eventHandler = new EventHandler();

  $.extend($.summernote, {
    /** @property {Renderer} */
    renderer: renderer,
    /** @property {EventHandler} */
    eventHandler: eventHandler,
    /**
     * @property {Object} core
     * @property {core.agent} core.agent
     * @property {core.dom} core.dom
     * @property {core.range} core.range
     */
    core: {
      agent: agent,
      list : list,
      dom: dom,
      range: range
    },
    /**
     * @property {Object}
     * pluginEvents event list for plugins
     * event has name and callback function.
     *
     * ```
     * $.summernote.addPlugin({
     *     events : {
     *          'hello' : function(layoutInfo, value, $target) {
     *              console.log('event name is hello, value is ' + value );
     *          }
     *     }
     * })
     * ```
     *
     * * event name is data-event property.
     * * layoutInfo is a summernote layout information.
     * * value is data-value property.
     */
    pluginEvents: {},

    plugins : []
  });

  /**
   * @method addPlugin
   *
   * add Plugin in Summernote
   *
   * Summernote can make a own plugin.
   *
   * ### Define plugin
   * ```
   * // get template function
   * var tmpl = $.summernote.renderer.getTemplate();
   *
   * // add a button
   * $.summernote.addPlugin({
   *     buttons : {
   *        // "hello"  is button's namespace.
   *        "hello" : function(lang, options) {
   *            // make icon button by template function
   *            return tmpl.iconButton('fa fa-header', {
   *                // callback function name when button clicked
   *                event : 'hello',
   *                // set data-value property
   *                value : 'hello',
   *                hide : true
   *            });
   *        }
   *
   *     },
   *
   *     events : {
   *        "hello" : function(layoutInfo, value) {
   *            // here is event code
   *        }
   *     }
   * });
   * ```
   * ### Use a plugin in toolbar
   *
   * ```
   *    $("#editor").summernote({
   *    ...
   *    toolbar : [
   *        // display hello plugin in toolbar
   *        ['group', [ 'hello' ]]
   *    ]
   *    ...
   *    });
   * ```
   *
   *
   * @param {Object} plugin
   * @param {Object} [plugin.buttons] define plugin button. for detail, see to Renderer.addButtonInfo
   * @param {Object} [plugin.dialogs] define plugin dialog. for detail, see to Renderer.addDialogInfo
   * @param {Object} [plugin.events] add event in $.summernote.pluginEvents
   * @param {Object} [plugin.langs] update $.summernote.lang
   * @param {Object} [plugin.options] update $.summernote.options
   */
  $.summernote.addPlugin = function (plugin) {

    // save plugin list
    $.summernote.plugins.push(plugin);

    if (plugin.buttons) {
      $.each(plugin.buttons, function (name, button) {
        renderer.addButtonInfo(name, button);
      });
    }

    if (plugin.dialogs) {
      $.each(plugin.dialogs, function (name, dialog) {
        renderer.addDialogInfo(name, dialog);
      });
    }

    if (plugin.events) {
      $.each(plugin.events, function (name, event) {
        $.summernote.pluginEvents[name] = event;
      });
    }

    if (plugin.langs) {
      $.each(plugin.langs, function (locale, lang) {
        if ($.summernote.lang[locale]) {
          $.extend($.summernote.lang[locale], lang);
        }
      });
    }

    if (plugin.options) {
      $.extend($.summernote.options, plugin.options);
    }
  };

  /*
   * extend $.fn
   */
  $.fn.extend({
    /**
     * @method
     * Initialize summernote
     *  - create editor layout and attach Mouse and keyboard events.
     *
     * ```
     * $("#summernote").summernote( { options ..} );
     * ```
     *
     * @member $.fn
     * @param {Object|String} options reference to $.summernote.options
     * @return {this}
     */
    summernote: function () {
      // check first argument's type
      //  - {String}: External API call {{module}}.{{method}}
      //  - {Object}: init options
      var type = $.type(list.head(arguments));
      var isExternalAPICalled = type === 'string';
      var hasInitOptions = type === 'object';

      // extend default options with custom user options
      var options = hasInitOptions ? list.head(arguments) : {};

      options = $.extend({}, $.summernote.options, options);

      // Include langInfo in options for later use, e.g. for image drag-n-drop
      // Setup language info with en-US as default
      options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);

      // override plugin options
      if (!isExternalAPICalled && hasInitOptions) {
        for (var i = 0, len = $.summernote.plugins.length; i < len; i++) {
          var plugin = $.summernote.plugins[i];

          if (options.plugin[plugin.name]) {
            $.summernote.plugins[i] = $.extend(true, plugin, options.plugin[plugin.name]);
          }
        }
      }

      this.each(function (idx, holder) {
        var $holder = $(holder);

        // if layout isn't created yet, createLayout and attach events
        if (!renderer.hasNoteEditor($holder)) {
          renderer.createLayout($holder, options);

          var layoutInfo = renderer.layoutInfoFromHolder($holder);
          $holder.data('layoutInfo', layoutInfo);

          eventHandler.attach(layoutInfo, options);
          eventHandler.attachCustomEvent(layoutInfo, options);

        }
      });

      // callback on init
      if (!isExternalAPICalled && this.length && options.oninit) {
        options.oninit();
      }

      var $first = this.first();
      if ($first.length) {
        var layoutInfo = renderer.layoutInfoFromHolder($first);

        // external API
        if (isExternalAPICalled) {
          var moduleAndMethod = list.head(list.from(arguments));
          var args = list.tail(list.from(arguments));

          // TODO now external API only works for editor
          var params = [moduleAndMethod, layoutInfo.editable()].concat(args);
          return eventHandler.invoke.apply(eventHandler, params);
        } else if (options.focus) {
          // focus on first editable element for initialize editor
          layoutInfo.editable().focus();
        }
      }

      return this;
    },

    /**
     * @method
     *
     * get the HTML contents of note or set the HTML contents of note.
     *
     * * get contents
     * ```
     * var content = $("#summernote").code();
     * ```
     * * set contents
     *
     * ```
     * $("#summernote").code(html);
     * ```
     *
     * @member $.fn
     * @param {String} [html] - HTML contents(optional, set)
     * @return {this|String} - context(set) or HTML contents of note(get).
     */
    code: function (html) {
      // get the HTML contents of note
      if (html === undefined) {
        var $holder = this.first();
        if (!$holder.length) {
          return;
        }

        var layoutInfo = renderer.layoutInfoFromHolder($holder);
        var $editable = layoutInfo && layoutInfo.editable();

        if ($editable && $editable.length) {
          var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo);
          eventHandler.invoke('codeview.sync', layoutInfo);
          return isCodeview ? layoutInfo.codable().val() :
                              layoutInfo.editable().html();
        }
        return dom.value($holder);
      }

      // set the HTML contents of note
      this.each(function (i, holder) {
        var layoutInfo = renderer.layoutInfoFromHolder($(holder));
        var $editable = layoutInfo && layoutInfo.editable();
        if ($editable) {
          $editable.html(html);
        }
      });

      return this;
    },

    /**
     * @method
     *
     * destroy Editor Layout and detach Key and Mouse Event
     *
     * @member $.fn
     * @return {this}
     */
    destroy: function () {
      this.each(function (idx, holder) {
        var $holder = $(holder);

        if (!renderer.hasNoteEditor($holder)) {
          return;
        }

        var info = renderer.layoutInfoFromHolder($holder);
        var options = info.editor().data('options');

        eventHandler.detach(info, options);
        renderer.removeLayout($holder, info, options);
      });

      return this;
    }
  });
});
<|endoftext|>


<commit_before>const juice = require('juice');
const template = require('./template');
const settingsCache = require('../../services/settings/cache');
const urlUtils = require('../../../shared/url-utils');
const moment = require('moment-timezone');
const cheerio = require('cheerio');
const api = require('../../api');
const {URL} = require('url');
const mobiledocLib = require('../../lib/mobiledoc');
const htmlToText = require('html-to-text');

const getSite = () => {
    const publicSettings = settingsCache.getPublic();
    return Object.assign({}, publicSettings, {
        url: urlUtils.urlFor('home', true),
        iconUrl: publicSettings.icon ? urlUtils.urlFor('image', {image: publicSettings.icon}, true) : null
    });
};

/**
 * createUnsubscribeUrl
 *
 * Takes a member uuid and returns the url that should be used to unsubscribe
 * In case of no member uuid, generates the preview unsubscribe url - `?preview=1`
 *
 * @param {string} uuid
 */
const createUnsubscribeUrl = (uuid) => {
    const siteUrl = urlUtils.getSiteUrl();
    const unsubscribeUrl = new URL(siteUrl);
    unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
    if (uuid) {
        unsubscribeUrl.searchParams.set('uuid', uuid);
    } else {
        unsubscribeUrl.searchParams.set('preview', '1');
    }

    return unsubscribeUrl.href;
};

// NOTE: serialization is needed to make sure we are using current API and do post transformations
//       such as image URL transformation from relative to absolute
const serializePostModel = async (model) => {
    // fetch mobiledoc rather than html and plaintext so we can render email-specific contents
    const frame = {options: {context: {user: true}, formats: 'mobiledoc'}};
    const apiVersion = model.get('api_version') || 'v3';
    const docName = 'posts';

    await api.shared
        .serializers
        .handle
        .output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame);

    return frame.response[docName][0];
};

// parses templates and extracts an array of replacements with desired fallbacks
// removes %% wrappers from unknown replacement strings (modifies emailTmpl in place)
const _parseReplacements = (emailTmpl) => {
    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    // the " is necessary here because `juice` will convert "->" for email compatibility
    const REPLACEMENT_STRING_REGEX = /\{(?<memberProp>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;
    const ALLOWED_REPLACEMENTS = ['first_name'];

    const replacements = [];
    ['html', 'plaintext'].forEach((format) => {
        emailTmpl[format] = emailTmpl[format].replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {memberProp, fallback} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(memberProp)) {
                    const id = `replacement_${replacements.length + 1}`;

                    replacements.push({
                        format,
                        id,
                        match: replacementMatch,
                        memberProp,
                        fallback
                    });

                    // keeps wrapping %% for later replacement with real data
                    return replacementMatch;
                }
            }

            // removes %% so output matches user supplied content
            return replacementStr;
        });
    });

    return replacements;
};

const serialize = async (postModel, options = {isBrowserPreview: false}) => {
    const post = await serializePostModel(postModel);

    const timezone = settingsCache.get('timezone');
    const momentDate = post.published_at ? moment(post.published_at) : moment();
    post.published_at = momentDate.tz(timezone).format('DD MMM YYYY');

    post.authors = post.authors && post.authors.map(author => author.name).join(',');
    if (post.posts_meta) {
        post.email_subject = post.posts_meta.email_subject;
    }

    // we use post.excerpt as a hidden piece of text that is picked up by some email
    // clients as a "preview" when listing emails. Our current plaintext/excerpt
    // generation outputs links as "Link [https://url/]" which isn't desired in the preview
    if (!post.custom_excerpt) {
        post.excerpt = post.excerpt.replace(/\s\[http(.*?)\]/g, '');
    }

    post.html = mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(post.mobiledoc), {target: 'email'});
    // same options as used in Post model for generating plaintext but without `wordwrap: 80`
    // to avoid replacement strings being split across lines and for mail clients to handle
    // word wrapping based on user preferences
    post.plaintext = htmlToText.fromString(post.html, {
        wordwrap: false,
        ignoreImage: true,
        hideLinkHrefIfSameAsText: true,
        preserveNewlines: true,
        returnDomByDefault: true,
        uppercaseHeadings: false
    });

    let htmlTemplate = template({post, site: getSite()});
    if (options.isBrowserPreview) {
        const previewUnsubscribeUrl = createUnsubscribeUrl();
        htmlTemplate = htmlTemplate.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
    }

    // Inline css to style attributes, turn on support for pseudo classes.
    const juiceOptions = {inlinePseudoElements: true};
    let juicedHtml = juice(htmlTemplate, juiceOptions);

    // Force all links to open in new tab
    let _cheerio = cheerio.load(juicedHtml);
    _cheerio('a').attr('target','_blank');
    juicedHtml = _cheerio.html();

    // Fix any unsupported chars in Outlook
    juicedHtml = juicedHtml.replace(/'/g, ''');

    const emailTmpl = {
        subject: post.email_subject || post.title,
        html: juicedHtml,
        plaintext: post.plaintext
    };

    // Extract known replacements and clean up unknown replacement strings
    const replacements = _parseReplacements(emailTmpl);

    return {emailTmpl, replacements};
};

module.exports = {
    serialize,
    createUnsubscribeUrl
};
<commit_msg>🐛 Fixed card spacing and caption styling for member emails in Outlook<commit_after>const juice = require('juice');
const template = require('./template');
const settingsCache = require('../../services/settings/cache');
const urlUtils = require('../../../shared/url-utils');
const moment = require('moment-timezone');
const cheerio = require('cheerio');
const api = require('../../api');
const {URL} = require('url');
const mobiledocLib = require('../../lib/mobiledoc');
const htmlToText = require('html-to-text');

const getSite = () => {
    const publicSettings = settingsCache.getPublic();
    return Object.assign({}, publicSettings, {
        url: urlUtils.urlFor('home', true),
        iconUrl: publicSettings.icon ? urlUtils.urlFor('image', {image: publicSettings.icon}, true) : null
    });
};

/**
 * createUnsubscribeUrl
 *
 * Takes a member uuid and returns the url that should be used to unsubscribe
 * In case of no member uuid, generates the preview unsubscribe url - `?preview=1`
 *
 * @param {string} uuid
 */
const createUnsubscribeUrl = (uuid) => {
    const siteUrl = urlUtils.getSiteUrl();
    const unsubscribeUrl = new URL(siteUrl);
    unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
    if (uuid) {
        unsubscribeUrl.searchParams.set('uuid', uuid);
    } else {
        unsubscribeUrl.searchParams.set('preview', '1');
    }

    return unsubscribeUrl.href;
};

// NOTE: serialization is needed to make sure we are using current API and do post transformations
//       such as image URL transformation from relative to absolute
const serializePostModel = async (model) => {
    // fetch mobiledoc rather than html and plaintext so we can render email-specific contents
    const frame = {options: {context: {user: true}, formats: 'mobiledoc'}};
    const apiVersion = model.get('api_version') || 'v3';
    const docName = 'posts';

    await api.shared
        .serializers
        .handle
        .output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame);

    return frame.response[docName][0];
};

// parses templates and extracts an array of replacements with desired fallbacks
// removes %% wrappers from unknown replacement strings (modifies emailTmpl in place)
const _parseReplacements = (emailTmpl) => {
    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    // the " is necessary here because `juice` will convert "->" for email compatibility
    const REPLACEMENT_STRING_REGEX = /\{(?<memberProp>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;
    const ALLOWED_REPLACEMENTS = ['first_name'];

    const replacements = [];
    ['html', 'plaintext'].forEach((format) => {
        emailTmpl[format] = emailTmpl[format].replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {memberProp, fallback} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(memberProp)) {
                    const id = `replacement_${replacements.length + 1}`;

                    replacements.push({
                        format,
                        id,
                        match: replacementMatch,
                        memberProp,
                        fallback
                    });

                    // keeps wrapping %% for later replacement with real data
                    return replacementMatch;
                }
            }

            // removes %% so output matches user supplied content
            return replacementStr;
        });
    });

    return replacements;
};

const serialize = async (postModel, options = {isBrowserPreview: false}) => {
    const post = await serializePostModel(postModel);

    const timezone = settingsCache.get('timezone');
    const momentDate = post.published_at ? moment(post.published_at) : moment();
    post.published_at = momentDate.tz(timezone).format('DD MMM YYYY');

    post.authors = post.authors && post.authors.map(author => author.name).join(',');
    if (post.posts_meta) {
        post.email_subject = post.posts_meta.email_subject;
    }

    // we use post.excerpt as a hidden piece of text that is picked up by some email
    // clients as a "preview" when listing emails. Our current plaintext/excerpt
    // generation outputs links as "Link [https://url/]" which isn't desired in the preview
    if (!post.custom_excerpt) {
        post.excerpt = post.excerpt.replace(/\s\[http(.*?)\]/g, '');
    }

    post.html = mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(post.mobiledoc), {target: 'email'});
    // same options as used in Post model for generating plaintext but without `wordwrap: 80`
    // to avoid replacement strings being split across lines and for mail clients to handle
    // word wrapping based on user preferences
    post.plaintext = htmlToText.fromString(post.html, {
        wordwrap: false,
        ignoreImage: true,
        hideLinkHrefIfSameAsText: true,
        preserveNewlines: true,
        returnDomByDefault: true,
        uppercaseHeadings: false
    });

    let htmlTemplate = template({post, site: getSite()});
    if (options.isBrowserPreview) {
        const previewUnsubscribeUrl = createUnsubscribeUrl();
        htmlTemplate = htmlTemplate.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
    }

    // Inline css to style attributes, turn on support for pseudo classes.
    const juiceOptions = {inlinePseudoElements: true};
    let juicedHtml = juice(htmlTemplate, juiceOptions);

    // convert juiced HTML to a DOM-like interface for further manipulation
    // happens after inlining of CSS so we can change element types without worrying about styling
    let _cheerio = cheerio.load(juicedHtml);
    // force all links to open in new tab
    _cheerio('a').attr('target','_blank');
    // convert figure and figcaption to div so that Outlook applies margins
    _cheerio('figure, figcaption').each((i, elem) => (elem.tagName = 'div'));
    juicedHtml = _cheerio.html();

    // Fix any unsupported chars in Outlook
    juicedHtml = juicedHtml.replace(/'/g, ''');

    const emailTmpl = {
        subject: post.email_subject || post.title,
        html: juicedHtml,
        plaintext: post.plaintext
    };

    // Extract known replacements and clean up unknown replacement strings
    const replacements = _parseReplacements(emailTmpl);

    return {emailTmpl, replacements};
};

module.exports = {
    serialize,
    createUnsubscribeUrl
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  ListView,
  TouchableHighlight,
} from 'react-native';

import Search from './search.js'

class Home extends Component{
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows([
        'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli','Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli'
      ])
    };
  }
  _onPressLogin() {
  }

  _onPressRegister() {
  }

  _onPressStory() {
    // this.props.navigator.push({
    //   title: 'Story',
    //   component: Story
    // })
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>HOME YA</Text>
        <TouchableHighlight onPress={ () => this._onPressLogin()}><Text style={styles.nav}>Login</Text></TouchableHighlight>
        <TouchableHighlight onPress={ () => this._onPressRegister()}><Text style={styles.nav}>Register</Text></TouchableHighlight>

        <ListView
          style={styles.listItems}
          dataSource={this.state.dataSource}
          renderRow={(rowData) => <TouchableHighlight onPress={ () => this._onPressStory()}><Text style={
             styles.listText}>{rowData}</Text></TouchableHighlight>}
          renderHeader={ () => <Search />} />
      </View>
    )
  }
};

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: '#27c2dc',
    paddingTop: 22,
  },
  title: {
    flex: .5,
    backgroundColor: 'white',
    fontSize: 20,
    textAlign: 'center',
  },
  listItems: {
    flex: 9,
    backgroundColor: 'powderblue',
  },
  listText: {
    color: '#32161F',
    textAlign: 'center',
  },
  nav: {
    flex: .25,
    alignItems: 'center',
    color: 'white',
    fontFamily: 'Farah',
    backgroundColor: '#FFB30F',
    textAlign: 'center',
  }
});

module.exports = Home;
<commit_msg>Add featureStory header to listview in Home<commit_after>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  ListView,
  TouchableHighlight,
} from 'react-native';

import Search from './search.js'

class Home extends Component{
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows([
        'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli','Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli', 'Bilbo', 'Aragorn', 'Frodo', 'Legolas', 'Saruman', 'Elrond', 'Smeagol', 'Gimli'
      ])
    };
  }
  _onPressLogin() {
  }

  _onPressRegister() {
  }

  _onPressStory() {
    // this.props.navigator.push({
    //   title: 'Story',
    //   component: Story
    // })
  }

  featuredStory() {
      return(
        <View style={{backgroundColor: 'lightgrey'}}>
        <Text>"My day today was very interesting. First I woke up late and I couldn't find my clean clothes and my mom......"</Text>
        <Text style={{color: 'purple', textAlign: 'right'}}>-Ahmed</Text>
        </View>
      )
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>HOME YA</Text>
        <TouchableHighlight onPress={ () => this._onPressLogin()}><Text style={styles.nav}>Login</Text></TouchableHighlight>
        <TouchableHighlight onPress={ () => this._onPressRegister()}><Text style={styles.nav}>Register</Text></TouchableHighlight>
        <Search />
        <ListView
          style={styles.listItems}
          dataSource={this.state.dataSource}
          renderRow={(rowData) => <TouchableHighlight onPress={ () => this._onPressStory()}><Text style={
             styles.listText}>{rowData}</Text></TouchableHighlight>}
          renderHeader={ () => this.featuredStory() } />
      </View>
    )
  }
};

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: '#27c2dc',
    paddingTop: 22,
  },
  title: {
    flex: .5,
    backgroundColor: 'white',
    fontSize: 20,
    textAlign: 'center',
  },
  listItems: {
    flex: 9,
    backgroundColor: 'powderblue',
  },
  listText: {
    color: '#32161F',
    textAlign: 'center',
  },
  nav: {
    flex: .25,
    alignItems: 'center',
    color: 'white',
    fontFamily: 'Farah',
    backgroundColor: '#FFB30F',
    textAlign: 'center',
  }
});

module.exports = Home;
<|endoftext|>


<commit_before>// see https://github.com/gajus/eslint-plugin-jsdoc

module.exports = {
	// Require JSDoc comments
	'require-jsdoc': 'error',
	// Ensure JSDoc comments are valid
	'valid-jsdoc': 'error',
	// Ensures that parameter names in JSDoc match those in the function declaration.
	'jsdoc/check-param-names': 'warn',
	// Reports invalid block tag names.
	'jsdoc/check-tag-names': 'warn',
	// Reports invalid types.
	'jsdoc/check-types': 'warn',
	// Enforces a consistent padding of the block description.
	'jsdoc/newline-after-description': 'warn',
	// Requires that block description and tag description are written in complete sentences.
	'jsdoc/require-description-complete-sentence': 'warn',
	// Requires that all functions have examples.
	'jsdoc/require-example': 'off',
	// Requires a hyphen before the @param description.
	'jsdoc/require-hyphen-before-param-description': 'off',
	// Requires that all function parameters are documented.
	'jsdoc/require-param': 'warn',
	// Requires that @param tag has description value.
	'jsdoc/require-param-description': 'warn',
	// Requires that @param tag has type value.
	'jsdoc/require-param-type': 'warn',
	// Requires that @returns tag has description value.
	'jsdoc/require-returns-description': 'warn',
	// Requires that @returns tag has type value.
	'jsdoc/require-returns-type': 'warn',
};
<commit_msg>Require JSDoc for methods<commit_after>// see https://github.com/gajus/eslint-plugin-jsdoc

module.exports = {
	// Require JSDoc comments
	'require-jsdoc': [ 'error', {
		'require': {
			'MethodDefinition': true
		}
	} ],
	// Ensure JSDoc comments are valid
	'valid-jsdoc': 'error',
	// Ensures that parameter names in JSDoc match those in the function declaration.
	'jsdoc/check-param-names': 'warn',
	// Reports invalid block tag names.
	'jsdoc/check-tag-names': 'warn',
	// Reports invalid types.
	'jsdoc/check-types': 'warn',
	// Enforces a consistent padding of the block description.
	'jsdoc/newline-after-description': 'warn',
	// Requires that block description and tag description are written in complete sentences.
	'jsdoc/require-description-complete-sentence': 'warn',
	// Requires that all functions have examples.
	'jsdoc/require-example': 'off',
	// Requires a hyphen before the @param description.
	'jsdoc/require-hyphen-before-param-description': 'off',
	// Requires that all function parameters are documented.
	'jsdoc/require-param': 'warn',
	// Requires that @param tag has description value.
	'jsdoc/require-param-description': 'warn',
	// Requires that @param tag has type value.
	'jsdoc/require-param-type': 'warn',
	// Requires that @returns tag has description value.
	'jsdoc/require-returns-description': 'warn',
	// Requires that @returns tag has type value.
	'jsdoc/require-returns-type': 'warn',
};
<|endoftext|>


<commit_before>(function (window, Cryptoloji, undefined) {
  'use strict'

  var linkClipboard = null
  var linkClipboardCta = null
  var keyClipboard = null

  Cryptoloji.states.share = {
    canEnter: function () {
      // prevent user from entering a broken share without message and key
      // informations. Redirect to encrypt state
      if (!Cryptoloji.current.key) {
        Cryptoloji.stateman.go('encrypt')
        return false
      }
      return true
    },
    enter: function () {
      $('.section_main.encryption').addClass('section-show')
      $(".section_share").addClass("section-show")
      // learn more banner
      $(".learnmore_popup_share").height($('#encryptHeader').innerHeight())
      $(".learnmore_popup_share").css('y', -$(".learnmore_popup_share").innerHeight())
      //
      // $(".learnmore_popup_share").addClass("section-show")
      // $(".learnmore_popup_share").transition({delay: 1500, duration:500, y: 0, easing:'easeInOutExpo'})

      $('.section_share').css({
          y: window.innerHeight,
          height: window.innerHeight-$('#encryptHeader').innerHeight()
      })
      setTimeout(function(){
        $('.section_share').transition({duration:750, y:$('#encryptHeader').innerHeight(), easing:'easeInOutExpo', complete: function(){
          $("#share_more_arrow").addClass("shown")
        }})
      }, 0)

      // hide share elements untill they are completely loaded
      // loader should be show instead
      $('#share_elements').hide()
      $('#share_elements_loader').show()

      // prevent href navigation on click n share link
      $('#share_copytoclipboard').click(function (e) {
        e.preventDefault()
        $('#share_copyIosHint').css({scale:1.25, opacity:1})
        $('#share_copyIosHint').transition({scale:1, opacity:.65, duration:900, easing:'easeInOutExpo'})
      })

      // resize font based on screen height
      Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')

      $(window).resize(function () {
        $('.section_share').css({
          y: $('#encryptHeader').innerHeight(),
          height: window.innerHeight-$('#encryptHeader').innerHeight()
        })
        // resize font based on screen height
        Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')
        if (!Cryptoloji.mq.matches) {
          // resize share modal in mobile layout
          setShareModalCoordinates()
        }
      })

      fillLinkForClipboardCopy()
      fillKeyForClipboardCopy()

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        copyLinkToClipboardHandler()
        copyKeyToClipboardHandler()
      }

      setupShareButtons()

      // share modal is fixed in desktop layout
      if (Cryptoloji.mq.matches) {
        $('.share_key').addClass('share_key-open')
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
      } else {
        setShareModalCoordinates()
        handleShareModal()
      }
    },
    leave: function () {

      Cryptoloji.Api.reset()

      $('.section_main.encryption').removeClass('section-show')

      // only in mobile
      if (!Cryptoloji.mq.matches) {
        // close the modal than exit
        if ($('.share_key').hasClass('share_key-open')) {
          $('.share_key').removeClass('share_key-open')
          $('.share_key').transition({duration:500, y:$('.share_key').height(), easing:'easeInOutExpo', complete: function(){
            $('.share_key_emoji-item').attr('id', '')
          }})
        }
      }
      // learn more banner
      $('.learnmore_popup_share').transition({delay: 0, duration:750, y: -$('.learnmore_popup_share').innerHeight(), easing:'easeInOutExpo', complete: function(){
        $('.learnmore_popup_share').removeClass('section-show')
      }})
      $('.section_share').transition({delay: 250, duration:750, y:window.innerHeight, easing:'easeInOutExpo', complete: function(){
        $('.section_share').removeClass('section-show')
      }})

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        linkClipboard.destroy()
        keyClipboard.destroy()
      }

      // sharer cleanup
      Cryptoloji.UI.Sharer('facebook').unbind()
      Cryptoloji.UI.Sharer('twitter').unbind()
      Cryptoloji.UI.Sharer('gplus').unbind()
      Cryptoloji.UI.Sharer('whatsapp').unbind()
      Cryptoloji.UI.Sharer('mail').unbind()
      Cryptoloji.UI.Sharer('sms').unbind()

      $('#share_copytoclipboard').off()
    }
  }

  function copyLinkToClipboardHandler () {
    function _success (e) {
      console.log(e)
      e.clearSelection()

      // give feedback in place
      var oldVal = $('#share_copytoclipboard').text()
      $('#share_copytoclipboard').text('Copied!')
      $('#share_copytoclipboard').css({scale:1.25})
      $('#share_copytoclipboard').transition({scale:1, duration:900, easing:'easeInOutExpo'})
      setTimeout(function () {
        $('#share_copytoclipboard').text(oldVal)
      }, 1000)
    }
    function _error (e) {
      // select as fallback
      var textVal = $('#share_copytoclipboard').text()
      showTooltip(e.trigger, e.action)
    }

    linkClipboard = new Clipboard('#share_copytoclipboard')
    linkClipboardCta = new Clipboard('#share_copytoclipboardcta', {
      text: function(trigger) {
        // make sure to return proper content
        return $('#share_copytoclipboard').attr('data-clipboard-text')
      }
    })

    linkClipboard.on('success', _success)
    linkClipboardCta.on('success', _success)
    linkClipboard.on('error', _error)
    linkClipboardCta.on('error', _error)
  }

  function copyKeyToClipboardHandler () {
    keyClipboard = new Clipboard('#share_copykeytoclipboard')

    keyClipboard.on('success', function(e) {
      console.log('success', e)
      e.clearSelection()

      // give feedback in place
      $('.share_key_emoji-item_feedback').text('COPIED!')
      setTimeout(function () {
        $('.share_key_emoji-item_feedback').text('')
      }, 350)
    });

    keyClipboard.on('error', function(e) {
      // select as fallback
      var keyVal = $(e.trigger).attr('data-clipboard-text')
      showTooltip(e.trigger, e.action)
    });
  }

  var _tooltipTimeout = null
  function showTooltip (elem, action) {
    // get the selected key
    var x = $(elem).offset().left
    var y = $(elem).offset().top

    var actionMsg = ''
    var actionKey = (action === 'cut' ? 'X' : 'C')

    if (iosTest()) {
      // actionMsg = 'No support :('
      return
    }
    if (/Mac/i.test(navigator.userAgent)) {
      actionMsg = 'Press ⌘-' + actionKey + ' to ' + action
    } else {
      actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action
    }

    // fill the tooltip and position on the trigger elem
    $("#tooltip_share").text(actionMsg)
    TweenLite.set($("#tooltip_share"), {display: "block", opacity: 1})
    TweenLite.to($("#tooltip_share"), 0, {x: x, y: y - 165})

    if (_tooltipTimeout) {
      clearTimeout(_tooltipTimeout)
    }
    _tooltipTimeout = setTimeout(function() {
      TweenLite.to($("#tooltip_share"), 1, {opacity: 0, onComplete: function(){
        TweenLite.set($("#tooltip_share"), {display: "none"})
      }})
    }, 2000);
  }

  function fillLinkForClipboardCopy () {
    // fill copy to clipboard link input
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        // fill link
        $('#share_copytoclipboard').attr('data-clipboard-text', shareURI)
        $('#share_copytoclipboard').attr('href', shareURI)
        $('#share_copytoclipboard').text('This is the link to share')

        // show learn more banner
        //
        $(".learnmore_popup_share").addClass("section-show")
        $(".learnmore_popup_share").transition({delay: 1000, duration:2000, y: 0, easing:'easeInOutExpo'})

        // copy link fallback if ios
        if (iosTest()) {
          $('#share_copytoclipboardcta').hide()
          $('#share_copyIosHint').show()
        }

        // when share links are loaded
        // hide the loader, show the share buttons div and recalculate share m0dal height
        setTimeout(function() {
          $('#share_elements_loader').hide()
          $('#share_elements').show()

          if (!Cryptoloji.mq.matches) {
            setShareModalCoordinates()
          }
        }, 500)

      })
  }

  function fillKeyForClipboardCopy () {
    $('.share_key_emoji-item').attr('data-clipboard-text', Cryptoloji.current.key)
    // copy key fallback if ios
    if (iosTest()) {
      $('.share_key_emoji-item').text(Cryptoloji.current.key)
    }
  }

  function iosTest () {
    return /iPhone|iPad/i.test(navigator.userAgent)
  }

  function setupShareButtons () {
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        var twitterMessage = Cryptoloji.current.output.substring(0, 16) +
          '... ' +
          'I scrambled a message for you using Codemoji from @Mozilla: Try to unscramble it!'

        var mailMessage = Cryptoloji.current.output +
          '\n\n'+
          'I wrote you a message and scrambled it with Codemoji, a fun tool from Mozilla to learn about ciphers.' +
          '\n\n'+
          'Try to unscramble it on ' + shareURI

        var smsMessage = Cryptoloji.current.output +
          '\n' +
          'Check out ' + shareURI

        Cryptoloji.UI.Sharer('facebook', '#share_button_fb')
          .setShareLink('https://www.facebook.com/sharer/sharer.php')
          .addParam('u', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('twitter', '#share_button_tw')
          .setShareLink('https://twitter.com/intent/tweet')
          .addParam('url', shareURI)
          .addParam('text', twitterMessage)
          .bind()
        Cryptoloji.UI.Sharer('gplus', '#share_button_go')
          .setShareLink('https://plus.google.com/share')
          .addParam('url', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('mail', '#share_button_ma')
          .sameWindow()
          .setShareLink('mailto:')
          .addParam('subject', 'Can you read this emoji message?')
          .addParam('body', mailMessage)
          .bind()

        // $('#share_elements').show()

        if (bowser.mobile) {
          // show whatsapp and sms on mobile only
          // see https://github.com/todotoit/cryptoloji/issues/156

          Cryptoloji.UI.Sharer('whatsapp', '#share_button_wa')
          .sameWindow()
          .setShareLink('whatsapp://send')
          .addParam('text', shareURI)
          .bind()

          if (bowser.ios) {
            // see http://stackoverflow.com/a/19126326
            // why differentation for ios is needed
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setSeparator('&')
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          } else {
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          }
        } else {
          $('#share_button_wa, #share_button_sms').hide()
        }
      })
  }

    var share_modal_height = 0
    var share_modal_margin = 0
  function setShareModalCoordinates () {
    // set the height and the top y coordinate to reach when share key modal is open
    // Cryptoloji.utils.remToPx(.5) is for add a margin
    // 23 is the magic number...
    //

    // if share components are still loading calculate loader height and position
    // else calculate share buttons height and position
    if ($('.share_social_wrapper').height() === 0) {
      share_modal_height = $('body').height() - $('#share_elements_loader').position().top - $('#share_elements_loader').height() - Cryptoloji.utils.remToPx(2)
    } else {
      share_modal_height = $('body').height() - $('.share_social_wrapper').position().top - $('.share_social_wrapper').height() + Cryptoloji.utils.remToPx(.5) + 23 -$('#encryptHeader').innerHeight()
    }
    share_modal_margin = Cryptoloji.utils.remToPx(12.5)
    if (Cryptoloji.mq.matches) {
      share_modal_margin = Cryptoloji.utils.remToPx(15)
    }
    $('.share_key').css('height', share_modal_height)
    TweenLite.set($('.share_key'), {y: share_modal_height - share_modal_margin})
  }

  function _closeModal () {
    $('.share_key').removeClass('share_key-open')
    TweenLite.to($('.share_key'), .75, {y: share_modal_height - share_modal_margin, ease:Expo.easeInOut, onComplete: function(){
      $('.share_key_emoji-item').attr('id', '')
    }})
    $('.section_share .share_main_wrap').off()
  }
  function handleShareModal () {
    $('.share_key_emoji-item').on('click', function () {
      // open modal
      $('.share_key').addClass('share_key-open')
      TweenLite.to($('.share_key'), .75, {y: 0, ease:Expo.easeInOut, onComplete: function(){
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
        handleCloseShareModal()
      }})
    })
    $('#share_key_hide').on('click', function () {
      _closeModal()
    })
  }
  function handleCloseShareModal () {
    $('.section_share .share_main_wrap').on("click", function () {
      _closeModal()
    })
  }

})(window, window.Cryptoloji);
<commit_msg>twitter encode emoji fallback<commit_after>(function (window, Cryptoloji, undefined) {
  'use strict'

  var linkClipboard = null
  var linkClipboardCta = null
  var keyClipboard = null

  Cryptoloji.states.share = {
    canEnter: function () {
      // prevent user from entering a broken share without message and key
      // informations. Redirect to encrypt state
      if (!Cryptoloji.current.key) {
        Cryptoloji.stateman.go('encrypt')
        return false
      }
      return true
    },
    enter: function () {
      $('.section_main.encryption').addClass('section-show')
      $(".section_share").addClass("section-show")
      // learn more banner
      $(".learnmore_popup_share").height($('#encryptHeader').innerHeight())
      $(".learnmore_popup_share").css('y', -$(".learnmore_popup_share").innerHeight())
      //
      // $(".learnmore_popup_share").addClass("section-show")
      // $(".learnmore_popup_share").transition({delay: 1500, duration:500, y: 0, easing:'easeInOutExpo'})

      $('.section_share').css({
          y: window.innerHeight,
          height: window.innerHeight-$('#encryptHeader').innerHeight()
      })
      setTimeout(function(){
        $('.section_share').transition({duration:750, y:$('#encryptHeader').innerHeight(), easing:'easeInOutExpo', complete: function(){
          $("#share_more_arrow").addClass("shown")
        }})
      }, 0)

      // hide share elements untill they are completely loaded
      // loader should be show instead
      $('#share_elements').hide()
      $('#share_elements_loader').show()

      // prevent href navigation on click n share link
      $('#share_copytoclipboard').click(function (e) {
        e.preventDefault()
        $('#share_copyIosHint').css({scale:1.25, opacity:1})
        $('#share_copyIosHint').transition({scale:1, opacity:.65, duration:900, easing:'easeInOutExpo'})
      })

      // resize font based on screen height
      Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')

      $(window).resize(function () {
        $('.section_share').css({
          y: $('#encryptHeader').innerHeight(),
          height: window.innerHeight-$('#encryptHeader').innerHeight()
        })
        // resize font based on screen height
        Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')
        if (!Cryptoloji.mq.matches) {
          // resize share modal in mobile layout
          setShareModalCoordinates()
        }
      })

      fillLinkForClipboardCopy()
      fillKeyForClipboardCopy()

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        copyLinkToClipboardHandler()
        copyKeyToClipboardHandler()
      }

      setupShareButtons()

      // share modal is fixed in desktop layout
      if (Cryptoloji.mq.matches) {
        $('.share_key').addClass('share_key-open')
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
      } else {
        setShareModalCoordinates()
        handleShareModal()
      }
    },
    leave: function () {

      Cryptoloji.Api.reset()

      $('.section_main.encryption').removeClass('section-show')

      // only in mobile
      if (!Cryptoloji.mq.matches) {
        // close the modal than exit
        if ($('.share_key').hasClass('share_key-open')) {
          $('.share_key').removeClass('share_key-open')
          $('.share_key').transition({duration:500, y:$('.share_key').height(), easing:'easeInOutExpo', complete: function(){
            $('.share_key_emoji-item').attr('id', '')
          }})
        }
      }
      // learn more banner
      $('.learnmore_popup_share').transition({delay: 0, duration:750, y: -$('.learnmore_popup_share').innerHeight(), easing:'easeInOutExpo', complete: function(){
        $('.learnmore_popup_share').removeClass('section-show')
      }})
      $('.section_share').transition({delay: 250, duration:750, y:window.innerHeight, easing:'easeInOutExpo', complete: function(){
        $('.section_share').removeClass('section-show')
      }})

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        linkClipboard.destroy()
        keyClipboard.destroy()
      }

      // sharer cleanup
      Cryptoloji.UI.Sharer('facebook').unbind()
      Cryptoloji.UI.Sharer('twitter').unbind()
      Cryptoloji.UI.Sharer('gplus').unbind()
      Cryptoloji.UI.Sharer('whatsapp').unbind()
      Cryptoloji.UI.Sharer('mail').unbind()
      Cryptoloji.UI.Sharer('sms').unbind()

      $('#share_copytoclipboard').off()
    }
  }

  function copyLinkToClipboardHandler () {
    function _success (e) {
      console.log(e)
      e.clearSelection()

      // give feedback in place
      var oldVal = $('#share_copytoclipboard').text()
      $('#share_copytoclipboard').text('Copied!')
      $('#share_copytoclipboard').css({scale:1.25})
      $('#share_copytoclipboard').transition({scale:1, duration:900, easing:'easeInOutExpo'})
      setTimeout(function () {
        $('#share_copytoclipboard').text(oldVal)
      }, 1000)
    }
    function _error (e) {
      // select as fallback
      var textVal = $('#share_copytoclipboard').text()
      showTooltip(e.trigger, e.action)
    }

    linkClipboard = new Clipboard('#share_copytoclipboard')
    linkClipboardCta = new Clipboard('#share_copytoclipboardcta', {
      text: function(trigger) {
        // make sure to return proper content
        return $('#share_copytoclipboard').attr('data-clipboard-text')
      }
    })

    linkClipboard.on('success', _success)
    linkClipboardCta.on('success', _success)
    linkClipboard.on('error', _error)
    linkClipboardCta.on('error', _error)
  }

  function copyKeyToClipboardHandler () {
    keyClipboard = new Clipboard('#share_copykeytoclipboard')

    keyClipboard.on('success', function(e) {
      console.log('success', e)
      e.clearSelection()

      // give feedback in place
      $('.share_key_emoji-item_feedback').text('COPIED!')
      setTimeout(function () {
        $('.share_key_emoji-item_feedback').text('')
      }, 350)
    });

    keyClipboard.on('error', function(e) {
      // select as fallback
      var keyVal = $(e.trigger).attr('data-clipboard-text')
      showTooltip(e.trigger, e.action)
    });
  }

  var _tooltipTimeout = null
  function showTooltip (elem, action) {
    // get the selected key
    var x = $(elem).offset().left
    var y = $(elem).offset().top

    var actionMsg = ''
    var actionKey = (action === 'cut' ? 'X' : 'C')

    if (iosTest()) {
      // actionMsg = 'No support :('
      return
    }
    if (/Mac/i.test(navigator.userAgent)) {
      actionMsg = 'Press ⌘-' + actionKey + ' to ' + action
    } else {
      actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action
    }

    // fill the tooltip and position on the trigger elem
    $("#tooltip_share").text(actionMsg)
    TweenLite.set($("#tooltip_share"), {display: "block", opacity: 1})
    TweenLite.to($("#tooltip_share"), 0, {x: x, y: y - 165})

    if (_tooltipTimeout) {
      clearTimeout(_tooltipTimeout)
    }
    _tooltipTimeout = setTimeout(function() {
      TweenLite.to($("#tooltip_share"), 1, {opacity: 0, onComplete: function(){
        TweenLite.set($("#tooltip_share"), {display: "none"})
      }})
    }, 2000);
  }

  function fillLinkForClipboardCopy () {
    // fill copy to clipboard link input
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        // fill link
        $('#share_copytoclipboard').attr('data-clipboard-text', shareURI)
        $('#share_copytoclipboard').attr('href', shareURI)
        $('#share_copytoclipboard').text('This is the link to share')

        // show learn more banner
        //
        $(".learnmore_popup_share").addClass("section-show")
        $(".learnmore_popup_share").transition({delay: 1000, duration:2000, y: 0, easing:'easeInOutExpo'})

        // copy link fallback if ios
        if (iosTest()) {
          $('#share_copytoclipboardcta').hide()
          $('#share_copyIosHint').show()
        }

        // when share links are loaded
        // hide the loader, show the share buttons div and recalculate share m0dal height
        setTimeout(function() {
          $('#share_elements_loader').hide()
          $('#share_elements').show()

          if (!Cryptoloji.mq.matches) {
            setShareModalCoordinates()
          }
        }, 500)

      })
  }

  function fillKeyForClipboardCopy () {
    $('.share_key_emoji-item').attr('data-clipboard-text', Cryptoloji.current.key)
    // copy key fallback if ios
    if (iosTest()) {
      $('.share_key_emoji-item').text(Cryptoloji.current.key)
    }
  }

  function iosTest () {
    return /iPhone|iPad/i.test(navigator.userAgent)
  }

  function setupShareButtons () {
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        var partial = ''
        try{
          partial = encodeURIComponent(Cryptoloji.current.output.substring(0, 16))
        } catch(err) {
          partial = encodeURIComponent("🏩📒🗻😁")
        }
        var twitterMessage = partial +
          '... ' +
          'I scrambled a message for you using Codemoji from @Mozilla: Try to unscramble it!'

        var mailMessage = Cryptoloji.current.output +
          '\n\n'+
          'I wrote you a message and scrambled it with Codemoji, a fun tool from Mozilla to learn about ciphers.' +
          '\n\n'+
          'Try to unscramble it on ' + shareURI

        var smsMessage = Cryptoloji.current.output +
          '\n' +
          'Check out ' + shareURI

        console.log(shareURI)

        Cryptoloji.UI.Sharer('facebook', '#share_button_fb')
          .setShareLink('https://www.facebook.com/sharer/sharer.php')
          .addParam('u', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('twitter', '#share_button_tw')
          .setShareLink('https://twitter.com/intent/tweet')
          .addParam('url', shareURI)
          .addParam('text', twitterMessage)
          .bind()
        Cryptoloji.UI.Sharer('gplus', '#share_button_go')
          .setShareLink('https://plus.google.com/share')
          .addParam('url', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('mail', '#share_button_ma')
          .sameWindow()
          .setShareLink('mailto:')
          .addParam('subject', 'Can you read this emoji message?')
          .addParam('body', mailMessage)
          .bind()

        // $('#share_elements').show()

        if (bowser.mobile) {
          // show whatsapp and sms on mobile only
          // see https://github.com/todotoit/cryptoloji/issues/156

          Cryptoloji.UI.Sharer('whatsapp', '#share_button_wa')
          .sameWindow()
          .setShareLink('whatsapp://send')
          .addParam('text', shareURI)
          .bind()

          if (bowser.ios) {
            // see http://stackoverflow.com/a/19126326
            // why differentation for ios is needed
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setSeparator('&')
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          } else {
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          }
        } else {
          $('#share_button_wa, #share_button_sms').hide()
        }
      })
  }

    var share_modal_height = 0
    var share_modal_margin = 0
  function setShareModalCoordinates () {
    // set the height and the top y coordinate to reach when share key modal is open
    // Cryptoloji.utils.remToPx(.5) is for add a margin
    // 23 is the magic number...
    //

    // if share components are still loading calculate loader height and position
    // else calculate share buttons height and position
    if ($('.share_social_wrapper').height() === 0) {
      share_modal_height = $('body').height() - $('#share_elements_loader').position().top - $('#share_elements_loader').height() - Cryptoloji.utils.remToPx(2)
    } else {
      share_modal_height = $('body').height() - $('.share_social_wrapper').position().top - $('.share_social_wrapper').height() + Cryptoloji.utils.remToPx(.5) + 23 -$('#encryptHeader').innerHeight()
    }
    share_modal_margin = Cryptoloji.utils.remToPx(12.5)
    if (Cryptoloji.mq.matches) {
      share_modal_margin = Cryptoloji.utils.remToPx(15)
    }
    $('.share_key').css('height', share_modal_height)
    TweenLite.set($('.share_key'), {y: share_modal_height - share_modal_margin})
  }

  function _closeModal () {
    $('.share_key').removeClass('share_key-open')
    TweenLite.to($('.share_key'), .75, {y: share_modal_height - share_modal_margin, ease:Expo.easeInOut, onComplete: function(){
      $('.share_key_emoji-item').attr('id', '')
    }})
    $('.section_share .share_main_wrap').off()
  }
  function handleShareModal () {
    $('.share_key_emoji-item').on('click', function () {
      // open modal
      $('.share_key').addClass('share_key-open')
      TweenLite.to($('.share_key'), .75, {y: 0, ease:Expo.easeInOut, onComplete: function(){
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
        handleCloseShareModal()
      }})
    })
    $('#share_key_hide').on('click', function () {
      _closeModal()
    })
  }
  function handleCloseShareModal () {
    $('.section_share .share_main_wrap').on("click", function () {
      _closeModal()
    })
  }

})(window, window.Cryptoloji);
<|endoftext|>


<commit_before>define([], function() {
	Path.map("#!/login").to(function(){
	}).enter(function() {
		require(['tpl!template/login.html'], function(tpl){
			$('#main').append($(tpl.apply()));

			$('header').hide();
			$('#main').addClass('transparent-background');
			$('footer').hide();

			$('#signupCheck').on('click', function(){
				if ($(this).is(':checked')){
					$('#pass').hide();
					$('#loginBtn').addClass('hide');
					$('#resetBtn').removeClass('hide');
				}else{
					$('#pass').show();
					$('#loginBtn').removeClass('hide');
					$('#resetBtn').addClass('hide');
				}
			});

			$('form').on('submit', function(){
				// 1. Check for login or reset password
				// 2. Perform action accordingly
				return false;
			})
		});
	}).exit(function() {
		// Exit from route
		$('header').show();
		$('#main').off().empty().removeClass('transparent-background');
		$('footer').show();
	});
});

<commit_msg>Add AJAX call for login<commit_after>define([], function() {
	Path.map("#!/login").to(function(){
	}).enter(function() {
		require(['tpl!template/login.html'], function(tpl){
			$('#main').append($(tpl.apply()));

			$('header').hide();
			$('#main').addClass('transparent-background');
			$('footer').hide();

			$('#signupCheck').on('click', function(){
				if ($(this).is(':checked')){
					$('#pass').hide();
					$('#loginBtn').addClass('hide');
					$('#resetBtn').removeClass('hide');
				}else{
					$('#pass').show();
					$('#loginBtn').removeClass('hide');
					$('#resetBtn').addClass('hide');
				}
			});

			$('form').on('submit', function(){
				// 1. Check for login or reset password
				// 2. Perform action accordingly
				return false;
			});

			$('#login').on('submit', function() {
				$.ajax({
					url: '/api/index.php/login',
					dataType: 'json',
					type: 'POST',
					data: JSON.stringify({
						username: $('#email').val(),
						password: $('#pass').val()
					})
				}).always(function() {
					console.debug(arguments);
				});
			});
		});
	}).exit(function() {
		// Exit from route
		$('header').show();
		$('#main').off().empty().removeClass('transparent-background');
		$('footer').show();
	});
});

<|endoftext|>


<commit_before>$(function() {
	$(document).items('http://schema.org/MusicAlbum').each(function() {
		var node = $(this);

		var albumName = node.microdata('name');
		var artistName = node.microdata('byArtist').microdata('name');
		var query = 'artist:' + encodeURIComponent(artistName) + ' album:' + encodeURIComponent(albumName);

		var status = $('<span/>').appendTo(node);

		var request = $.spotify.search('album', query).done(function(data) {
			if (data.albums.length) {
				status.text('Fetching…');

				var request = $.spotify.lookup(data.albums[0].href, { extras: 'track' }).done(function(data) {
					status.remove();
					$.spotify.link(data.album.href, '(' + data.album.released + ')').appendTo(node);
				});

				request.progress(status.text).fail(status.text);
			} else {
				status.remove();
			}
		});

		request.progress(status.text).fail(status.text);

	});
})<commit_msg>unencode variables<commit_after>$(function() {
	$(document).items('http://schema.org/MusicAlbum').each(function() {
		var node = $(this);

		var albumName = node.microdata('name');
		var artistName = node.microdata('byArtist').microdata('name');
		var query = 'artist:' + artistName + ' album:' + albumName;

		var status = $('<span/>').appendTo(node);

		var request = $.spotify.search('album', query).done(function(data) {
			if (data.albums.length) {
				status.text('Fetching…');

				var request = $.spotify.lookup(data.albums[0].href, { extras: 'track' }).done(function(data) {
					status.remove();
					$.spotify.link(data.album.href, '(' + data.album.released + ')').appendTo(node);
				});

				request.progress(status.text).fail(status.text);
			} else {
				status.remove();
			}
		});

		request.progress(status.text).fail(status.text);

	});
})<|endoftext|>


<commit_before>const Compiler = require('webpack/lib/Compiler')
const ImportDependency = require('webpack/lib/dependencies/ImportDependency')
const RawModule = require('webpack/lib/RawModule')
const Stats = require('webpack/lib/Stats')
const WebpackOptionsApply = require('webpack/lib/WebpackOptionsApply')
const WebpackOptionsDefaulter = require('webpack/lib/WebpackOptionsDefaulter')
const { ReplaceSource } = require('webpack-sources')
const { addBuiltInVariable } = require('../lib/webpack-utils')
const { relative } = require('path')

/*
  The idea is simple:
    - record any dependencies marked with ?universal
    - add the server loader to those modules
    - compile those modules again as separate entries using a web targetted compiler
    - add the client loader to those modules
*/

const p = 'react-universal-plugin'

// works only when entry is an object
module.exports = function reactUniversalPlugin (webCompilerOptions) {

  return {
    apply: compiler => {
      // keep a record of client entries for additional compiler runs (watch)
      const clientEntries = {}

      const webCompiler = createWebCompiler(compiler, webCompilerOptions, () => clientEntries)

      // when the webCompiler starts compiling add the recorded client entries
      webCompiler.hooks.makeAdditionalEntries.tapPromise(p, (compilation, addEntries) => {
        return addEntries(clientEntries)
      })

      // check the parent compiler before creating a module, it might have already
      // been processed
      let compilation
      compiler.hooks.compilation.tap(p, c => { compilation = c })
      webCompiler.hooks.compilation.tap(p, (webCompilation, { normalModuleFactory }) => {
        normalModuleFactory.hooks.createModule.tap(p, data => {
          const { path } = data.resourceResolveData
          if (!path.endsWith('.js') && !path.endsWith('.json')) {
            const parentCompilationModule = compilation.findModule(data.request)
            if (parentCompilationModule) {
              // mutation in webpack internals is a minefield, tread carefully
              const dependencies = parentCompilationModule.dependencies.slice()
              const parentSource = new ReplaceSource(parentCompilationModule.originalSource())

              const { dependencyTemplates, outputOptions, moduleTemplates: { javascript: { requestShortener } } } = webCompilation

              // from NormalModule.sourceDependency
              dependencies.forEach(dependency => {
                const template = dependencyTemplates.get(dependency.constructor)
                if (template) template.apply(dependency, parentSource, outputOptions, requestShortener, dependencyTemplates)
              })

              const result = new RawModule(parentSource.source())
              result.dependencies = dependencies
              return result
            }
          }
        })
      })

      // before we compile, make sure the timestamps (important for caching and changed by watch) are updated
      compiler.hooks.beforeCompile.tap(p, params => {
        webCompiler.fileTimestamps = compiler.fileTimestamps
        webCompiler.contextTimestamps = compiler.contextTimestamps
      })

      // we claim entries ending with `entry.js` and record them as client entries for the web compiler
      compiler.hooks.claimEntries.tap(p, entries => {
        const [claimed, unclaimed] = Object.keys(entries).reduce(
          ([claimed, unclaimed], name) => {
            const entry = entries[name]
            if (entry.endsWith('.entry.js')) claimed[name] = entry
            else unclaimed[name] = entry

            return [claimed, unclaimed]
          },
          [{}, {}]
        )

        Object.assign(clientEntries, claimed)

        return unclaimed
      })

      /*
        Prevent the usage of dynamic imports in server sided code, this could be problematic as both versions could
        have the same name.

        When a module marked with `?universal` has been resolved, add the `react-universal-server-loader` to it's
        loaders and add the module marked with `?universal-client` as client entry.
      */
      compiler.hooks.normalModuleFactory.tap(p, normalModuleFactory => {

        normalModuleFactory.hooks.beforeResolve.tap(p, data => {
          if (!data) return data

          if (data.dependencies.some(x => x instanceof ImportDependency)) return

          return data
        })

        normalModuleFactory.hooks.afterResolve.tap(p, data => {
          const { loaders, resourceResolveData: { query, path } } = data

          if (query === '?universal') {
            loaders.push({ loader: require.resolve('../webpack-loaders/react-universal-server-loader') })

            const name = relative(compiler.context, path)
            if (!clientEntries[name]) clientEntries[name] = './' + name + '?universal-client'
          }

          return data
        })
      })

      // tell the web compiler to compile, emit the assets and notify the appropriate plugins
      // Note, we can not use `make` because it's parallel
      compiler.hooks.makeAdditionalEntries.tapAsync(p, (compilation, _, callback) => {

        const startTime = Date.now()
        webCompiler.compile((err, webCompilation) => {
          if (err) return finish(err)

          compilation.children.push(webCompilation)

          webCompiler.emitAssets(webCompilation, err => {
            if (err) return finish(err)

            finish(null, webCompilation)
          })
        })

        function finish(err, compilation) {
          if (err) {
            webCompiler.hooks.failed.call(err)
            return callback(err)
          }

          const stats = new Stats(compilation)
          stats.startTime = startTime
          stats.endTime = Date.now()

          webCompiler.hooks.done.callAsync(stats, callback)
        }
      })

      // make sure the __webpack_js_chunk_information__ is available in modules
      compiler.hooks.compilation.tap(p, (compilation, { normalModuleFactory }) => {

        addBuiltInVariable({
          compilation, normalModuleFactory,
          pluginName: p,
          variableName: '__webpack_js_chunk_information__',
          abbreviation: 'jci',
          type: 'object',
          createValue: (source, chunk, hash) => {
            // get the manifest from the client compilation
            const [{ _kaliber_chunk_manifest_: manifest }] = compilation.children

            // find univeral modules in the current chunk (client chunk names) and grab their filenames (uniquely)
            const universalChunkNames = chunk.getModules()
              .filter(x => x.resource && x.resource.endsWith('?universal'))
              .map(x => relative(compiler.context, x.resource.replace('?universal', '')))

            return { universalChunkNames, manifest }
          }
        })
      })
    }
  }
}

function createWebCompiler(compiler, options, getEntries) {

  const webCompiler = createCompiler(compiler, options)

  /*
    push the client loader when appropriate

    provide a friendly error if @kaliber/config is loaded from a client module
  */
  webCompiler.hooks.normalModuleFactory.tap(p, normalModuleFactory => {
    normalModuleFactory.hooks.afterResolve.tap(p, data => {
      const { loaders, rawRequest, resourceResolveData: { query } } = data

      if (query === '?universal-client')
        loaders.push({ loader: require.resolve('../webpack-loaders/react-universal-client-loader') })

      if (rawRequest === '@kaliber/config')
        throw new Error('@kaliber/config\n------\nYou can not load @kaliber/config from a client module.\n\nIf you have a use-case, please open an issue so we can discuss how we can\nimplement this safely.\n------')

      return data
    })
  })

  // make the chunk manifest available
  webCompiler.hooks.compilation.tap(p, compilation => {
    compilation.hooks.chunkManifest.tap(p, chunkManifest => {
      compilation._kaliber_chunk_manifest_ = chunkManifest
    })
  })

  return webCompiler
}

function createCompiler(compiler, options) {
  /* from lib/webpack.js */
  options = new WebpackOptionsDefaulter().process(options)

  const childCompiler = new Compiler(options.context)
  childCompiler.options = options

  // instead of using the NodeEnvironmentPlugin
  childCompiler.inputFileSystem = compiler.inputFileSystem
  childCompiler.outputFileSystem = compiler.outputFileSystem
  childCompiler.watchFileSystem = compiler.watchFileSystem

  options.plugins.forEach(plugin => { plugin.apply(childCompiler) })
  childCompiler.hooks.environment.call()
  childCompiler.hooks.afterEnvironment.call()
  childCompiler.options = new WebpackOptionsApply().process(options, childCompiler)

  return childCompiler
}
<commit_msg>Add `entry-manifest.json`<commit_after>const { addBuiltInVariable } = require('../lib/webpack-utils')
const { RawSource } = require('webpack-sources')
const { relative } = require('path')
const { ReplaceSource } = require('webpack-sources')
const Compiler = require('webpack/lib/Compiler')
const ImportDependency = require('webpack/lib/dependencies/ImportDependency')
const RawModule = require('webpack/lib/RawModule')
const Stats = require('webpack/lib/Stats')
const WebpackOptionsApply = require('webpack/lib/WebpackOptionsApply')
const WebpackOptionsDefaulter = require('webpack/lib/WebpackOptionsDefaulter')

/*
  The idea is simple:
    - record any dependencies marked with ?universal
    - add the server loader to those modules
    - compile those modules again as separate entries using a web targetted compiler
    - add the client loader to those modules
*/

const p = 'react-universal-plugin'

// works only when entry is an object
module.exports = function reactUniversalPlugin (webCompilerOptions) {

  return {
    apply: compiler => {
      // keep a record of client entries for additional compiler runs (watch)
      const clientEntries = {}

      const webCompiler = createWebCompiler(compiler, webCompilerOptions, () => clientEntries)

      // when the webCompiler starts compiling add the recorded client entries
      webCompiler.hooks.makeAdditionalEntries.tapPromise(p, (compilation, addEntries) => {
        return addEntries(clientEntries)
      })

      // check the parent compiler before creating a module, it might have already
      // been processed
      let compilation
      compiler.hooks.compilation.tap(p, c => { compilation = c })
      webCompiler.hooks.compilation.tap(p, (webCompilation, { normalModuleFactory }) => {
        normalModuleFactory.hooks.createModule.tap(p, data => {
          const { path } = data.resourceResolveData
          if (!path.endsWith('.js') && !path.endsWith('.json')) {
            const parentCompilationModule = compilation.findModule(data.request)
            if (parentCompilationModule) {
              // mutation in webpack internals is a minefield, tread carefully
              const dependencies = parentCompilationModule.dependencies.slice()
              const parentSource = new ReplaceSource(parentCompilationModule.originalSource())

              const { dependencyTemplates, outputOptions, moduleTemplates: { javascript: { requestShortener } } } = webCompilation

              // from NormalModule.sourceDependency
              dependencies.forEach(dependency => {
                const template = dependencyTemplates.get(dependency.constructor)
                if (template) template.apply(dependency, parentSource, outputOptions, requestShortener, dependencyTemplates)
              })

              const result = new RawModule(parentSource.source())
              result.dependencies = dependencies
              return result
            }
          }
        })
      })

      // before we compile, make sure the timestamps (important for caching and changed by watch) are updated
      compiler.hooks.beforeCompile.tap(p, params => {
        webCompiler.fileTimestamps = compiler.fileTimestamps
        webCompiler.contextTimestamps = compiler.contextTimestamps
      })

      // we claim entries ending with `entry.js` and record them as client entries for the web compiler
      compiler.hooks.claimEntries.tap(p, entries => {
        const [claimed, unclaimed] = Object.keys(entries).reduce(
          ([claimed, unclaimed], name) => {
            const entry = entries[name]
            if (entry.endsWith('.entry.js')) claimed[name] = entry
            else unclaimed[name] = entry

            return [claimed, unclaimed]
          },
          [{}, {}]
        )

        Object.assign(clientEntries, claimed)

        return unclaimed
      })

      /*
        Prevent the usage of dynamic imports in server sided code, this could be problematic as both versions could
        have the same name.

        When a module marked with `?universal` has been resolved, add the `react-universal-server-loader` to it's
        loaders and add the module marked with `?universal-client` as client entry.
      */
      compiler.hooks.normalModuleFactory.tap(p, normalModuleFactory => {

        normalModuleFactory.hooks.beforeResolve.tap(p, data => {
          if (!data) return data

          if (data.dependencies.some(x => x instanceof ImportDependency)) return

          return data
        })

        normalModuleFactory.hooks.afterResolve.tap(p, data => {
          const { loaders, resourceResolveData: { query, path } } = data

          if (query === '?universal') {
            loaders.push({ loader: require.resolve('../webpack-loaders/react-universal-server-loader') })

            const name = relative(compiler.context, path)
            if (!clientEntries[name]) clientEntries[name] = './' + name + '?universal-client'
          }

          return data
        })
      })

      // tell the web compiler to compile, emit the assets and notify the appropriate plugins
      // Note, we can not use `make` because it's parallel
      compiler.hooks.makeAdditionalEntries.tapAsync(p, (compilation, _, callback) => {

        const startTime = Date.now()
        webCompiler.compile((err, webCompilation) => {
          if (err) return finish(err)

          compilation.children.push(webCompilation)

          webCompiler.emitAssets(webCompilation, err => {
            if (err) return finish(err)

            finish(null, webCompilation)
          })
        })

        function finish(err, compilation) {
          if (err) {
            webCompiler.hooks.failed.call(err)
            return callback(err)
          }

          const stats = new Stats(compilation)
          stats.startTime = startTime
          stats.endTime = Date.now()

          webCompiler.hooks.done.callAsync(stats, callback)
        }
      })

      // make sure the __webpack_js_chunk_information__ is available in modules
      compiler.hooks.compilation.tap(p, (compilation, { normalModuleFactory }) => {

        addBuiltInVariable({
          compilation, normalModuleFactory,
          pluginName: p,
          variableName: '__webpack_js_chunk_information__',
          abbreviation: 'jci',
          type: 'object',
          createValue: (source, chunk, hash) => {
            // get the manifest from the client compilation
            const [{ _kaliber_chunk_manifest_: manifest }] = compilation.children
            const universalChunkNames = getUniversalChunkNames(chunk, compiler)
            return { universalChunkNames, manifest }
          }
        })

        compilation.hooks.additionalChunkAssets.tap(p, chunks => {
          const entryManifest = chunks
            .filter(x => x.name)
            .reduce((result, x) => ({ ...result, [x.name]: getUniversalChunkNames(x, compiler) }), {})

          compilation.assets['entry-manifest.json'] = new RawSource(JSON.stringify(entryManifest, null, 2))
        })
      })
    }
  }
}

function getUniversalChunkNames(chunk, compiler) {
  // find univeral modules in the current chunk (client chunk names) and grab their filenames (uniquely)
  return chunk.getModules()
    .filter(x => x.resource && x.resource.endsWith('?universal'))
    .map(x => relative(compiler.context, x.resource.replace('?universal', '')))
}

function createWebCompiler(compiler, options, getEntries) {

  const webCompiler = createCompiler(compiler, options)

  /*
    push the client loader when appropriate

    provide a friendly error if @kaliber/config is loaded from a client module
  */
  webCompiler.hooks.normalModuleFactory.tap(p, normalModuleFactory => {
    normalModuleFactory.hooks.afterResolve.tap(p, data => {
      const { loaders, rawRequest, resourceResolveData: { query } } = data

      if (query === '?universal-client')
        loaders.push({ loader: require.resolve('../webpack-loaders/react-universal-client-loader') })

      if (rawRequest === '@kaliber/config')
        throw new Error('@kaliber/config\n------\nYou can not load @kaliber/config from a client module.\n\nIf you have a use-case, please open an issue so we can discuss how we can\nimplement this safely.\n------')

      return data
    })
  })

  // make the chunk manifest available
  webCompiler.hooks.compilation.tap(p, compilation => {
    compilation.hooks.chunkManifest.tap(p, chunkManifest => {
      compilation._kaliber_chunk_manifest_ = chunkManifest
    })
  })

  return webCompiler
}

function createCompiler(compiler, options) {
  /* from lib/webpack.js */
  options = new WebpackOptionsDefaulter().process(options)

  const childCompiler = new Compiler(options.context)
  childCompiler.options = options

  // instead of using the NodeEnvironmentPlugin
  childCompiler.inputFileSystem = compiler.inputFileSystem
  childCompiler.outputFileSystem = compiler.outputFileSystem
  childCompiler.watchFileSystem = compiler.watchFileSystem

  options.plugins.forEach(plugin => { plugin.apply(childCompiler) })
  childCompiler.hooks.environment.call()
  childCompiler.hooks.afterEnvironment.call()
  childCompiler.options = new WebpackOptionsApply().process(options, childCompiler)

  return childCompiler
}
<|endoftext|>


<commit_before>// sensorData.js
// retrieving acceleration data
window.onload = function() {
  console.log("loaded");
  var xA = document.getElementById("xA"),
    xV = document.getElementById("xV");
    xP = document.getElementById("xP");

  var lastVelocity = false,
    lastPosition = 0,
    timeStep,
    lastT = new Date();

  function velocity (lastVelocity, acceleration, timeStep) {
    return lastVelocity + (acceleration * timeStep);
  }

  function position (lastPosition, lastVelocity, acceleration, timeStep) {
    return lastPosition + lastVelocity * timeStep +
    (0.5 * acceleration * timeStep * timeStep);
  }

  function initGyro() {
    var listYAccelerations = [],
      noise = false,
      lastXA = false;

    gyro.frequency = 100;

    gyro.startTracking(function(o) {
      var a = o.x.toFixed(3),
        t = new Date(),
        timestep = t - lastT;

      if (parseFloat(a) >= 0.1 || parseFloat(a) <= -0.1) {
        lastT = t;
        lastPosition = position(lastPosition, lastVelocity, a,
          timestep);

        lastVelocity = velocity(lastVelocity, a, timestep);
      }

      xA.innerHTML = a;
      xV.innerHTML = lastVelocity;
      xP.innerHTML = lastPosition;
    });
  }

  initGyro();
}
<commit_msg>only update view if there is a change<commit_after>// sensorData.js
// retrieving acceleration data
window.onload = function() {
  console.log("loaded");
  var xA = document.getElementById("xA"),
    xV = document.getElementById("xV");
    xP = document.getElementById("xP");

  var lastVelocity = false,
    lastPosition = 0,
    timeStep,
    lastT = new Date();

  function velocity (lastVelocity, acceleration, timeStep) {
    return lastVelocity + (acceleration * timeStep);
  }

  function position (lastPosition, lastVelocity, acceleration, timeStep) {
    return lastPosition + lastVelocity * timeStep +
    (0.5 * acceleration * timeStep * timeStep);
  }

  function initGyro() {
    var listYAccelerations = [],
      noise = false,
      lastXA = false;

    gyro.frequency = 100;

    gyro.startTracking(function(o) {
      var a = o.x.toFixed(3),
        t = new Date(),
        timestep = t - lastT;

      if (parseFloat(a) >= 0.1 || parseFloat(a) <= -0.1) {
        lastT = t;
        lastPosition = position(lastPosition, lastVelocity, a,
          timestep);

        lastVelocity = velocity(lastVelocity, a, timestep);

        xA.innerHTML = a;
        xV.innerHTML = lastVelocity;
        xP.innerHTML = lastPosition;
      }

    });
  }

  initGyro();
}
<|endoftext|>


<commit_before>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2021  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('17.5.0');
		this.setClientTag('node:21-07-31');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<commit_msg>New clientlib ver<commit_after>// ===================================================================================================
//                           _  __     _ _
//                          | |/ /__ _| | |_ _  _ _ _ __ _
//                          | ' </ _` | |  _| || | '_/ _` |
//                          |_|\_\__,_|_|\__|\_,_|_| \__,_|
//
// This file is part of the Kaltura Collaborative Media Suite which allows users
// to do with audio, video, and animation what Wiki platforms allow them to do with
// text.
//
// Copyright (C) 2006-2021  Kaltura Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// @ignore
// ===================================================================================================
/**
 * The Kaltura Client - this is the facade through which all service actions should be called.
 * @param config the Kaltura configuration object holding partner credentials (type: KalturaConfiguration).
 */
var util = require('util');
var kaltura = require('./KalturaClientBase');
kaltura.services = require('./KalturaServices');
kaltura.objects = require('./KalturaModel');
kaltura.enums = require('./KalturaTypes');

class Client extends kaltura.ClientBase {

	/**
	 * @param Configuration config
	 */
	constructor(config) {
		super(config);
		this.setApiVersion('17.5.0');
		this.setClientTag('node:21-08-01');
	}
}

module.exports = kaltura;
module.exports.Client = Client;

<|endoftext|>