<commit_before>import MediumEditor from "medium-editor";

function last (text) {
    return text[text.length - 1];
}

export const atKeyCode = 50;
export const hashKeyCode = 51;

export function unwrapForTextNode (el, doc) {
    var parentNode = el.parentNode,
        prevNode,
        currentNode;
    MediumEditor.util.unwrap(el, doc);
    // Merge textNode
    currentNode = parentNode.lastChild;
    while (prevNode = currentNode.previousSibling) {
        if (3 === currentNode.nodeType && 3 === prevNode.nodeType) {
            prevNode.textContent += currentNode.textContent;
            parentNode.removeChild(currentNode);
        }
        currentNode = prevNode;
    }
}

export const TCMention = MediumEditor.Extension.extend({
    name: `mention`,

    /* @deprecated: use extraPanelClassName. Will remove in next major (3.0.0) release
     * extraClassName: [string]
     *
     * Extra className to be added with the `medium-editor-mention-panel` element.
     */
    extraClassName: ``,

    /* @deprecated: use extraActivePanelClassName. Will remove in next major (3.0.0) release
     * extraActiveClassName: [string]
     *
     * Extra active className to be added with the `medium-editor-mention-panel-active` element.
     */
    extraActiveClassName: ``,

    /* extraPanelClassName: [string]
     *
     * Extra className to be added with the `medium-editor-mention-panel` element.
     */
    extraPanelClassName: ``,

    /* extraActivePanelClassName: [string]
     *
     * Extra active className to be added with the `medium-editor-mention-panel-active` element.
     */
    extraActivePanelClassName: ``,

    extraTriggerClassNameMap: {},

    extraActiveTriggerClassNameMap: {},

    /* tagName: [string]
     *
     * Element tag name that would indicate that this mention. It will have
     * `medium-editor-mention-at` className applied on it.
     */
    tagName: `strong`,

    /* renderPanelContent: [function (panelEl: dom, currentMentionText: string, selectMentionCallback: function)]
     *
     * Render function that used to create the content of the panel when panel is show.
     *
     * @params panelEl: DOM element of the panel.
     *
     * @params currentMentionText: Often used as query criteria. e.g. @medium
     *
     * @params selectMentionCallback:
     *      callback used in customized panel content.
     *
     *      When called with null, it tells the Mention plugin to close the panel.
     *          e.g. selectMentionCallback(null);
     *
     *      When called with text, it tells the Mention plugin that the text is selected by the user.
     *          e.g. selectMentionCallback("@mediumrocks")
     */
    renderPanelContent: () => {},

    /* destroyPanelContent: [function (panelEl: dom)]
     *
     * Destroy function to remove any contents rendered by renderPanelContent before panelEl being removed from the document.
     *
     * @params panelEl: DOM element of the panel.
     */
    destroyPanelContent: () => {},

    activeTriggerList: [`@`],

    triggerClassNameMap: {
        "#": `medium-editor-mention-hash`,
        "@": `medium-editor-mention-at`,
    },

    activeTriggerClassNameMap: {
        "#": `medium-editor-mention-hash-active`,
        "@": `medium-editor-mention-at-active`,
    },

    hideOnBlurDelay: 300,

    init () {
        this.initMentionPanel();
        this.attachEventHandlers();
    },

    destroy () {
        if (this.mentionPanel) {
            if (this.mentionPanel.parentNode) {
                this.destroyPanelContent(this.mentionPanel);
                this.mentionPanel.parentNode.removeChild(this.mentionPanel);
            }
            delete this.mentionPanel;
        }
    },

    initMentionPanel () {
        const el = this.document.createElement(`div`);

        el.classList.add(`medium-editor-mention-panel`);
        if (this.extraPanelClassName || this.extraClassName) {
          el.classList.add(this.extraPanelClassName || this.extraClassName);
        }

        this.getEditorOption(`elementsContainer`).appendChild(el);

        this.mentionPanel = el;
    },

    attachEventHandlers () {
        if (null != this.hideOnBlurDelay) {
            // for hideOnBlurDelay, the panel should hide after blur event
            this.subscribe(`blur`, ::this.handleBlur);
            // and clear out hide timeout if focus again
            this.subscribe(`focus`, ::this.handleFocus);
        }
        // if the editor changes its content, we have to show or hide the panel
        this.subscribe(`editableKeyup`, ::this.handleKeyup);
    },

    handleBlur () {
        if (null != this.hideOnBlurDelay) {
            this.hideOnBlurDelayId = setTimeout(::this.hidePanel, this.hideOnBlurDelay);
        }
    },

    handleFocus () {
        if (this.hideOnBlurDelayId) {
            clearTimeout(this.hideOnBlurDelayId);
            this.hideOnBlurDelayId = null;
        }
    },

    handleKeyup (event) {
        const keyCode = MediumEditor.util.getKeyCode(event);
        const isSpace = keyCode === MediumEditor.util.keyCode.SPACE;
        this.getWordFromSelection(event.target, isSpace ? -1 : 0);

        if (!isSpace && -1 !== this.activeTriggerList.indexOf(this.trigger) && 1 < this.word.length) {
            this.showPanel();
        } else {
            this.hidePanel();
        }
    },

    hidePanel () {
        this.mentionPanel.classList.remove(`medium-editor-mention-panel-active`);
        if (this.extraActivePanelClassName || this.extraActiveClassName) {
            this.mentionPanel.classList.remove(this.extraActivePanelClassName || this.extraActiveClassName);
        }
        if (this.activeMentionAt) {
            this.activeMentionAt.classList.remove(this.activeTriggerClassName);
            if (this.extraActiveTriggerClassName) {
                this.activeMentionAt.classList.remove(this.extraActiveTriggerClassName);
            }
        }
        if (this.activeMentionAt) {
            // http://stackoverflow.com/a/27004526/1458162
            const {parentNode, nextSibling, firstChild} = this.activeMentionAt;
            let textNode = nextSibling;
            if (!nextSibling) {
                textNode = this.document.createTextNode(``);
                parentNode.appendChild(textNode);
            } else if (3 !== nextSibling.nodeType) {
                textNode = this.document.createTextNode(``);
                parentNode.insertBefore(textNode, nextSibling);
            }
            const lastEmptyWord = last(firstChild.textContent);
            const hasLastEmptyWord = 0 === lastEmptyWord.trim().length;
            if (hasLastEmptyWord) {
                firstChild.textContent = firstChild.textContent.substr(0, firstChild.textContent.length-1);
                textNode.textContent = `${ lastEmptyWord }${ textNode.textContent }`;
            } else {
                if (0 === textNode.textContent.length && 1 < firstChild.textContent.length) {
                    textNode.textContent = `\u00A0`;
                }
            }
            MediumEditor.selection.select(this.document, textNode, Math.min(textNode.length, 1));
            if (1 >= firstChild.textContent.length) {
                // LIKE core#execAction
                this.base.saveSelection();
                unwrapForTextNode(this.activeMentionAt, this.document);
                this.base.restoreSelection();
            }
            //
            this.activeMentionAt = null;
        }
    },

    getWordFromSelection (target, initialDiff) {
        const {startContainer, startOffset, endContainer, endOffset} = MediumEditor.selection.getSelectionRange(this.document);
        if (startContainer !== endContainer) {
            return;
        }
        const {textContent} = startContainer;

        function getWordPosition (position, diff) {
            const prevText = textContent[position - 1];
            if (null == prevText || 0 === prevText.trim().length || 0 >= position || textContent.length < position) {
                return position;
            } else {
                return getWordPosition(position + diff, diff);
            }
        }

        this.wordStart = getWordPosition(startOffset + initialDiff, -1);
        this.wordEnd = getWordPosition(startOffset + initialDiff, 1) - 1;
        this.word = textContent.slice(this.wordStart, this.wordEnd);
        this.trigger = this.word.slice(0, 1);
        this.triggerClassName = this.triggerClassNameMap[this.trigger];
        this.activeTriggerClassName = this.activeTriggerClassNameMap[this.trigger];
        //
        this.extraTriggerClassName = this.extraTriggerClassNameMap[this.trigger];
        this.extraActiveTriggerClassName = this.extraActiveTriggerClassNameMap[this.trigger];
    },

    showPanel () {
        if (!this.mentionPanel.classList.contains(`medium-editor-mention-panel-active`)) {
            this.activatePanel();
            this.wrapWordInMentionAt();
        }
        this.positionPanel();
        this.updatePanelContent();
    },

    activatePanel () {
        this.mentionPanel.classList.add(`medium-editor-mention-panel-active`);
        if (this.extraActivePanelClassName || this.extraActiveClassName) {
            this.mentionPanel.classList.add(this.extraActivePanelClassName || this.extraActiveClassName);
        }
    },

    wrapWordInMentionAt () {
        const selection = this.document.getSelection();
        if (!selection.rangeCount) {
            return;
        }
        // http://stackoverflow.com/a/6328906/1458162
        const range = selection.getRangeAt(0).cloneRange();
        if (range.startContainer.parentNode.classList.contains(this.triggerClassName)) {
            this.activeMentionAt = range.startContainer.parentNode;
        } else {
            range.setStart(range.startContainer, this.wordStart);
            range.setEnd(range.startContainer, Math.min(this.wordEnd, range.startContainer.textContent.length));
            // Instead, insert our own version of it.
            // TODO: Not sure why, but using <span> tag doens't work here
            const element = this.document.createElement(this.tagName);
            element.classList.add(this.triggerClassName);
            if (this.extraTriggerClassName) {
                element.classList.add(this.extraTriggerClassName);
            }
            this.activeMentionAt = element;
            //
            range.surroundContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
        }
        MediumEditor.selection.select(this.document, this.activeMentionAt.firstChild, this.word.length);
        this.activeMentionAt.classList.add(this.activeTriggerClassName);
        if (this.extraActiveTriggerClassName) {
            this.activeMentionAt.classList.add(this.extraActiveTriggerClassName);
        }
    },

    positionPanel () {
        const {bottom, left, width} = this.activeMentionAt.getBoundingClientRect();
        const {pageXOffset, pageYOffset} = this.window;

        this.mentionPanel.style.top = `${ pageYOffset + bottom }px`;
        this.mentionPanel.style.left = `${ pageXOffset + left + width }px`;
    },

    updatePanelContent () {
        this.renderPanelContent(this.mentionPanel, this.word, ::this.handleSelectMention);
    },

    handleSelectMention (seletedText) {
        if (seletedText) {
            const textNode = this.activeMentionAt.firstChild;
            textNode.textContent = seletedText;
            MediumEditor.selection.select(this.document, textNode, seletedText.length);
            //
            this.hidePanel();
        } else {
            this.hidePanel();
        }
    },

});

export default TCMention;
<commit_msg>fix(TCMention): select the text only when element is first time created<commit_after>import MediumEditor from "medium-editor";

function last (text) {
    return text[text.length - 1];
}

export const atKeyCode = 50;
export const hashKeyCode = 51;

export function unwrapForTextNode (el, doc) {
    var parentNode = el.parentNode,
        prevNode,
        currentNode;
    MediumEditor.util.unwrap(el, doc);
    // Merge textNode
    currentNode = parentNode.lastChild;
    while (prevNode = currentNode.previousSibling) {
        if (3 === currentNode.nodeType && 3 === prevNode.nodeType) {
            prevNode.textContent += currentNode.textContent;
            parentNode.removeChild(currentNode);
        }
        currentNode = prevNode;
    }
}

export const TCMention = MediumEditor.Extension.extend({
    name: `mention`,

    /* @deprecated: use extraPanelClassName. Will remove in next major (3.0.0) release
     * extraClassName: [string]
     *
     * Extra className to be added with the `medium-editor-mention-panel` element.
     */
    extraClassName: ``,

    /* @deprecated: use extraActivePanelClassName. Will remove in next major (3.0.0) release
     * extraActiveClassName: [string]
     *
     * Extra active className to be added with the `medium-editor-mention-panel-active` element.
     */
    extraActiveClassName: ``,

    /* extraPanelClassName: [string]
     *
     * Extra className to be added with the `medium-editor-mention-panel` element.
     */
    extraPanelClassName: ``,

    /* extraActivePanelClassName: [string]
     *
     * Extra active className to be added with the `medium-editor-mention-panel-active` element.
     */
    extraActivePanelClassName: ``,

    extraTriggerClassNameMap: {},

    extraActiveTriggerClassNameMap: {},

    /* tagName: [string]
     *
     * Element tag name that would indicate that this mention. It will have
     * `medium-editor-mention-at` className applied on it.
     */
    tagName: `strong`,

    /* renderPanelContent: [function (panelEl: dom, currentMentionText: string, selectMentionCallback: function)]
     *
     * Render function that used to create the content of the panel when panel is show.
     *
     * @params panelEl: DOM element of the panel.
     *
     * @params currentMentionText: Often used as query criteria. e.g. @medium
     *
     * @params selectMentionCallback:
     *      callback used in customized panel content.
     *
     *      When called with null, it tells the Mention plugin to close the panel.
     *          e.g. selectMentionCallback(null);
     *
     *      When called with text, it tells the Mention plugin that the text is selected by the user.
     *          e.g. selectMentionCallback("@mediumrocks")
     */
    renderPanelContent: () => {},

    /* destroyPanelContent: [function (panelEl: dom)]
     *
     * Destroy function to remove any contents rendered by renderPanelContent before panelEl being removed from the document.
     *
     * @params panelEl: DOM element of the panel.
     */
    destroyPanelContent: () => {},

    activeTriggerList: [`@`],

    triggerClassNameMap: {
        "#": `medium-editor-mention-hash`,
        "@": `medium-editor-mention-at`,
    },

    activeTriggerClassNameMap: {
        "#": `medium-editor-mention-hash-active`,
        "@": `medium-editor-mention-at-active`,
    },

    hideOnBlurDelay: 300,

    init () {
        this.initMentionPanel();
        this.attachEventHandlers();
    },

    destroy () {
        if (this.mentionPanel) {
            if (this.mentionPanel.parentNode) {
                this.destroyPanelContent(this.mentionPanel);
                this.mentionPanel.parentNode.removeChild(this.mentionPanel);
            }
            delete this.mentionPanel;
        }
    },

    initMentionPanel () {
        const el = this.document.createElement(`div`);

        el.classList.add(`medium-editor-mention-panel`);
        if (this.extraPanelClassName || this.extraClassName) {
          el.classList.add(this.extraPanelClassName || this.extraClassName);
        }

        this.getEditorOption(`elementsContainer`).appendChild(el);

        this.mentionPanel = el;
    },

    attachEventHandlers () {
        if (null != this.hideOnBlurDelay) {
            // for hideOnBlurDelay, the panel should hide after blur event
            this.subscribe(`blur`, ::this.handleBlur);
            // and clear out hide timeout if focus again
            this.subscribe(`focus`, ::this.handleFocus);
        }
        // if the editor changes its content, we have to show or hide the panel
        this.subscribe(`editableKeyup`, ::this.handleKeyup);
    },

    handleBlur () {
        if (null != this.hideOnBlurDelay) {
            this.hideOnBlurDelayId = setTimeout(::this.hidePanel, this.hideOnBlurDelay);
        }
    },

    handleFocus () {
        if (this.hideOnBlurDelayId) {
            clearTimeout(this.hideOnBlurDelayId);
            this.hideOnBlurDelayId = null;
        }
    },

    handleKeyup (event) {
        const keyCode = MediumEditor.util.getKeyCode(event);
        const isSpace = keyCode === MediumEditor.util.keyCode.SPACE;
        this.getWordFromSelection(event.target, isSpace ? -1 : 0);

        if (!isSpace && -1 !== this.activeTriggerList.indexOf(this.trigger) && 1 < this.word.length) {
            this.showPanel();
        } else {
            this.hidePanel();
        }
    },

    hidePanel () {
        this.mentionPanel.classList.remove(`medium-editor-mention-panel-active`);
        if (this.extraActivePanelClassName || this.extraActiveClassName) {
            this.mentionPanel.classList.remove(this.extraActivePanelClassName || this.extraActiveClassName);
        }
        if (this.activeMentionAt) {
            this.activeMentionAt.classList.remove(this.activeTriggerClassName);
            if (this.extraActiveTriggerClassName) {
                this.activeMentionAt.classList.remove(this.extraActiveTriggerClassName);
            }
        }
        if (this.activeMentionAt) {
            // http://stackoverflow.com/a/27004526/1458162
            const {parentNode, nextSibling, firstChild} = this.activeMentionAt;
            let textNode = nextSibling;
            if (!nextSibling) {
                textNode = this.document.createTextNode(``);
                parentNode.appendChild(textNode);
            } else if (3 !== nextSibling.nodeType) {
                textNode = this.document.createTextNode(``);
                parentNode.insertBefore(textNode, nextSibling);
            }
            const lastEmptyWord = last(firstChild.textContent);
            const hasLastEmptyWord = 0 === lastEmptyWord.trim().length;
            if (hasLastEmptyWord) {
                firstChild.textContent = firstChild.textContent.substr(0, firstChild.textContent.length-1);
                textNode.textContent = `${ lastEmptyWord }${ textNode.textContent }`;
            } else {
                if (0 === textNode.textContent.length && 1 < firstChild.textContent.length) {
                    textNode.textContent = `\u00A0`;
                }
            }
            MediumEditor.selection.select(this.document, textNode, Math.min(textNode.length, 1));
            if (1 >= firstChild.textContent.length) {
                // LIKE core#execAction
                this.base.saveSelection();
                unwrapForTextNode(this.activeMentionAt, this.document);
                this.base.restoreSelection();
            }
            //
            this.activeMentionAt = null;
        }
    },

    getWordFromSelection (target, initialDiff) {
        const {startContainer, startOffset, endContainer, endOffset} = MediumEditor.selection.getSelectionRange(this.document);
        if (startContainer !== endContainer) {
            return;
        }
        const {textContent} = startContainer;

        function getWordPosition (position, diff) {
            const prevText = textContent[position - 1];
            if (null == prevText || 0 === prevText.trim().length || 0 >= position || textContent.length < position) {
                return position;
            } else {
                return getWordPosition(position + diff, diff);
            }
        }

        this.wordStart = getWordPosition(startOffset + initialDiff, -1);
        this.wordEnd = getWordPosition(startOffset + initialDiff, 1) - 1;
        this.word = textContent.slice(this.wordStart, this.wordEnd);
        this.trigger = this.word.slice(0, 1);
        this.triggerClassName = this.triggerClassNameMap[this.trigger];
        this.activeTriggerClassName = this.activeTriggerClassNameMap[this.trigger];
        //
        this.extraTriggerClassName = this.extraTriggerClassNameMap[this.trigger];
        this.extraActiveTriggerClassName = this.extraActiveTriggerClassNameMap[this.trigger];
    },

    showPanel () {
        if (!this.mentionPanel.classList.contains(`medium-editor-mention-panel-active`)) {
            this.activatePanel();
            this.wrapWordInMentionAt();
        }
        this.positionPanel();
        this.updatePanelContent();
    },

    activatePanel () {
        this.mentionPanel.classList.add(`medium-editor-mention-panel-active`);
        if (this.extraActivePanelClassName || this.extraActiveClassName) {
            this.mentionPanel.classList.add(this.extraActivePanelClassName || this.extraActiveClassName);
        }
    },

    wrapWordInMentionAt () {
        const selection = this.document.getSelection();
        if (!selection.rangeCount) {
            return;
        }
        // http://stackoverflow.com/a/6328906/1458162
        const range = selection.getRangeAt(0).cloneRange();
        if (range.startContainer.parentNode.classList.contains(this.triggerClassName)) {
            this.activeMentionAt = range.startContainer.parentNode;
        } else {
            range.setStart(range.startContainer, this.wordStart);
            range.setEnd(range.startContainer, Math.min(this.wordEnd, range.startContainer.textContent.length));
            // Instead, insert our own version of it.
            // TODO: Not sure why, but using <span> tag doens't work here
            const element = this.document.createElement(this.tagName);
            element.classList.add(this.triggerClassName);
            if (this.extraTriggerClassName) {
                element.classList.add(this.extraTriggerClassName);
            }
            this.activeMentionAt = element;
            //
            range.surroundContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
            //
            MediumEditor.selection.select(this.document, this.activeMentionAt.firstChild, this.word.length);
        }
        this.activeMentionAt.classList.add(this.activeTriggerClassName);
        if (this.extraActiveTriggerClassName) {
            this.activeMentionAt.classList.add(this.extraActiveTriggerClassName);
        }
    },

    positionPanel () {
        const {bottom, left, width} = this.activeMentionAt.getBoundingClientRect();
        const {pageXOffset, pageYOffset} = this.window;

        this.mentionPanel.style.top = `${ pageYOffset + bottom }px`;
        this.mentionPanel.style.left = `${ pageXOffset + left + width }px`;
    },

    updatePanelContent () {
        this.renderPanelContent(this.mentionPanel, this.word, ::this.handleSelectMention);
    },

    handleSelectMention (seletedText) {
        if (seletedText) {
            const textNode = this.activeMentionAt.firstChild;
            textNode.textContent = seletedText;
            MediumEditor.selection.select(this.document, textNode, seletedText.length);
            //
            this.hidePanel();
        } else {
            this.hidePanel();
        }
    },

});

export default TCMention;
<|endoftext|>


<commit_before>"use strict"

const path = require("path")
const extract = require("./extract")
const oneLine = require("./utils").oneLine
const getSettings = require("./settings").getSettings

// Disclaimer:
//
// This is not a long term viable solution. ESLint needs to improve its processor API to
// provide access to the configuration before actually preprocess files, but it's not
// planed yet. This solution is quite ugly but shouldn't alter eslint process.
//
// Related github issues:
// https://github.com/eslint/eslint/issues/3422
// https://github.com/eslint/eslint/issues/4153

const needleV3 = path.join("lib", "eslint.js")
const needleV4 = path.join("lib", "linter.js")

iterateESLintModules(patch)

function getModulesFromRequire() {
  let eslint
  try {
    // V3-
    eslint = require("eslint/lib/eslint")
  }
  catch (e) {
    // V4+
    eslint = require("eslint/lib/linter").prototype
  }

  return {
    eslint,
    SourceCodeFixer: require("eslint/lib/util/source-code-fixer"),
  }
}

function getModulesFromCache(key) {
  const isV3 = key.endsWith(needleV3)
  const isV4 = key.endsWith(needleV4)
  if (!isV3 && !isV4) return

  const module = require.cache[key]
  if (!module || !module.exports) return

  const SourceCodeFixer =
    require.cache[path.join(key, "..", "util", "source-code-fixer.js")]
  if (!SourceCodeFixer || !SourceCodeFixer.exports) return

  const eslint = isV3 ? module.exports : module.exports.prototype
  if (typeof eslint.verify !== "function") return

  return {
    eslint,
    SourceCodeFixer: SourceCodeFixer.exports,
  }
}

function iterateESLintModules(fn) {
  if (!require.cache || Object.keys(require.cache).length === 0) {
    // Jest is replacing the node "require" function, and "require.cache" isn't available here.
    fn(getModulesFromRequire())
    return
  }

  let found = false

  for (const key in require.cache) {
    const modules = getModulesFromCache(key)
    if (modules) {
      fn(modules)
      found = true
    }
  }

  if (!found) {
    throw new Error(
      oneLine`
      eslint-plugin-html error: It seems that eslint is not loaded.
      If you think it is a bug, please file a report at
      https://github.com/BenoitZugmeyer/eslint-plugin-html/issues
    `
    )
  }
}

function patch(modules) {
  const eslint = modules.eslint
  const SourceCodeFixer = modules.SourceCodeFixer

  const sourceCodeForMessages = new WeakMap()

  const verify = eslint.verify
  eslint.verify = function(
    textOrSourceCode,
    config,
    filenameOrOptions,
    saveState
  ) {
    const localVerify = (code) =>
      verify.call(this, code, config, filenameOrOptions, saveState)

    let messages
    const filename =
      typeof filenameOrOptions === "object"
        ? filenameOrOptions.filename
        : filenameOrOptions
    const extension = path.extname(filename || "")

    const pluginSettings = getSettings(config.settings || {})
    const isHTML = pluginSettings.htmlExtensions.indexOf(extension) >= 0
    const isXML =
      !isHTML && pluginSettings.xmlExtensions.indexOf(extension) >= 0

    if (typeof textOrSourceCode === "string" && (isHTML || isXML)) {
      const currentInfos = extract(
        textOrSourceCode,
        pluginSettings.indent,
        isXML,
        pluginSettings.isJavaScriptMIMEType
      )

      messages = []

      currentInfos.code.forEach((code) => {
        messages.push.apply(
          messages,
          remapMessages(
            localVerify(String(code)),
            code,
            pluginSettings.reportBadIndent,
            currentInfos.badIndentationLines
          )
        )
      })

      sourceCodeForMessages.set(messages, textOrSourceCode)
    }
    else {
      messages = localVerify(textOrSourceCode)
    }

    return messages
  }

  const applyFixes = SourceCodeFixer.applyFixes
  SourceCodeFixer.applyFixes = function(sourceCode, messages) {
    const originalSourceCode = sourceCodeForMessages.get(messages)
    // The BOM is always included in the HTML, which is removed by the extract process
    return applyFixes.call(
      this,
      originalSourceCode === undefined
        ? sourceCode
        : { text: originalSourceCode, hasBOM: false },
      messages
    )
  }
}

function remapMessages(messages, code, reportBadIndent, badIndentationLines) {
  const newMessages = []

  for (const message of messages) {
    const location = code.originalLocation({
      line: message.line,
      column: message.column,
    })

    // Ignore messages if they were in transformed code
    if (location) {
      Object.assign(message, location)
      message.source = code.getOriginalLine(location.line)

      // Map fix range
      if (message.fix && message.fix.range) {
        message.fix.range = [
          code.originalIndex(message.fix.range[0]),
          // The range end is exclusive, meaning it should replace all characters  with indexes from
          // start to end - 1. We have to get the original index of the last targeted character.
          code.originalIndex(message.fix.range[1] - 1) + 1,
        ]
      }

      // Map end location
      if (message.endLine && message.endColumn) {
        const endLocation = code.originalLocation({
          line: message.endLine,
          column: message.endColumn,
        })
        if (endLocation) {
          message.endLine = endLocation.line
          message.endColumn = endLocation.column
        }
      }

      newMessages.push(message)
    }
  }

  if (reportBadIndent) {
    badIndentationLines.forEach((line) => {
      newMessages.push({
        message: "Bad line indentation.",
        line,
        column: 1,
        ruleId: "(html plugin)",
        severity: reportBadIndent === true ? 2 : reportBadIndent,
      })
    })
  }

  newMessages.sort((ma, mb) => {
    return ma.line - mb.line || ma.column - mb.column
  })

  return newMessages
}
<commit_msg>fix: eslint-comments plugin compatibility<commit_after>"use strict"

const path = require("path")
const extract = require("./extract")
const oneLine = require("./utils").oneLine
const getSettings = require("./settings").getSettings

// Disclaimer:
//
// This is not a long term viable solution. ESLint needs to improve its processor API to
// provide access to the configuration before actually preprocess files, but it's not
// planed yet. This solution is quite ugly but shouldn't alter eslint process.
//
// Related github issues:
// https://github.com/eslint/eslint/issues/3422
// https://github.com/eslint/eslint/issues/4153

const needleV3 = path.join("lib", "eslint.js")
const needleV4 = path.join("lib", "linter.js")

iterateESLintModules(patch)

function getModulesFromRequire() {
  let eslint
  try {
    // V3-
    eslint = require("eslint/lib/eslint")
  }
  catch (e) {
    // V4+
    eslint = require("eslint/lib/linter").prototype
  }

  return {
    eslint,
    SourceCodeFixer: require("eslint/lib/util/source-code-fixer"),
  }
}

function getModulesFromCache(key) {
  const isV3 = key.endsWith(needleV3)
  const isV4 = key.endsWith(needleV4)
  if (!isV3 && !isV4) return

  const module = require.cache[key]
  if (!module || !module.exports) return

  const SourceCodeFixer =
    require.cache[path.join(key, "..", "util", "source-code-fixer.js")]
  if (!SourceCodeFixer || !SourceCodeFixer.exports) return

  const eslint = isV3 ? module.exports : module.exports.prototype
  if (typeof eslint.verify !== "function") return

  return {
    eslint,
    SourceCodeFixer: SourceCodeFixer.exports,
  }
}

function iterateESLintModules(fn) {
  if (!require.cache || Object.keys(require.cache).length === 0) {
    // Jest is replacing the node "require" function, and "require.cache" isn't available here.
    fn(getModulesFromRequire())
    return
  }

  let found = false

  for (const key in require.cache) {
    const modules = getModulesFromCache(key)
    if (modules) {
      fn(modules)
      found = true
    }
  }

  if (!found) {
    throw new Error(
      oneLine`
      eslint-plugin-html error: It seems that eslint is not loaded.
      If you think it is a bug, please file a report at
      https://github.com/BenoitZugmeyer/eslint-plugin-html/issues
    `
    )
  }
}

function patch(modules) {
  const eslint = modules.eslint
  const SourceCodeFixer = modules.SourceCodeFixer

  const sourceCodeForMessages = new WeakMap()

  const verify = eslint.verify
  eslint.verify = function(
    textOrSourceCode,
    config,
    filenameOrOptions,
    saveState
  ) {
    const localVerify = (code) =>
      verify.call(this, code, config, filenameOrOptions, saveState)

    let messages
    const filename =
      typeof filenameOrOptions === "object"
        ? filenameOrOptions.filename
        : filenameOrOptions
    const extension = path.extname(filename || "")

    const pluginSettings = getSettings(config.settings || {})
    const isHTML = pluginSettings.htmlExtensions.indexOf(extension) >= 0
    const isXML =
      !isHTML && pluginSettings.xmlExtensions.indexOf(extension) >= 0

    if (typeof textOrSourceCode === "string" && (isHTML || isXML)) {
      const currentInfos = extract(
        textOrSourceCode,
        pluginSettings.indent,
        isXML,
        pluginSettings.isJavaScriptMIMEType
      )

      messages = []

      currentInfos.code.forEach((code) => {
        messages.push.apply(
          messages,
          remapMessages(
            localVerify(String(code)),
            code,
            pluginSettings.reportBadIndent,
            currentInfos.badIndentationLines
          )
        )
      })

      sourceCodeForMessages.set(messages, textOrSourceCode)
    }
    else {
      messages = localVerify(textOrSourceCode)
    }

    return messages
  }

  const applyFixes = SourceCodeFixer.applyFixes
  SourceCodeFixer.applyFixes = function(sourceCode, messages) {
    const originalSourceCode = sourceCodeForMessages.get(messages)
    // The BOM is always included in the HTML, which is removed by the extract process
    return applyFixes.call(
      this,
      originalSourceCode === undefined
        ? sourceCode
        : { text: originalSourceCode, hasBOM: false },
      messages
    )
  }
}

function remapMessages(messages, code, reportBadIndent, badIndentationLines) {
  const newMessages = []

  for (const message of messages) {
    const location = code.originalLocation({
      line: message.line,
      // eslint-plugin-eslint-comments is raising message with column=0 to bypass ESLint ignore
      // comments. Since messages are already ignored at this time, just reset the column to a valid
      // number. See https://github.com/BenoitZugmeyer/eslint-plugin-html/issues/70
      column: message.column || 1,
    })

    // Ignore messages if they were in transformed code
    if (location) {
      Object.assign(message, location)
      message.source = code.getOriginalLine(location.line)

      // Map fix range
      if (message.fix && message.fix.range) {
        message.fix.range = [
          code.originalIndex(message.fix.range[0]),
          // The range end is exclusive, meaning it should replace all characters  with indexes from
          // start to end - 1. We have to get the original index of the last targeted character.
          code.originalIndex(message.fix.range[1] - 1) + 1,
        ]
      }

      // Map end location
      if (message.endLine && message.endColumn) {
        const endLocation = code.originalLocation({
          line: message.endLine,
          column: message.endColumn,
        })
        if (endLocation) {
          message.endLine = endLocation.line
          message.endColumn = endLocation.column
        }
      }

      newMessages.push(message)
    }
  }

  if (reportBadIndent) {
    badIndentationLines.forEach((line) => {
      newMessages.push({
        message: "Bad line indentation.",
        line,
        column: 1,
        ruleId: "(html plugin)",
        severity: reportBadIndent === true ? 2 : reportBadIndent,
      })
    })
  }

  newMessages.sort((ma, mb) => {
    return ma.line - mb.line || ma.column - mb.column
  })

  return newMessages
}
<|endoftext|>


<commit_before>'use strict';

class keyv {}

module.exports = keyv;
<commit_msg>Wrap Map API<commit_after>'use strict';

class keyv {
	constructor() {
		this.store = new Map();
	}

	get(key) {
		return this.store.get(key);
	}

	set(key, value) {
		return this.store.set(key, value);
	}

	delete(key) {
		return this.store.delete(key);
	}
}

module.exports = keyv;
<|endoftext|>


<commit_before>const aws = require('aws-sdk');
const aws4 = require('aws4');
const https = require('https');
const plpr = require('plpr');
const fs = require('fs');
const pgn = require('pg-query-normalizer');

const es = require('./elasticsearch');
const bq = require('./bq');

const rds = new aws.RDS();

const DBInstanceIdentifier = process.env.AWS_DB_INSTANCE_IDENTIFIER;
const logLinePrefix = process.env.PSQL_LOG_LINE_PREFIX;
const backend = process.env.BACKEND_SERVICE;

async function downloadLogFile() {
  const params = {
    DBInstanceIdentifier,
  };
  if (typeof DBInstanceIdentifier === 'undefined') throw new Error('You have to export AWS RDS DB instance name to "AWS_DB_INSTANCE_IDENTIFIER"');

  const describeDBLogFilesPromise = rds.describeDBLogFiles(params).promise();
  const data = await describeDBLogFilesPromise;
  const obj = JSON.parse(JSON.stringify(data)).DescribeDBLogFiles;
  const files = obj.map(value => value.LogFileName);

  const filename = process.env.AWS_DB_LOGFILE_NAME;
  params.LogFileName = filename;
  if (typeof filename === 'undefined') {
    params.LogFileName = files[files.length - 2];
  }
  console.log(`Downloading: ${params.LogFileName}...`); // eslint-disable-line no-console

  const region = rds.config.region;
  process.env.AWS_ACCESS_KEY_ID = rds.config.credentials.accessKeyId;
  process.env.AWS_SECRET_ACCESS_KEY = rds.config.credentials.secretAccessKey;
  const path = `/v13/downloadCompleteLogFile/${DBInstanceIdentifier}/${params.LogFileName}`;

  const opt = aws4.sign({ service: 'rds', path, region });
  return new Promise((resolve, reject) => {
    const req = https.request(opt, (res) => { // eslint-disable-line
      let body = '';
      res.on('data', (chunk) => {
        body += chunk;
      });
      res.on('end', () => { // eslint-disable-line
        if (res.statusCode !== 200) {
          return reject(new Error(body));
        }
      });
    });
    req.end();
    req.on('err', err => reject(err));
  });
}

function dataReceiver() {
  const filepath = process.env.LOCAL_LOGFILE_PATH;
  if (typeof filepath === 'undefined') {
    return downloadLogFile();
  }
  return Promise.resolve(fs.readFileSync(filepath, 'utf-8'));
}

function normalizeQueries(logs) {
  const newLogs = [];
  for (let i = 0, len = logs.length; i < len; i += 1) {
    const normalized = pgn(logs[i].query);
    newLogs[i] = logs[i];
    newLogs[i].normalized_query = normalized;
  }
  return newLogs;
}

exports.handler = (event, context, callback) => {
  dataReceiver().then((data) => {
    if (typeof logLinePrefix === 'undefined') throw new Error('You have to set PostgreSQL log_line_prefix in PSQL_LOG_LINE_PREFIX');

    const logs = plpr(data, logLinePrefix);
    if (logs.length === 0) {
      return callback(null, 'success');
    }
    console.log(`Insert data length: ${logs.length}`); // eslint-disable-line no-console

    let inserter = null;
    switch (backend) {
      case 'es':
        inserter = es;
        break;
      case 'bq':
        inserter = bq;
        break;
      default:
        throw new Error('You have to export Backend Service "es" (Elasticsearch) or "bq" (BigQuery) to "BACKEND_SERVICE"');
    }

    const normalized = normalizeQueries(logs);
    return inserter(normalized, DBInstanceIdentifier).then(() => callback(null, 'success')).catch(err => callback(err, 'error'));
  }).catch(err => callback(err, 'error'));
};
<commit_msg>Tweak<commit_after>const aws = require('aws-sdk');
const aws4 = require('aws4');
const https = require('https');
const plpr = require('plpr');
const fs = require('fs');
const pgn = require('pg-query-normalizer');

const es = require('./elasticsearch');
const bq = require('./bq');

const DBInstanceIdentifier = process.env.AWS_DB_INSTANCE_IDENTIFIER;
const logLinePrefix = process.env.PSQL_LOG_LINE_PREFIX;
const backend = process.env.BACKEND_SERVICE;

async function downloadLogFile() {
  const rds = new aws.RDS();

  const params = {
    DBInstanceIdentifier,
  };
  if (typeof DBInstanceIdentifier === 'undefined') throw new Error('You have to export AWS RDS DB instance name to "AWS_DB_INSTANCE_IDENTIFIER"');

  const describeDBLogFilesPromise = rds.describeDBLogFiles(params).promise();
  const data = await describeDBLogFilesPromise;
  const obj = JSON.parse(JSON.stringify(data)).DescribeDBLogFiles;
  const files = obj.map(value => value.LogFileName);

  const filename = process.env.AWS_DB_LOGFILE_NAME;
  params.LogFileName = filename;
  if (typeof filename === 'undefined') {
    params.LogFileName = files[files.length - 2];
  }
  console.log(`Downloading: ${params.LogFileName}...`); // eslint-disable-line no-console

  const region = rds.config.region;
  process.env.AWS_ACCESS_KEY_ID = rds.config.credentials.accessKeyId;
  process.env.AWS_SECRET_ACCESS_KEY = rds.config.credentials.secretAccessKey;
  const path = `/v13/downloadCompleteLogFile/${DBInstanceIdentifier}/${params.LogFileName}`;

  const opt = aws4.sign({ service: 'rds', path, region });
  return new Promise((resolve, reject) => {
    const req = https.request(opt, (res) => { // eslint-disable-line
      let body = '';
      res.on('data', (chunk) => {
        body += chunk;
      });
      res.on('end', () => { // eslint-disable-line
        if (res.statusCode !== 200) {
          return reject(new Error(body));
        }
        return resolve(body);
      });
    });
    req.end();
    req.on('err', err => reject(err));
  });
}

function dataReceiver() {
  const filepath = process.env.LOCAL_LOGFILE_PATH;
  if (typeof filepath === 'undefined') {
    return downloadLogFile();
  }
  return Promise.resolve(fs.readFileSync(filepath, 'utf-8'));
}

function normalizeQueries(logs) {
  const newLogs = [];
  for (let i = 0, len = logs.length; i < len; i += 1) {
    const normalized = pgn(logs[i].query);
    newLogs[i] = logs[i];
    newLogs[i].normalized_query = normalized;
  }
  return newLogs;
}

exports.handler = (event, context, callback) => {
  dataReceiver().then((data) => {
    if (typeof logLinePrefix === 'undefined') throw new Error('You have to set PostgreSQL log_line_prefix in PSQL_LOG_LINE_PREFIX');

    const logs = plpr(data, logLinePrefix);
    if (logs.length === 0) {
      return callback(null, 'nothing logs');
    }
    console.log(`Insert data length: ${logs.length}`); // eslint-disable-line no-console

    let inserter = null;
    switch (backend) {
      case 'es':
        inserter = es;
        break;
      case 'bq':
        inserter = bq;
        break;
      default:
        throw new Error('You have to export Backend Service "es" (Elasticsearch) or "bq" (BigQuery) to "BACKEND_SERVICE"');
    }

    const normalized = normalizeQueries(logs);
    return inserter(normalized, DBInstanceIdentifier).then(() => callback(null, 'success')).catch(err => callback(err, 'error'));
  }).catch(err => callback(err, 'error'));
};
<|endoftext|>


<commit_before>require('babel-polyfill');

/* eslint-disable import/first */
import request from 'request-promise-native';
import FeedParser from 'feedparser';
import config from 'config';
import { Feed, Article } from 'mtg-omega-models-sql';
import { log } from 'zweer-utils';
/* eslint-enable import/first */

function parseRss(rss) {
  const feedparser = new FeedParser();
  const articles = [];

  log.silly(rss);
  log.info('Parsing rss');

  return new Promise((resolve, reject) => {
    feedparser
      .on('error', err => reject(err))
      .on('readable', () => {
        let article;

        while (article = feedparser.read()) { // eslint-disable-line no-cond-assign
          articles.push(article);
        }
      })
      .on('end', () => {
        resolve({
          meta: feedparser.meta,
          articles,
        });
      })
      .end(rss);
  });
}

export async function check() {
  log.info('Check phase begun');

  const feedsToImport = config.get('app.batch-feed-import.feeds');
  const feeds = await Feed.findAll({
    where: {
      url: {
        $notIn: feedsToImport,
      },
    },
  });

  log.info(`Found ${feeds.length} feed[s] to delete`);

  for (let i = 0, tot = feeds.length; i < tot; i += 1) {
    const feed = feeds[i];

    log.info(`Removing feed: ${feed.url}`);

    await feed.destroy();
  }
}

export async function init() {
  log.info('Init phase begun');

  const feedsToImport = config.get('app.batch-feed-import.feeds');

  for (let i = 0, tot = feedsToImport.length; i < tot; i += 1) {
    const url = feedsToImport[i];
    const [feed, isNew] = await Feed.findCreateFind({
      where: {
        url,
      },
    });

    log.info(`Feed ${feed.url} is ${isNew ? 'new' : 'old'}`);
  }
}

export async function batch() {
  log.info('Batch phase begun');

  const feeds = await Feed.findAll();
  const totFeeds = feeds.length;

  log.info(`Found ${totFeeds} feed[s]`);

  for (let i = 0; i < totFeeds; i += 1) {
    const feed = feeds[i];
    const url = feed.url;

    const rss = await request(url);
    const { meta, articles } = await parseRss(rss);
    const totArticles = articles.length;

    log.info(`Working on feed "${feed.title}": ${totArticles} article[s] found`);

    feed.title = meta.title;
    feed.description = meta.description;
    feed.link = meta.link;
    feed.url = meta.xmlurl;
    feed.author = meta.author;
    feed.language = meta.language;
    feed.copyright = meta.copyright;
    feed.generator = meta.generator;
    feed.categories = meta.categories;

    feed.image = meta.image ? meta.image.url : null;
    feed.favicon = meta.favicon ? meta.favicon.url : null;

    feed.mostRecentUpdateAt = new Date(meta.date);
    feed.publishedAt = new Date(meta.pubdate);

    await feed.save();

    for (let j = 0; j < totArticles; j += 1) {
      const article = articles[j];

      const articleCount = await Article.count({ where: { guid: article.guid } });
      if (articleCount === 0) {
        log.info(`Article ${article.title} is new!`);

        await feed.createArticle({
          title: article.title,
          description: article.description,
          summary: article.summary,
          author: article.author,
          guid: article.guid,

          link: article.link,
          originalLink: article.origlink,
          permalink: article.permalink,

          comments: article.comments,

          image: article.image ? article.image.url : null,

          categories: article.categories,

          articleUpdatedAt: new Date(article.date),
          articlePublishedAt: new Date(article.pubdate),
        });
      } else {
        log.info(`Article ${article.title} is already in our database!`);
      }
    }

    log.info(`Finished working on feed "${feed.title}"`);
  }
}

export function handler(event, context, done) {
  console.log('test test test');

  return Promise.resolve()
    .then(() => check())
    .then(() => init())
    .then(() => batch())
    .then(() => done())
    .catch((err) => {
      log.error('Error while executing the batch');
      log.info(err);

      done(err);
    });
}
<commit_msg>Added the sync phase<commit_after>require('babel-polyfill');

/* eslint-disable import/first */
import request from 'request-promise-native';
import FeedParser from 'feedparser';
import config from 'config';
import { Feed, Article, sequelize } from 'mtg-omega-models-sql';
import { log } from 'zweer-utils';
/* eslint-enable import/first */

function parseRss(rss) {
  const feedparser = new FeedParser();
  const articles = [];

  log.silly(rss);
  log.info('Parsing rss');

  return new Promise((resolve, reject) => {
    feedparser
      .on('error', err => reject(err))
      .on('readable', () => {
        let article;

        while (article = feedparser.read()) { // eslint-disable-line no-cond-assign
          articles.push(article);
        }
      })
      .on('end', () => {
        resolve({
          meta: feedparser.meta,
          articles,
        });
      })
      .end(rss);
  });
}

export async function check() {
  log.info('Check phase begun');

  const feedsToImport = config.get('app.batch-feed-import.feeds');
  const feeds = await Feed.findAll({
    where: {
      url: {
        $notIn: feedsToImport,
      },
    },
  });

  log.info(`Found ${feeds.length} feed[s] to delete`);

  for (let i = 0, tot = feeds.length; i < tot; i += 1) {
    const feed = feeds[i];

    log.info(`Removing feed: ${feed.url}`);

    await feed.destroy();
  }
}

export async function init() {
  log.info('Init phase begun');

  const feedsToImport = config.get('app.batch-feed-import.feeds');

  for (let i = 0, tot = feedsToImport.length; i < tot; i += 1) {
    const url = feedsToImport[i];
    const [feed, isNew] = await Feed.findCreateFind({
      where: {
        url,
      },
    });

    log.info(`Feed ${feed.url} is ${isNew ? 'new' : 'old'}`);
  }
}

export async function batch() {
  log.info('Batch phase begun');

  const feeds = await Feed.findAll();
  const totFeeds = feeds.length;

  log.info(`Found ${totFeeds} feed[s]`);

  for (let i = 0; i < totFeeds; i += 1) {
    const feed = feeds[i];
    const url = feed.url;

    const rss = await request(url);
    const { meta, articles } = await parseRss(rss);
    const totArticles = articles.length;

    log.info(`Working on feed "${feed.title}": ${totArticles} article[s] found`);

    feed.title = meta.title;
    feed.description = meta.description;
    feed.link = meta.link;
    feed.url = meta.xmlurl;
    feed.author = meta.author;
    feed.language = meta.language;
    feed.copyright = meta.copyright;
    feed.generator = meta.generator;
    feed.categories = meta.categories;

    feed.image = meta.image ? meta.image.url : null;
    feed.favicon = meta.favicon ? meta.favicon.url : null;

    feed.mostRecentUpdateAt = new Date(meta.date);
    feed.publishedAt = new Date(meta.pubdate);

    await feed.save();

    for (let j = 0; j < totArticles; j += 1) {
      const article = articles[j];

      const articleCount = await Article.count({ where: { guid: article.guid } });
      if (articleCount === 0) {
        log.info(`Article ${article.title} is new!`);

        await feed.createArticle({
          title: article.title,
          description: article.description,
          summary: article.summary,
          author: article.author,
          guid: article.guid,

          link: article.link,
          originalLink: article.origlink,
          permalink: article.permalink,

          comments: article.comments,

          image: article.image ? article.image.url : null,

          categories: article.categories,

          articleUpdatedAt: new Date(article.date),
          articlePublishedAt: new Date(article.pubdate),
        });
      } else {
        log.info(`Article ${article.title} is already in our database!`);
      }
    }

    log.info(`Finished working on feed "${feed.title}"`);
  }
}

export function handler(event, context, done) {
  console.log('test test test');

  return Promise.resolve()
    .then(() => sequelize.sync())
    .then(() => check())
    .then(() => init())
    .then(() => batch())
    .then(() => done())
    .catch((err) => {
      log.error('Error while executing the batch');
      log.info(err);

      done(err);
    });
}
<|endoftext|>


<commit_before>function addClass(elem, className) {
    if (elem.classList) {
        elem.classList.add(className);
    } else {
        elem.className += ' ' + className;
    }
    return elem;
}

function removeClass(elem, className) {
    if (elem.classList) {
        elem.classList.remove(className);
    } else {
        elem.className = elem.className.replace(new RegExp('(^|\\b)'
        + className.split(' ').join('|')
        + '(\\b|$)', 'gi'), ' ');
    }

    return elem;
}

function persistHistory(persistHistoryAs, history) {
    if (persistHistoryAs) {
        sessionStorage.setItem(persistHistoryAs, JSON.stringify(history));
    }
}

function createHistory(persistHistoryAs = null) {
    let history = [];
    if (persistHistoryAs) {
        if (sessionStorage.getItem(persistHistoryAs)) {
            history = JSON.parse(sessionStorage.getItem(persistHistoryAs));
        }
    }
    return {
        persistHistoryAs: persistHistoryAs,
        history,
        push(key) {
            this.history.push(key);
            persistHistory(this.persistHistoryAs, this.history);
        },
        pop() {
            this.history.pop();
            persistHistory(this.persistHistoryAs, this.history);
        },
        last() {
            return this.history[this.history.length - 1]
                ? this.history[this.history.length - 1]
                : undefined;
        }
    };
}

function loadClasses(classList, lastElem, elem, direction) {
    addClass(elem.parentNode, classList.parent);
    addClass(lastElem, classList.lastElem);
    addClass(elem, classList.newElem);
    addClass(elem, classList.direction.replace('<direction>', direction));
}

function unloadClasses(classList, barrier, parentNode, elem, direction) {
    const newBarrier = barrier - 1;
    if (newBarrier > 0) {
        return newBarrier;
    }
    removeClass(elem, classList.newElem);
    removeClass(elem, classList.direction.replace('<direction>', direction));

    if (elem.parentNode) {
        removeClass(elem.parentNode, classList.parent);
    }
    return newBarrier;
}

function config(key, elem, isInit, ctx) {
    if (this.useHistory && !key) {
        throw new Error('Error in mithril-transition: ' +
            'is required specified a key for the v-node.');
    }

    if (!isInit) {
        const parentNode = elem.parentNode;

        let direction = 'next';

        if (this.useHistory) {
            /**
             *  if the page that comes it was in the final
             *  of the history queue i pop from that
             */
            if (this.history.last() === key) {
                direction = 'prev';
                this.history.pop();
            } else {
                /**
                 *  if direction is go the next i save the state
                 *  of the last element
                 */
                if (this.last) {
                    this.history.push(this.last.key);
                }
            }
        }

        if (this.last) {
            let lastElem = this.last.elem;
            const id = 'mithril-transition-' + Date.now();
            loadClasses(this.classList, lastElem, elem, direction);
            lastElem.dataset.transitionId = id;
            parentNode
                .insertAdjacentHTML('beforeend', lastElem.outerHTML);
            this.last.elem = lastElem = parentNode
                .querySelector('[data-transition-id=' + id + ']');

            let barrier = 2;
            this.anim(
                lastElem,
                elem,
                direction, () => {
                    parentNode.removeChild(lastElem);
                    barrier = unloadClasses(
                        this.classList,
                        barrier,
                        parentNode,
                        elem,
                        direction
                    );
                }, () => {
                    barrier = unloadClasses(
                        this.classList,
                        barrier,
                        parentNode,
                        elem,
                        direction
                    );
                }
            );
        }

        const userOnUnload = ctx.onunload;
        ctx.onunload = () => {
            elem.style['pointer-events'] = 'none';
            /**
             *  the current element unloaded
             *  is going to be the "new last element"
             */
            this.last = {
                key: key,
                elem: elem
            };

            if (userOnUnload) {
                userOnUnload();
            }
        };
    }
}

export default function transition({
    anim,
    useHistory = true,
    persistHistoryAs = null,
    classList = {
        parent: 'm-transition-parent',
        lastElem: 'm-transition-last-element',
        newElem: 'm-transition-new-element',
        direction: 'm-transition-<direction>'
    }
} = {}) {
    if (!anim) {
        throw new Error('Error in mithril-transition: ' +
            'option `anim` is required.');
    }

    const that = {
        useHistory,
        anim,
        config,
        classList
    };

    if (that.useHistory) {
        that.history = createHistory(persistHistoryAs);
    }

    return function animate(elem, isInit, ctx) {
        that.config(this.attrs.key, elem, isInit, ctx);
    };
}
<commit_msg>remove empty space<commit_after>function addClass(elem, className) {
    if (elem.classList) {
        elem.classList.add(className);
    } else {
        elem.className += ' ' + className;
    }
    return elem;
}

function removeClass(elem, className) {
    if (elem.classList) {
        elem.classList.remove(className);
    } else {
        elem.className = elem.className.replace(new RegExp('(^|\\b)'
        + className.split(' ').join('|')
        + '(\\b|$)', 'gi'), ' ');
    }
    return elem;
}

function persistHistory(persistHistoryAs, history) {
    if (persistHistoryAs) {
        sessionStorage.setItem(persistHistoryAs, JSON.stringify(history));
    }
}

function createHistory(persistHistoryAs = null) {
    let history = [];
    if (persistHistoryAs) {
        if (sessionStorage.getItem(persistHistoryAs)) {
            history = JSON.parse(sessionStorage.getItem(persistHistoryAs));
        }
    }
    return {
        persistHistoryAs: persistHistoryAs,
        history,
        push(key) {
            this.history.push(key);
            persistHistory(this.persistHistoryAs, this.history);
        },
        pop() {
            this.history.pop();
            persistHistory(this.persistHistoryAs, this.history);
        },
        last() {
            return this.history[this.history.length - 1]
                ? this.history[this.history.length - 1]
                : undefined;
        }
    };
}

function loadClasses(classList, lastElem, elem, direction) {
    addClass(elem.parentNode, classList.parent);
    addClass(lastElem, classList.lastElem);
    addClass(elem, classList.newElem);
    addClass(elem, classList.direction.replace('<direction>', direction));
}

function unloadClasses(classList, barrier, parentNode, elem, direction) {
    const newBarrier = barrier - 1;
    if (newBarrier > 0) {
        return newBarrier;
    }
    removeClass(elem, classList.newElem);
    removeClass(elem, classList.direction.replace('<direction>', direction));

    if (elem.parentNode) {
        removeClass(elem.parentNode, classList.parent);
    }
    return newBarrier;
}

function config(key, elem, isInit, ctx) {
    if (this.useHistory && !key) {
        throw new Error('Error in mithril-transition: ' +
            'is required specified a key for the v-node.');
    }

    if (!isInit) {
        const parentNode = elem.parentNode;

        let direction = 'next';

        if (this.useHistory) {
            /**
             *  if the page that comes it was in the final
             *  of the history queue i pop from that
             */
            if (this.history.last() === key) {
                direction = 'prev';
                this.history.pop();
            } else {
                /**
                 *  if direction is go the next i save the state
                 *  of the last element
                 */
                if (this.last) {
                    this.history.push(this.last.key);
                }
            }
        }

        if (this.last) {
            let lastElem = this.last.elem;
            const id = 'mithril-transition-' + Date.now();
            loadClasses(this.classList, lastElem, elem, direction);
            lastElem.dataset.transitionId = id;
            parentNode
                .insertAdjacentHTML('beforeend', lastElem.outerHTML);
            this.last.elem = lastElem = parentNode
                .querySelector('[data-transition-id=' + id + ']');

            let barrier = 2;
            this.anim(
                lastElem,
                elem,
                direction, () => {
                    parentNode.removeChild(lastElem);
                    barrier = unloadClasses(
                        this.classList,
                        barrier,
                        parentNode,
                        elem,
                        direction
                    );
                }, () => {
                    barrier = unloadClasses(
                        this.classList,
                        barrier,
                        parentNode,
                        elem,
                        direction
                    );
                }
            );
        }

        const userOnUnload = ctx.onunload;
        ctx.onunload = () => {
            elem.style['pointer-events'] = 'none';
            /**
             *  the current element unloaded
             *  is going to be the "new last element"
             */
            this.last = {
                key: key,
                elem: elem
            };

            if (userOnUnload) {
                userOnUnload();
            }
        };
    }
}

export default function transition({
    anim,
    useHistory = true,
    persistHistoryAs = null,
    classList = {
        parent: 'm-transition-parent',
        lastElem: 'm-transition-last-element',
        newElem: 'm-transition-new-element',
        direction: 'm-transition-<direction>'
    }
} = {}) {
    if (!anim) {
        throw new Error('Error in mithril-transition: ' +
            'option `anim` is required.');
    }

    const that = {
        useHistory,
        anim,
        config,
        classList
    };

    if (that.useHistory) {
        that.history = createHistory(persistHistoryAs);
    }

    return function animate(elem, isInit, ctx) {
        that.config(this.attrs.key, elem, isInit, ctx);
    };
}
<|endoftext|>


<commit_before>import { extend_selection } from "./core/extend_selection";
import { Dispatcher } from './core/Dispatcher';
import { EventEmitter } from './core/EventEmitter';
import { assign } from './core/utils';

var SubUnit = {};

if ( typeof module === 'object' ) {
  module.exports = SubUnit;
}

SubUnit.select = function (object) {
  var node = typeof object === "function" ? object(): object;
  var root = extend_selection([[new THREE.Object3D()]]);
  root.parentNode = node;
  root[0][0].__data__ = {};
  root[0][0].__tags__ = [];
  node.add(root[0][0]);
  return root;
};

SubUnit.object = function (object) {
  return extend_selection([[object]]);
};

SubUnit.createDispatcher = function () {

  var dispatcher = new Dispatcher();

  dispatcher.serverAction = function (action) {
    var payload = {
      source: 'SERVER_ACTION',
      action: action
    };
    this.dispatch(payload);
  };

  dispatcher.viewAction = function (action) {
    var payload = {
      source: 'VIEW_ACTION',
      action: action
    };
    this.dispatch(payload);
  };

  return dispatcher;
};

SubUnit.createStore = function (methods) {
  var store = assign({}, EventEmitter.prototype, methods);

  store.emitChange = function() {
    this.emit('change');
  };

  store.addChangeListener = function (callback) {
    this.on('change', callback);
  };

  store.removeChangeListener = function(callback) {
    this.removeListener('change', callback);
  };

  return store;
};

function SubUnitView (parent) {
  this.state = {};

  var node = new THREE.Object3D();
  this.parentNode = parent;
  this.parentNode.add(node);

  this.root = SubUnit.object(node);
}

SubUnitView.prototype.render = function () {};

SubUnitView.prototype.setState = function (data) {
  this.state = data;
  this.render();
};

SubUnitView.prototype.remove = function () {
  if (this.viewWillUnmount && typeof this.viewWillUnmount === 'function') {
    this.viewWillUnmount();
  }
  this.parentNode.remove(this.root.node());
};

SubUnit.createView = function (parent, methods) {

  var view = assign(new SubUnitView(parent), methods);

  if (!view.getInitialState || typeof view.getInitialState !== 'function') {
    console.warn('SubUnit view must have a getInitialState method');
  } else {
    view.setState(view.getInitialState());
  }

  if (view.viewDidMount && typeof view.viewDidMount === 'function') {
    view.viewDidMount();
  }

  if (!view.render || typeof view.render !== 'function') {
    console.warn('SubUnit view must have a render method');
  } else {
    view.render();
  }

  return view;
};

this.SubUnit = SubUnit;<commit_msg>add cache, lru, and memoize.  Change export options<commit_after>import { extend_selection } from "./core/extend_selection";
import { Dispatcher } from './core/Dispatcher';
import { EventEmitter } from './core/EventEmitter';
import { assign } from './core/utils';
import { LRUCache } from './core/LRUCache';

var SubUnit = {};

SubUnit.select = function (object) {
  var node = typeof object === "function" ? object(): object;
  var root = extend_selection([[new THREE.Object3D()]]);
  root.parentNode = node;
  root[0][0].__data__ = {};
  root[0][0].__tags__ = [];
  node.add(root[0][0]);
  return root;
};

SubUnit.object = function (object) {
  return extend_selection([[object]]);
};

SubUnit.createDispatcher = function () {

  var dispatcher = new Dispatcher();

  dispatcher.serverAction = function (action) {
    var payload = {
      source: 'SERVER_ACTION',
      action: action
    };
    this.dispatch(payload);
  };

  dispatcher.viewAction = function (action) {
    var payload = {
      source: 'VIEW_ACTION',
      action: action
    };
    this.dispatch(payload);
  };

  return dispatcher;
};

SubUnit.createStore = function (methods) {
  var store = assign({}, EventEmitter.prototype, methods);

  store.emitChange = function() {
    this.emit('change');
  };

  store.addChangeListener = function (callback) {
    this.on('change', callback);
  };

  store.removeChangeListener = function(callback) {
    this.removeListener('change', callback);
  };

  return store;
};

function SubUnitView (parent) {
  this.state = {};

  var node = new THREE.Object3D();
  this.parentNode = parent;
  this.parentNode.add(node);

  this.root = SubUnit.object(node);
}

SubUnitView.prototype.render = function () {};

SubUnitView.prototype.setState = function (data) {
  this.state = data;
  this.render();
};

SubUnitView.prototype.remove = function () {
  if (this.viewWillUnmount && typeof this.viewWillUnmount === 'function') {
    this.viewWillUnmount();
  }
  this.parentNode.remove(this.root.node());
};

SubUnit.createView = function (parent, methods) {

  var view = assign(new SubUnitView(parent), methods);

  if (!view.getInitialState || typeof view.getInitialState !== 'function') {
    console.warn('SubUnit view must have a getInitialState method');
  } else {
    view.setState(view.getInitialState());
  }

  if (view.viewDidMount && typeof view.viewDidMount === 'function') {
    view.viewDidMount();
  }

  if (!view.render || typeof view.render !== 'function') {
    console.warn('SubUnit view must have a render method');
  } else {
    view.render();
  }

  return view;
};

// adapted from memoize.js by @philogb and @addyosmani
SubUnit.cache = function (max, fn) {
  return function () {
    var args = Array.prototype.slice.call(arguments);

    var key = "", len = args.length, cur = null, entry;

    while (len--) {
      cur = args[len];
      key += (cur === Object(cur))? JSON.stringify(cur): cur;

      if (!fn.memoize) {
        fn.memoize = new LRUCache({max: max});
      }
    }

    if (entry = fn.memoize.get(key)) {
      return entry;
    }

    entry = fn.apply(this, args);
    fn.memoize.set(key, entry);

    return entry;
  };
};

SubUnit.lru = function (max) {
  max = max || 0;

  return new LRUCache({max: max});
};

// adapted from memoize.js by @philogb and @addyosmani
SubUnit.memoize = function (fn) {
  return function () {
    var args = Array.prototype.slice.call(arguments);

    var key = "", len = args.length, cur = null;

    while (len--) {
      cur = args[len];
      key += (cur === Object(cur))? JSON.stringify(cur): cur;

      if (!fn.memoize) {
        fn.memoize = {};
      }
    }

    return (key in fn.memoize)? fn.memoize[key]:
    fn.memoize[key] = fn.apply(this, args);
  };
};

if (typeof define === "function" && define.amd) {
  define(this.SubUnit = SubUnit);
}
else if (typeof module === "object" && module.exports) {
  module.exports = SubUnit;
}
else {
 this.SubUnit = SubUnit;
}<|endoftext|>


<commit_before>(() => {

  // Compute the top and left coordinate of the given `element`.
  function computeElementOffset(element) {
    const rect = element.getBoundingClientRect();
    return {
      top: rect.top + (window.pageXOffset || document.documentElement.scrollLeft),
      left: rect.left + (window.pageYOffset || document.documentElement.scrollTop)
    };
  }

  // Compute the gradient of a line drawn from `pointA` to `pointB`.
  function computeGradient(pointA, pointB) {
    return (pointB.y - pointA.y) / (pointB.x - pointA.x);
  }

  function noop() {}

  function menuAim(element, options) {

    const rowSelector = options.rowSelector || 'li';
    const delay = options.delay || 250;
    const submenuDirection = options.submenuDirection || 'right';
    const activateCallback = options.activateCallback || noop;
    const deactivateCallback = options.deactivateCallback || noop;

    let activeRow = null;
    let timeoutId = null;
    let previousCoordinates = null;
    let currentCoordinates = null;
    let lastCheckedCoordinates = null;

    // Record the last 2 mouse coordinates.
    function saveMouseCoordinates(event) {
      previousCoordinates = currentCoordinates;
      currentCoordinates = {
        x: event.pageX,
        y: event.pageY
      };
    }

    // Cancel pending menu row activations.
    function cancelPendingRowActivations() {
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
    }

    // Set `activeRow` to the given menu `row`, and activate it immediately.
    function activateRow(row) {
      if (row === activeRow) {
        // Exit if the menu `row` we want to activate is already
        // the `activeRow`.
        return;
      }
      if (activeRow) {
        // If there is an `activeRow`, deactivate it.
        deactivateCallback(activeRow);
      }
      // Activate the given menu `row`.
      activateCallback(row);
      activeRow = row;
    }

    // Check if we should activate the given menu `row`. If we find that the
    // mouse is moving towards the content of `activeRow`, attempt to activate
    // the row again after a `delay`.
    function possiblyActivateRow(row) {
      cancelPendingRowActivations();
      if (shouldChangeActiveRow()) {
        return activateRow(row);
      }
      timeoutId = setTimeout(() => {
        possiblyActivateRow(row);
      }, delay);
    }

    // Returns `true` if the `activeRow` should be set to a new row, else
    // returns `false`.
    function shouldChangeActiveRow() {

      // If there isn't an `activeRow`, activate the new row immediately.
      if (!activeRow) {
        return true;
      }

      const offset = computeElementOffset(element);
      const topLeft = {
        x: offset.left,
        y: offset.top
      };
      const topRight = {
        x: offset.left + element.offsetWidth,
        y: topLeft.y
      };
      const bottomLeft = {
        x: offset.left,
        y: offset.top + element.offsetHeight
      };
      const bottomRight = {
        x: offset.left + element.offsetWidth,
        y: bottomLeft.y
      };

      // Activate the new row immediately if `currentCoordinates` or
      // `previousCoordinates` are still their initial values.
      if (!currentCoordinates || !previousCoordinates) {
        return true;
      }

      // If the mouse was previously outside the menu's bounds, activate the
      // new row immediately.
      if (previousCoordinates.x < topLeft.x ||
          previousCoordinates.x > bottomRight.x ||
          previousCoordinates.y < topLeft.y ||
          previousCoordinates.y > bottomRight.y) {
        return true;
      }

      // If the mouse hasn't move since the last time we checked, activate the
      // new row immediately.
      if (lastCheckedCoordinates &&
          currentCoordinates.x === lastCheckedCoordinates.x &&
          currentCoordinates.y === lastCheckedCoordinates.y) {
        return true;
      }

      // Our expectations for decreasing or increasing gradients depends on
      // the direction that the submenu shows relative to the menu rows. For
      // example, if the submenu is on the right, expect the slope between the
      // mouse coordinate and the upper right corner to decrease over time.
      let decreasingCorner = topRight;
      let increasingCorner = bottomRight;
      if (submenuDirection === 'left') {
        decreasingCorner = bottomLeft;
        increasingCorner = topLeft;
      } else if (submenuDirection === 'below') {
        decreasingCorner = bottomRight;
        increasingCorner = bottomLeft;
      } else if (submenuDirection === 'above') {
        decreasingCorner = topLeft;
        increasingCorner = topRight;
      }
      const currentDecreasingSlope = computeGradient(currentCoordinates, decreasingCorner);
      const currentIncreasingSlope = computeGradient(currentCoordinates, increasingCorner);
      const previousDecreasingSlope = computeGradient(previousCoordinates, decreasingCorner);
      const previousIncreasingSlope = computeGradient(previousCoordinates, increasingCorner);
      if (currentDecreasingSlope < previousDecreasingSlope &&
          currentIncreasingSlope > previousIncreasingSlope) {
        // The mouse moved from `previousCoordinates` towards the content of
        // the `activeRow`, so we should wait before attempting to activate
        // the new menu row again.
        lastCheckedCoordinates = currentCoordinates;
        return false;
      }

      // The mouse was not moving towards the content of `activeRow`, so
      // immediately activate the new menu row.
      lastCheckedCoordinates = null;
      return true;
    }

    function onRowClick() {
      // Immediately activate the row that was clicked.
      cancelPendingRowActivations();
      activateRow(this);
    }

    function onRowMouseEnter() {
      // Attempt to activate the row that the mouse is currently mousing over.
      possiblyActivateRow(this);
    }

    // Hook up the required event listeners.
    const rows = [].slice.call(element.querySelectorAll(rowSelector));
    rows.forEach(row => {
      row.addEventListener('click', onRowClick);
      row.addEventListener('mouseenter', onRowMouseEnter);
    });
    document.addEventListener('mousemove', saveMouseCoordinates);
    element.addEventListener('mouseleave', cancelPendingRowActivations);

    // Return a function for unhooking all event listeners.
    return function() {
      rows.forEach(row => {
        row.removeEventListener('click', onRowClick);
        row.removeEventListener('mouseenter', onRowMouseEnter);
      });
      document.removeEventListener('mousemove', saveMouseCoordinates);
      element.removeEventListener('mouseleave', cancelPendingRowActivations);
    };

  }

  if (typeof module === 'object') {
    module.exports = menuAim;
  } else {
    window.menuAim = menuAim;
  }

})();
<commit_msg>Change `rowSelector`<commit_after>(() => {

  // Compute the top and left coordinate of the given `element`.
  function computeElementOffset(element) {
    const rect = element.getBoundingClientRect();
    return {
      top: rect.top + (window.pageXOffset || document.documentElement.scrollLeft),
      left: rect.left + (window.pageYOffset || document.documentElement.scrollTop)
    };
  }

  // Compute the gradient of a line drawn from `pointA` to `pointB`.
  function computeGradient(pointA, pointB) {
    return (pointB.y - pointA.y) / (pointB.x - pointA.x);
  }

  function noop() {}

  function menuAim(element, options) {

    const rowSelector = options.rowSelector || '.menu-aim-item';
    const delay = options.delay || 250;
    const submenuDirection = options.submenuDirection || 'right';
    const activateCallback = options.activateCallback || noop;
    const deactivateCallback = options.deactivateCallback || noop;

    let activeRow = null;
    let timeoutId = null;
    let previousCoordinates = null;
    let currentCoordinates = null;
    let lastCheckedCoordinates = null;

    // Record the last 2 mouse coordinates.
    function saveMouseCoordinates(event) {
      previousCoordinates = currentCoordinates;
      currentCoordinates = {
        x: event.pageX,
        y: event.pageY
      };
    }

    // Cancel pending menu row activations.
    function cancelPendingRowActivations() {
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
    }

    // Set `activeRow` to the given menu `row`, and activate it immediately.
    function activateRow(row) {
      if (row === activeRow) {
        // Exit if the menu `row` we want to activate is already
        // the `activeRow`.
        return;
      }
      if (activeRow) {
        // If there is an `activeRow`, deactivate it.
        deactivateCallback(activeRow);
      }
      // Activate the given menu `row`.
      activateCallback(row);
      activeRow = row;
    }

    // Check if we should activate the given menu `row`. If we find that the
    // mouse is moving towards the content of `activeRow`, attempt to activate
    // the row again after a `delay`.
    function possiblyActivateRow(row) {
      cancelPendingRowActivations();
      if (shouldChangeActiveRow()) {
        return activateRow(row);
      }
      timeoutId = setTimeout(() => {
        possiblyActivateRow(row);
      }, delay);
    }

    // Returns `true` if the `activeRow` should be set to a new row, else
    // returns `false`.
    function shouldChangeActiveRow() {

      // If there isn't an `activeRow`, activate the new row immediately.
      if (!activeRow) {
        return true;
      }

      const offset = computeElementOffset(element);
      const topLeft = {
        x: offset.left,
        y: offset.top
      };
      const topRight = {
        x: offset.left + element.offsetWidth,
        y: topLeft.y
      };
      const bottomLeft = {
        x: offset.left,
        y: offset.top + element.offsetHeight
      };
      const bottomRight = {
        x: offset.left + element.offsetWidth,
        y: bottomLeft.y
      };

      // Activate the new row immediately if `currentCoordinates` or
      // `previousCoordinates` are still their initial values.
      if (!currentCoordinates || !previousCoordinates) {
        return true;
      }

      // If the mouse was previously outside the menu's bounds, activate the
      // new row immediately.
      if (previousCoordinates.x < topLeft.x ||
          previousCoordinates.x > bottomRight.x ||
          previousCoordinates.y < topLeft.y ||
          previousCoordinates.y > bottomRight.y) {
        return true;
      }

      // If the mouse hasn't move since the last time we checked, activate the
      // new row immediately.
      if (lastCheckedCoordinates &&
          currentCoordinates.x === lastCheckedCoordinates.x &&
          currentCoordinates.y === lastCheckedCoordinates.y) {
        return true;
      }

      // Our expectations for decreasing or increasing gradients depends on
      // the direction that the submenu shows relative to the menu rows. For
      // example, if the submenu is on the right, expect the slope between the
      // mouse coordinate and the upper right corner to decrease over time.
      let decreasingCorner = topRight;
      let increasingCorner = bottomRight;
      if (submenuDirection === 'left') {
        decreasingCorner = bottomLeft;
        increasingCorner = topLeft;
      } else if (submenuDirection === 'below') {
        decreasingCorner = bottomRight;
        increasingCorner = bottomLeft;
      } else if (submenuDirection === 'above') {
        decreasingCorner = topLeft;
        increasingCorner = topRight;
      }
      const currentDecreasingSlope = computeGradient(currentCoordinates, decreasingCorner);
      const currentIncreasingSlope = computeGradient(currentCoordinates, increasingCorner);
      const previousDecreasingSlope = computeGradient(previousCoordinates, decreasingCorner);
      const previousIncreasingSlope = computeGradient(previousCoordinates, increasingCorner);
      if (currentDecreasingSlope < previousDecreasingSlope &&
          currentIncreasingSlope > previousIncreasingSlope) {
        // The mouse moved from `previousCoordinates` towards the content of
        // the `activeRow`, so we should wait before attempting to activate
        // the new menu row again.
        lastCheckedCoordinates = currentCoordinates;
        return false;
      }

      // The mouse was not moving towards the content of `activeRow`, so
      // immediately activate the new menu row.
      lastCheckedCoordinates = null;
      return true;
    }

    function onRowClick() {
      // Immediately activate the row that was clicked.
      cancelPendingRowActivations();
      activateRow(this);
    }

    function onRowMouseEnter() {
      // Attempt to activate the row that the mouse is currently mousing over.
      possiblyActivateRow(this);
    }

    // Hook up the required event listeners.
    const rows = [].slice.call(element.querySelectorAll(rowSelector));
    rows.forEach(row => {
      row.addEventListener('click', onRowClick);
      row.addEventListener('mouseenter', onRowMouseEnter);
    });
    document.addEventListener('mousemove', saveMouseCoordinates);
    element.addEventListener('mouseleave', cancelPendingRowActivations);

    // Return a function for unhooking all event listeners.
    return function() {
      rows.forEach(row => {
        row.removeEventListener('click', onRowClick);
        row.removeEventListener('mouseenter', onRowMouseEnter);
      });
      document.removeEventListener('mousemove', saveMouseCoordinates);
      element.removeEventListener('mouseleave', cancelPendingRowActivations);
    };

  }

  if (typeof module === 'object') {
    module.exports = menuAim;
  } else {
    window.menuAim = menuAim;
  }

})();
<|endoftext|>


<commit_before>/**
 * @file This is the window node module for the automatic tracing functionality
 * that supplies init and binding code for the PancakePainter window API.
 * Exports function returns a window control object that allows triggering on
 * init, show, and hide events.
 *
 * We have full access to globals loaded in the mainWindow as needed, just
 * reference them below.
 **/
 /*globals paper, $, path, app, mainWindow */

module.exports = function() {
  // Central window detail object returned for windows[autotrace] object.
  var autotrace = {
    settings: {},
    paper: {}, // PaperScope for auto trace preview
    source: '/home/techninja/web/pancake/testimages/rams.png',
    intermediary: path.join(app.getPath('temp'), 'pp_tempraster.png'),
    tracebmp: path.join(app.getPath('temp'), 'pp_tracesource.bmp'),
  };

  // Switch for detecting if a setting was changed by preset or by hand.
  // Avoids update thrashing cause by mass updates.
  var setByPreset = false;

  // Load the auto trace PaperScript (only when the canvas is ready).
  var autoTraceLoaded = false;
  function autoTraceLoad() {
    if (!autoTraceLoaded) {
      autoTraceLoaded = true;
      autotrace.paper = paper.PaperScript.load($('<script>').attr({
        type:"text/paperscript",
        src: "autotrace.ps.js",
        canvas: "autotrace-preview"
      })[0]);
    }
  }

  // Bind the window's settings inputs into a single object on change.
  function bindSettings() {
    setByPreset = true; // Ignore updates for initial bind.
    $('input, select', '#autotrace').change(function() {
      // Save each setting based on name attribute.
      if (this.type === 'checkbox') {
        autotrace.settings[this.name] = $(this).prop('checked');
      } else {
        if (this.type === 'radio') {
          if ($(this).prop('checked')) {
            autotrace.settings[this.name] = $(this).val();
          }
        } else {
          autotrace.settings[this.name] = $(this).val();
        }
      }

      if (!setByPreset) {
        autotrace.paper.renderTraceImage()
          .then(autotrace.paper.renderTraceVector);
      }
    }).change(); // Trigger initial change to save data.

    setByPreset = false; // Ready for updates.
  }

  // Bind the buttons on the window.
  function bindButtons() {
    $('button', '#autotrace').click(function() {
      switch(this.name) {
        case 'simple':
          break;

        case 'complex':
          break;

        case 'cancel':
          mainWindow.overlay.toggleWindow('autotrace', false);
          break;

        case 'import':
          // TODO: Import content into main paper context.
          break;

        case 'transparent-pick':
          // TODO: add colorpicker
          break;
      }
    });
  }

  // Init after build event.
  autotrace.init = function() {
    bindSettings();
    bindButtons();
  };

  // Window show event.
  autotrace.show = function() {
    autoTraceLoad();
    // Activate the trace preview paperscope.
    autotrace.paper.activate();

    // Init load and build the images
    autotrace.paper.loadTraceImage()
      .then(autotrace.paper.renderTraceImage)
      .then(autotrace.paper.renderTraceVector);
  };

  // Window hide event.
  autotrace.hide = function() {
    // Re-activate the default editor paperscope .
    paper.activate();
  };

  return autotrace;
};
<commit_msg>Switch to using context in autotrace window module<commit_after>/**
 * @file This is the window node module for the automatic tracing functionality
 * that supplies init and binding code for the PancakePainter window API.
 * Exports function returns a window control object that allows triggering on
 * init, show, and hide events.
 *
 * We have full access to globals loaded in the mainWindow as needed, just
 * reference them below.
 **/
 /*globals paper, $, path, app, mainWindow */

module.exports = function(context) {
  // Central window detail object returned for windows[autotrace] object.
  var autotrace = {
    settings: {},
    paper: {}, // PaperScope for auto trace preview
    source: '/home/techninja/web/pancake/testimages/rams.png',
    intermediary: path.join(app.getPath('temp'), 'pp_tempraster.png'),
    tracebmp: path.join(app.getPath('temp'), 'pp_tracesource.bmp'),
  };

  // Switch for detecting if a setting was changed by preset or by hand.
  // Avoids update thrashing cause by mass updates.
  var setByPreset = false;

  // Load the auto trace PaperScript (only when the canvas is ready).
  var autoTraceLoaded = false;
  function autoTraceLoad() {
    if (!autoTraceLoaded) {
      autoTraceLoaded = true;
      autotrace.paper = paper.PaperScript.load($('<script>').attr({
        type:"text/paperscript",
        src: "autotrace.ps.js",
        canvas: "autotrace-preview"
      })[0]);
    }
  }

  // Bind the window's settings inputs into a single object on change.
  function bindSettings() {
    setByPreset = true; // Ignore updates for initial bind.
    $('input, select', context).change(function() {
      // Save each setting based on name attribute.
      if (this.type === 'checkbox') {
        autotrace.settings[this.name] = $(this).prop('checked');
      } else {
        if (this.type === 'radio') {
          if ($(this).prop('checked')) {
            autotrace.settings[this.name] = $(this).val();
          }
        } else {
          autotrace.settings[this.name] = $(this).val();
        }
      }

      if (!setByPreset) {
        autotrace.paper.renderTraceImage()
          .then(autotrace.paper.renderTraceVector);
      }
    }).change(); // Trigger initial change to save data.

    setByPreset = false; // Ready for updates.
  }

  // Bind the buttons on the window.
  function bindButtons() {
    $('button', context).click(function() {
      switch(this.name) {
        case 'simple':
          break;

        case 'complex':
          break;

        case 'cancel':
          mainWindow.overlay.toggleWindow('autotrace', false);
          break;

        case 'import':
          // TODO: Import content into main paper context.
          break;

        case 'transparent-pick':
          // TODO: add colorpicker
          break;
      }
    });
  }

  // Init after build event.
  autotrace.init = function() {
    bindSettings();
    bindButtons();
  };

  // Window show event.
  autotrace.show = function() {
    autoTraceLoad();
    // Activate the trace preview paperscope.
    autotrace.paper.activate();

    // Init load and build the images
    autotrace.paper.loadTraceImage()
      .then(autotrace.paper.renderTraceImage)
      .then(autotrace.paper.renderTraceVector);
  };

  // Window hide event.
  autotrace.hide = function() {
    // Re-activate the default editor paperscope .
    paper.activate();
  };

  return autotrace;
};
<|endoftext|>


<commit_before>const waitingResults = {};
module.exports = async (msg, worker) => {
	if(msg.type === "master") {
		try {
			let result = await eval(msg.input);
			worker.send({ type: "output", result, id: msg.id });
		} catch(err) {
			worker.send({ type: "output", error: err.stack, id: msg.id });
		}
	} else if(msg.type === "site") {
		let targetWorker = cluster.onlineWorkers.find(work => work.type === "site");
		if(!targetWorker) {
			worker.send({ type: "output", error: "No website worker", id: msg.id });
		} else {
			waitingResults[msg.id] = {
				expected: 1,
				results: [],
				callback: results => worker.send({ type: "output", result: results[0], id: msg.id })
			};
			targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
		}
	} else if(msg.type === "shard") {
		let targetShard = msg.target;
		if(!targetShard) {
			worker.send({ type: "output", error: "Invalid target", id: msg.id });
		} else {
			let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" &&
				targetShard >= work.shardStart &&
				targetShard <= work.shardEnd);

			if(!targetWorker) {
				worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id });
			} else {
				waitingResults[msg.id] = {
					expected: 1,
					results: [],
					callback: results => worker.send({ type: "output", result: results[0], id: msg.id })
				};
				targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
			}
		}
	} else if(msg.type === "guild") {
		if(!msg.target) {
			worker.send({ type: "output", error: "Invalid target", id: msg.id });
		} else {
			let targetShard = ~~((msg.target / 4194304) % process.totalShards);
			let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" &&
				targetShard >= work.shardStart &&
				targetShard <= work.shardEnd);

			if(!targetWorker) {
				worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id });
			} else {
				waitingResults[msg.id] = {
					expected: 1,
					results: [],
					callback: results => worker.send({ type: "output", result: results[0], id: msg.id })
				};
				targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
			}
		}
	} else if(msg.type === "all_shards") {
		let workers = cluster.onlineWorkers.filter(work => work.type === "bot");
		waitingResults[msg.id] = {
			expected: workers.length,
			results: [],
			worker
		};
		workers.forEach(work => work.send({ type: "eval", input: msg.input, id: msg.id }));
	} else if(msg.type === "output") {
		if(!waitingResults[msg.id]) return;
		let waiting = waitingResults[msg.id];

		if(msg.error) {
			waiting.worker.send({ type: "output", error: msg.error, id: msg.id });
			delete waitingResults[msg.id];
			return;
		}

		waiting.results.push(msg.result);
		if(waiting.results.length === waiting.expected) {
			if(waiting.expected > 1) waiting.worker.send({ type: "output", results: waiting.results, id: msg.id });
			else waiting.worker.send({ type: "output", result: waiting.results[0], id: msg.id });

			delete waitingResults[msg.id];
		}
	}
};
<commit_msg>fix messages<commit_after>const waitingResults = {};
module.exports = async (msg, worker) => {
	if(msg.type === "master") {
		try {
			let result = await eval(msg.input);
			worker.send({ type: "output", result, id: msg.id });
		} catch(err) {
			worker.send({ type: "output", error: err.stack, id: msg.id });
		}
	} else if(msg.type === "site") {
		let targetWorker = cluster.onlineWorkers.find(work => work.type === "site");
		if(!targetWorker) {
			worker.send({ type: "output", error: "No website worker", id: msg.id });
		} else {
			waitingResults[msg.id] = {
				expected: 1,
				results: [],
				worker
			};
			targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
		}
	} else if(msg.type === "shard") {
		let targetShard = msg.target;
		if(!targetShard) {
			worker.send({ type: "output", error: "Invalid target", id: msg.id });
		} else {
			let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" &&
				targetShard >= work.shardStart &&
				targetShard <= work.shardEnd);

			if(!targetWorker) {
				worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id });
			} else {
				waitingResults[msg.id] = {
					expected: 1,
					results: [],
					worker
				};
				targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
			}
		}
	} else if(msg.type === "guild") {
		if(!msg.target) {
			worker.send({ type: "output", error: "Invalid target", id: msg.id });
		} else {
			let targetShard = ~~((msg.target / 4194304) % process.totalShards);
			let targetWorker = cluster.onlineWorkers.find(work => work.type === "bot" &&
				targetShard >= work.shardStart &&
				targetShard <= work.shardEnd);

			if(!targetWorker) {
				worker.send({ type: "output", error: "Invalid target (not found)", id: msg.id });
			} else {
				waitingResults[msg.id] = {
					expected: 1,
					results: [],
					worker
				};
				targetWorker.send({ type: "eval", input: msg.input, id: msg.id });
			}
		}
	} else if(msg.type === "all_shards") {
		let workers = cluster.onlineWorkers.filter(work => work.type === "bot");
		waitingResults[msg.id] = {
			alwaysPlural: true,
			expected: workers.length,
			results: [],
			worker
		};
		workers.forEach(work => work.send({ type: "eval", input: msg.input, id: msg.id }));
	} else if(msg.type === "output") {
		if(!waitingResults[msg.id]) return;
		let waiting = waitingResults[msg.id];

		if(msg.error) {
			waiting.worker.send({ type: "output", error: msg.error, id: msg.id });
			delete waitingResults[msg.id];
			return;
		}

		waiting.results.push(msg.result);
		if(waiting.results.length === waiting.expected) {
			if(waiting.expected > 1 || waiting.alwaysPlural) {
				waiting.worker.send({ type: "output", results: waiting.results, id: msg.id });
			} else {
				waiting.worker.send({ type: "output", result: waiting.results[0], id: msg.id });
			}

			delete waitingResults[msg.id];
		}
	}
};
<|endoftext|>