<commit_before>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
'use strict';

var path = require('path'),
    log = require('./lib/log'),
    util = require('./lib/utils'),
    npmi = require('./lib/npmi'),
    create = require('./lib/create'),

    // todo: enable user configuration
    SRCPATHS = [path.resolve(__dirname, 'archetypes'), '.'];

function error(msg, exit_code) {
    var err = new Error(msg);
    err.errno = exit_code;
    return err;
}

function errorWithUsage(msg, exit_code) {
    var err = error(msg, exit_code);
    err.usage = module.exports.usage;
    return err;
}

function pathify(subpath) {
    return util.findInPaths(SRCPATHS, subpath); // full path if exists, or false
}

function checkName(name) {
    return name.indexOf(path.sep) !== -1;
}

function getDestinationDir(type, destopt, name) {
    var parts = [destopt || '.'];

    if ('mojit' === type.toLowerCase()) {
        parts.push('mojits'); // BC - type "mojit" goes inside mojits dir
    }

    parts.push(name);
    return path.resolve.apply(null, parts);
}

function subtypePath(type, args) {
    var subtype = args.length > 1 ? args.shift() : 'default';
    return path.join(type, subtype).toLowerCase(); // i.e. 'app/full'
}

function getSourceDir(type, args) {
    var source, err;

    // get archetype source directory based on first one OR two arguments
    switch (type.toLowerCase()) {
    case 'app':
    case 'mojit':
        // 1. mojito create [options] <app|mojit> [full|simple|default] <name>
        source = pathify(subtypePath(type, args));
        err = 'Invalid subtype.';
        break;

    case 'custom':
        // 2. mojito create [options] custom <path/to/archetype> <name>
        source = pathify(args.shift());
        err = 'Custom archtype path is invalid.';
        break;

    default:
        // 3. mojito create [options] <path/to/archetype> <name>
        //    (this *should* be the only supported syntax)
        source = pathify(type);
        err = type + ' is not a valid archetype or path.';
    }

    return source || errorWithUsage(err, 5);
}

function amMissingArgs(type, args) {
    var err = false;

    if (!type) {
        err = 'No parameters.';

    } else if (!args.length) {
        err = 'Missing subtype, name or path.';
    }

    return err && errorWithUsage(err, 3);
}

function main(args, opts, meta, cb) {
    var type = args.shift() || '',
        source = amMissingArgs(type, args) || getSourceDir(type, args),
        name = args.shift(),
        keyval = util.parseCsvObj(opts.keyval),
        dest;

    function npmCb(err) {
        if (!err && ('app' === type)) {
            log.info('Ok, "%s" created.', name);
            log.info('Installing mojito application "' + dest + '’s" dependencies with npm.');
            npmi(exports.npmcmd, dest, cb);
        } else {
            cb(err, 'Done.');
        }
    }

    if (opts.debug) {
        log.level = 'debug';
    }

    if (source instanceof Error) {
        cb(source);
        return;

    } else if (!name) {
        cb(errorWithUsage('Missing name.', 3));
        return;

    } else if (checkName(name)) {
        cb(error('Path separators not allowed in names.', 3));
        return;
    }

    if (('.' === name) && util.exists(source).isFile()) { // 2nd stat, fixme
        name = path.basename(source);
    }

    dest = getDestinationDir(type, opts.directory, name);
    keyval.name = opts.name || path.basename(dest);
    keyval.port = opts.port || 8666;

    log.info('Source: %s', source);
    log.info('Destination: %s', dest);

    create(source, dest, keyval, npmCb);
}

exports = main;

exports.usage = [
    'Usage: mojito create [options] <app|mojit> [full|simple|default] <name>',
    'Usage: mojito create [options] custom <path/to/archetype> <name>',
    'Usage: mojito create [options] <path/to/archetype> <name>',
    '',
    'Example: mojito create app Foo',
    '  (creates directory "Foo" containing a new Mojito application named "Foo")',
    '',
    'Example: mojito create mojit Bar',
    '  (creates directory "Bar" containing new Mojit named "Bar")',
    '',
    'OPTIONS: ',
    '  --port [number]  Specifies default port for your Mojito app to run on.',
    '  -p [number]      Short for --port',
    '  -keyval [string] key value pairs to pass to a custom archetype template',
    '                   a key/value is separated by colons, key/value pairs by',
    '                   commas: "key1:val1,key2:val2',
    '  -k [string]      Short for --keyval'].join('\n');

exports.options = [
    {shortName: 'd', hasValue: true,  longName: 'directory'},
    {shortName: 'k', hasValue: true,  longName: 'keyval'},
    {shortName: 'p', hasValue: true,  longName: 'port'}
];

// test hacks
exports.npmcmd = 'npm -s i';
exports.test = {
    getSourceDir: getSourceDir
};

module.exports = exports;
<commit_msg>minor noop<commit_after>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
'use strict';

var path = require('path'),
    log = require('./lib/log'),
    util = require('./lib/utils'),
    npmi = require('./lib/npmi'),
    create = require('./lib/create'),

    // todo: enable user configuration
    SRCPATHS = [path.resolve(__dirname, 'archetypes'), '.'];

function error(msg, exit_code) {
    var err = new Error(msg);
    err.errno = exit_code;
    return err;
}

function errorWithUsage(msg, exit_code) {
    var err = error(msg, exit_code);
    err.usage = module.exports.usage;
    return err;
}

function pathify(subpath) {
    return util.findInPaths(SRCPATHS, subpath); // full path if exists, or false
}

function checkName(name) {
    return name.indexOf(path.sep) !== -1;
}

function getDestinationDir(type, destopt, name) {
    var parts = [destopt || '.'];

    if ('mojit' === type.toLowerCase()) {
        parts.push('mojits'); // BC - type "mojit" goes inside mojits dir
    }

    parts.push(name);
    return path.resolve.apply(null, parts);
}

function subtypePath(type, args) {
    var subtype = args.length > 1 ? args.shift() : 'default';
    return path.join(type, subtype).toLowerCase(); // i.e. 'app/full'
}

function getSourceDir(type, args) {
    var source, err;

    // get archetype source directory based on first one OR two arguments
    switch (type.toLowerCase()) {
    case 'app':
    case 'mojit':
        // 1. mojito create [options] <app|mojit> [full|simple|default] <name>
        source = pathify(subtypePath(type, args));
        err = 'Invalid subtype.';
        break;

    case 'custom':
        // 2. mojito create [options] custom <path/to/archetype> <name>
        source = pathify(args.shift());
        err = 'Custom archtype path is invalid.';
        break;

    default:
        // 3. mojito create [options] <path/to/archetype> <name>
        //    (this *should* be the only supported syntax)
        source = pathify(type);
        err = type + ' is not a valid archetype or path.';
    }

    return source || errorWithUsage(err, 5);
}

function amMissingArgs(type, args) {
    var err = false;

    if (!type) {
        err = 'No parameters.';

    } else if (!args.length) {
        err = 'Missing subtype, name or path.';
    }

    return err && errorWithUsage(err, 3);
}

function main(args, opts, meta, cb) {
    var type = args.shift() || '',
        source = amMissingArgs(type, args) || getSourceDir(type, args),
        name = args.shift(),
        keyval = util.parseCsvObj(opts.keyval),
        dest;

    if (opts.debug) {
        log.level = 'debug';
    }

    if (source instanceof Error) {
        cb(source);
        return;

    } else if (!name) {
        cb(errorWithUsage('Missing name.', 3));
        return;

    } else if (checkName(name)) {
        cb(error('Path separators not allowed in names.', 3));
        return;
    }

    if (('.' === name) && util.exists(source).isFile()) { // 2nd stat, fixme
        name = path.basename(source);
    }

    dest = getDestinationDir(type, opts.directory, name);
    keyval.name = opts.name || path.basename(dest);
    keyval.port = opts.port || 8666;

    log.info('Source: %s', source);
    log.info('Destination: %s', dest);

    function npmCb(err) {
        if (!err && ('app' === type)) {
            log.info('Ok, "%s" created.', name);
            log.info('Installing mojito application "' + dest + '’s" dependencies with npm.');
            npmi(exports.npmcmd, dest, cb);
        } else {
            cb(err, 'Done.');
        }
    }

    create(source, dest, keyval, npmCb);
}

exports = main;

exports.usage = [
    'Usage: mojito create [options] <app|mojit> [full|simple|default] <name>',
    'Usage: mojito create [options] custom <path/to/archetype> <name>',
    'Usage: mojito create [options] <path/to/archetype> <name>',
    '',
    'Example: mojito create app Foo',
    '  (creates directory "Foo" containing a new Mojito application named "Foo")',
    '',
    'Example: mojito create mojit Bar',
    '  (creates directory "Bar" containing new Mojit named "Bar")',
    '',
    'OPTIONS: ',
    '  --port [number]  Specifies default port for your Mojito app to run on.',
    '  -p [number]      Short for --port',
    '  -keyval [string] key value pairs to pass to a custom archetype template',
    '                   a key/value is separated by colons, key/value pairs by',
    '                   commas: "key1:val1,key2:val2',
    '  -k [string]      Short for --keyval'].join('\n');

exports.options = [
    {shortName: 'd', hasValue: true,  longName: 'directory'},
    {shortName: 'k', hasValue: true,  longName: 'keyval'},
    {shortName: 'p', hasValue: true,  longName: 'port'}
];

// test hacks
exports.npmcmd = 'npm -s i';
exports.test = {
    getSourceDir: getSourceDir
};

module.exports = exports;
<|endoftext|>


<commit_before>var express = require('express');
var app = express();

app.set('port', (process.env.PORT || 5000));

app.get('/', function(request, response) {
	console.log(request.headers);
  response.send('{ "text": "heyo" }');
});

app.listen(app.get('port'), function() {
  console.log("Node app is running on port:" + app.get('port'))
});<commit_msg>improving<commit_after>var express = require('express');
var app = express();

app.set('port', (process.env.PORT || 5000));

vat request = function(request, response) {
	console.log(request.headers);
  response.send('{ "text": "heyo" }');
};

app.get('/', request);
app.post('/', request);

app.listen(app.get('port'), function() {
  console.log("Node app is running on port:" + app.get('port'))
});
<|endoftext|>


<commit_before>var predator = require('predator');
var scrollIntoView = require('scroll-into-view');

// List of tagNames ordered by their likeliness to be the target of a click event
var textWeighting = ['h1', 'h2', 'h3', 'h4', 'label', 'p', 'a', 'button'];
var clickWeighting = ['button', 'a', 'label', 'h1', 'h2', 'h3', 'h4', 'i', 'span'];
var valueWeighting = ['input', 'textarea', 'select', 'label'];

var types = {
        'button': ['button', 'a'],
        'label': ['label', 'span', 'div'],
        'heading': ['h1', 'h2', 'h3', 'h4'],
        'image': ['img', 'svg'],
        'field': ['input', 'textarea', 'select', 'label'],
        'all': ['*'],
        'text': ['*']
    },
    noElementOfType = 'no elements of type ',
    documentScope,
    windowScope,
    runDelay,
    initialised;

function _pressKey(key, done) {
    var element = documentScope.activeElement;

    element.value += key;

    var keydownEvent = new windowScope.KeyboardEvent('keydown'),
        keyupEvent = new windowScope.KeyboardEvent('keyup'),
        pressKeyEvent = new windowScope.KeyboardEvent('pressKey');

    var method = 'initKeyboardEvent' in keydownEvent ? 'initKeyboardEvent' : 'initKeyEvent';

    keydownEvent[method]('keydown', true, true, windowScope, key, 3, true, false, true, false, false);
    keyupEvent[method]('keyup', true, true, windowScope, key, 3, true, false, true, false, false);
    pressKeyEvent[method]('pressKey', true, true, windowScope, key, 3, true, false, true, false, false);

    element.dispatchEvent(keydownEvent);
    element.dispatchEvent(keyupEvent);
    element.dispatchEvent(pressKeyEvent);

    done(null, element);
}

function _pressKeys(keys, done) {
    String(keys).split('').forEach(function(key) {
        _pressKey(key, function noop() {});
    });

    done(null, documentScope.activeElement);
}

function findUi(selectors) {
    return documentScope.querySelectorAll(selectors);
}

function _navigate(location, previousElement, done) {
    var callbackTimer;

    function handlewindowScopeError(error) {
        clearTimeout(callbackTimer);

        done(error);
        windowScope.removeEventListener('error', handlewindowScopeError);
    }

    windowScope.addEventListener('error', handlewindowScopeError);
    windowScope.location = location;

    callbackTimer = setTimeout(done, 150);
}

function _getLocation(done) {
    setTimeout(function() {
        done(null, windowScope.location);
    }, 500);
}

function matchElementValue(element, value) {
    return (
            element.textContent.toLowerCase() === value.toLowerCase() ||
            (element.title && element.title.toLowerCase() === value.toLowerCase())
        );
}

function findMatchingElements(value, type, elementsList) {
    return Array.prototype.slice.call(elementsList)
        .filter(function(element) {
            return matchElementValue(element, value);
        });
}

function getElementTextWeight(element) {
    var index = textWeighting.indexOf(element.tagName.toLowerCase());
    return textWeighting.length - (index < 0 ? Infinity : index);
}

function getElementClickWeight(element) {
    var index = clickWeighting.indexOf(element.tagName.toLowerCase());
    return clickWeighting.length - (index < 0 ? Infinity : index);
}

function getElementValueWeight(element) {
    var index = valueWeighting.indexOf(element.tagName.toLowerCase());
    return valueWeighting.length - (index < 0 ? Infinity : index);
}

function _findAllUi(value, type, done){
    if(!type){
        type = 'all';
    }

    var elementTypes = types[type];

    if(!elementTypes) {
        return done(new Error(type + ' is not a valid ui type'));
    }

    var elements = findUi(elementTypes);

    if(!elements.length) {
        return done(new Error(noElementOfType + type));
    }

    var results = findMatchingElements(value, type, elements)
        .sort(function(a, b) {
            return getElementTextWeight(a) < getElementTextWeight(b);
        });

    done(null, results);
}

function _findUi(value, type, returnArray, done) {
    if(!done) {
        done = returnArray;
        returnArray = false;
    }

    _findAllUi(value, type, function(error, elements){
        if(error){
            return done(error);
        }

        var results = Array.prototype.slice.call(elements)
            .filter(function(element){
                return !predator(element).hidden;
            });

        if(!results.length){
            return done(new Error('"' + value + '" was found but not visible on screen'));
        }

        done(null, returnArray ? results : results.shift());
    });
}

function _setValue(value, type, text, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        element.value = text;

        done(null, element);
    });
}

function _wait(time, done) {
    setTimeout(done, time || 0);
}

function isClickable(element){
    var rect = element.getBoundingClientRect(),
        clickElement = documentScope.elementFromPoint(rect.left, rect.top),
        elementInClickElement = ~Array.prototype.indexOf.call(clickElement.children, element);

    return elementInClickElement || clickElement === element;
}

function executeClick(value, type, done) {
    _findUi(value, 'all', true, function(error, elements) {
        if(error) {
            return done(error);
        }

        var element = elements
            .sort(function(a, b) {
                return getElementClickWeight(a) < getElementClickWeight(b);
            })
            .find(isClickable);

        if(!element) {
            return done(new Error('could not find clickable element matching "' + value + '"'));
        }

        element.click();

        setTimeout(function(){
            done(null, element);
        }, clickDelay)

    });
}

function _focus(value, type, done) {
   _findUi(value, type, true, function(error, elements){
        if(error){
            return done(error);
        }

        var result = elements
            .sort(function(a, b) {
                return getElementValueWeight(a) < getElementValueWeight(b);
            })
            .shift();

        result.focus();

        done(null, result);
   });
}

function _changeValue(value, type, text, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        _pressKeys(text, function(error){
            if(error){
                return done(error);
            }

            element.blur();

            var event = document.createEvent('HTMLEvents');

            event.initEvent('change', false, true);
            element.dispatchEvent(event);

            done(null, element);
        });
    });
}

function _getValue(value, type, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        done(null, 'value' in element ? element.value : element.textContent);
    });
}

function _blur(done) {
    var element = documentScope.activeElement;
    element.blur();

    done(null, element);
}

function _scrollTo(value, type, done){
    _findAllUi(value, type, function(error, elements) {
        if(error) {
            return done(error);
        }

        var targetElement = elements.shift();

        scrollIntoView(targetElement, function(){
            done(null, targetElement);
        });
    });
}

function runTasks(state, tasks, callback) {
    if(tasks.length) {
        tasks.shift()(function(error, result) {
            if(error) {
                return callback(error);
            } else {
                state.lastResult = result;

                if(tasks.length === 0) {
                    callback(null, result);
                } else {
                    runTasks(state, tasks, callback);
                }
            }
        });
    }
}

function driveUi(){
    var tasks = [],
        driverFunctions = {},
        state = {};

    function addTask(task){
        tasks.push(task);

        return driverFunctions;
    }

    driverFunctions = {
        navigate: function(location){
            return addTask(_navigate.bind(state, location));
        },
        findUi: function(value, type){
            return addTask(_findUi.bind(state, value, type));
        },
        getLocation: function() {
            return addTask(_getLocation.bind(state));
        },
        focus: function(value, type) {
            return addTask(_focus.bind(state, value, type));
        },
        blur: function() {
            return addTask(_blur.bind(state));
        },
        click: function(value, type){
            return addTask(executeClick.bind(state, value, type));
        },
        pressKey: function(value) {
            return addTask(_pressKey.bind(state, value));
        },
        pressKeys: function(value) {
            return addTask(_pressKeys.bind(state, value));
        },
        changeValue: function(value, type, text) {
            return addTask(_changeValue.bind(state, value, type, text));
        },
        setValue: function(value, type, text) {
            return addTask(_setValue.bind(state, value, type, text));
        },
        getValue: function(value, type) {
            return addTask(_getValue.bind(state, value, type));
        },
        wait: function(time) {
            if(!arguments.length) {
                time = runDelay;
            }

            return addTask(_wait.bind(state, time));
        },
        do: function(driver){
            return addTask(driver.go);
        },
        check: function(task){
            return addTask(function(callback){
                task(state.lastResult, callback);
            });
        },
        scrollTo: function(value, type){
            return addTask(_scrollTo.bind(state, value, type));
        },
        go: function(callback) {
            if(!initialised) {
                throw(new Error('init must becalled before calling go'));
            }

            if(tasks.length) {
                tasks.unshift(_wait.bind(state, runDelay));
                runTasks(state, tasks, callback);
            } else {
                callback(new Error('No tasks defined'));
            }
        }
    };

    return driverFunctions;
}

driveUi.init = function(settings) {
    documentScope = settings.document || document;
    windowScope = settings.window || window;
    runDelay = settings.runDelay || 0;
    clickDelay = settings.clickDelay || 100;

    initialised = true;
};

module.exports = driveUi;
<commit_msg>bugfixes and generification<commit_after>var predator = require('predator');
var scrollIntoView = require('scroll-into-view');

// List of tagNames ordered by their likeliness to be the target of a click event
var textWeighting = ['h1', 'h2', 'h3', 'h4', 'label', 'p', 'a', 'button'];
var clickWeighting = ['button', 'input', 'a', 'h1', 'h2', 'h3', 'h4', 'i', 'label'];
var valueWeighting = ['input', 'textarea', 'select', 'label'];

var types = {
        'button': ['button', 'a'],
        'label': ['label', 'span', 'div'],
        'heading': ['h1', 'h2', 'h3', 'h4'],
        'image': ['img', 'svg'],
        'field': ['input', 'textarea', 'select', 'label'],
        'all': ['*'],
        'text': ['*']
    },
    noElementOfType = 'no elements of type ',
    documentScope,
    windowScope,
    runDelay,
    initialised;

function _pressKey(key, done) {
    var element = documentScope.activeElement;

    element.value += key;

    var keydownEvent = new windowScope.KeyboardEvent('keydown'),
        keyupEvent = new windowScope.KeyboardEvent('keyup'),
        pressKeyEvent = new windowScope.KeyboardEvent('pressKey');

    var method = 'initKeyboardEvent' in keydownEvent ? 'initKeyboardEvent' : 'initKeyEvent';

    keydownEvent[method]('keydown', true, true, windowScope, key, 3, true, false, true, false, false);
    keyupEvent[method]('keyup', true, true, windowScope, key, 3, true, false, true, false, false);
    pressKeyEvent[method]('pressKey', true, true, windowScope, key, 3, true, false, true, false, false);

    element.dispatchEvent(keydownEvent);
    element.dispatchEvent(keyupEvent);
    element.dispatchEvent(pressKeyEvent);

    done(null, element);
}

function _pressKeys(keys, done) {
    var nextKey = String(keys).charAt(0);

    if(nextKey === ''){
        return done(null, documentScope.activeElement);
    }

    _pressKey(nextKey, function() {
        setTimeout(function(){
            _pressKeys(String(keys).slice(1), done);
        }, 50);
    });
}

function findUi(selectors) {
    return Array.prototype.slice.call(documentScope.querySelectorAll(selectors))
        .sort(function(a, b){
            return !a.contains(b) ? -1 : 0;
        }); // deeper elements take precedence.
}

function _navigate(location, previousElement, done) {
    var callbackTimer;

    function handlewindowScopeError(error) {
        clearTimeout(callbackTimer);

        done(error);
        windowScope.removeEventListener('error', handlewindowScopeError);
    }

    windowScope.addEventListener('error', handlewindowScopeError);
    windowScope.location = location;

    callbackTimer = setTimeout(done, 150);
}

function _getLocation(done) {
    setTimeout(function() {
        done(null, windowScope.location);
    }, 500);
}

function matchElementValue(element, value) {
    return (
            element.textContent.toLowerCase() === value.toLowerCase() ||
            (element.title && element.title.toLowerCase() === value.toLowerCase())
        );
}

function findMatchingElements(value, type, elementsList) {
    return Array.prototype.slice.call(elementsList)
        .filter(function(element) {
            return matchElementValue(element, value);
        });
}

function getElementTextWeight(element) {
    var index = textWeighting.indexOf(element.tagName.toLowerCase());
    return textWeighting.length - (index < 0 ? Infinity : index);
}

function getElementClickWeight(element) {
    var index = clickWeighting.indexOf(element.tagName.toLowerCase());
    return clickWeighting.length - (index < 0 ? Infinity : index);
}

function getElementValueWeight(element) {
    var index = valueWeighting.indexOf(element.tagName.toLowerCase());
    return valueWeighting.length - (index < 0 ? Infinity : index);
}

function _findAllUi(value, type, done){
    if(!type){
        type = 'all';
    }

    var elementTypes = types[type];

    if(!elementTypes) {
        return done(new Error(type + ' is not a valid ui type'));
    }

    var elements = findUi(elementTypes);

    if(!elements.length) {
        return done(new Error(noElementOfType + type));
    }

    var results = findMatchingElements(value, type, elements)
        .sort(function(a, b) {
            return getElementTextWeight(a) < getElementTextWeight(b);
        });

    done(null, results);
}

function _findUi(value, type, returnArray, done) {
    if(!done) {
        done = returnArray;
        returnArray = false;
    }

    _findAllUi(value, type, function(error, elements){
        if(error){
            return done(error);
        }

        var results = Array.prototype.slice.call(elements)
            .filter(function(element){
                return !predator(element).hidden;
            });

        if(!results.length){
            return done(new Error('"' + value + '" was found but not visible on screen'));
        }

        done(null, returnArray ? results : results.shift());
    });
}

function _setValue(value, type, text, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        element.value = text;

        done(null, element);
    });
}

function _wait(time, done) {
    setTimeout(done, time || 0);
}

function findClickable(elements){
    for(var i = 0; i < elements.length; i++){
        var element = elements[i];
            rect = element.getBoundingClientRect(),
            clickElement = documentScope.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2),
            clickElementInElement = element.contains(clickElement),
            elementInClickElement = clickElement.contains(element);

        if(clickElementInElement || elementInClickElement || clickElement === element){
            return clickElement;
        }
    }
}

function executeClick(value, type, done) {
    _findUi(value, 'all', true, function(error, elements) {
        if(error) {
            return done(error);
        }

        var clickableElements = elements
            .sort(function(a, b) {
                return getElementClickWeight(a) < getElementClickWeight(b);
            });

        var element = findClickable(elements);

        if(!element) {
            return done(new Error('could not find clickable element matching "' + value + '"'));
        }

        // SVG paths
        while(!element.click){
            element = element.parentNode;
        }

        element.click();

        setTimeout(function(){
            done(null, element);
        }, clickDelay)

    });
}

function _focus(value, type, done) {
   _findUi(value, type, true, function(error, elements){
        if(error){
            return done(error);
        }

        var result = elements
            .sort(function(a, b) {
                return getElementValueWeight(a) < getElementValueWeight(b);
            })
            .shift();

        result.focus();

        done(null, result);
   });
}

function _changeValue(value, type, text, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        _pressKeys(text, function(error){
            if(error){
                return done(error);
            }

            element.blur();

            var event = document.createEvent('HTMLEvents');

            event.initEvent('change', false, true);
            element.dispatchEvent(event);

            done(null, element);
        });
    });
}

function _getValue(value, type, done) {
    _focus(value, type, function(error, element) {
        if(error){
            return done(error);
        }

        done(null, 'value' in element ? element.value : element.textContent);
    });
}

function _blur(done) {
    var element = documentScope.activeElement;
    element.blur();

    done(null, element);
}

function _scrollTo(value, type, done){
    _findAllUi(value, type, function(error, elements) {
        if(error) {
            return done(error);
        }

        var targetElement = elements.shift();

        scrollIntoView(targetElement, function(){
            done(null, targetElement);
        });
    });
}

function runTasks(state, tasks, callback) {
    if(tasks.length) {
        tasks.shift()(function(error, result) {
            if(error) {
                return callback(error);
            } else {
                state.lastResult = result;

                if(tasks.length === 0) {
                    callback(null, result);
                } else {
                    runTasks(state, tasks, callback);
                }
            }
        });
    }
}

function driveUi(){
    var tasks = [],
        driverFunctions = {},
        state = {};

    function addTask(task){
        tasks.push(task);

        return driverFunctions;
    }

    driverFunctions = {
        navigate: function(location){
            return addTask(_navigate.bind(state, location));
        },
        findUi: function(value, type){
            return addTask(_findUi.bind(state, value, type));
        },
        getLocation: function() {
            return addTask(_getLocation.bind(state));
        },
        focus: function(value, type) {
            return addTask(_focus.bind(state, value, type));
        },
        blur: function() {
            return addTask(_blur.bind(state));
        },
        click: function(value, type){
            return addTask(executeClick.bind(state, value, type));
        },
        pressKey: function(value) {
            return addTask(_pressKey.bind(state, value));
        },
        pressKeys: function(value) {
            return addTask(_pressKeys.bind(state, value));
        },
        changeValue: function(value, type, text) {
            return addTask(_changeValue.bind(state, value, type, text));
        },
        setValue: function(value, type, text) {
            return addTask(_setValue.bind(state, value, type, text));
        },
        getValue: function(value, type) {
            return addTask(_getValue.bind(state, value, type));
        },
        wait: function(time) {
            if(!arguments.length) {
                time = runDelay;
            }

            return addTask(_wait.bind(state, time));
        },
        do: function(driver){
            return addTask(driver.go);
        },
        check: function(task){
            return addTask(function(callback){
                task(state.lastResult, callback);
            });
        },
        scrollTo: function(value, type){
            return addTask(_scrollTo.bind(state, value, type));
        },
        go: function(callback) {
            if(!initialised) {
                throw(new Error('init must becalled before calling go'));
            }

            if(tasks.length) {
                tasks.unshift(_wait.bind(state, runDelay));
                runTasks(state, tasks, callback);
            } else {
                callback(new Error('No tasks defined'));
            }
        }
    };

    return driverFunctions;
}

driveUi.init = function(settings) {
    documentScope = settings.document || document;
    windowScope = settings.window || window;
    runDelay = settings.runDelay || 0;
    clickDelay = settings.clickDelay || 100;

    initialised = true;
};

module.exports = driveUi;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.session',
  name: 'Session',

  implements: [
    'foam.nanos.auth.Authorizable',
    'foam.nanos.auth.CreatedAware',
    'foam.nanos.auth.CreatedByAware'
  ],

  javaImports: [
    'foam.nanos.auth.AuthService',
    'foam.nanos.auth.AuthorizationException',
    'foam.nanos.auth.User',
    'foam.util.SafetyUtil',
    'java.util.Date'
  ],

  properties: [
    {
      class: 'String',
      name: 'id',
      visibility: 'RO'
    },
    {
      class: 'Long',
      name: 'userId',
      tableCellFormatter: function(value, obj) {
        this.add(value);
        this.__context__.userDAO.find(value).then(function(user) {
          this.add(' ', user && user.label());
        }.bind(this));
      },
      required: true,
      visibility: 'FINAL',
    },
    {
      class: 'Long',
      name: 'agentId',
      tableCellFormatter: function(value, obj) {
        if ( ! value ) return;
        this.add(value);
        this.__context__.userDAO.find(value).then(function(user) {
          this.add(' ', user.label());
        }.bind(this));
      },
      visibility: 'RO',
    },
    {
      class: 'DateTime',
      name: 'created',
      visibility: 'RO'
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdBy',
      visibility: 'RO'
    },
    {
      class: 'DateTime',
      name: 'lastUsed',
      visibility: 'RO',
      storageTransient: true
    },
    {
      class: 'Duration',
      name: 'ttl',
      label: 'TTL',
      documentation: 'The "time to live" of the session. The amount of time in milliseconds that the session should be kept alive after its last use before being destroyed. Must be a positive value or zero.',
      value: 28800000, // 1000 * 60 * 60 * 8 = number of milliseconds in 8 hours
      validationPredicates: [
        {
          args: ['ttl'],
          predicateFactory: function(e) {
            return e.GTE(foam.nanos.session.Session.TTL, 0);
          },
          errorString: 'TTL must be 0 or greater.'
        }
      ]
    },
    {
      class: 'Long',
      name: 'uses',
      storageTransient: true
    },
    {
      class: 'String',
      name: 'remoteHost',
      visibility: 'RO'
    },
    {
      documentation: 'Intended to be used with long TTL sessions, further restricting to a known set of IPs.',
      class: 'StringArray',
      name: 'remoteHostWhiteList'
    },
    {
      class: 'Object',
      name: 'context',
      type: 'Context',
      // Put a null user to prevent sytem user from leaking into subcontexts
      javaFactory: 'return getX().put("user", null).put("group", null).put(Session.class, this);',
      hidden: true,
      transient: true
    },
    {
      class: 'Boolean',
      name: 'ignoreRemoteHostChanges',
      documentation: `
        Set to true if this session should not be deleted when the remote host
        changes. The intended use case for this flag is to allow customers that
        will be accessing the server via a cluster to use the same session.
        We turn this off by default since we want to delete users' sessions when
        they're accessed from a new remote host to prevent session hijacking
        attacks.
      `
    }
  ],

  methods: [
    // Disable cloneing and freezing so that Sessions can be mutated while
    // in the SessionDAO.
    {
      name: 'fclone',
      type: 'foam.core.FObject',
      javaCode: 'return this;'
    },
    {
      name: 'freeze',
      type: 'foam.core.FObject',
      javaCode: ' return this; '
    },
    {
      name: 'touch',
      documentation: 'Called when session used to track usage statistics.',
      javaCode: `
        synchronized ( this ) {
          setLastUsed(new Date());
          setUses(getUses()+1);
        }
      `
    },
    {
      name: 'validRemoteHost',
      type: 'Boolean',
      args: [
        {
          name: 'remoteHost', type: 'String'
        }
      ],
      javaCode: `
        if ( getIgnoreRemoteHostChanges() ) {
          return true;
        }

        if ( SafetyUtil.equals(getRemoteHost(), remoteHost) ) {
          return true;
        }

        for ( String host : getRemoteHostWhiteList() ) {
          if ( SafetyUtil.equals(host, remoteHost) ) {
            return true;
          }
        }

        return false;
      `
    },
    {
      name: 'checkOwnership',
      args: [
        { name: 'x', type: 'Context' }
      ],
      type: 'Boolean',
      javaCode: `
        User user = (User) x.get("user");
        return user != null && this.getUserId() == user.getId();
      `
    },
    {
      name: 'authorizeOnCreate',
      javaCode: `
      AuthService auth = (AuthService) x.get("auth");
      if ( ! checkOwnership(x) && ! auth.check(x, createPermission("create")) ) throw new AuthorizationException("You don't have permission to create this session.");
      `
    },
    {
      name: 'authorizeOnUpdate',
      javaCode: `
      AuthService auth = (AuthService) x.get("auth");
      if ( ! checkOwnership(x) && ! auth.check(x, createPermission("update")) ) throw new AuthorizationException("You don't have permission to update sessions other than your own.");
      `
    },
    {
      name: 'authorizeOnDelete',
      javaCode: `
      AuthService auth = (AuthService) x.get("auth");
      if ( ! checkOwnership(x) && ! auth.check(x, "*") ) throw new AuthorizationException("You don't have permission to delete sessions other than your own.");
      `
    },
    {
      name: 'authorizeOnRead',
      javaCode: `
      AuthService auth = (AuthService) x.get("auth");
      if ( ! checkOwnership(x) && ! auth.check(x, createPermission("read")) ) throw new AuthorizationException("You don't have permission to view sessions other than your own.");
      `
    },
    {
      name: 'createPermission',
      args: [
        { name: 'operation', type: 'String' }
      ],
      type: 'String',
      javaCode: `
        return "session." + operation + "." + getId();
      `
    }
  ]
});
<commit_msg>Formatting<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.session',
  name: 'Session',

  implements: [
    'foam.nanos.auth.Authorizable',
    'foam.nanos.auth.CreatedAware',
    'foam.nanos.auth.CreatedByAware'
  ],

  javaImports: [
    'foam.nanos.auth.AuthService',
    'foam.nanos.auth.AuthorizationException',
    'foam.nanos.auth.User',
    'foam.util.SafetyUtil',
    'java.util.Date'
  ],

  properties: [
    {
      class: 'String',
      name: 'id',
      visibility: 'RO'
    },
    {
      class: 'Long',
      name: 'userId',
      tableCellFormatter: function(value, obj) {
        this.add(value);
        this.__context__.userDAO.find(value).then(function(user) {
          this.add(' ', user && user.label());
        }.bind(this));
      },
      required: true,
      visibility: 'FINAL',
    },
    {
      class: 'Long',
      name: 'agentId',
      tableCellFormatter: function(value, obj) {
        if ( ! value ) return;
        this.add(value);
        this.__context__.userDAO.find(value).then(function(user) {
          this.add(' ', user.label());
        }.bind(this));
      },
      visibility: 'RO',
    },
    {
      class: 'DateTime',
      name: 'created',
      visibility: 'RO'
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdBy',
      visibility: 'RO'
    },
    {
      class: 'DateTime',
      name: 'lastUsed',
      visibility: 'RO',
      storageTransient: true
    },
    {
      class: 'Duration',
      name: 'ttl',
      label: 'TTL',
      documentation: 'The "time to live" of the session. The amount of time in milliseconds that the session should be kept alive after its last use before being destroyed. Must be a positive value or zero.',
      value: 28800000, // 1000 * 60 * 60 * 8 = number of milliseconds in 8 hours
      validationPredicates: [
        {
          args: ['ttl'],
          predicateFactory: function(e) {
            return e.GTE(foam.nanos.session.Session.TTL, 0);
          },
          errorString: 'TTL must be 0 or greater.'
        }
      ]
    },
    {
      class: 'Long',
      name: 'uses',
      storageTransient: true
    },
    {
      class: 'String',
      name: 'remoteHost',
      visibility: 'RO'
    },
    {
      documentation: 'Intended to be used with long TTL sessions, further restricting to a known set of IPs.',
      class: 'StringArray',
      name: 'remoteHostWhiteList'
    },
    {
      class: 'Object',
      name: 'context',
      type: 'Context',
      // Put a null user to prevent sytem user from leaking into subcontexts
      javaFactory: 'return getX().put("user", null).put("group", null).put(Session.class, this);',
      hidden: true,
      transient: true
    },
    {
      class: 'Boolean',
      name: 'ignoreRemoteHostChanges',
      documentation: `
        Set to true if this session should not be deleted when the remote host
        changes. The intended use case for this flag is to allow customers that
        will be accessing the server via a cluster to use the same session.
        We turn this off by default since we want to delete users' sessions when
        they're accessed from a new remote host to prevent session hijacking
        attacks.
      `
    }
  ],

  methods: [
    // Disable cloneing and freezing so that Sessions can be mutated while
    // in the SessionDAO.
    {
      name: 'fclone',
      type: 'foam.core.FObject',
      javaCode: 'return this;'
    },
    {
      name: 'freeze',
      type: 'foam.core.FObject',
      javaCode: ' return this; '
    },
    {
      name: 'touch',
      documentation: 'Called when session used to track usage statistics.',
      javaCode: `
        synchronized ( this ) {
          setLastUsed(new Date());
          setUses(getUses()+1);
        }
      `
    },
    {
      name: 'validRemoteHost',
      type: 'Boolean',
      args: [
        {
          name: 'remoteHost', type: 'String'
        }
      ],
      javaCode: `
        if ( getIgnoreRemoteHostChanges() ) {
          return true;
        }

        if ( SafetyUtil.equals(getRemoteHost(), remoteHost) ) {
          return true;
        }

        for ( String host : getRemoteHostWhiteList() ) {
          if ( SafetyUtil.equals(host, remoteHost) ) {
            return true;
          }
        }

        return false;
      `
    },
    {
      name: 'checkOwnership',
      args: [
        { name: 'x', type: 'Context' }
      ],
      type: 'Boolean',
      javaCode: `
        User user = (User) x.get("user");
        return user != null && this.getUserId() == user.getId();
      `
    },
    {
      name: 'authorizeOnCreate',
      javaCode: `
        AuthService auth = (AuthService) x.get("auth");

        if (
          ! checkOwnership(x) &&
          ! auth.check(x, createPermission("create"))
        ) {
          throw new AuthorizationException("You don't have permission to create sessions other than your own.");
        }
      `
    },
    {
      name: 'authorizeOnUpdate',
      javaCode: `
        AuthService auth = (AuthService) x.get("auth");

        if (
          ! checkOwnership(x) &&
          ! auth.check(x, createPermission("update"))
        ) {
          throw new AuthorizationException("You don't have permission to update sessions other than your own.");
        }
      `
    },
    {
      name: 'authorizeOnDelete',
      javaCode: `
        AuthService auth = (AuthService) x.get("auth");

        if (
          ! checkOwnership(x) &&
          ! auth.check(x, "*")
        ) {
          throw new AuthorizationException("You don't have permission to delete sessions other than your own.");
        }
      `
    },
    {
      name: 'authorizeOnRead',
      javaCode: `
        AuthService auth = (AuthService) x.get("auth");

        if (
          ! checkOwnership(x) &&
          ! auth.check(x, createPermission("read"))
        ) {
          throw new AuthorizationException("You don't have permission to view sessions other than your own.");
        }
      `
    },
    {
      name: 'createPermission',
      args: [
        { name: 'operation', type: 'String' }
      ],
      type: 'String',
      javaCode: `
        return "session." + operation + "." + getId();
      `
    }
  ]
});
<|endoftext|>


<commit_before>import {Pos} from "../model"
import {defineOption} from "../edit"
import {elt, insertCSS} from "../dom"
import {Tooltip} from "../ui/tooltip"
import {UpdateScheduler} from "../ui/update"

import {renderGrouped, inlineGroup, insertMenu, textblockMenu, blockGroup} from "./menu"

const classPrefix = "ProseMirror-tooltipmenu"

// :: union<bool, Object> #path=tooltipMenu #kind=option
//
// When given a truthy value, enables the tooltip menu module for this
// editor. This menu shows up when there is a selection, and
// optionally in certain other circumstances, providing
// context-relevant commands.
//
// By default, the tooltip will show inline menu commands (registered
// with the [`menuGroup`](#CommandSpec.menuGroup) command property)
// when there is an inline selection, and block related commands when
// there is a node selection on a block.
//
// The module can be configured by passing an object. These properties
// are recognized:
//
// **`showLinks`**`: bool = true`
//   : Causes a tooltip with the link target to show up when the
//     cursor is inside of a link (without a selection).
//
// **`selectedBlockMenu`**`: bool = false`
//   : When enabled, and a whole block is selected or the cursor is
//     inside an empty block, the block menu gets shown.
//
// **`inlineContent`**`: [`[`MenuGroup`](#MenuGroup)`]`
//   : The menu elements to show when displaying the menu for inline
//     content.
//
// **`blockContent`**`: [`[`MenuGroup`](#MenuGroup)`]`
//   : The menu elements to show when displaying the menu for block
//     content.
//
// **`selectedBlockContent`**`: [MenuGroup]`
//   : The elements to show when a full block has been selected and
//     `selectedBlockMenu` is enabled. Defaults to concatenating
//     `inlineContent` and `blockContent`.

defineOption("tooltipMenu", false, function(pm, value) {
  if (pm.mod.tooltipMenu) pm.mod.tooltipMenu.detach()
  pm.mod.tooltipMenu = value ? new TooltipMenu(pm, value) : null
})

const defaultInline = [inlineGroup, insertMenu]
const defaultBlock = [[textblockMenu, blockGroup]]

class TooltipMenu {
  constructor(pm, config) {
    this.pm = pm
    this.config = config || {}

    this.showLinks = this.config.showLinks !== false
    this.selectedBlockMenu = this.config.selectedBlockMenu
    this.updater = new UpdateScheduler(pm, "change selectionChange blur commandsChanged", () => this.update())
    this.onContextMenu = this.onContextMenu.bind(this)
    pm.content.addEventListener("contextmenu", this.onContextMenu)

    this.tooltip = new Tooltip(pm.wrapper, "above")
    this.inlineContent = this.config.inlineContent || defaultInline
    this.blockContent = this.config.blockContent || defaultBlock
    this.selectedBlockContent = this.config.selectedBlockContent || this.inlineContent.concat(this.blockContent)
  }

  detach() {
    this.updater.detach()
    this.tooltip.detach()
    this.pm.content.removeEventListener("contextmenu", this.onContextMenu)
  }

  show(content, coords) {
    this.tooltip.open(elt("div", null, renderGrouped(this.pm, content)), coords)
  }

  update() {
    let {empty, node, from, to} = this.pm.selection, link
    if (!this.pm.hasFocus()) {
      this.tooltip.close()
    } else if (node && node.isBlock) {
      return () => {
        let coords = topOfNodeSelection(this.pm)
        return () => this.show(this.blockContent, coords)
      }
    } else if (!empty) {
      return () => {
        let coords = node ? topOfNodeSelection(this.pm) : topCenterOfSelection()
        let showBlock = this.selectedBlockMenu && Pos.samePath(from.path, to.path) &&
            from.offset == 0 && to.offset == this.pm.doc.path(from.path).size
        return () => this.show(showBlock ? this.selectedBlockContent : this.inlineContent, coords)
      }
    } else if (this.selectedBlockMenu && this.pm.doc.path(from.path).size == 0) {
      return () => {
        let coords = this.pm.coordsAtPos(from)
        return () => this.show(this.blockContent, coords)
      }
    } else if (this.showLinks && (link = this.linkUnderCursor())) {
      return () => {
        let coords = this.pm.coordsAtPos(from)
        return () => this.showLink(link, coords)
      }
    } else {
      this.tooltip.close()
    }
  }

  linkUnderCursor() {
    let head = this.pm.selection.head
    if (!head) return null
    let marks = this.pm.doc.marksAt(head)
    return marks.reduce((found, m) => found || (m.type.name == "link" && m), null)
  }

  showLink(link, pos) {
    let node = elt("div", {class: classPrefix + "-linktext"},
                   elt("a", {href: link.attrs.href, title: link.attrs.title}, link.attrs.href))
    this.tooltip.open(node, pos)
  }

  onContextMenu(e) {
    if (!this.pm.selection.empty) return
    let pos = this.pm.posAtCoords({left: e.clientX, top: e.clientY})
    if (!pos || !pos.isValid(this.pm.doc, true)) return

    this.pm.setTextSelection(pos, pos)
    this.pm.flush()
    this.show(this.inlineContent, topCenterOfSelection())
  }
}

// Get the x and y coordinates at the top center of the current DOM selection.
function topCenterOfSelection() {
  let range = window.getSelection().getRangeAt(0), rects = range.getClientRects()
  if (!rects.length) return range.getBoundingClientRect()
  let {left, right, top} = rects[0], i = 1
  while (left == right && rects.length > i) {
    ;({left, right, top} = rects[i++])
  }
  for (; i < rects.length; i++) {
    if (rects[i].top < rects[0].bottom - 1 &&
        // Chrome bug where bogus rectangles are inserted at span boundaries
        (i == rects.length - 1 || Math.abs(rects[i + 1].left - rects[i].left) > 1)) {
      left = Math.min(left, rects[i].left)
      right = Math.max(right, rects[i].right)
      top = Math.min(top, rects[i].top)
    }
  }
  return {top, left: (left + right) / 2}
}

function topOfNodeSelection(pm) {
  let selected = pm.content.querySelector(".ProseMirror-selectednode")
  if (!selected) return {left: 0, top: 0}
  let box = selected.getBoundingClientRect()
  return {left: Math.min((box.left + box.right) / 2, box.left + 20), top: box.top}
}

insertCSS(`

.${classPrefix}-linktext a {
  color: #444;
  text-decoration: none;
  padding: 0 5px;
}

.${classPrefix}-linktext a:hover {
  text-decoration: underline;
}

`)
<commit_msg>Update tooltip menu on focus<commit_after>import {Pos} from "../model"
import {defineOption} from "../edit"
import {elt, insertCSS} from "../dom"
import {Tooltip} from "../ui/tooltip"
import {UpdateScheduler} from "../ui/update"

import {renderGrouped, inlineGroup, insertMenu, textblockMenu, blockGroup} from "./menu"

const classPrefix = "ProseMirror-tooltipmenu"

// :: union<bool, Object> #path=tooltipMenu #kind=option
//
// When given a truthy value, enables the tooltip menu module for this
// editor. This menu shows up when there is a selection, and
// optionally in certain other circumstances, providing
// context-relevant commands.
//
// By default, the tooltip will show inline menu commands (registered
// with the [`menuGroup`](#CommandSpec.menuGroup) command property)
// when there is an inline selection, and block related commands when
// there is a node selection on a block.
//
// The module can be configured by passing an object. These properties
// are recognized:
//
// **`showLinks`**`: bool = true`
//   : Causes a tooltip with the link target to show up when the
//     cursor is inside of a link (without a selection).
//
// **`selectedBlockMenu`**`: bool = false`
//   : When enabled, and a whole block is selected or the cursor is
//     inside an empty block, the block menu gets shown.
//
// **`inlineContent`**`: [`[`MenuGroup`](#MenuGroup)`]`
//   : The menu elements to show when displaying the menu for inline
//     content.
//
// **`blockContent`**`: [`[`MenuGroup`](#MenuGroup)`]`
//   : The menu elements to show when displaying the menu for block
//     content.
//
// **`selectedBlockContent`**`: [MenuGroup]`
//   : The elements to show when a full block has been selected and
//     `selectedBlockMenu` is enabled. Defaults to concatenating
//     `inlineContent` and `blockContent`.

defineOption("tooltipMenu", false, function(pm, value) {
  if (pm.mod.tooltipMenu) pm.mod.tooltipMenu.detach()
  pm.mod.tooltipMenu = value ? new TooltipMenu(pm, value) : null
})

const defaultInline = [inlineGroup, insertMenu]
const defaultBlock = [[textblockMenu, blockGroup]]

class TooltipMenu {
  constructor(pm, config) {
    this.pm = pm
    this.config = config || {}

    this.showLinks = this.config.showLinks !== false
    this.selectedBlockMenu = this.config.selectedBlockMenu
    this.updater = new UpdateScheduler(pm, "change selectionChange blur focus commandsChanged", () => this.update())
    this.onContextMenu = this.onContextMenu.bind(this)
    pm.content.addEventListener("contextmenu", this.onContextMenu)

    this.tooltip = new Tooltip(pm.wrapper, "above")
    this.inlineContent = this.config.inlineContent || defaultInline
    this.blockContent = this.config.blockContent || defaultBlock
    this.selectedBlockContent = this.config.selectedBlockContent || this.inlineContent.concat(this.blockContent)
  }

  detach() {
    this.updater.detach()
    this.tooltip.detach()
    this.pm.content.removeEventListener("contextmenu", this.onContextMenu)
  }

  show(content, coords) {
    this.tooltip.open(elt("div", null, renderGrouped(this.pm, content)), coords)
  }

  update() {
    let {empty, node, from, to} = this.pm.selection, link
    if (!this.pm.hasFocus()) {
      this.tooltip.close()
    } else if (node && node.isBlock) {
      return () => {
        let coords = topOfNodeSelection(this.pm)
        return () => this.show(this.blockContent, coords)
      }
    } else if (!empty) {
      return () => {
        let coords = node ? topOfNodeSelection(this.pm) : topCenterOfSelection()
        let showBlock = this.selectedBlockMenu && Pos.samePath(from.path, to.path) &&
            from.offset == 0 && to.offset == this.pm.doc.path(from.path).size
        return () => this.show(showBlock ? this.selectedBlockContent : this.inlineContent, coords)
      }
    } else if (this.selectedBlockMenu && this.pm.doc.path(from.path).size == 0) {
      return () => {
        let coords = this.pm.coordsAtPos(from)
        return () => this.show(this.blockContent, coords)
      }
    } else if (this.showLinks && (link = this.linkUnderCursor())) {
      return () => {
        let coords = this.pm.coordsAtPos(from)
        return () => this.showLink(link, coords)
      }
    } else {
      this.tooltip.close()
    }
  }

  linkUnderCursor() {
    let head = this.pm.selection.head
    if (!head) return null
    let marks = this.pm.doc.marksAt(head)
    return marks.reduce((found, m) => found || (m.type.name == "link" && m), null)
  }

  showLink(link, pos) {
    let node = elt("div", {class: classPrefix + "-linktext"},
                   elt("a", {href: link.attrs.href, title: link.attrs.title}, link.attrs.href))
    this.tooltip.open(node, pos)
  }

  onContextMenu(e) {
    if (!this.pm.selection.empty) return
    let pos = this.pm.posAtCoords({left: e.clientX, top: e.clientY})
    if (!pos || !pos.isValid(this.pm.doc, true)) return

    this.pm.setTextSelection(pos, pos)
    this.pm.flush()
    this.show(this.inlineContent, topCenterOfSelection())
  }
}

// Get the x and y coordinates at the top center of the current DOM selection.
function topCenterOfSelection() {
  let range = window.getSelection().getRangeAt(0), rects = range.getClientRects()
  if (!rects.length) return range.getBoundingClientRect()
  let {left, right, top} = rects[0], i = 1
  while (left == right && rects.length > i) {
    ;({left, right, top} = rects[i++])
  }
  for (; i < rects.length; i++) {
    if (rects[i].top < rects[0].bottom - 1 &&
        // Chrome bug where bogus rectangles are inserted at span boundaries
        (i == rects.length - 1 || Math.abs(rects[i + 1].left - rects[i].left) > 1)) {
      left = Math.min(left, rects[i].left)
      right = Math.max(right, rects[i].right)
      top = Math.min(top, rects[i].top)
    }
  }
  return {top, left: (left + right) / 2}
}

function topOfNodeSelection(pm) {
  let selected = pm.content.querySelector(".ProseMirror-selectednode")
  if (!selected) return {left: 0, top: 0}
  let box = selected.getBoundingClientRect()
  return {left: Math.min((box.left + box.right) / 2, box.left + 20), top: box.top}
}

insertCSS(`

.${classPrefix}-linktext a {
  color: #444;
  text-decoration: none;
  padding: 0 5px;
}

.${classPrefix}-linktext a:hover {
  text-decoration: underline;
}

`)
<|endoftext|>


<commit_before>const {SculptureStore, DisksActionCreator, Disk} = require('@anyware/game-logic');

const SerialManager = require('../serial/serial-manager');
const serialProtocol = require('../serial/serial-protocol');
const {SerialProtocolCommandBuilder} = serialProtocol;

const MAX_RESET_ATTEMPTS = 5;
const DISK_ID_TO_HARDWARE_MAP = {
  disk0: "0",
  disk1: "1",
  disk2: "2"
};
const DISK_DIRECTION_TO_HARDWARE_MAP = {
  [Disk.CLOCKWISE]: -1,
  [Disk.COUNTERCLOCKWISE]: 1,
  [Disk.STOPPED]: 0,
  [Disk.CONFLICT]: 0
};

export default class DiskView {
  constructor(store, dispatcher, serialManager) {
    this.store = store;
    this.serialManager = serialManager;
    this.serialManager.on(SerialManager.EVENT_COMMAND, this._handleCommand.bind(this));

    this.disksActionCreator = new DisksActionCreator(dispatcher);

    this._animating = false;
    this._previousHardwarePositions = {};

    this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this));
  }

  /**
   * Start homing the disks back to their original positions
   * Makes multiple attempts if it doesn't work the first time
   * @param {Number} maxAttempts - The maximum number of attempts that can be made
   */
  resetDisks(maxAttempts=MAX_RESET_ATTEMPTS) {
    let attempt = 0;

    const reset = () => {
      attempt += 1;

      const commandString = SerialProtocolCommandBuilder.buildDiskReset();
      const sent = this.serialManager.dispatchCommand(commandString);

      if (!sent && attempt < maxAttempts) {
        setTimeout(reset, 300);
      }
    };

    reset();
  }

  _handleChanges(changes) {
    if (changes.hasOwnProperty('disk') && changes.disk.hasOwnProperty('level')) {
      this.resetDisks();
    }

    const diskChanges = changes.disks;
    if (!diskChanges) {
      return;
    }

    for (let diskId of Object.keys(diskChanges)) {
      const hardwareDiskId = DISK_ID_TO_HARDWARE_MAP[diskId];

      const newDiskValues = diskChanges[diskId];

      let position;
      if (newDiskValues.position !== this._previousHardwarePositions[diskId]) {
        position = newDiskValues.position;
      }

      const hardwareDirection = DISK_DIRECTION_TO_HARDWARE_MAP[newDiskValues.direction];

      const commandString = SerialProtocolCommandBuilder.buildDisk({
        diskId: hardwareDiskId,
        position: position,
        direction: hardwareDirection,
        user: newDiskValues.user
      });
      this.serialManager.dispatchCommand(commandString);
    }
  }

  _handleCommand(commandName, commandArgs) {
    if (commandName === serialProtocol.DISK_COMMAND) {
      let {diskId, position, direction} = commandArgs;
      diskId = this._lookupDiskIdFromHardware(diskId);
      position = parseInt(position);
      direction = this._lookupDirectionFromHardware(direction);

      if (!isNaN(position)) {
        this._previousHardwarePositions[diskId] = position;
      }

      this.disksActionCreator.sendDiskUpdate(diskId, {
        position: position,
        direction: direction
      });
    }
  }

  _lookupDiskIdFromHardware(hardwareDiskId) {
    for (let diskId of Object.keys(DISK_ID_TO_HARDWARE_MAP)) {
      if (DISK_ID_TO_HARDWARE_MAP[diskId] === hardwareDiskId) {
        return diskId;
      }
    }
  }

  _lookupDirectionFromHardware(hardwareDirection) {
    for (let direction of Object.keys(DISK_DIRECTION_TO_HARDWARE_MAP)) {
      if (DISK_DIRECTION_TO_HARDWARE_MAP[direction] === hardwareDirection) {
        return direction;
      }
    }
  }
}
<commit_msg>Made it so that disks reset at the beginning of the game<commit_after>const {SculptureStore, DisksActionCreator, Disk} = require('@anyware/game-logic');

const SerialManager = require('../serial/serial-manager');
const serialProtocol = require('../serial/serial-protocol');
const {SerialProtocolCommandBuilder} = serialProtocol;

const MAX_RESET_ATTEMPTS = 5;
const DISK_ID_TO_HARDWARE_MAP = {
  disk0: "0",
  disk1: "1",
  disk2: "2"
};
const DISK_DIRECTION_TO_HARDWARE_MAP = {
  [Disk.CLOCKWISE]: -1,
  [Disk.COUNTERCLOCKWISE]: 1,
  [Disk.STOPPED]: 0,
  [Disk.CONFLICT]: 0
};

export default class DiskView {
  constructor(store, dispatcher, serialManager) {
    this.store = store;
    this.serialManager = serialManager;
    this.serialManager.on(SerialManager.EVENT_COMMAND, this._handleCommand.bind(this));

    this.disksActionCreator = new DisksActionCreator(dispatcher);

    this._animating = false;
    this._previousHardwarePositions = {};

    this.store.on(SculptureStore.EVENT_CHANGE, this._handleChanges.bind(this));
  }

  /**
   * Start homing the disks back to their original positions
   * Makes multiple attempts if it doesn't work the first time
   * @param {Number} maxAttempts - The maximum number of attempts that can be made
   */
  resetDisks(maxAttempts=MAX_RESET_ATTEMPTS) {
    let attempt = 0;

    const reset = () => {
      attempt += 1;

      const commandString = SerialProtocolCommandBuilder.buildDiskReset();
      const sent = this.serialManager.dispatchCommand(commandString);

      if (!sent && attempt < maxAttempts) {
        setTimeout(reset, 300);
      }
    };

    reset();
  }

  _handleChanges(changes) {
    if (changes.hasOwnProperty('disk') && changes.disk.hasOwnProperty('level') || changes.currentGame === "disk") {
      this.resetDisks();
    }

    const diskChanges = changes.disks;
    if (!diskChanges) {
      return;
    }

    for (let diskId of Object.keys(diskChanges)) {
      const hardwareDiskId = DISK_ID_TO_HARDWARE_MAP[diskId];

      const newDiskValues = diskChanges[diskId];

      let position;
      if (newDiskValues.position !== this._previousHardwarePositions[diskId]) {
        position = newDiskValues.position;
      }

      const hardwareDirection = DISK_DIRECTION_TO_HARDWARE_MAP[newDiskValues.direction];

      const commandString = SerialProtocolCommandBuilder.buildDisk({
        diskId: hardwareDiskId,
        position: position,
        direction: hardwareDirection,
        user: newDiskValues.user
      });
      this.serialManager.dispatchCommand(commandString);
    }
  }

  _handleCommand(commandName, commandArgs) {
    if (commandName === serialProtocol.DISK_COMMAND) {
      let {diskId, position, direction} = commandArgs;
      diskId = this._lookupDiskIdFromHardware(diskId);
      position = parseInt(position);
      direction = this._lookupDirectionFromHardware(direction);

      if (!isNaN(position)) {
        this._previousHardwarePositions[diskId] = position;
      }

      this.disksActionCreator.sendDiskUpdate(diskId, {
        position: position,
        direction: direction
      });
    }
  }

  _lookupDiskIdFromHardware(hardwareDiskId) {
    for (let diskId of Object.keys(DISK_ID_TO_HARDWARE_MAP)) {
      if (DISK_ID_TO_HARDWARE_MAP[diskId] === hardwareDiskId) {
        return diskId;
      }
    }
  }

  _lookupDirectionFromHardware(hardwareDirection) {
    for (let direction of Object.keys(DISK_DIRECTION_TO_HARDWARE_MAP)) {
      if (DISK_DIRECTION_TO_HARDWARE_MAP[direction] === hardwareDirection) {
        return direction;
      }
    }
  }
}
<|endoftext|>


<commit_before>'use strict';
var captureStackTrace = require('capture-stack-trace');

function inherits(ctor, superCtor) {
	ctor.super_ = superCtor;
	ctor.prototype = Object.create(superCtor.prototype, {
		constructor: {
			value: ctor,
			enumerable: false,
			writable: true,
			configurable: true
		}
	});
}

module.exports = function createErrorClass(className, setup) {
	if (typeof className !== 'string') {
		throw new TypeError('Expected className to be a string');
	}

	if (/[^0-9a-zA-Z_$]/.test(className)) {
		throw new Error('className contains invalid characters');
	}

	setup = setup || function (message) {
		this.message = message;
	};

	var ErrorClass = function () {
		Object.defineProperty(this, 'name', {
			configurable: true,
			value: className,
			writable: true
		});

		captureStackTrace(this, this.constructor);

		setup.apply(this, arguments);
	};

	inherits(ErrorClass, Error);
	ErrorClass.prototype.name = className;

	return ErrorClass;
};
<commit_msg>remove duplicate name property assignment<commit_after>'use strict';
var captureStackTrace = require('capture-stack-trace');

function inherits(ctor, superCtor) {
	ctor.super_ = superCtor;
	ctor.prototype = Object.create(superCtor.prototype, {
		constructor: {
			value: ctor,
			enumerable: false,
			writable: true,
			configurable: true
		}
	});
}

module.exports = function createErrorClass(className, setup) {
	if (typeof className !== 'string') {
		throw new TypeError('Expected className to be a string');
	}

	if (/[^0-9a-zA-Z_$]/.test(className)) {
		throw new Error('className contains invalid characters');
	}

	setup = setup || function (message) {
		this.message = message;
	};

	var ErrorClass = function () {
		Object.defineProperty(this, 'name', {
			configurable: true,
			value: className,
			writable: true
		});

		captureStackTrace(this, this.constructor);

		setup.apply(this, arguments);
	};

	inherits(ErrorClass, Error);

	return ErrorClass;
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailPropertyView',
  extends: 'foam.u2.Element',

  properties: [
    'prop',
    [ 'nodeName', 'tr' ]
  ],

  templates: [
    function CSS() {/*
      .foam-u2-PropertyView-label {
        color: #444;
        display: block;
        float: left;
        font-size: 13px;
        padding-left: 6px;
        padding-top: 6px;
        text-align: right;
        vertical-align: top;
      }
      .foam-u2-PropertyView-units  {
        color: #444;
        font-size: 12px;
        padding: 4px;
        text-align: right;
      }
    */}
  ],

  methods: [
    function initE() {
      var prop = this.prop;

      this.cssClass('foam-u2-PropertyView-')
          .start('td').cssClass('foam-u2-PropertyView-label').add(prop.label).end()
          .start('td').cssClass('foam-u2-PropertyView-view').add(
              prop,
              prop.units && this.E('span').cssClass('foam-u2-PropertyView-units').add(' ', prop.units))
          .end();
    }
  ]
});
<commit_msg>Formatting.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'DetailPropertyView',
  extends: 'foam.u2.Element',

  properties: [
    'prop',
    [ 'nodeName', 'tr' ]
  ],

  templates: [
    function CSS() {/*
      .foam-u2-PropertyView-label {
        color: #444;
        display: block;
        float: left;
        font-size: 13px;
        padding-left: 6px;
        padding-top: 6px;
        text-align: right;
        vertical-align: top;
      }
      .foam-u2-PropertyView-units  {
        color: #444;
        font-size: 12px;
        padding: 4px;
        text-align: right;
      }
    */}
  ],

  methods: [
    function initE() {
      var prop = this.prop;

      this.cssClass('foam-u2-PropertyView-').
          start('td').cssClass('foam-u2-PropertyView-label').add(prop.label).end().
          start('td').cssClass('foam-u2-PropertyView-view').add(
              prop,
              prop.units && this.E('span').cssClass('foam-u2-PropertyView-units').add(' ', prop.units)).
          end();
    }
  ]
});
<|endoftext|>


<commit_before>(function () {
  function promise() {
    var resolved = false,
        fulfilled = false,
        val,
        waiting = [], running = false;

    function next() {
      if (waiting.length) {
        running = true;
        waiting.shift()();
      } else {
        running = false;
      }
    }
    function then(cb, eb, pb) {
      var def = promise();
      function done() {
        var callback = fulfilled ? cb : eb;
        if (callback) {
          setTimeout(function () {
            var value;
            try {
              value = callback(val);
            } catch (ex) {
              def.reject(ex);
              return next();
            }
            def.fulfill(value);
            next();
          }, 0);
        } else if (fulfilled) {
          def.fulfill(val);
          next();
        } else {
          def.reject(val);
          next();
        }
      }
      waiting.push(done);
      if (resolved && !running) {
        next();
      }
      return def.promise;
    }
    function resolve(success, value) {
      if (resolved) return;
      if (success && typeof value === 'object' && typeof value.then === 'function') {
        value.then(fulfill, reject);
        return;
      }
      resolved = true;
      fulfilled = success;
      val = value;
      next();
    }
    function fulfill(val) {
      resolve(true, val);
    }
    function reject(err) {
      resolve(false, err);
    }

    return {promise: {then: then}, fulfill: fulfill, reject: reject};
  };

  if (typeof module != 'undefined' && typeof module.exports != 'undefined') {
    module.exports = promise;
  } else {
    window.promise = promise;
  }
}());<commit_msg>Rewmove un-necessary semi-colons<commit_after>;(function () {
  function promise() {
    var resolved = false,
        fulfilled = false,
        val,
        waiting = [],
        running = false

    function next() {
      if (waiting.length) {
        running = true
        waiting.shift()()
      } else {
        running = false
      }
    }
    function then(cb, eb, pb) {
      var def = promise()
      function done() {
        var callback = fulfilled ? cb : eb
        if (callback) {
          setTimeout(function () {
            var value;
            try {
              value = callback(val)
            } catch (ex) {
              def.reject(ex)
              return next()
            }
            def.fulfill(value);
            next();
          }, 0)
        } else if (fulfilled) {
          def.fulfill(val)
          next()
        } else {
          def.reject(val)
          next()
        }
      }
      waiting.push(done);
      if (resolved && !running) {
        next()
      }
      return def.promise
    }
    function resolve(success, value) {
      if (resolved) return;
      if (success && typeof value === 'object' && typeof value.then === 'function') {
        value.then(fulfill, reject)
        return
      }
      resolved = true
      fulfilled = success
      val = value
      next()
    }
    function fulfill(val) {
      resolve(true, val)
    }
    function reject(err) {
      resolve(false, err)
    }

    return {
      promise: {then: then},
      fulfill: fulfill,
      reject: reject
    }
  }

  if (typeof module != 'undefined' && typeof module.exports != 'undefined')
    module.exports = promise
  else
    window.promise = promise
}())<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.md',
  name: 'OverlayDropdown',
  extends: 'foam.u2.Element',

  imports: [
    'window'
  ],

  exports: [
    'as dropdown'
  ],

  documentation: 'A popup overlay that grows from the top-right corner of ' +
      'its container. Useful for e.g. "..." overflow menus in action bars. ' +
      'Just $$DOC{ref:".add"} things to this container.',

  properties: [
    {
      class: 'Float',
      name: 'height',
      value: 0
    },
    {
      class: 'Boolean',
      name: 'opened',
      documentation: 'True when the overlay has been commanded to be open. ' +
          'It might still be animating; see $$DOC{ref:".animationComplete"}.',
      value: false
    },
    {
      class: 'Boolean',
      name: 'animationComplete',
      documentation: 'True when an animation is running. The overlay hasn\'t ' +
          'really reached the state commanded by $$DOC{ref:".opened"} until ' +
          'this is true.',
      value: true
    },
    {
      name: 'dropdownE_',
      factory: function() {
        // This needs to be created early so it's safe to add() things to it.
        return this.E('dropdown');
      }
    },
    {
      name: 'addToSelf_',
      value: false
    }
  ],

  methods: [
    function add() {
      if (this.addToSelf_) this.SUPER.apply(this, arguments);
      else this.dropdownE_.add.apply(this.dropdownE_, arguments);
      return this;
    },

    function open() {
      if (this.opened) return;
      this.opened = true;
      this.dropdownE_.style({ height: this.getFullHeight() + 'px' });
      this.animationComplete = false;
    },

    function close() {
      if (!this.opened) return;
      this.height = 0;
      this.dropdownE_.style({ height: 0 + 'px' });
      this.opened = false;
    },

    function getFullHeight() {
      if (this.state !== this.LOADED) return;

      var myStyle = this.window.getComputedStyle(this.dropdownE_.el());

      var border = 0;
      ['border-top', 'border-bottom'].forEach(function(name) {
        var match = myStyle[name].match(/^([0-9]+)px/);
        if (match) border += parseInt(match[1]);
      });

      var last = this.dropdownE_.children[this.dropdownE_.children.length - 1].el();
      var margin = parseInt(this.window.getComputedStyle(last)['margin-bottom']);
      if ( Number.isNaN(margin) ) margin = 0;

      return Math.min(border + last.offsetTop + last.offsetHeight + margin,
          this.window.innerHeight - this.dropdownE_.el().getBoundingClientRect().top);
    },

    function initE() {
      this.addToSelf_ = true;
      this.addClass(this.myClass('container'));
      var view = this;

      this.addClass(this.slot(function(open) {
        this.shown = open;
      }, this.opened$));

      this.start('dropdown-overlay')
          .addClass(this.myClass('overlay'))
          .addClass(this.slot(function(open) {
            return ( open ) ? view.myClass('zeroOverlay') : view.myClass('initialOverlay')
          }, this.opened$))
          .on('click', this.onCancel)
          .end();

      this.dropdownE_.addClass(this.myClass())
          .addClass(this.slot(function(openComplete) {
            return openComplete ? this.myClass('open') : '';
          }, this.opened$ && this.animationComplete$))
          .on('transitionend', this.onTransitionEnd)
          .on('mouseleave', this.onMouseLeave)
          .on('click', this.onClick);

      this.add(this.dropdownE_);

      this.addToSelf_ = false;
    }
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function CSS() {/*
      ^overlay {
        position: fixed;
        z-index: 1009;
      }

      ^container {
        position: relative;
        right: 0;
        top: 0;
        z-index: 100;
      }

      ^ {
        background: white;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.38);
        display: block;
        font-size: 13px;
        font-weight: 400;
        overflow-x: hidden;
        overflow-y: hidden;
        position: absolute;
        width: 125px;
        padding: 10px;
        padding-bottom: -20px;
        margin-bottom: -20px;
        right: 3px;
        top: 4px;
        transition: height 0.25s cubic-bezier(0, .3, .8, 1);
        z-index: 1010;
      }

      ^open {
        overflow-y: auto;
      }

      ^zeroOverlay {
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
      }

      ^initialOverlay {
        top: initial;
        bottom: initial;
        left: initial;
        right: initial;
      }
    */}
    })
  ],

  listeners: [
    function onCancel() {
      this.close();
    },

    function onTransitionEnd() {
      this.animationComplete = true;
    },

    function onMouseLeave(e) {
      console.assert(e.target === this.dropdownE_.el(),
          'mouseleave should only fire on this, not on children');
      this.close();
    },

    /**
     * Prevent clicks inside the dropdown from closing it.
     * Block them before they reach the overlay.
     */
    function onClick(e) {
      e.stopPropagation();
    }
  ]
});
<commit_msg>Removed redundant `value: false` for overlay dropdown 'open' state<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.md',
  name: 'OverlayDropdown',
  extends: 'foam.u2.Element',

  imports: [
    'window'
  ],

  exports: [
    'as dropdown'
  ],

  documentation: 'A popup overlay that grows from the top-right corner of ' +
      'its container. Useful for e.g. "..." overflow menus in action bars. ' +
      'Just $$DOC{ref:".add"} things to this container.',

  properties: [
    {
      class: 'Float',
      name: 'height',
      value: 0
    },
    {
      class: 'Boolean',
      name: 'opened',
      documentation: 'True when the overlay has been commanded to be open. ' +
          'It might still be animating; see $$DOC{ref:".animationComplete"}.',
    },
    {
      class: 'Boolean',
      name: 'animationComplete',
      documentation: 'True when an animation is running. The overlay hasn\'t ' +
          'really reached the state commanded by $$DOC{ref:".opened"} until ' +
          'this is true.',
      value: true
    },
    {
      name: 'dropdownE_',
      factory: function() {
        // This needs to be created early so it's safe to add() things to it.
        return this.E('dropdown');
      }
    },
    {
      name: 'addToSelf_',
      value: false
    }
  ],

  methods: [
    function add() {
      if (this.addToSelf_) this.SUPER.apply(this, arguments);
      else this.dropdownE_.add.apply(this.dropdownE_, arguments);
      return this;
    },

    function open() {
      if (this.opened) return;
      this.opened = true;
      this.dropdownE_.style({ height: this.getFullHeight() + 'px' });
      this.animationComplete = false;
    },

    function close() {
      if (!this.opened) return;
      this.height = 0;
      this.dropdownE_.style({ height: 0 + 'px' });
      this.opened = false;
    },

    function getFullHeight() {
      if (this.state !== this.LOADED) return;

      var myStyle = this.window.getComputedStyle(this.dropdownE_.el());

      var border = 0;
      ['border-top', 'border-bottom'].forEach(function(name) {
        var match = myStyle[name].match(/^([0-9]+)px/);
        if (match) border += parseInt(match[1]);
      });

      var last = this.dropdownE_.children[this.dropdownE_.children.length - 1].el();
      var margin = parseInt(this.window.getComputedStyle(last)['margin-bottom']);
      if ( Number.isNaN(margin) ) margin = 0;

      return Math.min(border + last.offsetTop + last.offsetHeight + margin,
          this.window.innerHeight - this.dropdownE_.el().getBoundingClientRect().top);
    },

    function initE() {
      this.addToSelf_ = true;
      this.addClass(this.myClass('container'));
      var view = this;

      this.addClass(this.slot(function(open) {
        this.shown = open;
      }, this.opened$));

      this.start('dropdown-overlay')
          .addClass(this.myClass('overlay'))
          .addClass(this.slot(function(open) {
            return ( open ) ? view.myClass('zeroOverlay') : view.myClass('initialOverlay')
          }, this.opened$))
          .on('click', this.onCancel)
          .end();

      this.dropdownE_.addClass(this.myClass())
          .addClass(this.slot(function(openComplete) {
            return openComplete ? this.myClass('open') : '';
          }, this.opened$ && this.animationComplete$))
          .on('transitionend', this.onTransitionEnd)
          .on('mouseleave', this.onMouseLeave)
          .on('click', this.onClick);

      this.add(this.dropdownE_);

      this.addToSelf_ = false;
    }
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function CSS() {/*
      ^overlay {
        position: fixed;
        z-index: 1009;
      }

      ^container {
        position: relative;
        right: 0;
        top: 0;
        z-index: 100;
      }

      ^ {
        background: white;
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.38);
        display: block;
        font-size: 13px;
        font-weight: 400;
        overflow-x: hidden;
        overflow-y: hidden;
        position: absolute;
        width: 125px;
        padding: 10px;
        padding-bottom: -20px;
        margin-bottom: -20px;
        right: 3px;
        top: 4px;
        transition: height 0.25s cubic-bezier(0, .3, .8, 1);
        z-index: 1010;
      }

      ^open {
        overflow-y: auto;
      }

      ^zeroOverlay {
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
      }

      ^initialOverlay {
        top: initial;
        bottom: initial;
        left: initial;
        right: initial;
      }
    */}
    })
  ],

  listeners: [
    function onCancel() {
      this.close();
    },

    function onTransitionEnd() {
      this.animationComplete = true;
    },

    function onMouseLeave(e) {
      console.assert(e.target === this.dropdownE_.el(),
          'mouseleave should only fire on this, not on children');
      this.close();
    },

    /**
     * Prevent clicks inside the dropdown from closing it.
     * Block them before they reach the overlay.
     */
    function onClick(e) {
      e.stopPropagation();
    }
  ]
});
<|endoftext|>


<commit_before>var utils = require('../utils');
var HtmlParserProxy = require('./htmlparser');
var HttpStatus = require('http-status-codes');
var ReliableGet = require('reliable-get');
var url = require('url');

module.exports = function backendProxyMiddleware(config, eventHandler) {

    var reliableGet = new ReliableGet(config),
        htmlParserMiddleware = HtmlParserProxy.getMiddleware(config, reliableGet, eventHandler);

    reliableGet.on('log', eventHandler.logger);
    reliableGet.on('stat', eventHandler.stats);

    return function(req, res) {

      htmlParserMiddleware(req, res, function() {

        req.tracer = req.headers['x-tracer'] || 'no-tracer';

        var DEFAULT_LOW_TIMEOUT = 5000,
            referer = req.headers.referer || 'direct',
            userAgent = req.headers['user-agent'] || 'unknown',
            remoteAddress = req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress,
            remoteIp = req.headers['x-forwarded-for'] || remoteAddress,
            backend = req.backend,
            targetUrl = backend.target + (backend.dontPassUrl ? '' : req.url),
            targetHost = url.parse(backend.target).hostname,
            host = backend.host || targetHost,
            backendHeaders = {
              'x-forwarded-host': req.headers.host || 'no-forwarded-host',
              'x-forwarded-for': req.headers['x-forwarded-for'] || remoteAddress,
              host: host,
              'x-tracer': req.tracer
            },
            targetCacheKey = backend.cacheKey || utils.urlToCacheKey(targetUrl),
            targetCacheTTL = utils.timeToMillis(backend.ttl || '30s'),
            explicitNoCache = backend.noCache || req.explicitNoCache,
            options;

        if (config.cdn && config.cdn.url) { backendHeaders['x-cdn-url'] = config.cdn.url; }

        if (req.cookies && req.headers.cookie) {
            var whitelist = config.cookies && config.cookies.whitelist;
            backendHeaders.cookie = whitelist ? utils.filterCookies(whitelist, req.cookies) : req.headers.cookie;
        }

        if(backend.headers){
            backend.headers.forEach(function(header) {
                backendHeaders[header] = req.headers[header] || '';
            });
        }

        eventHandler.logger('info', 'GET ' + req.url, {tracer: req.tracer, referer: referer, remoteIp: remoteIp, userAgent: userAgent});

        options = {
          url: targetUrl,
          cacheKey: targetCacheKey,
          cacheTTL: targetCacheTTL,
          explicitNoCache: explicitNoCache,
          timeout: utils.timeToMillis(backend.timeout || DEFAULT_LOW_TIMEOUT),
          headers: backendHeaders,
          tracer: req.tracer,
          type: 'backend',
          statsdKey: 'backend_' + utils.urlToCacheKey(host),
          eventHandler: eventHandler
        };

        var handleError = function(err, oldCacheData) {

          // Check to see if we have any statusCode handlers defined
          if(err.statusCode && config.statusCodeHandlers && config.statusCodeHandlers[err.statusCode]) {
              var handlerDefn = config.statusCodeHandlers[err.statusCode];
              var handlerFn = config.functions && config.functions[handlerDefn.fn];
              if(handlerFn) {
                  return handlerFn(req, res, req.templateVars, handlerDefn.data, options, err);
              }
          }

          if (req.backend.quietFailure && oldCacheData) {
            req.templateVars = utils.updateTemplateVariables(req.templateVars, oldCacheData.headers);
            res.parse(oldCacheData.content);
            eventHandler.logger('error', 'Backend FAILED but serving STALE content: ' + err.message, {
              tracer: req.tracer
            });
          } else {
            if (!res.headersSent) {
              res.writeHead(err.statusCode || HttpStatus.INTERNAL_SERVER_ERROR);
              res.end(err.message);
            }
            eventHandler.logger('error', 'Backend FAILED to respond: ' + err.message, {
              tracer: req.tracer
            });
          }
        }

        reliableGet.get(options, function(err, response) {
          if(err) {
            handleError(err, response);
          } else {
            req.templateVars = utils.updateTemplateVariables(req.templateVars, response.headers);
            res.parse(response.content);
          }
        });

      });

    }
}
<commit_msg>Pass the user browser language header to other requests for intl<commit_after>var utils = require('../utils');
var HtmlParserProxy = require('./htmlparser');
var HttpStatus = require('http-status-codes');
var ReliableGet = require('reliable-get');
var url = require('url');

module.exports = function backendProxyMiddleware(config, eventHandler) {

    var reliableGet = new ReliableGet(config),
        htmlParserMiddleware = HtmlParserProxy.getMiddleware(config, reliableGet, eventHandler);

    reliableGet.on('log', eventHandler.logger);
    reliableGet.on('stat', eventHandler.stats);

    return function(req, res) {

      htmlParserMiddleware(req, res, function() {

        req.tracer = req.headers['x-tracer'] || 'no-tracer';

        var DEFAULT_LOW_TIMEOUT = 5000,
            referer = req.headers.referer || 'direct',
            userAgent = req.headers['user-agent'] || 'unknown',
            remoteAddress = req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress,
            remoteIp = req.headers['x-forwarded-for'] || remoteAddress,
            backend = req.backend,
            targetUrl = backend.target + (backend.dontPassUrl ? '' : req.url),
            targetHost = url.parse(backend.target).hostname,
            host = backend.host || targetHost,
            backendHeaders = {
              'x-forwarded-host': req.headers.host || 'no-forwarded-host',
              'x-forwarded-for': req.headers['x-forwarded-for'] || remoteAddress,
              'accept-language': req.headers['accept-language'],
              host: host,
              'x-tracer': req.tracer
            },
            targetCacheKey = backend.cacheKey || utils.urlToCacheKey(targetUrl),
            targetCacheTTL = utils.timeToMillis(backend.ttl || '30s'),
            explicitNoCache = backend.noCache || req.explicitNoCache,
            options;

        if (config.cdn && config.cdn.url) { backendHeaders['x-cdn-url'] = config.cdn.url; }

        if (req.cookies && req.headers.cookie) {
            var whitelist = config.cookies && config.cookies.whitelist;
            backendHeaders.cookie = whitelist ? utils.filterCookies(whitelist, req.cookies) : req.headers.cookie;
        }

        if(backend.headers){
            backend.headers.forEach(function(header) {
                backendHeaders[header] = req.headers[header] || '';
            });
        }

        eventHandler.logger('info', 'GET ' + req.url, {tracer: req.tracer, referer: referer, remoteIp: remoteIp, userAgent: userAgent});

        options = {
          url: targetUrl,
          cacheKey: targetCacheKey,
          cacheTTL: targetCacheTTL,
          explicitNoCache: explicitNoCache,
          timeout: utils.timeToMillis(backend.timeout || DEFAULT_LOW_TIMEOUT),
          headers: backendHeaders,
          tracer: req.tracer,
          type: 'backend',
          statsdKey: 'backend_' + utils.urlToCacheKey(host),
          eventHandler: eventHandler
        };

        var handleError = function(err, oldCacheData) {

          // Check to see if we have any statusCode handlers defined
          if(err.statusCode && config.statusCodeHandlers && config.statusCodeHandlers[err.statusCode]) {
              var handlerDefn = config.statusCodeHandlers[err.statusCode];
              var handlerFn = config.functions && config.functions[handlerDefn.fn];
              if(handlerFn) {
                  return handlerFn(req, res, req.templateVars, handlerDefn.data, options, err);
              }
          }

          if (req.backend.quietFailure && oldCacheData) {
            req.templateVars = utils.updateTemplateVariables(req.templateVars, oldCacheData.headers);
            res.parse(oldCacheData.content);
            eventHandler.logger('error', 'Backend FAILED but serving STALE content: ' + err.message, {
              tracer: req.tracer
            });
          } else {
            if (!res.headersSent) {
              res.writeHead(err.statusCode || HttpStatus.INTERNAL_SERVER_ERROR);
              res.end(err.message);
            }
            eventHandler.logger('error', 'Backend FAILED to respond: ' + err.message, {
              tracer: req.tracer
            });
          }
        }

        reliableGet.get(options, function(err, response) {
          if(err) {
            handleError(err, response);
          } else {
            req.templateVars = utils.updateTemplateVariables(req.templateVars, response.headers);
            res.parse(response.content);
          }
        });

      });

    }
}
<|endoftext|>