<commit_before>//

let parser = require('./parser.js');

let verbose = 446;

function evaluate (string) {
    let ast = parser.parse(string);
    if ( verbose > 5 ) {
        console.log(JSON.stringify(ast));
    }
    _output = '';
    let initial = mkInitial();
    var result = {
        value: process(ast, initial.r, initial.r, initial.k, initial.c),
        out: _output
    };
    return result;
}

exports.evaluate = evaluate;

// {{{ Preparation of the initial state of the interpreter

function mkInitial () {
    function k0 (value) {
        return value;
    }
    var r0 = mkInitialEnv();
    var c0 = {
        catcher: function (exc) {
            console.log("Uncaught exception " + exc);
            throw new Error(exc);
        },
        finalizer: null,
        next: null
    };
    return {
        r: r0,
        k: k0,
        c: c0
    };
}

var _output = '';            // stream instead ??
function mkInitialEnv () {
    let r0 = new GlobalEnvironment();
    r0.adjoinVarVariable('console', {
        log: function (arg) {
            let s = arg.toString();
            _output += s;
        }
    });
    r0.adjoinVarVariable('global', r0.variables);
    return r0;
}

//     {{{ Environment
// Implemented as a linked list of hashtables.

class Environment {
    constructor (next) {
        this.next = next;
        this.variables = {};
    }
    lookup (variableName) {
        if ( this.variables.hasOwnProperty(variableName) ) {
            return this.variables[variableName];
        } else if ( this.next ) {
            return this.next.lookup(variableName);
        } else {
            throw new Error("No such variable " + variableName);
        }
    }
    update (variableName, value) {
        if ( this.variables.hasOwnProperty(variableName) ) {
            return ( this.variables[variableName] = value );
        } else if ( this.next ) {
            return this.next.update(variableName, value);
        } else {
            this.adjoinVarVariable(variableName, value);
            return value;
        }
    }
}
class GlobalEnvironment extends Environment {
    constructor () {
        super(null);
    }
    adjoinVarVariable (variableName, value) {
        this.variables[variableName] = value;
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}
class FunctionCallEnvironment extends Environment {
    adjoinVarVariable (variableName, value) {
        this.variables[variableName] = value;
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}
class BlockEnvironment extends Environment {
    adjoinVarVariable (variableName, value) {
        this.adjoinVarVariable(variableName, value);
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}

//     }}}
// }}}
// {{{ Predefined operators
// NOTA: they are implemented lazily with the usual Javascript operators
// therefore they take care of coercions and other weird cases.

let _unaryOperator = {};
function getUnaryOperator (unaryOperatorName) {
    var f = _unaryOperator[unaryOperatorName];
    if ( f ) {
        return f;
    }
    // Create unary operators on the fly:
    f = new Function("argument",
                     `return ${unaryOperatorName} argument;`);
    _unaryOperator[unaryOperatorName] = f;
    return f;
}

let _binaryOperator = {};
function getBinaryOperator (binaryOperatorName) {
    var f = _binaryOperator[binaryOperatorName];
    if ( f ) {
        return f;
    }
    // Create binary operators on the fly:
    f = new Function("left, right",
                     `return left ${binaryOperatorName} right;`);
    _binaryOperator[binaryOperatorName] = f;
    return f;
}

// }}}
// {{{ Evaluation methods
// NOTA: the AST just use plain objects without inheritance (see
// http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
//

let dispatcher = {};

function process (ast, self, r, k, c) {
    let method = dispatcher['process' + ast.type];
    if ( typeof method === 'function' ) {
        if ( verbose > 20 ) {
            function ktrace (value) {
                console.log("<== " + value);
                return k(value);
            }
            console.log("==> " + JSON.stringify(ast));
            return method(ast, self, r, ktrace, c);
        } else {
            return method(ast, self, r, k, c);
        }
    } else {
        // So we may see what is missing!
        throw new Error("Cannot process " + ast.type);
    }
}

function processProgram (ast, self, r, k, c) {
    return processSequenceExpression(ast.body, self, r, k, c);
}
dispatcher.processProgram = processProgram;

function processBlockStatement (ast, self, r, k, c) {
    let rblock = new BlockEnvironment(r);
    return processSequenceExpression(ast.body, self, rblock, k, c);
}
dispatcher.processProgram =
    dispatcher.processBlockStatement = processProgram;

function processSequenceExpression (asts, self, r, k, c) {
    let n = asts.length;
    if ( n === 0 ) {
        return k(null);
    } else if ( n === 1 ) {
        return process(asts[0], self, r, k, c);
    } else {
        function knext (ignore) {
            return processSequenceExpression(asts.splice(1), self, r, k, c);
        }
        return process(asts[0], self, r, knext, c);
    }
}
dispatcher.processSequenceExpression = processSequenceExpression;

function processExpressionStatement (ast, self, r, k, c) {
    return process(ast.expression, self, r, k, c);
}
dispatcher.processExpressionStatement = processExpressionStatement;

function processLiteral (ast, self, r, k, c) {
    return k(ast.value);
}
dispatcher.processLiteral = processLiteral;

function processUnaryExpression (ast, self, r, k, c) {
    function kapply (argument) {
        let op = getUnaryOperator(ast.operator);
        return k(op(argument));
    }
    return process(ast.argument, self, r, kapply, c);
}
dispatcher.processUnaryExpression = processUnaryExpression;

function processBinaryExpression (ast, self, r, k, c) {
    function kright (left) {
        function kapply (right) {
            let op = getBinaryOperator(ast.operator);
            return k(op(left, right));
        }
        return process(ast.right, self, r, kapply, c);
    }
    return process(ast.left, self, r, kright, c);
}
dispatcher.processBinaryExpression = processBinaryExpression;

function processIdentifier (ast, self, r, k, c) {
    return k(r.lookup(ast.name));
}
dispatcher.processIdentifier = processIdentifier;

function processAssignmentExpression (ast, self, r, k, c) {
    function knext (place) {
        function kassign (value) {
            return k(place(value));
        }
        return process(ast.right, self, r, kassign, c);
    }
    return computePlace(ast.left, self, r, knext, c);
}
dispatcher.processAssignmentExpression = processAssignmentExpression;

function processVariableDeclaration (ast, self, r, k, c) {
    function processDeclarations (asts) {
        let n = asts.length;
        if ( n === 0 ) {
            return k(null);
        } else {
            function knext () {
                return processDeclarations(asts.splice(1));
            }
            return process(asts[0], self, r, knext, c);
        }
    }
    return processDeclarations(ast.declarations);
}
dispatcher.processVariableDeclaration = processVariableDeclaration;

function processVariableDeclarator (ast, self, r, k, c) {
    function kdeclare (value) {
        r.adjoinVarVariable(ast.id.name, value);
        return k(value);
    }
    if ( ast.init ) {
        return process(ast.init, self, r, kdeclare, c);
    } else {
        return kdeclare(undefined);
    }
}
dispatcher.processVariableDeclarator = processVariableDeclarator;

function processMemberExpression (ast, self, r, k, c) {
    function kmember (object) {
        function kfetch (key) {
            return k(object[key]);
        }
        if ( ast.computed ) {
            return process(ast.property, self, r, kfetch, c);
        } else {
            return kfetch(ast.property.name);
        }
    }
    return process(ast.object, self, r, kmember, c);
}
dispatcher.processMemberExpression = processMemberExpression;

function computePlace (ast, self, r, k, c) {
    if ( ast.type === 'Identifier' ) {
        function write (value) {
            return r.update(ast.name, value);
        }
        return k(write);
    } else if ( ast.type === 'MemberExpression' ) {
        function knext (object) {
            function kwrite (key) {
                function write (value) {
                    return (object[key] = value);
                }
                return k(write);
            }
            if ( ast.computed ) {
                return process(ast.property, self, r, kwrite, c);
            } else {
                return kwrite(ast.property.name);
            }
        }
        return process(ast.object, self, r, knext, c);
    } else {
        throw new Error("Cannot computePlace " + ast);
    }
}

// end of main.js
<commit_msg>tests ok up to 27<commit_after>//

let parser = require('./parser.js');

let verbose = 446;

function evaluate (string) {
    let ast = parser.parse(string);
    if ( verbose > 5 ) {
        console.log(JSON.stringify(ast));
    }
    _output = '';
    let initial = mkInitial();
    var result = {
        value: process(ast, initial.r, initial.r, initial.k, initial.c),
        out: _output
    };
    return result;
}

exports.evaluate = evaluate;

// {{{ Preparation of the initial state of the interpreter

function mkInitial () {
    function k0 (value) {
        return value;
    }
    var r0 = mkInitialEnv();
    var c0 = {
        catcher: function (exc) {
            console.log("Uncaught exception " + exc);
            throw new Error(exc);
        },
        finalizer: null,
        next: null
    };
    return {
        r: r0,
        k: k0,
        c: c0
    };
}

var _output = '';            // stream instead ??
function mkInitialEnv () {
    let r0 = new GlobalEnvironment();
    r0.adjoinVarVariable('console', {
        log: function (arg) {
            let s = arg.toString();
            _output += s;
        }
    });
    r0.adjoinVarVariable('global', r0.variables);
    return r0;
}

//     {{{ Environment
// Implemented as a linked list of hashtables.

class Environment {
    constructor (next) {
        this.next = next;
        this.variables = {};
    }
    lookup (variableName) {
        if ( this.variables.hasOwnProperty(variableName) ) {
            return this.variables[variableName];
        } else if ( this.next ) {
            return this.next.lookup(variableName);
        } else {
            throw new Error("No such variable " + variableName);
        }
    }
    update (variableName, value) {
        if ( this.variables.hasOwnProperty(variableName) ) {
            return ( this.variables[variableName] = value );
        } else if ( this.next ) {
            return this.next.update(variableName, value);
        } else {
            this.adjoinVarVariable(variableName, value);
            return value;
        }
    }
}
class GlobalEnvironment extends Environment {
    constructor () {
        super(null);
    }
    adjoinVarVariable (variableName, value) {
        this.variables[variableName] = value;
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}
class FunctionCallEnvironment extends Environment {
    adjoinVarVariable (variableName, value) {
        this.variables[variableName] = value;
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}
class BlockEnvironment extends Environment {
    adjoinVarVariable (variableName, value) {
        this.adjoinVarVariable(variableName, value);
    }
    adjoinLetVariable (variableName, value) {
        this.variables[variableName] = value;
    }
}

//     }}}
// }}}
// {{{ Predefined operators
// NOTA: they are implemented lazily with the usual Javascript operators
// therefore they take care of coercions and other weird cases.

let _unaryOperator = {};
function getUnaryOperator (unaryOperatorName) {
    var f = _unaryOperator[unaryOperatorName];
    if ( f ) {
        return f;
    }
    // Create unary operators on the fly:
    f = new Function("argument",
                     `return ${unaryOperatorName} argument;`);
    _unaryOperator[unaryOperatorName] = f;
    return f;
}

let _binaryOperator = {};
function getBinaryOperator (binaryOperatorName) {
    var f = _binaryOperator[binaryOperatorName];
    if ( f ) {
        return f;
    }
    // Create binary operators on the fly:
    f = new Function("left, right",
                     `return left ${binaryOperatorName} right;`);
    _binaryOperator[binaryOperatorName] = f;
    return f;
}

// }}}
// {{{ Evaluation methods
// NOTA: the AST just use plain objects without inheritance (see
// http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
//

let dispatcher = {};

function process (ast, self, r, k, c) {
    let method = dispatcher['process' + ast.type];
    if ( typeof method === 'function' ) {
        if ( verbose > 20 ) {
            function ktrace (value) {
                console.log("<== " + value);
                return k(value);
            }
            console.log("==> " + JSON.stringify(ast));
            return method(ast, self, r, ktrace, c);
        } else {
            return method(ast, self, r, k, c);
        }
    } else {
        // So we may see what is missing!
        throw new Error("Cannot process " + ast.type);
    }
}

function processProgram (ast, self, r, k, c) {
    return processSequenceExpression(ast.body, self, r, k, c);
}
dispatcher.processProgram = processProgram;

function processBlockStatement (ast, self, r, k, c) {
    let rblock = new BlockEnvironment(r);
    return processSequenceExpression(ast.body, self, rblock, k, c);
}
dispatcher.processProgram =
    dispatcher.processBlockStatement = processProgram;

function processSequenceExpression (asts, self, r, k, c) {
    let n = asts.length;
    if ( n === 0 ) {
        return k(null);
    } else if ( n === 1 ) {
        return process(asts[0], self, r, k, c);
    } else {
        function knext (ignore) {
            return processSequenceExpression(asts.splice(1), self, r, k, c);
        }
        return process(asts[0], self, r, knext, c);
    }
}
dispatcher.processSequenceExpression = processSequenceExpression;

function processExpressionStatement (ast, self, r, k, c) {
    return process(ast.expression, self, r, k, c);
}
dispatcher.processExpressionStatement = processExpressionStatement;

function processLiteral (ast, self, r, k, c) {
    return k(ast.value);
}
dispatcher.processLiteral = processLiteral;

function processUnaryExpression (ast, self, r, k, c) {
    function kapply (argument) {
        let op = getUnaryOperator(ast.operator);
        return k(op(argument));
    }
    return process(ast.argument, self, r, kapply, c);
}
dispatcher.processUnaryExpression = processUnaryExpression;

function processBinaryExpression (ast, self, r, k, c) {
    function kright (left) {
        function kapply (right) {
            let op = getBinaryOperator(ast.operator);
            return k(op(left, right));
        }
        return process(ast.right, self, r, kapply, c);
    }
    return process(ast.left, self, r, kright, c);
}
dispatcher.processBinaryExpression = processBinaryExpression;

function processIdentifier (ast, self, r, k, c) {
    return k(r.lookup(ast.name));
}
dispatcher.processIdentifier = processIdentifier;

function processAssignmentExpression (ast, self, r, k, c) {
    function knext (place) {
        function kassign (value) {
            return k(place(value));
        }
        return process(ast.right, self, r, kassign, c);
    }
    return computePlace(ast.left, self, r, knext, c);
}
dispatcher.processAssignmentExpression = processAssignmentExpression;

function processVariableDeclaration (ast, self, r, k, c) {
    function processDeclarations (asts) {
        let n = asts.length;
        if ( n === 0 ) {
            return k(null);
        } else {
            function knext () {
                return processDeclarations(asts.splice(1));
            }
            return process(asts[0], self, r, knext, c);
        }
    }
    return processDeclarations(ast.declarations);
}
dispatcher.processVariableDeclaration = processVariableDeclaration;

function processVariableDeclarator (ast, self, r, k, c) {
    function kdeclare (value) {
        r.adjoinVarVariable(ast.id.name, value);
        return k(value);
    }
    if ( ast.init ) {
        return process(ast.init, self, r, kdeclare, c);
    } else {
        return kdeclare(undefined);
    }
}
dispatcher.processVariableDeclarator = processVariableDeclarator;

function processMemberExpression (ast, self, r, k, c) {
    function kmember (object) {
        function kfetch (key) {
            return k(object[key]);
        }
        if ( ast.computed ) {
            return process(ast.property, self, r, kfetch, c);
        } else {
            return kfetch(ast.property.name);
        }
    }
    return process(ast.object, self, r, kmember, c);
}
dispatcher.processMemberExpression = processMemberExpression;

function computePlace (ast, self, r, k, c) {
    if ( ast.type === 'Identifier' ) {
        function write (value) {
            return r.update(ast.name, value);
        }
        return k(write);
    } else if ( ast.type === 'MemberExpression' ) {
        function knext (object) {
            function kwrite (key) {
                function write (value) {
                    return (object[key] = value);
                }
                return k(write);
            }
            if ( ast.computed ) {
                return process(ast.property, self, r, kwrite, c);
            } else {
                return kwrite(ast.property.name);
            }
        }
        return process(ast.object, self, r, knext, c);
    } else {
        throw new Error("Cannot computePlace " + ast);
    }
}

// }}}
// end of main.js
<|endoftext|>


<commit_before>/**
 * @file mdjs
 * @author xiaowu
 * @email fe.xiaowu@gmail.com
 */

'use strict';

import template from 'art-template/node/template-native';

import express from 'express';
import serve_static from 'serve-static';

import fs from 'fs';
import url from 'url';
import path from 'path';

import Key_cache from 'key-cache';

import marked from 'marked';
import highlight from 'highlight.js';

export default class Mdjs {
    static options = {
        /**
         * 文档名
         *
         * @type {String}
         */
        name: 'mdjs',

        /**
         * 监听的端口
         *
         * @type {Number}
         */
        port: 8091,

        /**
         * 文档根目录
         *
         * @type {String}
         */
        root: './',

        /**
         * 缓存文件目录
         *
         * @type {String}
         */
        cache_path: './.cache/',

        /**
         * 目录别名
         *
         * @type {Object}
         */
        dir_alias: {},

        /**
         * mdjs静态资源前缀
         *
         * @description 监听内置的静态资源，配置是为了解决与别的名冲突
         * @type {String}
         */
        static_prefix: 'static',

        /**
         * 忽略的目录
         *
         * @type {Array}
         */
        ignore_dir: [
            '.svn',
            '.git',
            'node_modules'
        ],

        /**
         * 导航里额外追加的链接
         *
         * @example
         *     [
         *         {
         *             "text": "链接名称-默认往导航之前插件",
         *             "url": "链接"
         *         },
         *         {
         *             "text": "链接名称-往导航之后追加",
         *             "url": "链接",
         *             "type": "after"
         *         }
         *     ]
         * @type {Array}
         */
        links: [
        ],

        /**
         * 调试模式
         *
         * @description 开启后不使用缓存
         * @type {Boolean}
         */
        debug: false
    }

    /**
     * 构造器
     *
     * @param  {Object} options 配置参数
     */
    constructor(options = {}) {
        let package_options;
        try {
            package_options = require(path.resolve('./package.json')).mdjs;
        }
        catch (e) {
            package_options = {};
        }

        // 合并默认配置
        // 合并的顺序是： 参数 > package.mdjs > 默认 （由左向右合并）
        options = this.options = Object.assign({}, Mdjs.options, package_options, options);

        options.root = path.resolve(options.root);
        options.cache_path = path.resolve(options.cache_path);

        // 缓存当前运行的目录
        this.__dirname = path.dirname(__dirname);

        // 缓存express
        this.express = express();

        // 初始化express
        this._init_express();
    }

    /**
     * 获取渲染后的导航html代码
     *
     * @param  {string|undefined} uri 当前高亮的路径，如果为空则全不高亮， 高亮即展开
     *
     * @return {string}     html代码
     */
    get_render_nav(uri) {
        let data = this.get_list();
        let str = '';

        if (!data || !data.length) {
            return str;
        }

        if (uri) {
            uri = decodeURIComponent(uri);
        }

        let filter = (filepath, type) => {
            if (!uri) {
                return false;
            }

            if (type === 'dir') {
                return uri.indexOf(filepath + '/') === 0;
            }
            return uri === filepath;
        };

        let fn = (res) => {
            let html = '';

            res.forEach((val) => {
                if (!val.children || !val.children.length) {
                    if (filter(val.uri, 'file')) {
                        html += `<li class="nav-tree-file nav-tree-current">`;
                    }
                    else {
                        html += `<li class="nav-tree-file">`;
                    }
                    html += `

                                    ${val.text}

                    `;
                }
                else {
                    if (filter(val.uri, 'dir')) {
                        html += `<li class="nav-tree-dir nav-tree-dir-open">`;
                    }
                    else {
                        html += `<li class="nav-tree-dir">`;
                    }
                    html += `

                                    ${val.text}

                            ${fn(val.children)}

                    `;
                }
            });

            return '
' + html + '
';
        };

        return fn(data);
    }

    /**
     * 清空缓存
     *
     * @return {Object} this
     */
    clear_cache() {
        // 调用缓存对象清空缓存
        new Key_cache({
            dir: this.options.cache_path
        }).remove();

        return this;
    }

    /**
     * 获取navtree使用数据，会追加options.links
     *
     * @description 会先读取缓存
     * @return {Array} 数组
     */
    get_list() {
        return this.cache('nav_data', () => {
            let data = this._get_list();

            if (!data.children) {
                data.children = [];
            }

            // 如果有链接，则追加
            if (this.options.links && this.options.links.length) {
                this.options.links.forEach(val => {
                    if (val.type === 'after') {
                        data.children.push(val);
                    }
                    else {
                        data.children.unshift(val);
                    }
                });
            }

            return data.children;
        });
    }

    /**
     * 渲染md文件
     *
     * @param  {string} content md源码
     *
     * @return {Object}         {content:html代码, catalog: h2,3分类}
     */
    renderMarkdown(content = '') {
        let renderer = new marked.Renderer();
        let cachekey = {};

        let catalog = [];

        // 渲染标题
        renderer.heading = (text, level) => {
            let key;

            if (level !== 2 && level !== 3) {
                return `<h${level}>${text}</h${level}>`;
            }

            if (cachekey[level] === undefined) {
                cachekey[level] = 0;
            }

            key = ++cachekey[level];

            catalog.push({
                text: text,
                level: level,
                id: `h${level}-${key}`
            });

            return `
                <h${level}>

                        ${text}

                </h${level}>
            `;
        };

        // 渲染代码
        renderer.code = (data, lang) => {
            data = highlight.highlightAuto(data).value;

            // 有语言时
            if (lang) {

                // 超过3行有提示
                if (data.split(/\n/).length >= 3) {
                    let html = `<pre><code class="hljs lang-${lang}"><span class="hljs-lang-tips">${lang}</span>`;
                    return html + `${data}</code></pre>`;
                }

                return `<pre><code class="hljs lang-${lang}">${data}</code></pre>`;
            }

            return `<pre><code class="hljs">${data}</code></pre>`;
        };

        // md => html
        content = marked(content, {
            renderer: renderer
        });

        // 兼容todo
        content = content.replace(/
\s*\[ \]\s*/g, '
<input type="checkbox" class="ui-todo" disabled>');
        content = content.replace(/
\s*\[x\]\s*/g, '
<input type="checkbox" disabled checked class="ui-todo">');

        return {
            content: content,
            catalog: catalog
        };
    }

    /**
     * 运行
     *
     * @return {Object} this
     */
    run() {
        this.express.listen(this.options.port);
        return this;
    }

    /**
     * 缓存
     *
     * @description 如果开启了debug，则忽略缓存
     * @param  {string}   key  缓存key
     * @param {Function} fn 当没有缓存时执行的回调
     * @return {Object|undefined} 缓存数据或者回调返回值
     */
    cache(key, fn = () => {}) {
        // 如果没有key则返回空
        if (!key) {
            return undefined;
        }

        // 如果有debug
        if (this.options.debug) {
            return fn();
        }

        let cache = new Key_cache({
            dir: this.options.cache_path
        });
        let value = cache.get(key);
        if (value) {
            return value;
        }

        value = fn();

        // 如果不为空则设置缓存
        if (value !== undefined) {
            cache.set(key, value);
        }

        return value;
    }

    /**
     * 初始化express
     *
     * @private
     */
    _init_express() {
        let app = this.express;

        template.config('base', '');
        template.config('extname', '.html');

        app.engine('.html', template.__express);
        app.set('views', path.resolve(this.__dirname, './views/'));
        app.set('view engine', 'html');

        // 写入变量
        app.use((req, res, next) => {
            // 写入变量
            res.locals.options = this.options;

            return next();
        });

        // 绑定.md文档
        app.get(/([\s\S]+?)\.md$/, this._md.bind(this));

        // 监听以目录结束的，其实是为了解决默认主页为md文档问题
        app.get(/(^\/$|\/$)/, (req, res, next) => {
            let parseUrl = url.parse(req.url, true);
            let pathname = parseUrl.pathname;

            // 相对文件的路径,用来判断文件是否存在
            let filepath = decodeURIComponent('.' + pathname);

            // 默认主页
            let default_index = [
                'readme.md',
                'README.md'
            ];
            let flag = false;
            for (let i = 0, len = default_index.length; i < len; i++) {
                if (fs.existsSync(path.resolve(this.options.root, filepath, default_index[i]))) {
                    flag = default_index[i];
                    break;
                }
            }

            if (flag) {
                req.url = url.format({
                    hash: parseUrl.hash,
                    search: parseUrl.search,
                    pathname: pathname + flag,
                    query: parseUrl.query
                });

                return this._md(req, res, next);
            }

            next();
        });

        // 委托静态资源
        app.use('/' + this.options.static_prefix, serve_static(path.resolve(this.__dirname, './static/')));

        // 委托源目录
        app.use('/', serve_static(this.options.root));
    }

    /**
     * 内部获取列表数据
     *
     * @private
     * @param  {string|undefined} dir 目录
     *
     * @return {Object}     {path:'', children:[]}
     */
    _get_list(dir) {
        let options = this.options;
        let result;

        dir = dir || options.root;

        let basename = path.basename(dir);

        result = {
            uri: dir.replace(options.root, '') || '/',
            children: [],
            text: options.dir_alias[basename] || basename
        };

        // 如果目录不存在
        if (!fs.existsSync(dir)) {
            return result;
        }

        let data = fs.readdirSync(dir);

        data.forEach(file => {
            let filepath = path.resolve(dir, file);
            let stat = fs.statSync(filepath);

            if (stat.isDirectory()) {
                if (options.ignore_dir && options.ignore_dir.indexOf(file) !== -1) {
                    return;
                }

                let res = this._get_list(filepath);

                if (res.children && res.children.length) {
                    result.children.push(res);
                }
            }
            else {
                if (path.extname(file) !== '.md') {
                    return;
                }

                result.children.push({
                    text: this._get_md_title(dir + '/' + file),
                    uri: (dir + '/' + file).replace(options.root, '')
                });
            }
        });

        return result;
    }

    /**
     * 渲染md文档
     *
     * @private
     * @param  {Object}   req  express.req
     * @param  {Object}   res  express.res
     * @param  {Function} next 下一个路由
     * @return {Object} res
     */
    _md(req, res, next) {
        let parseUrl = url.parse(req.url, true);

        // 如果要读取源码
        if (parseUrl.query.source) {
            return next();
        }

        // 加.是为了变成相对路径
        let filepath = path.resolve(this.options.root, '.' + parseUrl.pathname);

        // 为了中文
        filepath = decodeURIComponent(filepath);

        // 如果md文件不存在
        if (!fs.existsSync(filepath)) {
            return next();
        }

        let htmldata = this.renderMarkdown(fs.readFileSync(filepath).toString()).content;

        // 如果是pjax
        if (parseUrl.query.pjax) {
            return res.end(htmldata);
        }

        // 渲染md
        return res.render('markdown', {
            nav_data: this.get_render_nav(parseUrl.pathname),
            markdown_data: htmldata,
            title: `${this._get_md_title(filepath)} - ${this.options.name}`
        });
    }

    /**
     * 内部获取md文档的标题
     *
     * @private
     * @param  {string} filepath 文件路径
     *
     * @return {string}     标题
     */
    _get_md_title(filepath) {
        // 如果是md扩展
        if (path.extname(filepath) === '.md') {
            // 获取文件内容
            let filedata = fs.readFileSync(filepath).toString();

            // 正则取出#标题的文字
            if (filedata.match(/^\#+\s?(.+)/)) {
                return String(RegExp.$1).trim();
            }
        }

        return path.basename(filepath);
    }
}
<commit_msg>添加目录、文件排序<commit_after>/**
 * @file mdjs
 * @author xiaowu
 * @email fe.xiaowu@gmail.com
 */

'use strict';

import template from 'art-template/node/template-native';

import express from 'express';
import serve_static from 'serve-static';

import fs from 'fs';
import url from 'url';
import path from 'path';

import Key_cache from 'key-cache';

import marked from 'marked';
import highlight from 'highlight.js';

export default class Mdjs {
    static options = {
        /**
         * 文档名
         *
         * @type {String}
         */
        name: 'mdjs',

        /**
         * 监听的端口
         *
         * @type {Number}
         */
        port: 8091,

        /**
         * 文档根目录
         *
         * @type {String}
         */
        root: './',

        /**
         * 缓存文件目录
         *
         * @type {String}
         */
        cache_path: './.cache/',

        /**
         * 目录别名
         *
         * @type {Object}
         */
        dir_alias: {},

        /**
         * mdjs静态资源前缀
         *
         * @description 监听内置的静态资源，配置是为了解决与别的名冲突
         * @type {String}
         */
        static_prefix: 'static',

        /**
         * 忽略的目录
         *
         * @type {Array}
         */
        ignore_dir: [
            '.svn',
            '.git',
            'node_modules'
        ],

        /**
         * 导航里额外追加的链接
         *
         * @example
         *     [
         *         {
         *             "text": "链接名称-默认往导航之前插件",
         *             "url": "链接"
         *         },
         *         {
         *             "text": "链接名称-往导航之后追加",
         *             "url": "链接",
         *             "type": "after"
         *         }
         *     ]
         * @type {Array}
         */
        links: [
        ],

        /**
         * 调试模式
         *
         * @description 开启后不使用缓存
         * @type {Boolean}
         */
        debug: false
    }

    /**
     * 构造器
     *
     * @param  {Object} options 配置参数
     */
    constructor(options = {}) {
        let package_options;
        try {
            package_options = require(path.resolve('./package.json')).mdjs;
        }
        catch (e) {
            package_options = {};
        }

        // 合并默认配置
        // 合并的顺序是： 参数 > package.mdjs > 默认 （由左向右合并）
        options = this.options = Object.assign({}, Mdjs.options, package_options, options);

        options.root = path.resolve(options.root);
        options.cache_path = path.resolve(options.cache_path);

        // 缓存当前运行的目录
        this.__dirname = path.dirname(__dirname);

        // 缓存express
        this.express = express();

        // 初始化express
        this._init_express();
    }

    /**
     * 获取渲染后的导航html代码
     *
     * @param  {string|undefined} uri 当前高亮的路径，如果为空则全不高亮， 高亮即展开
     *
     * @return {string}     html代码
     */
    get_render_nav(uri) {
        let data = this.get_list();
        let str = '';

        if (!data || !data.length) {
            return str;
        }

        if (uri) {
            uri = decodeURIComponent(uri);
        }

        let filter = (filepath, type) => {
            if (!uri) {
                return false;
            }

            if (type === 'dir') {
                return uri.indexOf(filepath + '/') === 0;
            }
            return uri === filepath;
        };

        let fn = (res) => {
            let html = '';

            res.forEach((val) => {
                if (!val.children || !val.children.length) {
                    if (filter(val.uri, 'file')) {
                        html += `<li class="nav-tree-file nav-tree-current">`;
                    }
                    else {
                        html += `<li class="nav-tree-file">`;
                    }
                    html += `

                                    ${val.text}

                    `;
                }
                else {
                    if (filter(val.uri, 'dir')) {
                        html += `<li class="nav-tree-dir nav-tree-dir-open">`;
                    }
                    else {
                        html += `<li class="nav-tree-dir">`;
                    }
                    html += `

                                    ${val.text}

                            ${fn(val.children)}

                    `;
                }
            });

            return '
' + html + '
';
        };

        return fn(data);
    }

    /**
     * 清空缓存
     *
     * @return {Object} this
     */
    clear_cache() {
        // 调用缓存对象清空缓存
        new Key_cache({
            dir: this.options.cache_path
        }).remove();

        return this;
    }

    /**
     * 获取navtree使用数据，会追加options.links
     *
     * @description 会先读取缓存
     * @return {Array} 数组
     */
    get_list() {
        return this.cache('nav_data', () => {
            let data = this._get_list();

            if (!data.children) {
                data.children = [];
            }

            // 如果有链接，则追加
            if (this.options.links && this.options.links.length) {
                this.options.links.forEach(val => {
                    if (val.type === 'after') {
                        data.children.push(val);
                    }
                    else {
                        data.children.unshift(val);
                    }
                });
            }

            return data.children;
        });
    }

    /**
     * 渲染md文件
     *
     * @param  {string} content md源码
     *
     * @return {Object}         {content:html代码, catalog: h2,3分类}
     */
    renderMarkdown(content = '') {
        let renderer = new marked.Renderer();
        let cachekey = {};

        let catalog = [];

        // 渲染标题
        renderer.heading = (text, level) => {
            let key;

            if (level !== 2 && level !== 3) {
                return `<h${level}>${text}</h${level}>`;
            }

            if (cachekey[level] === undefined) {
                cachekey[level] = 0;
            }

            key = ++cachekey[level];

            catalog.push({
                text: text,
                level: level,
                id: `h${level}-${key}`
            });

            return `
                <h${level}>

                        ${text}

                </h${level}>
            `;
        };

        // 渲染代码
        renderer.code = (data, lang) => {
            data = highlight.highlightAuto(data).value;

            // 有语言时
            if (lang) {

                // 超过3行有提示
                if (data.split(/\n/).length >= 3) {
                    let html = `<pre><code class="hljs lang-${lang}"><span class="hljs-lang-tips">${lang}</span>`;
                    return html + `${data}</code></pre>`;
                }

                return `<pre><code class="hljs lang-${lang}">${data}</code></pre>`;
            }

            return `<pre><code class="hljs">${data}</code></pre>`;
        };

        // md => html
        content = marked(content, {
            renderer: renderer
        });

        // 兼容todo
        content = content.replace(/
\s*\[ \]\s*/g, '
<input type="checkbox" class="ui-todo" disabled>');
        content = content.replace(/
\s*\[x\]\s*/g, '
<input type="checkbox" disabled checked class="ui-todo">');

        return {
            content: content,
            catalog: catalog
        };
    }

    /**
     * 运行
     *
     * @return {Object} this
     */
    run() {
        this.express.listen(this.options.port);
        return this;
    }

    /**
     * 缓存
     *
     * @description 如果开启了debug，则忽略缓存
     * @param  {string}   key  缓存key
     * @param {Function} fn 当没有缓存时执行的回调
     * @return {Object|undefined} 缓存数据或者回调返回值
     */
    cache(key, fn = () => {}) {
        // 如果没有key则返回空
        if (!key) {
            return undefined;
        }

        // 如果有debug
        if (this.options.debug) {
            return fn();
        }

        let cache = new Key_cache({
            dir: this.options.cache_path
        });
        let value = cache.get(key);
        if (value) {
            return value;
        }

        value = fn();

        // 如果不为空则设置缓存
        if (value !== undefined) {
            cache.set(key, value);
        }

        return value;
    }

    /**
     * 初始化express
     *
     * @private
     */
    _init_express() {
        let app = this.express;

        template.config('base', '');
        template.config('extname', '.html');

        app.engine('.html', template.__express);
        app.set('views', path.resolve(this.__dirname, './views/'));
        app.set('view engine', 'html');

        // 写入变量
        app.use((req, res, next) => {
            // 写入变量
            res.locals.options = this.options;

            return next();
        });

        // 绑定.md文档
        app.get(/([\s\S]+?)\.md$/, this._md.bind(this));

        // 监听以目录结束的，其实是为了解决默认主页为md文档问题
        app.get(/(^\/$|\/$)/, (req, res, next) => {
            let parseUrl = url.parse(req.url, true);
            let pathname = parseUrl.pathname;

            // 相对文件的路径,用来判断文件是否存在
            let filepath = decodeURIComponent('.' + pathname);

            // 默认主页
            let default_index = [
                'readme.md',
                'README.md'
            ];
            let flag = false;
            for (let i = 0, len = default_index.length; i < len; i++) {
                if (fs.existsSync(path.resolve(this.options.root, filepath, default_index[i]))) {
                    flag = default_index[i];
                    break;
                }
            }

            if (flag) {
                req.url = url.format({
                    hash: parseUrl.hash,
                    search: parseUrl.search,
                    pathname: pathname + flag,
                    query: parseUrl.query
                });

                return this._md(req, res, next);
            }

            next();
        });

        // 委托静态资源
        app.use('/' + this.options.static_prefix, serve_static(path.resolve(this.__dirname, './static/')));

        // 委托源目录
        app.use('/', serve_static(this.options.root));
    }

    /**
     * 内部获取列表数据
     *
     * @private
     * @param  {string|undefined} dir 目录
     *
     * @return {Object}     {path:'', children:[]}
     */
    _get_list(dir) {
        let options = this.options;
        let result;

        dir = dir || options.root;

        let basename = path.basename(dir);

        result = {
            uri: dir.replace(options.root, '') || '/',
            children: [],
            text: options.dir_alias[basename] || basename
        };

        // 如果目录不存在
        if (!fs.existsSync(dir)) {
            return result;
        }

        // 读取目录里数据
        let data = fs.readdirSync(dir);

        // 定义目录数据和文件数据
        let dir_data = [];
        let file_data = [];

        // 遍历数据，拿出目录、文件的数据
        data.forEach(file => {
            let filepath = path.resolve(dir, file);
            let stat = fs.statSync(filepath);

            if (stat.isDirectory()) {
                if (options.ignore_dir && options.ignore_dir.indexOf(file) === -1) {
                    dir_data.push({
                        type: 'dir',
                        filepath: filepath
                    });
                }
            }
            else {
                if (path.extname(file) === '.md') {
                    file_data.push({
                        filepath: filepath
                    });
                }
            }
        });

        // 合并目录+文件数据
        dir_data.concat(file_data).forEach(file => {
            if (file.type === 'dir') {
                let res = this._get_list(file.filepath);

                // 必须有子集才算
                if (res.children && res.children.length) {
                    result.children.push(res);
                }
            }
            else {
                result.children.push({
                    text: this._get_md_title(file.filepath),
                    uri: file.filepath.replace(options.root, '')
                });
            }
        });

        return result;
    }

    /**
     * 渲染md文档
     *
     * @private
     * @param  {Object}   req  express.req
     * @param  {Object}   res  express.res
     * @param  {Function} next 下一个路由
     * @return {Object} res
     */
    _md(req, res, next) {
        let parseUrl = url.parse(req.url, true);

        // 如果要读取源码
        if (parseUrl.query.source) {
            return next();
        }

        // 加.是为了变成相对路径
        let filepath = path.resolve(this.options.root, '.' + parseUrl.pathname);

        // 为了中文
        filepath = decodeURIComponent(filepath);

        // 如果md文件不存在
        if (!fs.existsSync(filepath)) {
            return next();
        }

        let htmldata = this.renderMarkdown(fs.readFileSync(filepath).toString()).content;

        // 如果是pjax
        if (parseUrl.query.pjax) {
            return res.end(htmldata);
        }

        // 渲染md
        return res.render('markdown', {
            nav_data: this.get_render_nav(parseUrl.pathname),
            markdown_data: htmldata,
            title: `${this._get_md_title(filepath)} - ${this.options.name}`
        });
    }

    /**
     * 内部获取md文档的标题
     *
     * @private
     * @param  {string} filepath 文件路径
     *
     * @return {string}     标题
     */
    _get_md_title(filepath) {
        // 如果是md扩展
        if (path.extname(filepath) === '.md') {
            // 获取文件内容
            let filedata = fs.readFileSync(filepath).toString();

            // 正则取出#标题的文字
            if (filedata.match(/^\#+\s?(.+)/)) {
                return String(RegExp.$1).trim();
            }
        }

        return path.basename(filepath);
    }
}
<|endoftext|>


<commit_before>"use strict";

import React from 'react';
import ReactDOM from 'react-dom';

import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';

import App from './components/app.jsx';
import reducers from './reducers/reducer';

let middlewares = compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : f => f
);
let store = createStore(reducers, null, middlewares);

ReactDOM.render(
  React.createElement(Provider, {store},
    React.createElement(App)
  ),
  document.getElementById('app')
);

/*************************************************************************
 ****************************** DEBUG ************************************
 *************************************************************************/
let Metior = window.Metior = {};
Metior.ReactPerf = require('react-addons-perf');
Metior.store = store;
Metior.dispatch = store.dispatch;
Metior.getState = () => store.getState().toJS();
Metior.actions = {
  projectActions: require('./actions/projectActions'),
  viewer2d: require('./actions/viewer2dActions')
};
console.group("Metior");
console.info("Metior is ready");
console.info("console.log(Metior)");
console.log(Metior);
console.groupEnd();
<commit_msg>enhance Metior debug<commit_after>"use strict";

import React from 'react';
import ReactDOM from 'react-dom';

import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';

import App from './components/app.jsx';
import reducers from './reducers/reducer';

let middlewares = compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : f => f
);
let store = createStore(reducers, null, middlewares);

ReactDOM.render(
  React.createElement(Provider, {store},
    React.createElement(App)
  ),
  document.getElementById('app')
);

/*************************************************************************
 ****************************** DEBUG ************************************
 *************************************************************************/
let Metior = window.Metior = {};
Metior.ReactPerf = require('react-addons-perf');
Metior.store = store;
Metior.dispatch = store.dispatch;
Metior.getState = () => store.getState().toJS();
Metior.actions = {
  projectActions: require('./actions/projectActions'),
  viewer2d: require('./actions/viewer2dActions')
};
console.groupCollapsed("Metior");
console.info("Metior is ready");
console.info("console.log(Metior)");
console.log(Metior);
console.groupEnd();
<|endoftext|>


<commit_before>import express from 'express'

const app = express()
app.get('/', (request, response) => {
  response.send('Hello, world!')
})

const port = 3000
app.listen(port, () => {
  console.log(`app listening on port ${port}`)
})
<commit_msg>Read a port from an environment<commit_after>import express from 'express'

const app = express()
app.get('/', (request, response) => {
  response.send('Hello, world!')
})

const port = process.env.VK_GROUP_STATS_PORT || 3000
app.listen(port, () => {
  console.log(`app listening on port ${port}`)
})
<|endoftext|>


<commit_before>export default class Diascope {
	constructor(frame, reel, options = {}) {
		this.options = Object.assign(getDefaultOptions(), options);

		this.elementFrame = frame;
		this.elementReel = reel;
		this.elementsSlides = Array.from(this.elementReel.children);

		if (this.options.hasOwnProperty('elementNavigateNext')) {
			this.addElementNavigateNext(this.options.elementNavigateNext);
		}

		if (this.options.hasOwnProperty('elementNavigatePrevious')) {
			this.addElementNavigatePrevious(this.options.elementNavigatePrevious);
		}
	}

	next() {
		this.panSlides(this.options.step);
	}

	previous() {
		this.panSlides(this.options.step * -1);
	}

	panSlides(pan) {
		let newSlides = findNewVisibleSlides(pan, this.elementsSlides, this.elementFrame, this.options.loop);

		if (newSlides.length > 0) {
			let reelOffsetLeft = calculateLeftReelOffsetToBringSlidesIntoFrame(
				newSlides,
				this.elementReel,
				this.elementFrame,
				this.options.shouldCenter
			);

			this.elementReel.style.transform = `translateX(${reelOffsetLeft}px)`;
		}
	}

	addElementNavigateNext(element) {
		if (element instanceof Element) {
			addEvent('click', element, this.next.bind(this));
		}
	}

	addElementNavigatePrevious(element) {
		if (element instanceof Element) {
			addEvent('click', element, this.previous.bind(this));
		}
	}
}

function getDefaultOptions() {
	return {
		step: 1,
		loop: false,
		shouldCenter: false,
	};
}

function findNewVisibleSlides(pan, slides, frame, shouldLoop) {
	const firstSlideIndex = 0;
	const lastSlideIndex = slides.length - 1;

	let newSlides = [],
		firstVisibleSlideIndex,
		lastVisibleSlideIndex,
		firstNewSlideIndex,
		lastNewSlideIndex;

	firstVisibleSlideIndex = findIndexOfFirstVisibleSlideInFrame(slides, frame);
	lastVisibleSlideIndex = findIndexOfLastVisibleSlideInFrame(slides, frame);

	// loop to the first slide.
	if (pan > 0 && shouldLoop && lastVisibleSlideIndex === lastSlideIndex) {
		return [slides[0]];
	}

	// loop to the last slide.
	if (pan < 0 && shouldLoop && firstVisibleSlideIndex === firstSlideIndex) {
		return [slides[lastSlideIndex]];
	}

	if (lastVisibleSlideIndex + pan > lastSlideIndex) {
		pan = lastSlideIndex - lastVisibleSlideIndex;
		firstNewSlideIndex = lastSlideIndex - pan;
		lastNewSlideIndex = lastSlideIndex;
	} else if (firstVisibleSlideIndex + pan < 0) {
		pan = firstSlideIndex - firstVisibleSlideIndex;
		firstNewSlideIndex = 0
		lastNewSlideIndex = 0 - pan;
	} else {
		firstNewSlideIndex = firstVisibleSlideIndex + pan;
		lastNewSlideIndex = lastVisibleSlideIndex + pan;
	}

	for (let i = firstNewSlideIndex; i <= lastNewSlideIndex; i++) {
		newSlides.push(slides[i]);
	}

	return newSlides;
}

function findIndexOfFirstVisibleSlideInFrame(slides, frame) {
	for (let currentSlideIndex = 0; currentSlideIndex < slides.length; currentSlideIndex++) {
		if (isSlideInFrame(slides[currentSlideIndex], frame)) {
			return currentSlideIndex;
		}
	}
}

function findIndexOfLastVisibleSlideInFrame(slides, frame) {
	let lastVisibleSlideIndex;

	for (let currentSlideIndex = 0; currentSlideIndex < slides.length; currentSlideIndex++) {
		if (isSlideInFrame(slides[currentSlideIndex], frame)) {
			lastVisibleSlideIndex = currentSlideIndex;
		}
	}

	return lastVisibleSlideIndex;
}

function calculateLeftReelOffsetToBringSlidesIntoFrame(slides, reel, frame, shouldCenter = false) {
	let slidesBounds = getHorizontalBoundsOfSlides(slides);
	let reelBounds = reel.getBoundingClientRect();
	let frameBounds = frame.getBoundingClientRect();

	if (shouldCenter) {
		let reelOffsetLeft = (frameBounds.left - slidesBounds.left) - (frameBounds.left - reelBounds.left) + ((frameBounds.width - slidesBounds.width) / 2);

		if ((reelOffsetLeft * -1) > (reelBounds.width - frameBounds.width)) {
			return (frameBounds.width - reelBounds.width);
		}

		if (reelOffsetLeft >= 0) {
			return 0;
		}

		return reelOffsetLeft;
	}

	if (slidesBounds.right > frameBounds.right) {
		return (reelBounds.left - frameBounds.left) - (slidesBounds.right - frameBounds.right);
	}

	if (slidesBounds.left < frameBounds.left) {
		return (frameBounds.left - slidesBounds.left) - (frameBounds.left - reelBounds.left);
	}

	return (frameBounds.left - reelBounds.left);
}

function getHorizontalBoundsOfSlides(slides) {
	let maxLeft;
	let maxRight;

	slides.forEach((slide) => {
		if (maxLeft === undefined || slide.getBoundingClientRect().left < maxLeft) {
			maxLeft = slide.getBoundingClientRect().left;
		}

		if (maxRight === undefined || slide.getBoundingClientRect().right > maxRight) {
			maxRight = slide.getBoundingClientRect().right;
		}
	});

	return {
		left: maxLeft,
		right: maxRight,
		width: maxRight - maxLeft,
	}
}

function isSlideInFrame(slide, frame) {
	let slideBounds = slide.getBoundingClientRect();
	let frameBounds = frame.getBoundingClientRect();

	return slideBounds.left >= frameBounds.left
		&& slideBounds.right <= frameBounds.right;
}

function addEvent(type, element, callback, options = {}, useCapture = true) {
	/**
	 * Browsers that support the `passive` option are those that allow
	 * for the usage of the options parameter in event listeners.
	 *
	 * See: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
	 */
	if (doesSupportPassive()) {
		/**
		 * The following events should never `preventDefault()`, due to heavy
		 * performance impact. The `passive` option enforces this, and
		 * tells the browser not to expect any `preventDefault()``.
		 *
		 * See: https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners
		 */
		if (type === 'wheel' || type === 'mousewheel' || type === 'touchstart' || type === 'touchmove') {
			options.passive = true;
		}

		element.addEventListener(type, callback, options, useCapture);
	} else {
		element.addEventListener(type, callback, useCapture);
	}
}

/**
 * Check if the browser knows about the `passive` option and actively looks for it.
 *
 * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
 */
function doesSupportPassive() {
	let supportsPassive = false;

	try {
		let options = Object.defineProperty({}, 'passive', {
			get: function() {
				supportsPassive = true;
			}
		});

		window.addEventListener('test', null, options);
		window.removeEventListener('test', null, options);
	} catch (e) {
		// Do nothing
	}

	return supportsPassive;
}
<commit_msg>Corrected conditionless offset calculation<commit_after>export default class Diascope {
	constructor(frame, reel, options = {}) {
		this.options = Object.assign(getDefaultOptions(), options);

		this.elementFrame = frame;
		this.elementReel = reel;
		this.elementsSlides = Array.from(this.elementReel.children);

		if (this.options.hasOwnProperty('elementNavigateNext')) {
			this.addElementNavigateNext(this.options.elementNavigateNext);
		}

		if (this.options.hasOwnProperty('elementNavigatePrevious')) {
			this.addElementNavigatePrevious(this.options.elementNavigatePrevious);
		}
	}

	next() {
		this.panSlides(this.options.step);
	}

	previous() {
		this.panSlides(this.options.step * -1);
	}

	panSlides(pan) {
		let newSlides = findNewVisibleSlides(pan, this.elementsSlides, this.elementFrame, this.options.loop);

		if (newSlides.length > 0) {
			let reelOffsetLeft = calculateLeftReelOffsetToBringSlidesIntoFrame(
				newSlides,
				this.elementReel,
				this.elementFrame,
				this.options.shouldCenter
			);

			this.elementReel.style.transform = `translateX(${reelOffsetLeft}px)`;
		}
	}

	addElementNavigateNext(element) {
		if (element instanceof Element) {
			addEvent('click', element, this.next.bind(this));
		}
	}

	addElementNavigatePrevious(element) {
		if (element instanceof Element) {
			addEvent('click', element, this.previous.bind(this));
		}
	}
}

function getDefaultOptions() {
	return {
		step: 1,
		loop: false,
		shouldCenter: false,
	};
}

function findNewVisibleSlides(pan, slides, frame, shouldLoop) {
	const firstSlideIndex = 0;
	const lastSlideIndex = slides.length - 1;

	let newSlides = [],
		firstVisibleSlideIndex,
		lastVisibleSlideIndex,
		firstNewSlideIndex,
		lastNewSlideIndex;

	firstVisibleSlideIndex = findIndexOfFirstVisibleSlideInFrame(slides, frame);
	lastVisibleSlideIndex = findIndexOfLastVisibleSlideInFrame(slides, frame);

	// loop to the first slide.
	if (pan > 0 && shouldLoop && lastVisibleSlideIndex === lastSlideIndex) {
		return [slides[0]];
	}

	// loop to the last slide.
	if (pan < 0 && shouldLoop && firstVisibleSlideIndex === firstSlideIndex) {
		return [slides[lastSlideIndex]];
	}

	if (lastVisibleSlideIndex + pan > lastSlideIndex) {
		pan = lastSlideIndex - lastVisibleSlideIndex;
		firstNewSlideIndex = lastSlideIndex - pan;
		lastNewSlideIndex = lastSlideIndex;
	} else if (firstVisibleSlideIndex + pan < 0) {
		pan = firstSlideIndex - firstVisibleSlideIndex;
		firstNewSlideIndex = 0
		lastNewSlideIndex = 0 - pan;
	} else {
		firstNewSlideIndex = firstVisibleSlideIndex + pan;
		lastNewSlideIndex = lastVisibleSlideIndex + pan;
	}

	for (let i = firstNewSlideIndex; i <= lastNewSlideIndex; i++) {
		newSlides.push(slides[i]);
	}

	return newSlides;
}

function findIndexOfFirstVisibleSlideInFrame(slides, frame) {
	for (let currentSlideIndex = 0; currentSlideIndex < slides.length; currentSlideIndex++) {
		if (isSlideInFrame(slides[currentSlideIndex], frame)) {
			return currentSlideIndex;
		}
	}
}

function findIndexOfLastVisibleSlideInFrame(slides, frame) {
	let lastVisibleSlideIndex;

	for (let currentSlideIndex = 0; currentSlideIndex < slides.length; currentSlideIndex++) {
		if (isSlideInFrame(slides[currentSlideIndex], frame)) {
			lastVisibleSlideIndex = currentSlideIndex;
		}
	}

	return lastVisibleSlideIndex;
}

function calculateLeftReelOffsetToBringSlidesIntoFrame(slides, reel, frame, shouldCenter = false) {
	let slidesBounds = getHorizontalBoundsOfSlides(slides);
	let reelBounds = reel.getBoundingClientRect();
	let frameBounds = frame.getBoundingClientRect();

	if (shouldCenter) {
		let reelOffsetLeft = (frameBounds.left - slidesBounds.left) - (frameBounds.left - reelBounds.left) + ((frameBounds.width - slidesBounds.width) / 2);

		if ((reelOffsetLeft * -1) > (reelBounds.width - frameBounds.width)) {
			return (frameBounds.width - reelBounds.width);
		}

		if (reelOffsetLeft >= 0) {
			return 0;
		}

		return reelOffsetLeft;
	}

	if (slidesBounds.right > frameBounds.right) {
		return (reelBounds.left - frameBounds.left) - (slidesBounds.right - frameBounds.right);
	}

	if (slidesBounds.left < frameBounds.left) {
		return (frameBounds.left - slidesBounds.left) - (frameBounds.left - reelBounds.left);
	}

	return (reelBounds.left - frameBounds.left);
}

function getHorizontalBoundsOfSlides(slides) {
	let maxLeft;
	let maxRight;

	slides.forEach((slide) => {
		if (maxLeft === undefined || slide.getBoundingClientRect().left < maxLeft) {
			maxLeft = slide.getBoundingClientRect().left;
		}

		if (maxRight === undefined || slide.getBoundingClientRect().right > maxRight) {
			maxRight = slide.getBoundingClientRect().right;
		}
	});

	return {
		left: maxLeft,
		right: maxRight,
		width: maxRight - maxLeft,
	}
}

function isSlideInFrame(slide, frame) {
	let slideBounds = slide.getBoundingClientRect();
	let frameBounds = frame.getBoundingClientRect();

	return slideBounds.left >= frameBounds.left
		&& slideBounds.right <= frameBounds.right;
}

function addEvent(type, element, callback, options = {}, useCapture = true) {
	/**
	 * Browsers that support the `passive` option are those that allow
	 * for the usage of the options parameter in event listeners.
	 *
	 * See: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
	 */
	if (doesSupportPassive()) {
		/**
		 * The following events should never `preventDefault()`, due to heavy
		 * performance impact. The `passive` option enforces this, and
		 * tells the browser not to expect any `preventDefault()``.
		 *
		 * See: https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners
		 */
		if (type === 'wheel' || type === 'mousewheel' || type === 'touchstart' || type === 'touchmove') {
			options.passive = true;
		}

		element.addEventListener(type, callback, options, useCapture);
	} else {
		element.addEventListener(type, callback, useCapture);
	}
}

/**
 * Check if the browser knows about the `passive` option and actively looks for it.
 *
 * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
 */
function doesSupportPassive() {
	let supportsPassive = false;

	try {
		let options = Object.defineProperty({}, 'passive', {
			get: function() {
				supportsPassive = true;
			}
		});

		window.addEventListener('test', null, options);
		window.removeEventListener('test', null, options);
	} catch (e) {
		// Do nothing
	}

	return supportsPassive;
}
<|endoftext|>


<commit_before>const getBrowserBottomDelta = () => {
  // Firefox displays tooltip at the bottom which obstructs the view.
  // As a workaround ensure extra space from the bottom in the viewport
  // firefox detection (https://stackoverflow.com/a/7000222/2870889).
  if (navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {
    // Hardcoded height of the tooltip plus some margin
    return 26;
  }
  return 0;
};

// Returns true if scrolling was done.
const scrollToElement = (searchEngine, element) => {
  let topMargin = 0;
  if (searchEngine.getTopMargin) {
    topMargin = searchEngine.getTopMargin(element);
  }
  const bottomMargin = getBrowserBottomDelta();
  const elementBounds = element.getBoundingClientRect();
  const scrollY = window.scrollY;
  // It seems that it's only possible to scroll by
  if (elementBounds.top < topMargin) {
    // scroll element to top
    element.scrollIntoView(true);
    window.scrollBy(0, -topMargin);
  } else if (elementBounds.bottom + bottomMargin > window.innerHeight) {
    // scroll element to bottom
    element.scrollIntoView(false);
    window.scrollBy(0, bottomMargin);
  }
  return Math.abs(window.scrollY - scrollY) > 0.01;
};

class SearchResultsManager {
  constructor(searchEngine, options) {
    this.searchEngine = searchEngine;
    this.options = options;
    this.focusedIndex = 0;
  }

  reloadSearchResults() {
    this.searchResults = this.searchEngine.getSearchResults();
  }

  /**
   * Returns the element to click on upon navigation. The focused element in the
   * document is preferred (if there is one) over the highlighted result. Note
   * that the focused element does not have to be an anchor <a> element.
   *
   * @param {boolean} linkOnly If true the focused element is preferred only
   * when it is a link with "href" attribute.
   * @return {Element}
   */
  getElementToNavigate(linkOnly = false) {
    const focusedElement = document.activeElement;
    if (focusedElement == null) {
      return this.searchResults[this.focusedIndex].anchor;
    }
    const isLink = focusedElement.localName === 'a' &&
        focusedElement.hasAttribute('href');
    if (!linkOnly || isLink) {
      return focusedElement;
    }
  }

  focus(index, scrollToResult = true) {
    if (this.focusedIndex >= 0) {
      const searchResult = this.searchResults[this.focusedIndex];
      // If the current result is outside the viewport and scrolling was
      // requested, only scroll to it, but don't focus on the new result.
      if (scrollToResult && scrollToElement(this.searchEngine,
          searchResult.container)) {
        return;
      }
      const highlighted = searchResult.highlightedElement;
      // Remove highlighting from previous item.
      highlighted.classList.remove(searchResult.highlightClass);
      highlighted.classList.remove('wsn-no-outline');
    }
    const searchResult = this.searchResults[index];
    if (!searchResult) {
      this.focusedIndex = -1;
      return;
    }
    const highlighted = searchResult.highlightedElement;
    // Add the focus outline and caret.
    highlighted.classList.add(searchResult.highlightClass);
    if (this.options.hideOutline || searchResult.anchor !== highlighted) {
      searchResult.anchor.classList.add('wsn-no-outline');
    }
    // We already scroll below, so no need for focus to scroll. The scrolling
    // behavior of `focus` also seems less predictable and caused an issue, see:
    // https://github.com/infokiller/web-search-navigator/issues/35
    searchResult.anchor.focus({preventScroll: true});
    // Ensure whole search result container is visible in the viewport, not only
    // the search result link.
    if (scrollToResult) {
      scrollToElement(this.searchEngine, searchResult.container);
    }
    this.focusedIndex = index;
  }

  focusNext(shouldWrap) {
    if (this.focusedIndex < this.searchResults.length - 1) {
      this.focus(this.focusedIndex + 1);
    } else if (shouldWrap) {
      this.focus(0);
    }
  }

  focusPrevious(shouldWrap) {
    if (this.focusedIndex > 0) {
      this.focus(this.focusedIndex - 1);
    } else if (shouldWrap) {
      this.focus(this.searchResults.length - 1);
    } else {
      window.scrollTo(window.scrollX, 0);
    }
  }
}

class WebSearchNavigator {
  async init() {
    /* eslint-disable-next-line no-undef */
    this.options = new ExtensionOptions();
    await this.options.load();
    /* eslint-disable-next-line no-undef */
    this.searchEngine = await getSearchEngine(this.options.sync.getAll());
    if (this.searchEngine == null) {
      return;
    }
    await sleep(this.options.sync.get('delay'));
    this.injectCSS();
    this.initResultsNavigation();
    this.initSearchInputNavigation();
    this.initTabsNavigation();
    this.initChangeToolsNavigation();
  }

  injectCSS() {
    const style = document.createElement('style');
    style.textContent = this.options.sync.get('customCSS');
    document.head.append(style);
  }

  initSearchInputNavigation() {
    const searchInput = document.querySelector(
        this.searchEngine.searchBoxSelector);
    // Only apply the extension logic if the key is not something the user may
    // have wanted to type into the searchbox, so that we don't interfere with
    // regular typing.
    const shouldHandleSearchInputKey = (event) => {
      return event.ctrlKey || event.metaKey || event.key === 'Escape';
    };
    // If insideSearchboxHandler returns true, outsideSearchboxHandler will also
    // be called (because it's defined on document, hence has lower priority),
    // in which case we don't want to handle the event. Therefore, we store the
    // last event handled in insideSearchboxHandler, and only handle the event
    // in outsideSearchboxHandler if it's not the same one.
    let lastEvent;
    const outsideSearchboxHandler = (event) => {
      if (event === lastEvent) {
        return !shouldHandleSearchInputKey(event);
      }
      // Scroll to the search box in case it's outside the viewport so that it's
      // clear to the user that it has focus.
      scrollToElement(this.searchEngine, searchInput);
      searchInput.select();
      searchInput.click();
      return false;
    };
    const insideSearchboxHandler = (event) => {
      lastEvent = event;
      if (!shouldHandleSearchInputKey(event)) {
        return true;
      }
      // Everything is selected; deselect all.
      if (searchInput.selectionStart === 0 &&
          searchInput.selectionEnd === searchInput.value.length) {
        // Scroll to the search box in case it's outside the viewport so that
        // it's clear to the user that it has focus.
        scrollToElement(this.searchEngine, searchInput);
        searchInput.setSelectionRange(
            searchInput.value.length, searchInput.value.length);
        return false;
      }
      // Closing search suggestions via document.body.click() or
      // searchInput.blur() breaks the state of google's controller.
      // The suggestion box is closed, yet it won't re-appear on the next
      // search box focus event.

      // Input can be blurred only when the suggestion box is already
      // closed, hence the blur event is queued.
      window.setTimeout(() => searchInput.blur());
      // Invoke the default handler which will close-up search suggestions
      // properly (google's controller won't break), but it won't remove the
      // focus.
      return true;
    };
    this.register(this.options.sync.get('focusSearchInput'),
        outsideSearchboxHandler);
    // Bind globally, otherwise Mousetrap ignores keypresses inside inputs.
    // We must bind it separately to the search box element, or otherwise the
    // key event won't always be captured (for example this is the case on
    // Google Search as of 2020-06-22), presumably because the javascript in the
    // page will disable further processing.
    this.registerGlobal(this.options.sync.get('focusSearchInput'),
        insideSearchboxHandler, searchInput);
  }

  initTabsNavigation() {
    const tabs = this.searchEngine.tabs || {};
    for (const [optionName, element] of Object.entries(tabs)) {
      this.register(this.options.sync.get(optionName), () => {
        if (element == null) {
          return true;
        }
        // Some search engines use forms instead of links for navigation
        if (element.tagName == 'FORM') {
          element.submit();
        } else {
          element.click();
        }
      });
    }
  }

  initResultsNavigation() {
    this.resetResultsManager();
    this.registerResultsNavigationKeybindings();
    if (!this.searchEngine.onChangedResults) {
      return;
    }
    this.searchEngine.onChangedResults((appendedOnly) => {
      if (appendedOnly) {
        this.resultsManager.reloadSearchResults();
      } else {
        this.resetResultsManager();
      }
    });
  }

  resetResultsManager() {
    this.resultsManager = new SearchResultsManager(this.searchEngine,
        this.options.sync.getAll());
    this.resultsManager.reloadSearchResults();
    if (this.resultsManager.searchResults.length === 0) {
      return;
    }
    this.isFirstNavigation = true;
    if (this.options.sync.get('autoSelectFirst')) {
      // Highlight the first result when the page is loaded, but don't scroll to
      // it because there may be KP cards such as stock graphs.
      this.resultsManager.focus(0, false);
    }
    const lastNavigation = this.options.local.values;
    if (location.href === lastNavigation.lastQueryUrl) {
      this.isFirstNavigation = false;
      this.resultsManager.focus(lastNavigation.lastFocusedIndex);
    }
  }

  registerResultsNavigationKeybindings() {
    const getOpt = (key) => {
      return this.options.sync.get(key);
    };
    this.register(getOpt('nextKey'), () => {
      if (!getOpt('autoSelectFirst') && this.isFirstNavigation) {
        this.resultsManager.focus(0);
        this.isFirstNavigation = false;
      } else {
        this.resultsManager.focusNext(getOpt('wrapNavigation'));
      }
      return false;
    });
    this.register(getOpt('previousKey'), () => {
      if (!getOpt('autoSelectFirst') && this.isFirstNavigation) {
        this.resultsManager.focus(0);
        this.isFirstNavigation = false;
      } else {
        this.resultsManager.focusPrevious(getOpt('wrapNavigation'));
      }
      return false;
    });
    this.register(getOpt('navigateKey'), () => {
      const link = this.resultsManager.getElementToNavigate();
      const lastNavigation = this.options.local.values;
      lastNavigation.lastQueryUrl = location.href;
      lastNavigation.lastFocusedIndex = this.resultsManager.focusedIndex;
      this.options.local.save();
      // If the element is a link, use the href to directly navigate, since some
      // websites will open it in a new tab.
      if (link.localName === 'a' && link.href) {
        document.location.href = link.href;
      } else {
        link.click();
      }
      return false;
    });
    this.register(getOpt('navigateNewTabKey'), () => {
      const link = this.resultsManager.getElementToNavigate(true);
      /* eslint-disable-next-line no-undef */
      browser.runtime.sendMessage({
        type: 'tabsCreate',
        options: {
          url: link.href,
          active: true,
        },
      });
      return false;
    });
    this.register(getOpt('navigateNewTabBackgroundKey'), () => {
      const link = this.resultsManager.getElementToNavigate(true);
      /* eslint-disable-next-line no-undef */
      browser.runtime.sendMessage({
        type: 'tabsCreate',
        options: {
          url: link.href,
          active: false,
        },
      });
      return false;
    });
  }

  initChangeToolsNavigation() {
    const getOpt = (key) => {
      return this.options.sync.get(key);
    };
    this.register(getOpt('navigateShowAll'), () =>
      this.searchEngine.changeTools('a'));
    this.register(getOpt('navigateShowHour'), () =>
      this.searchEngine.changeTools('h'));
    this.register(getOpt('navigateShowDay'), () =>
      this.searchEngine.changeTools('d'));
    this.register(getOpt('navigateShowWeek'), () =>
      this.searchEngine.changeTools('w'));
    this.register(getOpt('navigateShowMonth'), () =>
      this.searchEngine.changeTools('m'));
    this.register(getOpt('navigateShowYear'), () =>
      this.searchEngine.changeTools('y'));
    this.register(getOpt('toggleVerbatimSearch'), () =>
      this.searchEngine.changeTools('v'));
    this.register(getOpt('toggleSort'), () =>
      this.searchEngine.changeTools(null));
  }

  registerGlobal(shortcut, callback, element = document) {
    /* eslint-disable-next-line no-undef,new-cap */
    Mousetrap(element).bindGlobal(shortcut, (event) => {
      return callback(event);
    });
  }

  register(shortcut, callback, element = document) {
    /* eslint-disable-next-line no-undef,new-cap */
    Mousetrap(element).bind(shortcut, (event) => {
      return callback(event);
    });
  }
}

/**
 * Make functions sleeps
 *
 * Can be used with then() callback :
 * sleep.then(() => { stuff to do after sleeps }),
 * Or in an async function, like we do below extension initialization
 * @param {*} milliseconds, How long you want your function to sleep
 * @return {Promise} a Promise resolving a timeout
 */
const sleep = (milliseconds) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
};

const extension = new WebSearchNavigator();
extension.init();
<commit_msg>updates<commit_after>const getBrowserBottomDelta = () => {
  // Firefox displays tooltip at the bottom which obstructs the view.
  // As a workaround ensure extra space from the bottom in the viewport
  // firefox detection (https://stackoverflow.com/a/7000222/2870889).
  if (navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {
    // Hardcoded height of the tooltip plus some margin
    return 26;
  }
  return 0;
};

// Returns true if scrolling was done.
const scrollToElement = (searchEngine, element) => {
  let topMargin = 0;
  if (searchEngine.getTopMargin) {
    topMargin = searchEngine.getTopMargin(element);
  }
  const bottomMargin = getBrowserBottomDelta();
  const elementBounds = element.getBoundingClientRect();
  const scrollY = window.scrollY;
  // It seems that it's only possible to scroll by
  if (elementBounds.top < topMargin) {
    // scroll element to top
    element.scrollIntoView(true);
    window.scrollBy(0, -topMargin);
  } else if (elementBounds.bottom + bottomMargin > window.innerHeight) {
    // scroll element to bottom
    element.scrollIntoView(false);
    window.scrollBy(0, bottomMargin);
  }
  return Math.abs(window.scrollY - scrollY) > 0.01;
};

class SearchResultsManager {
  constructor(searchEngine, options) {
    this.searchEngine = searchEngine;
    this.options = options;
    this.focusedIndex = 0;
  }

  reloadSearchResults() {
    this.searchResults = this.searchEngine.getSearchResults();
  }

  /**
   * Returns the element to click on upon navigation. The focused element in the
   * document is preferred (if there is one) over the highlighted result. Note
   * that the focused element does not have to be an anchor <a> element.
   *
   * @param {boolean} linkOnly If true the focused element is preferred only
   * when it is a link with "href" attribute.
   * @return {Element}
   */
  getElementToNavigate(linkOnly = false) {
    const focusedElement = document.activeElement;
    if (focusedElement == null) {
      return this.searchResults[this.focusedIndex].anchor;
    }
    const isLink = focusedElement.localName === 'a' &&
        focusedElement.hasAttribute('href');
    if (!linkOnly || isLink) {
      return focusedElement;
    }
  }

  focus(index, scrollToResult = true) {
    if (this.focusedIndex >= 0) {
      const searchResult = this.searchResults[this.focusedIndex];
      // If the current result is outside the viewport and scrolling was
      // requested, only scroll to it, but don't focus on the new result.
      if (scrollToResult && scrollToElement(this.searchEngine,
          searchResult.container)) {
        return;
      }
      const highlighted = searchResult.highlightedElement;
      // Remove highlighting from previous item.
      highlighted.classList.remove(searchResult.highlightClass);
      highlighted.classList.remove('wsn-no-outline');
    }
    const searchResult = this.searchResults[index];
    if (!searchResult) {
      this.focusedIndex = -1;
      return;
    }
    const highlighted = searchResult.highlightedElement;
    // Add the focus outline and caret.
    highlighted.classList.add(searchResult.highlightClass);
    if (this.options.hideOutline || searchResult.anchor !== highlighted) {
      searchResult.anchor.classList.add('wsn-no-outline');
    }
    // We already scroll below, so no need for focus to scroll. The scrolling
    // behavior of `focus` also seems less predictable and caused an issue, see:
    // https://github.com/infokiller/web-search-navigator/issues/35
    searchResult.anchor.focus({preventScroll: true});
    // Ensure whole search result container is visible in the viewport, not only
    // the search result link.
    if (scrollToResult) {
      scrollToElement(this.searchEngine, searchResult.container);
    }
    this.focusedIndex = index;
  }

  focusNext(shouldWrap) {
    if (this.focusedIndex < this.searchResults.length - 1) {
      this.focus(this.focusedIndex + 1);
    } else if (shouldWrap) {
      this.focus(0);
    }
  }

  focusPrevious(shouldWrap) {
    if (this.focusedIndex > 0) {
      this.focus(this.focusedIndex - 1);
    } else if (shouldWrap) {
      this.focus(this.searchResults.length - 1);
    } else {
      window.scrollTo(window.scrollX, 0);
    }
  }
}

class WebSearchNavigator {
  async init() {
    /* eslint-disable-next-line no-undef */
    this.options = new ExtensionOptions();
    await this.options.load();
    /* eslint-disable-next-line no-undef */
    this.searchEngine = await getSearchEngine(this.options.sync.getAll());
    if (this.searchEngine == null) {
      return;
    }
    const sleep = (milliseconds) => {
      return new Promise((resolve) => setTimeout(resolve, milliseconds));
    };
    await sleep(this.options.sync.get('delay'));
    this.injectCSS();
    this.initResultsNavigation();
    this.initSearchInputNavigation();
    this.initTabsNavigation();
    this.initChangeToolsNavigation();
  }

  injectCSS() {
    const style = document.createElement('style');
    style.textContent = this.options.sync.get('customCSS');
    document.head.append(style);
  }

  initSearchInputNavigation() {
    const searchInput = document.querySelector(
        this.searchEngine.searchBoxSelector);
    // Only apply the extension logic if the key is not something the user may
    // have wanted to type into the searchbox, so that we don't interfere with
    // regular typing.
    const shouldHandleSearchInputKey = (event) => {
      return event.ctrlKey || event.metaKey || event.key === 'Escape';
    };
    // If insideSearchboxHandler returns true, outsideSearchboxHandler will also
    // be called (because it's defined on document, hence has lower priority),
    // in which case we don't want to handle the event. Therefore, we store the
    // last event handled in insideSearchboxHandler, and only handle the event
    // in outsideSearchboxHandler if it's not the same one.
    let lastEvent;
    const outsideSearchboxHandler = (event) => {
      if (event === lastEvent) {
        return !shouldHandleSearchInputKey(event);
      }
      // Scroll to the search box in case it's outside the viewport so that it's
      // clear to the user that it has focus.
      scrollToElement(this.searchEngine, searchInput);
      searchInput.select();
      searchInput.click();
      return false;
    };
    const insideSearchboxHandler = (event) => {
      lastEvent = event;
      if (!shouldHandleSearchInputKey(event)) {
        return true;
      }
      // Everything is selected; deselect all.
      if (searchInput.selectionStart === 0 &&
          searchInput.selectionEnd === searchInput.value.length) {
        // Scroll to the search box in case it's outside the viewport so that
        // it's clear to the user that it has focus.
        scrollToElement(this.searchEngine, searchInput);
        searchInput.setSelectionRange(
            searchInput.value.length, searchInput.value.length);
        return false;
      }
      // Closing search suggestions via document.body.click() or
      // searchInput.blur() breaks the state of google's controller.
      // The suggestion box is closed, yet it won't re-appear on the next
      // search box focus event.

      // Input can be blurred only when the suggestion box is already
      // closed, hence the blur event is queued.
      window.setTimeout(() => searchInput.blur());
      // Invoke the default handler which will close-up search suggestions
      // properly (google's controller won't break), but it won't remove the
      // focus.
      return true;
    };
    this.register(this.options.sync.get('focusSearchInput'),
        outsideSearchboxHandler);
    // Bind globally, otherwise Mousetrap ignores keypresses inside inputs.
    // We must bind it separately to the search box element, or otherwise the
    // key event won't always be captured (for example this is the case on
    // Google Search as of 2020-06-22), presumably because the javascript in the
    // page will disable further processing.
    this.registerGlobal(this.options.sync.get('focusSearchInput'),
        insideSearchboxHandler, searchInput);
  }

  initTabsNavigation() {
    const tabs = this.searchEngine.tabs || {};
    for (const [optionName, element] of Object.entries(tabs)) {
      this.register(this.options.sync.get(optionName), () => {
        if (element == null) {
          return true;
        }
        // Some search engines use forms instead of links for navigation
        if (element.tagName == 'FORM') {
          element.submit();
        } else {
          element.click();
        }
      });
    }
  }

  initResultsNavigation() {
    this.resetResultsManager();
    this.registerResultsNavigationKeybindings();
    if (!this.searchEngine.onChangedResults) {
      return;
    }
    this.searchEngine.onChangedResults((appendedOnly) => {
      if (appendedOnly) {
        this.resultsManager.reloadSearchResults();
      } else {
        this.resetResultsManager();
      }
    });
  }

  resetResultsManager() {
    this.resultsManager = new SearchResultsManager(this.searchEngine,
        this.options.sync.getAll());
    this.resultsManager.reloadSearchResults();
    if (this.resultsManager.searchResults.length === 0) {
      return;
    }
    this.isFirstNavigation = true;
    if (this.options.sync.get('autoSelectFirst')) {
      // Highlight the first result when the page is loaded, but don't scroll to
      // it because there may be KP cards such as stock graphs.
      this.resultsManager.focus(0, false);
    }
    const lastNavigation = this.options.local.values;
    if (location.href === lastNavigation.lastQueryUrl) {
      this.isFirstNavigation = false;
      this.resultsManager.focus(lastNavigation.lastFocusedIndex);
    }
  }

  registerResultsNavigationKeybindings() {
    const getOpt = (key) => {
      return this.options.sync.get(key);
    };
    this.register(getOpt('nextKey'), () => {
      if (!getOpt('autoSelectFirst') && this.isFirstNavigation) {
        this.resultsManager.focus(0);
        this.isFirstNavigation = false;
      } else {
        this.resultsManager.focusNext(getOpt('wrapNavigation'));
      }
      return false;
    });
    this.register(getOpt('previousKey'), () => {
      if (!getOpt('autoSelectFirst') && this.isFirstNavigation) {
        this.resultsManager.focus(0);
        this.isFirstNavigation = false;
      } else {
        this.resultsManager.focusPrevious(getOpt('wrapNavigation'));
      }
      return false;
    });
    this.register(getOpt('navigateKey'), () => {
      const link = this.resultsManager.getElementToNavigate();
      const lastNavigation = this.options.local.values;
      lastNavigation.lastQueryUrl = location.href;
      lastNavigation.lastFocusedIndex = this.resultsManager.focusedIndex;
      this.options.local.save();
      // If the element is a link, use the href to directly navigate, since some
      // websites will open it in a new tab.
      if (link.localName === 'a' && link.href) {
        document.location.href = link.href;
      } else {
        link.click();
      }
      return false;
    });
    this.register(getOpt('navigateNewTabKey'), () => {
      const link = this.resultsManager.getElementToNavigate(true);
      /* eslint-disable-next-line no-undef */
      browser.runtime.sendMessage({
        type: 'tabsCreate',
        options: {
          url: link.href,
          active: true,
        },
      });
      return false;
    });
    this.register(getOpt('navigateNewTabBackgroundKey'), () => {
      const link = this.resultsManager.getElementToNavigate(true);
      /* eslint-disable-next-line no-undef */
      browser.runtime.sendMessage({
        type: 'tabsCreate',
        options: {
          url: link.href,
          active: false,
        },
      });
      return false;
    });
  }

  initChangeToolsNavigation() {
    const getOpt = (key) => {
      return this.options.sync.get(key);
    };
    this.register(getOpt('navigateShowAll'), () =>
      this.searchEngine.changeTools('a'));
    this.register(getOpt('navigateShowHour'), () =>
      this.searchEngine.changeTools('h'));
    this.register(getOpt('navigateShowDay'), () =>
      this.searchEngine.changeTools('d'));
    this.register(getOpt('navigateShowWeek'), () =>
      this.searchEngine.changeTools('w'));
    this.register(getOpt('navigateShowMonth'), () =>
      this.searchEngine.changeTools('m'));
    this.register(getOpt('navigateShowYear'), () =>
      this.searchEngine.changeTools('y'));
    this.register(getOpt('toggleVerbatimSearch'), () =>
      this.searchEngine.changeTools('v'));
    this.register(getOpt('toggleSort'), () =>
      this.searchEngine.changeTools(null));
  }

  registerGlobal(shortcut, callback, element = document) {
    /* eslint-disable-next-line no-undef,new-cap */
    Mousetrap(element).bindGlobal(shortcut, (event) => {
      return callback(event);
    });
  }

  register(shortcut, callback, element = document) {
    /* eslint-disable-next-line no-undef,new-cap */
    Mousetrap(element).bind(shortcut, (event) => {
      return callback(event);
    });
  }
}

const extension = new WebSearchNavigator();
extension.init();
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs')
  , path = require('path')
  , Q = require('q')
  , unicodeHack = require('../lib/unicode_hack')
  , langMap = require('./lang-mapping.json')
  , Spellcheck = require('spellcheck');

function Pasquale () {}

Pasquale.prototype.setLanguage = function(lang) {
  var langPaths = langMap[lang]
    , dictPath = ''
    , affPath = ''
    , dicPath = '';

  if (!langPaths) throw new Error('No path defined for this language');

  dictPath = path.resolve(__dirname, '../bower_components/Dictionaries');
  affPath = path.join(dictPath, langPaths.aff);
  dicPath = path.join(dictPath, langPaths.dic);

  if (!(affPath.match(/\.aff$/) || dicPath.match(/\.aff$/)))
    throw new Error('.aff and a .dic files must be specified');

  if (!(fs.existsSync(affPath) || fs.existsSync(dicPath)))
    throw new Error('Both of the specified files must exist');

  this.sp = new Spellcheck(affPath, dicPath);
};

Pasquale.prototype.checkTextSpell = function (text) {
  var lines = text.split('\n')
    , promises = []
    , dfd = Q.defer();

  for (var i in lines)
    promises.push(this.checkLineSpell(lines[i], +i));

  Q.all(promises)
    .then(function (results) {
      dfd.resolve(results);
    }, function (err) {
      dfd.reject(err);
    });

  return dfd.promise;
};

Pasquale.prototype.checkLineSpell = function (text, lineCount) {
  var regex = unicodeHack(/\p{L}+/gi)
    , scope = this
    , promises = []
    , dfd = Q.defer()
    , match;

  while (match = regex.exec(text), match) {
    match.line = lineCount;
    promises.push(scope.checkWordSpell(match[0], match));
  }

  Q.all(promises)
    .then(function (results) {
      dfd.resolve(results);
    }, function (err) {
      dfd.reject(err);
    });

  return dfd.promise;
};

Pasquale.prototype.checkWordSpell = function (word, opts) {
  var dfd = Q.defer();

  this.sp.check(word, function (err, correct, suggestions) {
    if (err) dfd.reject(err);
    if (correct) dfd.resolve({word: word, correct: true});
    else dfd.resolve({
      word: word,
      correct: false,
      position: {
        l: opts.line,
        c: opts.index
      },
      suggestions: suggestions
    });
  });

  return dfd.promise;
};

module.exports = Pasquale;
<commit_msg>adds doc<commit_after>'use strict';

var fs = require('fs')
  , path = require('path')
  , Q = require('q')
  , unicodeHack = require('../lib/unicode_hack')
  , langMap = require('./lang-mapping.json')
  , Spellcheck = require('spellcheck');

function Pasquale () {}

/**
 * Sets the language to check the texts against
 * @param {string} lang The language as described in lang-mapping
 */
Pasquale.prototype.setLanguage = function(lang) {
  var langPaths = langMap[lang]
    , dictPath
    , affPath
    , dicPath;

  if (!langPaths) throw new Error('No path defined for this language');

  dictPath = path.resolve(__dirname, '../bower_components/Dictionaries');
  affPath = path.join(dictPath, langPaths.aff);
  dicPath = path.join(dictPath, langPaths.dic);

  if (!(affPath.match(/\.aff$/) || dicPath.match(/\.aff$/)))
    throw new Error('.aff and a .dic files must be specified');

  if (!(fs.existsSync(affPath) || fs.existsSync(dicPath)))
    throw new Error('Both of the specified files must exist');

  this.sp = new Spellcheck(affPath, dicPath);
};

/**
 * Checks the spelling of a given text (multi or single line)
 * @param  {string} text The text to check
 * @return {Promise}      a promise containing the results (array of
 *                          arrays)
 */
Pasquale.prototype.checkTextSpell = function (text) {
  var lines = text.split('\n')
    , promises = []
    , dfd = Q.defer();

  for (var i in lines)
    promises.push(this.checkLineSpell(lines[i], +i));

  Q.all(promises)
    .then(function (results) {
      dfd.resolve(results);
    }, function (err) {
      dfd.reject(err);
    });

  return dfd.promise;
};

/**
 * Checks the spelling for a given line.
 * @param  {string} text      A line of text
 * @param  {string|number} lineCount the number of the line
 * @return {[type]}           A promisse with the results (array)
 */
Pasquale.prototype.checkLineSpell = function (text, lineCount) {
  var regex = unicodeHack(/\p{L}+/gi)
    , scope = this
    , promises = []
    , dfd = Q.defer()
    , match;

  while (match = regex.exec(text), match) {
    match.line = lineCount;
    promises.push(scope.checkWordSpell(match[0], match));
  }

  Q.all(promises)
    .then(function (results) {
      dfd.resolve(results);
    }, function (err) {
      dfd.reject(err);
    });

  return dfd.promise;
};

/**
 * Checks the spelling for a given word
 * @param  {string} word the word to check
 * @param  {obj} opts some more info about where the word was found
 * @return {promise}      A promise containing the results for the
 *                          word
 */
Pasquale.prototype.checkWordSpell = function (word, opts) {
  var dfd = Q.defer();

  this.sp.check(word, function (err, correct, suggestions) {
    if (err) dfd.reject(err);
    if (correct) dfd.resolve({word: word, correct: true});
    else dfd.resolve({
      word: word,
      correct: false,
      position: {
        l: opts.line,
        c: opts.index
      },
      suggestions: suggestions
    });
  });

  return dfd.promise;
};

module.exports = Pasquale;
<|endoftext|>