<commit_before>/*
Copyright 2017 Vector Creations Ltd

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.
*/

import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { ContentRepo } from 'matrix-js-sdk';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';

module.exports = React.createClass({
    displayName: 'RoomAvatarEvent',

    propTypes: {
        /* the MatrixEvent to show */
        mxEvent: React.PropTypes.object.isRequired,
    },

    onAvatarClick: function(name) {
        var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
        var ImageView = sdk.getComponent("elements.ImageView");
        var params = {
            src: httpUrl,
            name: name,
        };
        Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
    },

    render: function() {
        var ev = this.props.mxEvent;
        var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
        var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");

        var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
                senderDisplayName: senderDisplayName,
                roomName: room ? room.name : '',
        });

        if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
            return (

                    { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }

            );
        }

        var url = ContentRepo.getHttpUriForMxc(
                    MatrixClientPeg.get().getHomeserverUrl(),
                    ev.getContent().url,
                    14 * window.devicePixelRatio,
                    14 * window.devicePixelRatio,
                    'crop'
                );

        // it sucks that _tJsx doesn't support normal _t substitutions :((
        return (

                { _tJsx('$senderDisplayName changed the room avatar to ',
                         [
                            /\$senderDisplayName/,
                            //,
                         ],
                         [
                            (sub) => senderDisplayName,
                            (sub) =>
                                <AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
                                                  onClick={ this.onAvatarClick.bind(this, name) }>
                                    <BaseAvatar width={14} height={14} url={ url }
                                                name={ name } />
                                </AccessibleButton>,
                         ]
                    )
                }

        );
    },
});
<commit_msg>width and height must be int otherwise synapse cries<commit_after>/*
Copyright 2017 Vector Creations Ltd

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.
*/

import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { ContentRepo } from 'matrix-js-sdk';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';

module.exports = React.createClass({
    displayName: 'RoomAvatarEvent',

    propTypes: {
        /* the MatrixEvent to show */
        mxEvent: React.PropTypes.object.isRequired,
    },

    onAvatarClick: function(name) {
        var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
        var ImageView = sdk.getComponent("elements.ImageView");
        var params = {
            src: httpUrl,
            name: name,
        };
        Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
    },

    render: function() {
        var ev = this.props.mxEvent;
        var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
        var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");

        var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
                senderDisplayName: senderDisplayName,
                roomName: room ? room.name : '',
        });

        if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
            return (

                    { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }

            );
        }

        var url = ContentRepo.getHttpUriForMxc(
                    MatrixClientPeg.get().getHomeserverUrl(),
                    ev.getContent().url,
                    Math.ceil(14 * window.devicePixelRatio),
                    Math.ceil(14 * window.devicePixelRatio),
                    'crop'
                );

        // it sucks that _tJsx doesn't support normal _t substitutions :((
        return (

                { _tJsx('$senderDisplayName changed the room avatar to ',
                         [
                            /\$senderDisplayName/,
                            //,
                         ],
                         [
                            (sub) => senderDisplayName,
                            (sub) =>
                                <AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
                                                  onClick={ this.onAvatarClick.bind(this, name) }>
                                    <BaseAvatar width={14} height={14} url={ url }
                                                name={ name } />
                                </AccessibleButton>,
                         ]
                    )
                }

        );
    },
});
<|endoftext|>


<commit_before>/*
 * # DEPENDENCIES #
 */
var Metalsmith  = require('metalsmith');
var templates   = require('metalsmith-templates');
var collections = require('metalsmith-collections');
var setMetadata = require('metalsmith-filemetadata');
var filepath    = require('metalsmith-filepath');
var pandoc      = require('metalsmith-pandoc');
var ignore      = require('metalsmith-ignore');
var relative    = require('metalsmith-relative');
var define      = require('metalsmith-define');
var marked      = require('marked'); // for md strings in YAML header
var path        = require('path');
var getPlaylists = require('./playlist');
var _           = require('lodash');
var changed     = require('metalsmith-changed');
var paths       = require('metalsmith-paths');
// code highlighting
var highlight   = require('metalsmith-code-highlight');
var branch      = require('metalsmith-branch');
// search
var lunr        = require('lunr');
require('lunr-no/lunr.stemmer.support')(lunr);
require('lunr-no')(lunr);
var metlunr     = require('metalsmith-lunr');
// get configuration variables
var config      = require('./config.js');

/*
 * # SETUP OBJECTS #
 */
// metadata
var metadataOptions = [
  // search
  { pattern: path.join('**', '*.md'),
    metadata: { lunr: true }},
  // template for lessons
  { pattern: path.join('*', '**', '*.md'),
    metadata: { template: 'lesson.jade' }},
  // scratch lesson template
  { pattern: path.join('scratch', '**', '*.md'),
    metadata: { template: 'scratch.jade' }},
  // front page template
  { pattern: path.join('index.md'),
    metadata: { template: 'index.jade' }},
  // lesson index template
  { pattern: path.join('*', 'index.md'),
    metadata: { template: 'lesson-index.jade' }},
];

// ignores
var ignoreOptions = [
  path.join('**', 'README.md'),
];

// collections
var collectionOptions = {};
config.collections.forEach(function(collection){
  // options for collections
  var tmp = {};
  tmp.pattern = path.join(collection, '**', '*.md');
  collectionOptions[collection] = tmp;
});

// defines available in template
var defineOptions = {
  marked: marked,
  _: _,
  config: config,
};

// template
var templateOptions = {
  engine: 'jade',
  directory: path.join(config.builderRoot, 'templates'),
};

/*
 * # EXPORT #
 * build-function which takes a callback
 */
module.exports = function build(callback, options){
  options = options || {};
  var forceBuild = options.force || false;

  // read playlists upon every build
  var playlists = {};
  config.collections.forEach(function(collection){
    // playlists
    collectionFolder = path.join(config.lessonRoot, config.sourceFolder, collection);
    playlists[collection] = getPlaylists(collectionFolder, config.playlistFolder);
  });
  // make it available in template
  defineOptions.playlists = playlists;

  // do the building
  Metalsmith(config.lessonRoot)
  .source(config.sourceFolder)
  .use(ignore(ignoreOptions))
  .clean(false) // do not delete files, allow gulp tasks in parallel
  .use(paths())
  // set template for exercises
  .use(setMetadata(metadataOptions))
  // add relative(path) for use in templates
  .use(relative())
  // create collections for index
  .use(collections(collectionOptions))
  .use(metlunr({
    pipelineFunctions: [
      lunr.no.trimmer,
      lunr.no.stopWordFilter,
      lunr.no.stemmer
    ]
  }))
  // remove files not to build *after* we have set collections metadata
  .use(paths())
  .use(changed({
      force: forceBuild,
      extnames: {
          '.md': '.html',
      }
  }))
  // convert to html
  .use(pandoc({
    to: 'html5',
    args: ['--section-divs', '--smart']
  }))
  // highlight code - exclude scratch code blocks
  .use(branch()
    .pattern(['**/*.html', '!scratch/**/*.html']) // no highlight on scratch blocks
    .use(highlight({
      languages: ['java', 'python', 'lua', 'html', 'css', 'js'] // prevent bug in highlight.js < 8.5: https://github.com/isagalaev/highlight.js/issues/701
    }))
  )
  // add file.link metadata (now files are .html)
  .use(filepath())
  // globals for use in templates
  .use(define(defineOptions))
  // apply templates
  .use(templates(templateOptions))
  //build
  .destination('build')
  .build(function(err){
    if (err) console.log(err);
    // callback when build is done
    callback(err);
  });
};
<commit_msg>for not stopword, weighted fields<commit_after>/*
 * # DEPENDENCIES #
 */
var Metalsmith  = require('metalsmith');
var templates   = require('metalsmith-templates');
var collections = require('metalsmith-collections');
var setMetadata = require('metalsmith-filemetadata');
var filepath    = require('metalsmith-filepath');
var pandoc      = require('metalsmith-pandoc');
var ignore      = require('metalsmith-ignore');
var relative    = require('metalsmith-relative');
var define      = require('metalsmith-define');
var marked      = require('marked'); // for md strings in YAML header
var path        = require('path');
var getPlaylists = require('./playlist');
var _           = require('lodash');
var changed     = require('metalsmith-changed');
var paths       = require('metalsmith-paths');
// code highlighting
var highlight   = require('metalsmith-code-highlight');
var branch      = require('metalsmith-branch');
// search
var lunr        = require('lunr');
require('lunr-no/lunr.stemmer.support')(lunr);
require('lunr-no')(lunr);
var metlunr     = require('metalsmith-lunr');
// get configuration variables
var config      = require('./config.js');

/*
 * # SETUP OBJECTS #
 */
// metadata
var metadataOptions = [
  // search
  { pattern: path.join('**', '*.md'),
    metadata: { lunr: true }},
  // template for lessons
  { pattern: path.join('*', '**', '*.md'),
    metadata: { template: 'lesson.jade' }},
  // scratch lesson template
  { pattern: path.join('scratch', '**', '*.md'),
    metadata: { template: 'scratch.jade' }},
  // front page template
  { pattern: path.join('index.md'),
    metadata: { template: 'index.jade' }},
  // lesson index template
  { pattern: path.join('*', 'index.md'),
    metadata: { template: 'lesson-index.jade' }},
];

// search - for is not a stopword in this context
var words = lunr.no.stopWordFilter.stopWords.elements;
words.splice(words.indexOf('for'), 1);

// ignores
var ignoreOptions = [
  path.join('**', 'README.md'),
];

// collections
var collectionOptions = {};
config.collections.forEach(function(collection){
  // options for collections
  var tmp = {};
  tmp.pattern = path.join(collection, '**', '*.md');
  collectionOptions[collection] = tmp;
});

// defines available in template
var defineOptions = {
  marked: marked,
  _: _,
  config: config,
};

// template
var templateOptions = {
  engine: 'jade',
  directory: path.join(config.builderRoot, 'templates'),
};

/*
 * # EXPORT #
 * build-function which takes a callback
 */
module.exports = function build(callback, options){
  options = options || {};
  var forceBuild = options.force || false;

  // read playlists upon every build
  var playlists = {};
  config.collections.forEach(function(collection){
    // playlists
    collectionFolder = path.join(config.lessonRoot, config.sourceFolder, collection);
    playlists[collection] = getPlaylists(collectionFolder, config.playlistFolder);
  });
  // make it available in template
  defineOptions.playlists = playlists;

  // do the building
  Metalsmith(config.lessonRoot)
  .source(config.sourceFolder)
  .use(ignore(ignoreOptions))
  .clean(false) // do not delete files, allow gulp tasks in parallel
  .use(paths())
  // set template for exercises
  .use(setMetadata(metadataOptions))
  // add relative(path) for use in templates
  .use(relative())
  // create collections for index
  .use(collections(collectionOptions))
  .use(metlunr({
    fields: {
      contents: 1,
      title: 10,
      tags: 20,
    },
    pipelineFunctions: [
      lunr.no.trimmer,
      lunr.no.stopWordFilter,
      lunr.no.stemmer
    ]
  }))
  // remove files not to build *after* we have set collections metadata
  .use(paths())
  .use(changed({
      force: forceBuild,
      extnames: {
          '.md': '.html',
      }
  }))
  // convert to html
  .use(pandoc({
    to: 'html5',
    args: ['--section-divs', '--smart']
  }))
  // highlight code - exclude scratch code blocks
  .use(branch()
    .pattern(['**/*.html', '!scratch/**/*.html']) // no highlight on scratch blocks
    .use(highlight({
      languages: ['java', 'python', 'lua', 'html', 'css', 'js'] // prevent bug in highlight.js < 8.5: https://github.com/isagalaev/highlight.js/issues/701
    }))
  )
  // add file.link metadata (now files are .html)
  .use(filepath())
  // globals for use in templates
  .use(define(defineOptions))
  // apply templates
  .use(templates(templateOptions))
  //build
  .destination('build')
  .build(function(err){
    if (err) console.log(err);
    // callback when build is done
    callback(err);
  });
};
<|endoftext|>


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

/*jslint anon:true, sloppy:true, nomen:true, regexp:true, stupid:true,
  continue: true*/

var libpath = require('path'),
    utils = require(libpath.join(__dirname, '../../management/utils')),
    Mojito = require(libpath.join(__dirname, '../../mojito')),
    Store = require(libpath.join(__dirname, '../../store')),
    fs = require('fs'),
    libqs = require('querystring'),
    MODE_755 = parseInt('755', 8),
    getSpecURL,
    mkdirP,
    rmdirR,
    writeWebPagesToFiles,
    YUI = require('yui').YUI,
    Y = YUI({useSync: true}).use('json-parse', 'json-stringify', 'escape');

Y.applyConfig({useSync: false});

/**
 * The usage string for this command.
 */
exports.usage = 'mojito build {type} [destination]\n' +
    "\t- type: 'html5app'\n" +
    '\t- destination: (optional) the directory where the build output goes.\n' +
    "\t  By default this is the type i.e. './artifacts/builds/<type>'\n" +
    '\nOPTIONS: \n' +
    '\t --replace: Tells the build system to delete the destination' +
    ' directory and replace it.\n' +
    '\t        -r: Short for --replace\n' +
    '\t --context: Tells the build system what context to build with' +
    ' i.e. device=iphone&lang=en-GB.\n' +
    '\t        -c: Short for --context\n';

/**
 * The options list for this command.
 */
exports.options = [
    {
        shortName: 'r',
        longName: 'replace',
        hasValue: false
    },
    {
        shortName: 'm',
        longName: 'mojit',
        hasValue: true
    },
    {
        shortName: 'c',
        longName: 'context',
        hasValue: true
    }
];

/**
 * The run() method which handles processing for this command.
 * @param {Array} params Optional parameters to the command.
 * @param {Object} options Optional options for the command.
 * @param {Function} callback Function to invoke on commmand completion.
 */
exports.run = function(params, options, callback) {
    var store,
        type = 'html5app',
        cwd = process.cwd(),
        destination,
        appConfig,
        config = {},
        context = {};

    if (typeof options.context === 'string') {
        // Parse the context into an object
        context = utils.contextCsvToObject(options.context);
    }

    store = Store.createStore({
        root: cwd,
        context: context
    });

    if (!params[0]) {
        params[0] = '';
    }

    switch (params[0].toUpperCase()) {
    case 'HTML5APP':
        type = 'html5app';
        break;
    default:
        utils.error("Can only build type '" + type + "'", exports.usage);
        return;
    }

    // Set the destination for the generated files
    if (params[1] && params[1][0] === '/') {
        destination = libpath.join(params[1]);
    } else if (params[1]) {
        destination = libpath.join(cwd, params[1]);
    } else {
        destination = libpath.join(cwd, 'artifacts/builds', type);
    }

    // Are we in a Mojito App?
    utils.isMojitoApp(cwd, exports.usage, true);

    appConfig = store.getStaticAppConfig();
    // Is there a "builds" section for this "type" in the appConfig
    if (appConfig.builds && appConfig.builds[type]) {
        config = appConfig.builds[type];
    }

    if (options.replace) {
        try {
            rmdirR(destination);
            console.log('Removing all files in folder "' + destination + '"');
        } catch (err2) {    // err2 to get past JSLint
            // Dirty way to check dir
        }
    }

    exports['build' + type](options, store, config, destination, callback);
};

/**
 * The build command itself.
 * @param {Object} cmdOptions Options for the command.
 * @param {Object} store The relevant resource store reference.
 * @param {Object} config Configuration data for the context.
 * @param {string} destination A path to target with build output.
 * @param {Function} callback A function to invoke on completion.
 */
exports.buildhtml5app = function(cmdOptions, store, config, destination,
    callback) {

    var type = 'HTML5 Application',
        i,
        from,
        to,
        // files to fetch through the server
        serverFiles = { css: true, js: true, json: true },
        extension,
        manifest = 'CACHE MANIFEST\n',
        indexJs = "module.exports = require('express')." +
            "createServer(require('express')['static'](__dirname));",
        url,
        storeURLs,
        urls = {}, // from: to
        app,
        context = '',
        contextObj = libqs.parse(cmdOptions.context),
        appConfig,
        tunnelPrefix,
        dynamicURLs = {},
        mr,
        mojitRes,
        mojitRess,
        sr,
        specRes,
        specRess,
        id;

    if (typeof cmdOptions.context === 'string') {
        // Parse the context into an object
        config.context = utils.contextCsvToObject(cmdOptions.context);
        // Stringify the context object into query string
        context = '?' + libqs.stringify(config.context);
    }

    urls['/' + context] = '/index.html';

    if (config.urls && config.urls.length) {
        for (i = 0; i < config.urls.length; i += 1) {
            urls[config.urls[i] + context] = config.urls[i];
        }
    }

    store.preload();

    appConfig = store.getAppConfig(contextObj);
    tunnelPrefix = appConfig.tunnelPrefix || '/tunnel';

    console.log('Building a "' + type + '" of the Mojito application at "' +
        store._config.root + '"');

    console.log('...');

    // Set the cachable files in the manifest
    manifest += 'CACHE:\n';

    // Copy all the files into the destination directory
    storeURLs = store.getAllURLs();
    for (url in storeURLs) {
        if (storeURLs.hasOwnProperty(url)) {
            from = storeURLs[url];  // filesystem path
            to = libpath.join(destination, url);
            extension = from.split('.').pop();

            mkdirP(libpath.dirname(to), MODE_755);

            manifest += url + '\n';

            if (serverFiles[extension]) {
                urls[url + context] = url;
                mkdirP(libpath.dirname(libpath.join(destination, url)),
                    MODE_755);
            } else {
                fs.writeFileSync(to, fs.readFileSync(from), 'utf8');
            }
        }
    }

    mojitRess = store.getResources('client', contextObj, {type: 'mojit'});
    for (mr = 0; mr < mojitRess.length; mr += 1) {
        mojitRes = mojitRess[mr];
        if (!mojitRes.url) {
            continue;
        }
        url = mojitRes.url + '/definition.json';
        dynamicURLs[url] = true;

        specRess = store.getResources('client', contextObj, {type: 'spec', mojit: mojitRes.name});
        for (sr = 0; sr < specRess.length; sr += 1) {
            specRes = specRess[sr];
            dynamicURLs[specRes.url] = true;
        }
    }

    for (id in appConfig.specs) {
        if (appConfig.specs.hasOwnProperty(id)) {
            url = getSpecURL(appConfig, id);
            dynamicURLs[url] = true;
        }
    }

    // Get all the dynamic URLs we have to call via the "tunnel"
    for (url in dynamicURLs) {
        if (dynamicURLs.hasOwnProperty(url)) {
            urls[tunnelPrefix + url + context] = url;
            mkdirP(libpath.dirname(libpath.join(destination, url)), MODE_755);
        }
    }

    for (url in urls) {
        if (urls.hasOwnProperty(url)) {
            manifest += (urls[url] === '/' ? '/index.html' : urls[url]) + '\n';
        }
    }

    // Write the "cache.manifest" file
    fs.writeFileSync(libpath.join(destination, 'cache.manifest'), manifest,
        'utf8');

    // Write a "quick test" index.js file
    fs.writeFileSync(libpath.join(destination, 'index.js'), indexJs, 'utf8');

    // Now use the server to generate some of the files
    writeWebPagesToFiles(destination, urls, config, callback);
};

getSpecURL = function(appConfig, id) {
    var prefix = '/static',
        parts = id.split(':'),
        typeName = parts[0],
        specName = parts[1] || 'default',
        ns = typeName.replace(/\./g, '_'),
        url;

    if (appConfig && appConfig.staticHandling &&
            appConfig.staticHandling.hasOwnProperty('prefix')) {
        prefix = (appConfig.staticHandling.prefix ? '/' +
            appConfig.staticHandling.prefix : '');
    }
    url = prefix + '/' + typeName + '/specs/' + specName + '.json';
    return url;
};

mkdirP = function(p, mode) {
    var ps = libpath.normalize(p).split('/'),
        i;

    for (i = 0; i <= ps.length; i += 1) {
        try {
            fs.mkdirSync(ps.slice(0, i).join('/'), mode);
        } catch (err) {
            // Dirty way to check dir
        }
    }
};

rmdirR = function(path) {
    var files = fs.readdirSync(path),
        currDir = path,
        i,
        currFile;

    /* Loop through and delete everything in the sub-tree after checking it */
    for (i = 0; i < files.length; i += 1) {
        currFile = fs.statSync(currDir + '/' + files[i]);

        if (currFile.isDirectory()) {
            // Recursive function back to the beginning
            rmdirR(currDir + '/' + files[i]);
        } else if (currFile.isSymbolicLink()) {
            // Unlink symlinks
            fs.unlinkSync(currDir + '/' + files[i]);
        } else {
            // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(currDir + '/' + files[i]);
        }
    }

    /*
     * Now that we know everything in the sub-tree has been deleted,
     * we can delete the main directory.
     */
    return fs.rmdirSync(path);
};

/**
 * Calculates the filesystem path to get from source to destination.
 *
 * @private
 * @param {string} dst The destination directory.
 * @param {string} src The source directory.
 * @return {string} The path from source to destination.
 */
function pathTo(dst, src) {
    var dstParts,
        srcParts,
        i,
        j,
        path = [];

    // trimming leading and trailing slashes removes nasty corner cases from the
    // algorithm
    if ('/' === dst.charAt(0)) {
        dst = dst.substring(1);
    }
    if ('/' === dst.charAt(dst.length - 1)) {
        dst = dst.substring(0, dst.length - 1);
    }
    if ('/' === src.charAt(0)) {
        src = src.substring(1);
    }
    if ('/' === src.charAt(src.length - 1)) {
        src = src.substring(0, src.length - 1);
    }

    dstParts = dst ? dst.split('/') : [];
    srcParts = src ? src.split('/') : [];
    path = [];
    for (i = 0; i < dstParts.length && i < srcParts.length; i += 1) {
        if (dstParts[i] !== srcParts[i]) {
            break;
        }
    }
    for (j = i; j < srcParts.length; j += 1) {
        path.push('..');
    }
    for (j = i; j < dstParts.length; j += 1) {
        path.push(dstParts[j]);
    }
    return libpath.join.apply(libpath, path) || '.';
}

/**
 * Adds the HTML5 cache manifest to the <html>
 *
 * @private
 * @param {string} root The directory where html5app is being built into.
 * @param {string} relativePath The file path, relative to root.
 * @param {string} content The body of the file.
 * @param {Boolean} force Whether to fixup the content, even though the path
 *     might not end in .html.
 * @return {string} The fixed up content.
 */
function attachManifest(root, relativePath, content, force) {
    var extname = libpath.extname(relativePath),
        dirname,
        pathToRoot;

    if (force || '.html' === extname) {
        dirname = libpath.dirname(relativePath);
        pathToRoot = pathTo('/', dirname);

        content = content.replace(/<html>/, '<html manifest="' + pathToRoot +
            '/cache.manifest">');
    }

    return content;
}

/**
 * Changes server-relative paths to file-relative paths.
 *
 * @private
 * @param {string} root The directory where html5app is being built into.
 * @param {string} relativePath The file path, relative to root.
 * @param {string} content The body of the file.
 * @param {Boolean} force Whether to fixup the content, even though the path
 *     might not end in .html.
 * @return {string} The fixed up content.
 */
function forceRelativePaths(root, relativePath, content, force) {
    var extname = libpath.extname(relativePath),
        dirname;

    if (force || '.html' === extname) {
        dirname = libpath.dirname(relativePath);

        content = content.replace(/(src|href)="([^"]+)"/g,
            function(all, name, val) {
                // FUTURE:  once the "/" aren't escaped, we can do this easier
                var fixed = utils.decodeHTMLEntities(val);
                if ('/' === fixed.charAt(0)) {
                    fixed = libpath.join(pathTo(libpath.dirname(fixed), dirname),
                        libpath.basename(fixed));
                }
                fixed = Y.Escape.html(fixed);
                return name + '="' + fixed + '"';
            });
    }

    return content;
}

writeWebPagesToFiles = function(destination, urls, config, callback) {
    var options,
        userPages = {},
        i,
        app;

    if (config.urls) {
        // If the user is using builds.html5app.attachManifest or
        // .forceRelativePaths, we explicitly want to fix those URLs which are
        // pages (the ones they've specified).
        for (i = 0; i < config.urls.length; i += 1) {
            userPages[config.urls[i]] = true;
        }
    }

    options = {
        port: 11111,
        context: config.context
    };

    app = Mojito.createServer(options);

    app.listen(null, null, function(err) {
        var got = 0,
            need;

        if (err) {
            utils.error(err);
            return;
        }
        need = Object.keys(urls).length;

        Object.keys(urls).forEach(function(u) {
            var opts = {
                    headers: {
                        'x-mojito-build': 'html5app',
                        'x-mojito-build-path-to-root': pathTo('/',
                            libpath.dirname(urls[u]))
                    }
                };
            app.getWebPage(u, opts, function(err, url, content) {
                var dest;

                got += 1;

                if (err) {
                    utils.error('FAILED to get ' + url + ' with error:\n' +
                                err, null, true);
                } else {
                    if (config.attachManifest) {
                        content = attachManifest(destination, urls[url],
                            content,
                            userPages[url]);
                    }
                    if (config.forceRelativePaths) {
                        content = forceRelativePaths(destination, urls[url],
                            content,
                            userPages[url]
                            );
                    }
                    dest = libpath.join(destination, urls[url]);
                    mkdirP(libpath.dirname(dest), MODE_755);
                    fs.writeFileSync(dest, content, 'utf8');
                }

                if (got === need) {
                    app.close();
                    callback();
                }
            });
        });
    });
};
<commit_msg>FIX: copying yui build dir for offline apps<commit_after>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*jslint anon:true, sloppy:true, nomen:true, regexp:true, stupid:true,
  continue: true*/

var libpath = require('path'),
    libwrench = require('wrench'),
    utils = require(libpath.join(__dirname, '../../management/utils')),
    Mojito = require(libpath.join(__dirname, '../../mojito')),
    Store = require(libpath.join(__dirname, '../../store')),
    fs = require('fs'),
    libqs = require('querystring'),
    MODE_755 = parseInt('755', 8),
    getSpecURL,
    mkdirP,
    rmdirR,
    writeWebPagesToFiles,
    findYUIDir,
    YUI = require('yui').YUI,
    Y = YUI({useSync: true}).use('json-parse', 'json-stringify', 'escape');

Y.applyConfig({useSync: false});

/**
 * The usage string for this command.
 */
exports.usage = 'mojito build {type} [destination]\n' +
    "\t- type: 'html5app'\n" +
    '\t- destination: (optional) the directory where the build output goes.\n' +
    "\t  By default this is the type i.e. './artifacts/builds/<type>'\n" +
    '\nOPTIONS: \n' +
    '\t --replace: Tells the build system to delete the destination' +
    ' directory and replace it.\n' +
    '\t        -r: Short for --replace\n' +
    '\t --context: Tells the build system what context to build with' +
    ' i.e. device=iphone&lang=en-GB.\n' +
    '\t        -c: Short for --context\n';

/**
 * The options list for this command.
 */
exports.options = [
    {
        shortName: 'r',
        longName: 'replace',
        hasValue: false
    },
    {
        shortName: 'm',
        longName: 'mojit',
        hasValue: true
    },
    {
        shortName: 'c',
        longName: 'context',
        hasValue: true
    }
];

/**
 * The run() method which handles processing for this command.
 * @param {Array} params Optional parameters to the command.
 * @param {Object} options Optional options for the command.
 * @param {Function} callback Function to invoke on commmand completion.
 */
exports.run = function(params, options, callback) {
    var store,
        type = 'html5app',
        cwd = process.cwd(),
        destination,
        appConfig,
        config = {},
        context = {};

    if (typeof options.context === 'string') {
        // Parse the context into an object
        context = utils.contextCsvToObject(options.context);
    }

    store = Store.createStore({
        root: cwd,
        context: context
    });

    if (!params[0]) {
        params[0] = '';
    }

    switch (params[0].toUpperCase()) {
    case 'HTML5APP':
        type = 'html5app';
        break;
    default:
        utils.error("Can only build type '" + type + "'", exports.usage);
        return;
    }

    // Set the destination for the generated files
    if (params[1] && params[1][0] === '/') {
        destination = libpath.join(params[1]);
    } else if (params[1]) {
        destination = libpath.join(cwd, params[1]);
    } else {
        destination = libpath.join(cwd, 'artifacts/builds', type);
    }

    // Are we in a Mojito App?
    utils.isMojitoApp(cwd, exports.usage, true);

    appConfig = store.getStaticAppConfig();
    // Is there a "builds" section for this "type" in the appConfig
    if (appConfig.builds && appConfig.builds[type]) {
        config = appConfig.builds[type];
    }

    if (options.replace) {
        try {
            rmdirR(destination);
            console.log('Removing all files in folder "' + destination + '"');
        } catch (err2) {    // err2 to get past JSLint
            // Dirty way to check dir
        }
    }

    exports['build' + type](options, store, config, destination, callback);
};

/**
 * The build command itself.
 * @param {Object} cmdOptions Options for the command.
 * @param {Object} store The relevant resource store reference.
 * @param {Object} config Configuration data for the context.
 * @param {string} destination A path to target with build output.
 * @param {Function} callback A function to invoke on completion.
 */
exports.buildhtml5app = function(cmdOptions, store, config, destination,
    callback) {

    var type = 'HTML5 Application',
        i,
        from,
        to,
        // files to fetch through the server
        serverFiles = { css: true, js: true, json: true },
        extension,
        manifest = 'CACHE MANIFEST\n',
        indexJs = "module.exports = require('express')." +
            "createServer(require('express')['static'](__dirname));",
        url,
        storeURLs,
        urls = {}, // from: to
        app,
        context = '',
        contextObj = libqs.parse(cmdOptions.context),
        appConfig,
        tunnelPrefix,
        dynamicURLs = {},
        mr,
        mojitRes,
        mojitRess,
        sr,
        specRes,
        specRess,
        id,
        sourceYUI,
        destinationYUI;

    if (typeof cmdOptions.context === 'string') {
        // Parse the context into an object
        config.context = utils.contextCsvToObject(cmdOptions.context);
        // Stringify the context object into query string
        context = '?' + libqs.stringify(config.context);
    }

    urls['/' + context] = '/index.html';

    if (config.urls && config.urls.length) {
        for (i = 0; i < config.urls.length; i += 1) {
            urls[config.urls[i] + context] = config.urls[i];
        }
    }

    store.preload();

    appConfig = store.getAppConfig(contextObj);
    tunnelPrefix = appConfig.tunnelPrefix || '/tunnel';

    console.log('Building a "' + type + '" of the Mojito application at "' +
        store._config.root + '"');

    console.log('...');

    // Set the cachable files in the manifest
    manifest += 'CACHE:\n';

    // Copy all the files into the destination directory
    storeURLs = store.getAllURLs();
    for (url in storeURLs) {
        if (storeURLs.hasOwnProperty(url)) {
            from = storeURLs[url];  // filesystem path
            to = libpath.join(destination, url);
            extension = from.split('.').pop();

            mkdirP(libpath.dirname(to), MODE_755);

            manifest += url + '\n';

            if (serverFiles[extension]) {
                urls[url + context] = url;
                mkdirP(libpath.dirname(libpath.join(destination, url)),
                    MODE_755);
            } else {
                fs.writeFileSync(to, fs.readFileSync(from), 'utf8');
            }
        }
    }

    mojitRess = store.getResources('client', contextObj, {type: 'mojit'});
    for (mr = 0; mr < mojitRess.length; mr += 1) {
        mojitRes = mojitRess[mr];
        if (!mojitRes.url) {
            continue;
        }
        url = mojitRes.url + '/definition.json';
        dynamicURLs[url] = true;

        specRess = store.getResources('client', contextObj, {type: 'spec', mojit: mojitRes.name});
        for (sr = 0; sr < specRess.length; sr += 1) {
            specRes = specRess[sr];
            dynamicURLs[specRes.url] = true;
        }
    }

    for (id in appConfig.specs) {
        if (appConfig.specs.hasOwnProperty(id)) {
            url = getSpecURL(appConfig, id);
            dynamicURLs[url] = true;
        }
    }

    // Get all the dynamic URLs we have to call via the "tunnel"
    for (url in dynamicURLs) {
        if (dynamicURLs.hasOwnProperty(url)) {
            urls[tunnelPrefix + url + context] = url;
            mkdirP(libpath.dirname(libpath.join(destination, url)), MODE_755);
        }
    }

    for (url in urls) {
        if (urls.hasOwnProperty(url)) {
            manifest += (urls[url] === '/' ? '/index.html' : urls[url]) + '\n';
        }
    }

    // Requires mojito-0.5.0+
    sourceYUI = findYUIDir();
    destinationYUI = libpath.join(destination, 'static');
    libwrench.copyDirSyncRecursive(sourceYUI, destinationYUI);

    // Write the "cache.manifest" file
    fs.writeFileSync(libpath.join(destination, 'cache.manifest'), manifest,
        'utf8');

    // Write a "quick test" index.js file
    fs.writeFileSync(libpath.join(destination, 'index.js'), indexJs, 'utf8');

    // Now use the server to generate some of the files
    writeWebPagesToFiles(destination, urls, config, callback);
};

/**
 * Locate the directory where YUI is installed within "mojito" module
 */
findYUIDir = function() {
    var source;
    source = libpath.join(__dirname, '../../../node_modules', 'yui');
    return source;
};

getSpecURL = function(appConfig, id) {
    var prefix = '/static',
        parts = id.split(':'),
        typeName = parts[0],
        specName = parts[1] || 'default',
        ns = typeName.replace(/\./g, '_'),
        url;

    if (appConfig && appConfig.staticHandling &&
            appConfig.staticHandling.hasOwnProperty('prefix')) {
        prefix = (appConfig.staticHandling.prefix ? '/' +
            appConfig.staticHandling.prefix : '');
    }
    url = prefix + '/' + typeName + '/specs/' + specName + '.json';
    return url;
};

mkdirP = function(p, mode) {
    var ps = libpath.normalize(p).split('/'),
        i;

    for (i = 0; i <= ps.length; i += 1) {
        try {
            fs.mkdirSync(ps.slice(0, i).join('/'), mode);
        } catch (err) {
            // Dirty way to check dir
        }
    }
};

rmdirR = function(path) {
    var files = fs.readdirSync(path),
        currDir = path,
        i,
        currFile;

    /* Loop through and delete everything in the sub-tree after checking it */
    for (i = 0; i < files.length; i += 1) {
        currFile = fs.statSync(currDir + '/' + files[i]);

        if (currFile.isDirectory()) {
            // Recursive function back to the beginning
            rmdirR(currDir + '/' + files[i]);
        } else if (currFile.isSymbolicLink()) {
            // Unlink symlinks
            fs.unlinkSync(currDir + '/' + files[i]);
        } else {
            // Assume it's a file - perhaps a try/catch belongs here?
            fs.unlinkSync(currDir + '/' + files[i]);
        }
    }

    /*
     * Now that we know everything in the sub-tree has been deleted,
     * we can delete the main directory.
     */
    return fs.rmdirSync(path);
};

/**
 * Calculates the filesystem path to get from source to destination.
 *
 * @private
 * @param {string} dst The destination directory.
 * @param {string} src The source directory.
 * @return {string} The path from source to destination.
 */
function pathTo(dst, src) {
    var dstParts,
        srcParts,
        i,
        j,
        path = [];

    // trimming leading and trailing slashes removes nasty corner cases from the
    // algorithm
    if ('/' === dst.charAt(0)) {
        dst = dst.substring(1);
    }
    if ('/' === dst.charAt(dst.length - 1)) {
        dst = dst.substring(0, dst.length - 1);
    }
    if ('/' === src.charAt(0)) {
        src = src.substring(1);
    }
    if ('/' === src.charAt(src.length - 1)) {
        src = src.substring(0, src.length - 1);
    }

    dstParts = dst ? dst.split('/') : [];
    srcParts = src ? src.split('/') : [];
    path = [];
    for (i = 0; i < dstParts.length && i < srcParts.length; i += 1) {
        if (dstParts[i] !== srcParts[i]) {
            break;
        }
    }
    for (j = i; j < srcParts.length; j += 1) {
        path.push('..');
    }
    for (j = i; j < dstParts.length; j += 1) {
        path.push(dstParts[j]);
    }
    return libpath.join.apply(libpath, path) || '.';
}

/**
 * Adds the HTML5 cache manifest to the <html>
 *
 * @private
 * @param {string} root The directory where html5app is being built into.
 * @param {string} relativePath The file path, relative to root.
 * @param {string} content The body of the file.
 * @param {Boolean} force Whether to fixup the content, even though the path
 *     might not end in .html.
 * @return {string} The fixed up content.
 */
function attachManifest(root, relativePath, content, force) {
    var extname = libpath.extname(relativePath),
        dirname,
        pathToRoot;

    if (force || '.html' === extname) {
        dirname = libpath.dirname(relativePath);
        pathToRoot = pathTo('/', dirname);

        content = content.replace(/<html>/, '<html manifest="' + pathToRoot +
            '/cache.manifest">');
    }

    return content;
}

/**
 * Changes server-relative paths to file-relative paths.
 *
 * @private
 * @param {string} root The directory where html5app is being built into.
 * @param {string} relativePath The file path, relative to root.
 * @param {string} content The body of the file.
 * @param {Boolean} force Whether to fixup the content, even though the path
 *     might not end in .html.
 * @return {string} The fixed up content.
 */
function forceRelativePaths(root, relativePath, content, force) {
    var extname = libpath.extname(relativePath),
        dirname;

    if (force || '.html' === extname) {
        dirname = libpath.dirname(relativePath);

        content = content.replace(/(src|href)="([^"]+)"/g,
            function(all, name, val) {
                // FUTURE:  once the "/" aren't escaped, we can do this easier
                var fixed = utils.decodeHTMLEntities(val);
                if ('/' === fixed.charAt(0)) {
                    fixed = libpath.join(pathTo(libpath.dirname(fixed), dirname),
                        libpath.basename(fixed));
                }
                // Causing all the /, & and = to be HTML encoded. Those are URLs,
                // no need to do so. Just URLEncode is good enough,
                // fixed = Y.Escape.html(fixed);
                fixed = encodeURI(fixed);
                return name + '="' + fixed + '"';
            });
    }

    return content;
}

writeWebPagesToFiles = function(destination, urls, config, callback) {
    var options,
        userPages = {},
        i,
        app;

    if (config.urls) {
        // If the user is using builds.html5app.attachManifest or
        // .forceRelativePaths, we explicitly want to fix those URLs which are
        // pages (the ones they've specified).
        for (i = 0; i < config.urls.length; i += 1) {
            userPages[config.urls[i]] = true;
        }
    }

    options = {
        port: 11111,
        context: config.context
    };

    app = Mojito.createServer(options);

    app.listen(null, null, function(err) {
        var got = 0,
            need;

        if (err) {
            utils.error(err);
            return;
        }
        need = Object.keys(urls).length;

        Object.keys(urls).forEach(function(u) {
            var opts = {
                    headers: {
                        'x-mojito-build': 'html5app',
                        'x-mojito-build-path-to-root': pathTo('/',
                            libpath.dirname(urls[u]))
                    }
                };
            app.getWebPage(u, opts, function(err, url, content) {
                var dest;

                got += 1;

                if (err) {
                    utils.error('FAILED to get ' + url + ' with error:\n' +
                                err, null, true);
                } else {
                    if (config.attachManifest) {
                        content = attachManifest(destination, urls[url],
                            content,
                            userPages[url]);
                    }
                    if (config.forceRelativePaths) {
                        content = forceRelativePaths(destination, urls[url],
                            content,
                            userPages[url]
                            );
                    }
                    dest = libpath.join(destination, urls[url]);
                    mkdirP(libpath.dirname(dest), MODE_755);
                    fs.writeFileSync(dest, content, 'utf8');
                }

                if (got === need) {
                    app.close();
                    callback();
                }
            });
        });
    });
};
<|endoftext|>


<commit_before>import { BrowserWindow, globalShortcut, app, screen, shell } from 'electron'
import debounce from 'lodash/debounce'
import EventEmitter from 'events'
import { trackEvent, screenView } from '../lib/trackEvent'

import {
  INPUT_HEIGHT,
  WINDOW_WIDTH
} from './constants/ui'

import buildMenu from './createWindow/buildMenu'
import toggleWindow from './createWindow/toggleWindow'
import handleUrl from './createWindow/handleUrl'
import config from '../lib/config'
import getWindowPosition from '../lib/getWindowPosition'
import * as donateDialog from './createWindow/donateDialog'

export default ({ src, isDev }) => {
  const [x, y] = getWindowPosition({})

  const browserWindowOptions = {
    width: WINDOW_WIDTH,
    minWidth: WINDOW_WIDTH,
    height: INPUT_HEIGHT,
    x,
    y,
    frame: false,
    resizable: false,
    // Show main window on launch only when application started for the first time
    show: config.get('firstStart')
  }

  if (process.platform === 'linux') {
    browserWindowOptions.type = 'splash'
  }

  const mainWindow = new BrowserWindow(browserWindowOptions)

  // Float main window above full-screen apps
  mainWindow.setAlwaysOnTop(true, 'modal-panel')

  mainWindow.loadURL(src)
  mainWindow.settingsChanges = new EventEmitter()

  // Get global shortcut from app settings
  let shortcut = config.get('hotkey')

  // Function to toggle main window
  const toggleMainWindow = () => toggleWindow(mainWindow)
  // Function to show main window
  const showMainWindow = () => {
    mainWindow.show()
    mainWindow.focus()
  }

  // Setup event listeners for main window
  globalShortcut.register(shortcut, toggleMainWindow)

  mainWindow.on('blur', () => {
    if (!isDev() && config.get('hideOnBlur')) {
      // Hide window on blur in production
      // In development we usually use developer tools that can blur a window
      mainWindow.hide()
    }
  })

  // Save window position when it is being moved
  mainWindow.on('move', debounce(() => {
    if (!mainWindow.isVisible()) {
      return
    }
    const display = screen.getDisplayNearestPoint(
      screen.getCursorScreenPoint()
    )

    const positions = config.get('positions') || {}
    positions[display.id] = mainWindow.getPosition()
    config.set('positions', positions)
  }, 100))

  mainWindow.on('close', app.quit)

  mainWindow.webContents.on('new-window', (event, url) => {
    shell.openExternal(url)
    event.preventDefault()
  })

  mainWindow.webContents.on('will-navigate', (event, url) => {
    if (url !== mainWindow.webContents.getURL()) {
      shell.openExternal(url)
      event.preventDefault()
    }
  })

  // Change global hotkey if it is changed in app settings
  mainWindow.settingsChanges.on('hotkey', (value) => {
    globalShortcut.unregister(shortcut)
    shortcut = value
    globalShortcut.register(shortcut, toggleMainWindow)
  })

  // Change theme css file
  mainWindow.settingsChanges.on('theme', (value) => {
    mainWindow.webContents.send('message', {
      message: 'updateTheme',
      payload: value
    })
  })

  // Handle window.hide: if cleanOnHide value in preferences is true
  // we clear all results and show empty window every time
  const resetResults = () => {
    mainWindow.webContents.send('message', {
      message: 'showTerm',
      payload: ''
    })
  }

  // Handle change of cleanOnHide value in settins
  const handleCleanOnHideChange = (value) => {
    if (value) {
      mainWindow.on('hide', resetResults)
    } else {
      mainWindow.removeListener('hide', resetResults)
    }
  }

  // Set or remove handler when settings changed
  mainWindow.settingsChanges.on('cleanOnHide', handleCleanOnHideChange)

  // Set initial handler if it is needed
  handleCleanOnHideChange(config.get('cleanOnHide'))

  // Restore focus in previous application
  // MacOS only: https://github.com/electron/electron/blob/master/docs/api/app.md#apphide-macos
  if (process.platform === 'darwin') {
    mainWindow.on('hide', () => {
      app.hide()
    })
  }

  // Show main window when user opens application, but it is already opened
  app.on('open-file', (event, path) => handleUrl(mainWindow, path))
  app.on('open-url', (event, path) => handleUrl(mainWindow, path))
  app.on('activate', showMainWindow)

  // Someone tried to run a second instance, we should focus our window.
  const shouldQuit = app.makeSingleInstance(() => {
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore()
      mainWindow.focus()
    }
  })

  if (shouldQuit) {
    app.quit()
  }

  if (donateDialog.shouldShow()) {
    setTimeout(donateDialog.show, 1000)
  }

  // Track app start event
  trackEvent({
    category: 'App Start',
    event: config.get('firstStart') ? 'First' : 'Secondary'
  })

  screenView('Search')

  // Save in config information, that application has been started
  config.set('firstStart', false)

  buildMenu(mainWindow)
  return mainWindow
}
<commit_msg>Add transparency to BrowserWindow options (#430)<commit_after>import { BrowserWindow, globalShortcut, app, screen, shell } from 'electron'
import debounce from 'lodash/debounce'
import EventEmitter from 'events'
import { trackEvent, screenView } from '../lib/trackEvent'

import {
  INPUT_HEIGHT,
  WINDOW_WIDTH
} from './constants/ui'

import buildMenu from './createWindow/buildMenu'
import toggleWindow from './createWindow/toggleWindow'
import handleUrl from './createWindow/handleUrl'
import config from '../lib/config'
import getWindowPosition from '../lib/getWindowPosition'
import * as donateDialog from './createWindow/donateDialog'

export default ({ src, isDev }) => {
  const [x, y] = getWindowPosition({})

  const browserWindowOptions = {
    width: WINDOW_WIDTH,
    minWidth: WINDOW_WIDTH,
    height: INPUT_HEIGHT,
    x,
    y,
    frame: false,
    resizable: false,
    transparent: true,
    // Show main window on launch only when application started for the first time
    show: config.get('firstStart')
  }

  if (process.platform === 'linux') {
    browserWindowOptions.type = 'splash'
  }

  const mainWindow = new BrowserWindow(browserWindowOptions)

  // Float main window above full-screen apps
  mainWindow.setAlwaysOnTop(true, 'modal-panel')

  mainWindow.loadURL(src)
  mainWindow.settingsChanges = new EventEmitter()

  // Get global shortcut from app settings
  let shortcut = config.get('hotkey')

  // Function to toggle main window
  const toggleMainWindow = () => toggleWindow(mainWindow)
  // Function to show main window
  const showMainWindow = () => {
    mainWindow.show()
    mainWindow.focus()
  }

  // Setup event listeners for main window
  globalShortcut.register(shortcut, toggleMainWindow)

  mainWindow.on('blur', () => {
    if (!isDev() && config.get('hideOnBlur')) {
      // Hide window on blur in production
      // In development we usually use developer tools that can blur a window
      mainWindow.hide()
    }
  })

  // Save window position when it is being moved
  mainWindow.on('move', debounce(() => {
    if (!mainWindow.isVisible()) {
      return
    }
    const display = screen.getDisplayNearestPoint(
      screen.getCursorScreenPoint()
    )

    const positions = config.get('positions') || {}
    positions[display.id] = mainWindow.getPosition()
    config.set('positions', positions)
  }, 100))

  mainWindow.on('close', app.quit)

  mainWindow.webContents.on('new-window', (event, url) => {
    shell.openExternal(url)
    event.preventDefault()
  })

  mainWindow.webContents.on('will-navigate', (event, url) => {
    if (url !== mainWindow.webContents.getURL()) {
      shell.openExternal(url)
      event.preventDefault()
    }
  })

  // Change global hotkey if it is changed in app settings
  mainWindow.settingsChanges.on('hotkey', (value) => {
    globalShortcut.unregister(shortcut)
    shortcut = value
    globalShortcut.register(shortcut, toggleMainWindow)
  })

  // Change theme css file
  mainWindow.settingsChanges.on('theme', (value) => {
    mainWindow.webContents.send('message', {
      message: 'updateTheme',
      payload: value
    })
  })

  // Handle window.hide: if cleanOnHide value in preferences is true
  // we clear all results and show empty window every time
  const resetResults = () => {
    mainWindow.webContents.send('message', {
      message: 'showTerm',
      payload: ''
    })
  }

  // Handle change of cleanOnHide value in settins
  const handleCleanOnHideChange = (value) => {
    if (value) {
      mainWindow.on('hide', resetResults)
    } else {
      mainWindow.removeListener('hide', resetResults)
    }
  }

  // Set or remove handler when settings changed
  mainWindow.settingsChanges.on('cleanOnHide', handleCleanOnHideChange)

  // Set initial handler if it is needed
  handleCleanOnHideChange(config.get('cleanOnHide'))

  // Restore focus in previous application
  // MacOS only: https://github.com/electron/electron/blob/master/docs/api/app.md#apphide-macos
  if (process.platform === 'darwin') {
    mainWindow.on('hide', () => {
      app.hide()
    })
  }

  // Show main window when user opens application, but it is already opened
  app.on('open-file', (event, path) => handleUrl(mainWindow, path))
  app.on('open-url', (event, path) => handleUrl(mainWindow, path))
  app.on('activate', showMainWindow)

  // Someone tried to run a second instance, we should focus our window.
  const shouldQuit = app.makeSingleInstance(() => {
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore()
      mainWindow.focus()
    }
  })

  if (shouldQuit) {
    app.quit()
  }

  if (donateDialog.shouldShow()) {
    setTimeout(donateDialog.show, 1000)
  }

  // Track app start event
  trackEvent({
    category: 'App Start',
    event: config.get('firstStart') ? 'First' : 'Secondary'
  })

  screenView('Search')

  // Save in config information, that application has been started
  config.set('firstStart', false)

  buildMenu(mainWindow)
  return mainWindow
}
<|endoftext|>


<commit_before>'use babel'

const fs = require('fs-plus')
const path = require('path')
const temp = require('temp')
const Git = require('nodegit')
const CompositeDisposable = require('event-kit').CompositeDisposable

temp.track()

const GitRepositoryAsync = require('../src/git-repository-async')

const openFixture = (fixture) => {
  return GitRepositoryAsync.open(path.join(__dirname, 'fixtures', 'git', fixture))
}

const copyRepository = () => {
  let workingDirPath = temp.mkdirSync('atom-working-dir')
  fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
  fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
  return fs.realpathSync(workingDirPath)
}

async function terribleWait(fn) {
  const p = new Promise()
  waitsFor(fn)
  runs(() => p.resolve())
}

// Git uses heuristics to avoid having to needlessly hash a file to tell if it
// changed. One of those is mtime. If our tests are running Super Fast, we could
// end up changing a file multiple times within the same mtime tick, which could
// lead git to think the file didn't change at all. So sometimes we'll need to
// sleep. (https://www.kernel.org/pub/software/scm/git/docs/technical/racy-git.txt)
function terribleSleep() {
  for (let _ of range(1, 1000)) { ; }
}

describe('GitRepositoryAsync-js', () => {
  let repo

  afterEach(() => {
    if (repo != null) repo.destroy()
  })

  describe('@open(path)', () => {
    it('repo is null when no repository is found', async () => {
      repo = GitRepositoryAsync.open(path.join(temp.dir, 'nogit.txt'))

      let threw = false
      try {
        await repo.repoPromise
      } catch (e) {
        threw = true
      }

      expect(threw).toBeTruthy()
      expect(repo.repo).toBe(null)
    })
  })

  describe('.getPath()', () => {
    xit('returns the repository path for a .git directory path')

    it('returns the repository path for a repository path', async () => {
      repo = openFixture('master.git')
      let path = await repo.getPath()
      expect(path).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
    })
  })

  describe('.isPathIgnored(path)', () => {
    it('resolves true for an ignored path', async () => {
      repo = openFixture('ignore.git')
      let ignored = await repo.isPathIgnored('a.txt')
      expect(ignored).toBeTruthy()
    })

    it('resolves false for a non-ignored path', async () => {
      repo = openFixture('ignore.git')
      let ignored = await repo.isPathIgnored('b.txt')
      expect(ignored).toBeFalsy()
    })
  })

  describe('.isPathModified(path)', () => {
    let filePath, newPath, emptyPath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      newPath = path.join(workingDirPath, 'new-path.txt')
      fs.writeFileSync(newPath, "i'm new here")
      emptyPath = path.join(workingDirPath, 'empty-path.txt')
    })

    describe('when the path is unstaged', () => {
      it('resolves false if the path has not been modified', async () => {
        let modified = await repo.isPathModified(filePath)
        expect(modified).toBeFalsy()
      })

      it('resolves true if the path is modified', async () => {
        fs.writeFileSync(filePath, "change")
        let modified = await repo.isPathModified(filePath)
        expect(modified).toBeTruthy()
      })

      it('resolves false if the path is new', async () => {
        let modified = await repo.isPathModified(newPath)
        expect(modified).toBeFalsy()
      })

      it('resolves false if the path is invalid', async () => {
        let modified = await repo.isPathModified(emptyPath)
        expect(modified).toBeFalsy()
      })
    })
  })

  describe('.isPathNew(path)', () => {
    let filePath, newPath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      newPath = path.join(workingDirPath, 'new-path.txt')
      fs.writeFileSync(newPath, "i'm new here")
    })

    describe('when the path is unstaged', () => {
      it('returns true if the path is new', async () => {
        let isNew = await repo.isPathNew(newPath)
        expect(isNew).toBeTruthy()
      })

      it("returns false if the path isn't new", async () => {
        let modified = await repo.isPathModified(newPath)
        expect(modified).toBeFalsy()
      })
    })
  })

  describe('.checkoutHead(path)', () => {
    let filePath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
    })

    it('no longer reports a path as modified after checkout', async () => {
      let modified = await repo.isPathModified(filePath)
      expect(modified).toBeFalsy()

      fs.writeFileSync(filePath, 'ch ch changes')
      terribleSleep()

      modified = await repo.isPathModified(filePath)
      expect(modified).toBeTruthy()

      await repo.checkoutHead(filePath)
      terribleSleep()

      modified = await repo.isPathModified(filePath)
      expect(modified).toBeFalsy()
    })

    it('restores the contents of the path to the original text', async () => {
      fs.writeFileSync(filePath, 'ch ch changes')
      await repo.checkoutHead(filePath)
      xxpect(fs.readFileSync(filePath, 'utf8')).toBe('')
    })

    it('fires a did-change-status event if the checkout completes successfully', async () => {
      fs.writeFileSync(filePath, 'ch ch changes')

      await repo.getPathStatus(filePath)

      let statusHandler = jasmine.createSpy('statusHandler')
      repo.onDidChangeStatus(statusHandler)

      await repo.checkoutHead(filePath)

      await terribleWait(() => statusHandler.callCount > 0)
      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: 0})

      await repo.checkoutHead(filePath)
      expect(statusHandler.callCount).toBe(1)
    })
  })

  xdescribe('.checkoutHeadForEditor(editor)', () => {
    let filePath, editor

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      fs.writeFileSync(filePath, 'ch ch changes')

      waitsForPromise(() => atom.workspace.open(filePath))
      runs(() => editor = atom.workspace.getActiveTextEditor())
    })

    xit('displays a confirmation dialog by default', () => {
      spyOn(atom, 'confirm').andCallFake(buttons, () => buttons[0].OK())
      atom.config.set('editor.confirmCheckoutHeadRevision', true)

      waitsForPromise(() => repo.checkoutHeadForEditor(editor))
      runs(() => expect(fs.readFileSync(filePath, 'utf8')).toBe(''))
    })

    xit('does not display a dialog when confirmation is disabled', () => {
      spyOn(atom, 'confirm')
      atom.config.set('editor.confirmCheckoutHeadRevision', false)

      waitsForPromise(() => repo.checkoutHeadForEditor(editor))
      runs(() => {
        expect(fs.readFileSync(filePath, 'utf8')).toBe('')
        expect(atom.confirm).not.toHaveBeenCalled()
      })
    })
  })

  describe('.getPathStatus(path)', () => {
    let filePath

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      filePath = path.join(workingDirectory, 'file.txt')
    })

    it('trigger a status-changed event when the new status differs from the last cached one', async () => {
      let statusHandler = jasmine.createSpy("statusHandler")
      repo.onDidChangeStatus(statusHandler)
      fs.writeFileSync(filePath, '')

      await repo.getPathStatus(filePath)

      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      let status = Git.Status.STATUS.WT_MODIFIED
      expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
      fs.writeFileSync(filePath, 'abc')

      await repo.getPathStatus(filePath)
      expect(statusHandler.callCount).toBe(1)
    })
  })

  describe('.getDirectoryStatus(path)', () => {
    let directoryPath, filePath

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      directoryPath = path.join(workingDirectory, 'dir')
      filePath = path.join(directoryPath, 'b.txt')
    })

    it('gets the status based on the files inside the directory', async () => {
      await repo.checkoutHead(filePath)
      terribleSleep()

      let result = await repo.getDirectoryStatus(directoryPath)
      expect(repo.isStatusModified(result)).toBe(false)

      fs.writeFileSync(filePath, 'abc')
      terribleSleep()

      result = await repo.getDirectoryStatus(directoryPath)
      expect(repo.isStatusModified(result)).toBe(true)
    })
  })

  describe('.refreshStatus()', () => {
    let newPath, modifiedPath, cleanPath, originalModifiedPathText

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      modifiedPath = path.join(workingDirectory, 'file.txt')
      newPath = path.join(workingDirectory, 'untracked.txt')
      cleanPath = path.join(workingDirectory, 'other.txt')
      fs.writeFileSync(cleanPath, 'Full of text')
      fs.writeFileSync(newPath, '')
      newPath = fs.absolute(newPath) // specs could be running under symbol path.
    })

    it('returns status information for all new and modified files', async () => {
      fs.writeFileSync(modifiedPath, 'making this path modified')
      await repo.refreshStatus()

      expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined()
      expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
      expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
    })
  })

  describe('buffer events', () => {
    beforeEach(() => {
      // This is sync, should be fine in a beforeEach
      atom.project.setPaths([copyRepository()])
    })

    it('emits a status-changed events when a buffer is saved', async () => {
      let editor = await atom.workspace.open('other.txt')

      editor.insertNewline()

      let repository = atom.project.getRepositories()[0].async
      let called
      repository.onDidChangeStatus(c => called = c)
      editor.save()

      await terribleWait(() => Boolean(called))
      expect(called).toEqual({path: editor.getPath(), pathStatus: 256})
    })

    it('emits a status-changed event when a buffer is reloaded', async () => {
      let statusHandler = jasmine.createSpy('statusHandler')
      let reloadHandler = jasmine.createSpy('reloadHandler')

      let editor = await atom.workspace.open('other.txt')

      fs.writeFileSync(editor.getPath(), 'changed')

      let repository = atom.project.getRepositories()[0].async
      repository.onDidChangeStatus(statusHandler)
      editor.getBuffer().reload()

      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})

      let buffer = editor.getBuffer()
      buffer.onDidReload(reloadHandler)
      buffer.reload()

      await terribleWait(() => reloadHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
    })

    it("emits a status-changed event when a buffer's path changes", async () => {
      let editor = await atom.workspace.open('other.txt')

      fs.writeFileSync(editor.getPath(), 'changed')

      let statusHandler = jasmine.createSpy('statusHandler')
      let repository = atom.project.getRepositories()[0].async
      repository.onDidChangeStatus(statusHandler)
      editor.getBuffer().emitter.emit('did-change-path')
      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})

      let pathHandler = jasmine.createSpy('pathHandler')
      let buffer = editor.getBuffer()
      buffer.onDidChangePath(pathHandler)
      buffer.emitter.emit('did-change-path')
      await terribleWait(() => pathHandler.callCount > 0)
      expect(statusHandler.callCount).toBe(1)
    })

    // it('stops listening to the buffer when the repository is destroyed (regression)', () => {
    //   waitsForPromise(() => {
    //     atom.workspace.open('other.txt').then(o => editor = o)
    //   })
    //   runs(() => {
    //     atom.project.getRepositories()[0].destroy()
    //     expect(-> editor.save()).not.toThrow()
    //   })
    // })
  })

})
<commit_msg>add final buffer event spec<commit_after>'use babel'

const fs = require('fs-plus')
const path = require('path')
const temp = require('temp')
const Git = require('nodegit')
const CompositeDisposable = require('event-kit').CompositeDisposable

temp.track()

const GitRepositoryAsync = require('../src/git-repository-async')

const openFixture = (fixture) => {
  return GitRepositoryAsync.open(path.join(__dirname, 'fixtures', 'git', fixture))
}

const copyRepository = () => {
  let workingDirPath = temp.mkdirSync('atom-working-dir')
  fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
  fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
  return fs.realpathSync(workingDirPath)
}

async function terribleWait(fn) {
  const p = new Promise()
  waitsFor(fn)
  runs(() => p.resolve())
}

// Git uses heuristics to avoid having to needlessly hash a file to tell if it
// changed. One of those is mtime. If our tests are running Super Fast, we could
// end up changing a file multiple times within the same mtime tick, which could
// lead git to think the file didn't change at all. So sometimes we'll need to
// sleep. (https://www.kernel.org/pub/software/scm/git/docs/technical/racy-git.txt)
function terribleSleep() {
  for (let _ of range(1, 1000)) { ; }
}

describe('GitRepositoryAsync-js', () => {
  let repo

  afterEach(() => {
    if (repo != null) repo.destroy()
  })

  describe('@open(path)', () => {
    it('repo is null when no repository is found', async () => {
      repo = GitRepositoryAsync.open(path.join(temp.dir, 'nogit.txt'))

      let threw = false
      try {
        await repo.repoPromise
      } catch (e) {
        threw = true
      }

      expect(threw).toBeTruthy()
      expect(repo.repo).toBe(null)
    })
  })

  describe('.getPath()', () => {
    xit('returns the repository path for a .git directory path')

    it('returns the repository path for a repository path', async () => {
      repo = openFixture('master.git')
      let path = await repo.getPath()
      expect(path).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
    })
  })

  describe('.isPathIgnored(path)', () => {
    it('resolves true for an ignored path', async () => {
      repo = openFixture('ignore.git')
      let ignored = await repo.isPathIgnored('a.txt')
      expect(ignored).toBeTruthy()
    })

    it('resolves false for a non-ignored path', async () => {
      repo = openFixture('ignore.git')
      let ignored = await repo.isPathIgnored('b.txt')
      expect(ignored).toBeFalsy()
    })
  })

  describe('.isPathModified(path)', () => {
    let filePath, newPath, emptyPath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      newPath = path.join(workingDirPath, 'new-path.txt')
      fs.writeFileSync(newPath, "i'm new here")
      emptyPath = path.join(workingDirPath, 'empty-path.txt')
    })

    describe('when the path is unstaged', () => {
      it('resolves false if the path has not been modified', async () => {
        let modified = await repo.isPathModified(filePath)
        expect(modified).toBeFalsy()
      })

      it('resolves true if the path is modified', async () => {
        fs.writeFileSync(filePath, "change")
        let modified = await repo.isPathModified(filePath)
        expect(modified).toBeTruthy()
      })

      it('resolves false if the path is new', async () => {
        let modified = await repo.isPathModified(newPath)
        expect(modified).toBeFalsy()
      })

      it('resolves false if the path is invalid', async () => {
        let modified = await repo.isPathModified(emptyPath)
        expect(modified).toBeFalsy()
      })
    })
  })

  describe('.isPathNew(path)', () => {
    let filePath, newPath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      newPath = path.join(workingDirPath, 'new-path.txt')
      fs.writeFileSync(newPath, "i'm new here")
    })

    describe('when the path is unstaged', () => {
      it('returns true if the path is new', async () => {
        let isNew = await repo.isPathNew(newPath)
        expect(isNew).toBeTruthy()
      })

      it("returns false if the path isn't new", async () => {
        let modified = await repo.isPathModified(newPath)
        expect(modified).toBeFalsy()
      })
    })
  })

  describe('.checkoutHead(path)', () => {
    let filePath

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
    })

    it('no longer reports a path as modified after checkout', async () => {
      let modified = await repo.isPathModified(filePath)
      expect(modified).toBeFalsy()

      fs.writeFileSync(filePath, 'ch ch changes')
      terribleSleep()

      modified = await repo.isPathModified(filePath)
      expect(modified).toBeTruthy()

      await repo.checkoutHead(filePath)
      terribleSleep()

      modified = await repo.isPathModified(filePath)
      expect(modified).toBeFalsy()
    })

    it('restores the contents of the path to the original text', async () => {
      fs.writeFileSync(filePath, 'ch ch changes')
      await repo.checkoutHead(filePath)
      xxpect(fs.readFileSync(filePath, 'utf8')).toBe('')
    })

    it('fires a did-change-status event if the checkout completes successfully', async () => {
      fs.writeFileSync(filePath, 'ch ch changes')

      await repo.getPathStatus(filePath)

      let statusHandler = jasmine.createSpy('statusHandler')
      repo.onDidChangeStatus(statusHandler)

      await repo.checkoutHead(filePath)

      await terribleWait(() => statusHandler.callCount > 0)
      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: 0})

      await repo.checkoutHead(filePath)
      expect(statusHandler.callCount).toBe(1)
    })
  })

  xdescribe('.checkoutHeadForEditor(editor)', () => {
    let filePath, editor

    beforeEach(() => {
      let workingDirPath = copyRepository()
      repo = GitRepositoryAsync.open(workingDirPath)
      filePath = path.join(workingDirPath, 'a.txt')
      fs.writeFileSync(filePath, 'ch ch changes')

      waitsForPromise(() => atom.workspace.open(filePath))
      runs(() => editor = atom.workspace.getActiveTextEditor())
    })

    xit('displays a confirmation dialog by default', () => {
      spyOn(atom, 'confirm').andCallFake(buttons, () => buttons[0].OK())
      atom.config.set('editor.confirmCheckoutHeadRevision', true)

      waitsForPromise(() => repo.checkoutHeadForEditor(editor))
      runs(() => expect(fs.readFileSync(filePath, 'utf8')).toBe(''))
    })

    xit('does not display a dialog when confirmation is disabled', () => {
      spyOn(atom, 'confirm')
      atom.config.set('editor.confirmCheckoutHeadRevision', false)

      waitsForPromise(() => repo.checkoutHeadForEditor(editor))
      runs(() => {
        expect(fs.readFileSync(filePath, 'utf8')).toBe('')
        expect(atom.confirm).not.toHaveBeenCalled()
      })
    })
  })

  describe('.getPathStatus(path)', () => {
    let filePath

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      filePath = path.join(workingDirectory, 'file.txt')
    })

    it('trigger a status-changed event when the new status differs from the last cached one', async () => {
      let statusHandler = jasmine.createSpy("statusHandler")
      repo.onDidChangeStatus(statusHandler)
      fs.writeFileSync(filePath, '')

      await repo.getPathStatus(filePath)

      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      let status = Git.Status.STATUS.WT_MODIFIED
      expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
      fs.writeFileSync(filePath, 'abc')

      await repo.getPathStatus(filePath)
      expect(statusHandler.callCount).toBe(1)
    })
  })

  describe('.getDirectoryStatus(path)', () => {
    let directoryPath, filePath

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      directoryPath = path.join(workingDirectory, 'dir')
      filePath = path.join(directoryPath, 'b.txt')
    })

    it('gets the status based on the files inside the directory', async () => {
      await repo.checkoutHead(filePath)
      terribleSleep()

      let result = await repo.getDirectoryStatus(directoryPath)
      expect(repo.isStatusModified(result)).toBe(false)

      fs.writeFileSync(filePath, 'abc')
      terribleSleep()

      result = await repo.getDirectoryStatus(directoryPath)
      expect(repo.isStatusModified(result)).toBe(true)
    })
  })

  describe('.refreshStatus()', () => {
    let newPath, modifiedPath, cleanPath, originalModifiedPathText

    beforeEach(() => {
      let workingDirectory = copyRepository()
      repo = GitRepositoryAsync.open(workingDirectory)
      modifiedPath = path.join(workingDirectory, 'file.txt')
      newPath = path.join(workingDirectory, 'untracked.txt')
      cleanPath = path.join(workingDirectory, 'other.txt')
      fs.writeFileSync(cleanPath, 'Full of text')
      fs.writeFileSync(newPath, '')
      newPath = fs.absolute(newPath) // specs could be running under symbol path.
    })

    it('returns status information for all new and modified files', async () => {
      fs.writeFileSync(modifiedPath, 'making this path modified')
      await repo.refreshStatus()

      expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined()
      expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
      expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
    })
  })

  describe('buffer events', () => {
    beforeEach(() => {
      // This is sync, should be fine in a beforeEach
      atom.project.setPaths([copyRepository()])
    })

    it('emits a status-changed events when a buffer is saved', async () => {
      let editor = await atom.workspace.open('other.txt')

      editor.insertNewline()

      let repository = atom.project.getRepositories()[0].async
      let called
      repository.onDidChangeStatus(c => called = c)
      editor.save()

      await terribleWait(() => Boolean(called))
      expect(called).toEqual({path: editor.getPath(), pathStatus: 256})
    })

    it('emits a status-changed event when a buffer is reloaded', async () => {
      let statusHandler = jasmine.createSpy('statusHandler')
      let reloadHandler = jasmine.createSpy('reloadHandler')

      let editor = await atom.workspace.open('other.txt')

      fs.writeFileSync(editor.getPath(), 'changed')

      let repository = atom.project.getRepositories()[0].async
      repository.onDidChangeStatus(statusHandler)
      editor.getBuffer().reload()

      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})

      let buffer = editor.getBuffer()
      buffer.onDidReload(reloadHandler)
      buffer.reload()

      await terribleWait(() => reloadHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
    })

    it("emits a status-changed event when a buffer's path changes", async () => {
      let editor = await atom.workspace.open('other.txt')

      fs.writeFileSync(editor.getPath(), 'changed')

      let statusHandler = jasmine.createSpy('statusHandler')
      let repository = atom.project.getRepositories()[0].async
      repository.onDidChangeStatus(statusHandler)
      editor.getBuffer().emitter.emit('did-change-path')
      await terribleWait(() => statusHandler.callCount > 0)

      expect(statusHandler.callCount).toBe(1)
      expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})

      let pathHandler = jasmine.createSpy('pathHandler')
      let buffer = editor.getBuffer()
      buffer.onDidChangePath(pathHandler)
      buffer.emitter.emit('did-change-path')
      await terribleWait(() => pathHandler.callCount > 0)
      expect(statusHandler.callCount).toBe(1)
    })

    it('stops listening to the buffer when the repository is destroyed (regression)', function () {
      let editor
      waitsForPromise(async function () {
        editor = await atom.workspace.open('other.txt')
      })
      runs(function () {
        atom.project.getRepositories()[0].destroy()
        expect(function () { editor.save() }).not.toThrow()
      })
    })
  })

})
<|endoftext|>


<commit_before>var errorHandlerFactory = require('../services/error_handler_factory');

module.exports = function errorMiddleware() {
    return function error(err, req, res, next) {
        const errorHandler = errorHandlerFactory(err);
        let errorResponse = errorHandler.getResponse();

        if (global.settings.environment === 'development') {
            errorResponse.stack = err.stack;
        }

        if (global.settings.environment !== 'test') {
            // TODO: email this Exception report
            console.error("EXCEPTION REPORT: " + err.stack);
        }

        // Force inline content disposition
        res.header("Content-Disposition", 'inline');

        if (req && req.profiler) {
            req.profiler.done('finish');
            res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
        }

        setErrorHeader(errorHandler, res);

        res.header('Content-Type', 'application/json; charset=utf-8');
        res.status(getStatusError(errorHandler, req));
        if (req.query && req.query.callback) {
            res.jsonp(errorResponse);
        } else {
            res.json(errorResponse);
        }

        if (req && req.profiler) {
            res.req.profiler.sendStats();
        }

        next();
    };
};

function getStatusError(errorHandler, req) {

    var statusError = errorHandler.http_status;

    // JSONP has to return 200 status error
    if (req && req.query && req.query.callback) {
        statusError = 200;
    }

    return statusError;
}

function setErrorHeader(errorHandler, res) {
    const errorsLog = {
        context: errorHandler.context,
        detail: errorHandler.detail,
        hint: errorHandler.hint,
        statusCode: errorHandler.http_status,
        message: errorHandler.message
    };

    res.set('X-SQLAPI-Errors', stringifyForLogs(errorsLog));
}

/**
 * Remove problematic nested characters
 * from object for logs RegEx
 *
 * @param {Object} object
 */
function stringifyForLogs(object) {
    Object.keys(object).map(key => {
        if (typeof object[key] === 'string') {
            object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
        } else if (typeof object[key] === 'object') {
            stringifyForLogs(object[key]);
        } else if (object[key] instanceof Array) {
            for (let element of object[key]) {
                stringifyForLogs(element);
            }
        }
    });

    return JSON.stringify(object);
}
<commit_msg>let & const vars<commit_after>const errorHandlerFactory = require('../services/error_handler_factory');

module.exports = function errorMiddleware() {
    return function error(err, req, res, next) {
        const errorHandler = errorHandlerFactory(err);
        let errorResponse = errorHandler.getResponse();

        if (global.settings.environment === 'development') {
            errorResponse.stack = err.stack;
        }

        if (global.settings.environment !== 'test') {
            // TODO: email this Exception report
            console.error("EXCEPTION REPORT: " + err.stack);
        }

        // Force inline content disposition
        res.header("Content-Disposition", 'inline');

        if (req && req.profiler) {
            req.profiler.done('finish');
            res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
        }

        setErrorHeader(errorHandler, res);

        res.header('Content-Type', 'application/json; charset=utf-8');
        res.status(getStatusError(errorHandler, req));
        if (req.query && req.query.callback) {
            res.jsonp(errorResponse);
        } else {
            res.json(errorResponse);
        }

        if (req && req.profiler) {
            res.req.profiler.sendStats();
        }

        next();
    };
};

function getStatusError(errorHandler, req) {
    let statusError = errorHandler.http_status;

    // JSONP has to return 200 status error
    if (req && req.query && req.query.callback) {
        statusError = 200;
    }

    return statusError;
}

function setErrorHeader(errorHandler, res) {
    const errorsLog = {
        context: errorHandler.context,
        detail: errorHandler.detail,
        hint: errorHandler.hint,
        statusCode: errorHandler.http_status,
        message: errorHandler.message
    };

    res.set('X-SQLAPI-Errors', stringifyForLogs(errorsLog));
}

/**
 * Remove problematic nested characters
 * from object for logs RegEx
 *
 * @param {Object} object
 */
function stringifyForLogs(object) {
    Object.keys(object).map(key => {
        if (typeof object[key] === 'string') {
            object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
        } else if (typeof object[key] === 'object') {
            stringifyForLogs(object[key]);
        } else if (object[key] instanceof Array) {
            for (let element of object[key]) {
                stringifyForLogs(element);
            }
        }
    });

    return JSON.stringify(object);
}
<|endoftext|>


<commit_before>'use strict';

var megamark = require('megamark');
var insane = require('insane');
var textService = require('./text');
var domains = [
  '//codepen.io/',
  '//assets.codepen.io/',
  'http://codepen.io/',
  'http://assets.codepen.io/'
];

megamark.parser.renderer.rules.link_open = link;

module.exports = {
  compile: compile
};

function getLabel (title) {
  var trimmedTitle = title ? title.trim() : '';
  if (trimmedTitle) {
    return textService.format(' aria-label="%s"', trimmedTitle);
  }
  return '';
}

function attr (token, name) {
  if (token[name]) {
    return token[name];
  }
  if (!token.attrs) {
    return null;
  }
  if (token.attrs[name]) {
    return token.attrs[name];
  }
  var i = 0;
  var len = token.attrs.length;
  for (; i < len; i++) {
    if (token.attrs[i][0] === name) {
      return token.attrs[i][1];
    }
  }
  return null;
}

function link (tokens, i) {
  var fmt = '';
  var open = tokens[i];
  var href = attr(open, 'href');
  var title = attr(open, 'title');
  var html = textService.format(fmt, href, getLabel(title));
  return html;
}

function filter (token) {
  var unsourced = token.tag !== 'iframe' && token.tag !== 'script';
  return unsourced || domains.some(starts);
  function starts (beginning) {
    var src = attr(token, 'src');
    return src && src.indexOf(beginning) === 0;
  }
}

function compile (text) {
  return megamark(text, {
    sanitizer: {
      filter: filter,
      allowedTags: insane.defaults.allowedTags.concat('iframe', 'script'),
      allowedAttributes: {
        script: ['src', 'async'],
        p: ['data-height', 'data-theme-id', 'data-slug-hash', 'data-default-tab', 'data-user']
      },
      allowedClasses: {
        p: ['codepen']
      }
    }
  });
}
<commit_msg>tweets!<commit_after>'use strict';

var megamark = require('megamark');
var insane = require('insane');
var textService = require('./text');
var domains = [
  '//codepen.io/',
  '//assets.codepen.io/',
  'http://codepen.io/',
  'http://assets.codepen.io/'
];

megamark.parser.renderer.rules.link_open = link;

module.exports = {
  compile: compile
};

function getLabel (title) {
  var trimmedTitle = title ? title.trim() : '';
  if (trimmedTitle) {
    return textService.format(' aria-label="%s"', trimmedTitle);
  }
  return '';
}

function attr (token, name) {
  if (token[name]) {
    return token[name];
  }
  if (!token.attrs) {
    return null;
  }
  if (token.attrs[name]) {
    return token.attrs[name];
  }
  var i = 0;
  var len = token.attrs.length;
  for (; i < len; i++) {
    if (token.attrs[i][0] === name) {
      return token.attrs[i][1];
    }
  }
  return null;
}

function link (tokens, i) {
  var fmt = '';
  var open = tokens[i];
  var href = attr(open, 'href');
  var title = attr(open, 'title');
  var html = textService.format(fmt, href, getLabel(title));
  return html;
}

function filter (token) {
  var unsourced = token.tag !== 'iframe' && token.tag !== 'script';
  return unsourced || domains.some(starts);
  function starts (beginning) {
    var src = attr(token, 'src');
    return src && src.indexOf(beginning) === 0;
  }
}

function compile (text) {
  return megamark(text, {
    sanitizer: {
      filter: filter,
      allowedTags: insane.defaults.allowedTags.concat('iframe', 'script'),
      allowedAttributes: {
        script: ['src', 'async'],
        p: ['data-height', 'data-theme-id', 'data-slug-hash', 'data-default-tab', 'data-user']
      },
      allowedClasses: {
        p: ['codepen'],
        blockquote: ['twitter-tweet']
      }
    }
  });
}
<|endoftext|>