<commit_before>(function($angular) {

    "use strict";

    // The truest wisdom is a resolute determination...
    $angular.module('ngDroplet', []).directive('droplet', ['$rootScope', '$window', '$timeout',

    function DropletDirective($rootScope, $window, $timeout) {

        return {

            /**
             * @property restrict
             * @type {String}
             */
            restrict: 'EA',

            /**
             * @property template
             * @type {String}
             */
            template: '<droplet-preview ng-repeat="file in filterFiles(FILE_TYPES.VALID)" ng-model="file"></droplet-preview>',

            /**
             * @property require
             * @type {String}
             */
            require: '?ngModel',

            /**
             * @property scope
             * @type {Object}
             */
            scope: {
                directiveInterface: '=ngModel'
            },

            /**
             * @method controller
             * @param $scope {Object}
             * @return {void}
             */
            controller: ['$scope', function DropletController($scope) {

                /**
                 * @constant FILE_TYPES
                 * @type {Object}
                 */
                $scope.FILE_TYPES = { VALID: 1, INVALID: 2, DELETED: 4, UPLOADED: 8 };

                /**
                 * @property files
                 * @type {Array}
                 */
                $scope.files = [];

                /**
                 * @property options
                 * @type {Object}
                 */
                $scope.options = {
                    disableXFileSize: false
                };

                /**
                 * @property extensions
                 * @type {Array}
                 */
                $scope.extensions = [];

                /**
                 * @property requestUrl
                 * @type {String}
                 */
                $scope.requestUrl = '';

                /**
                 * @property requestHeaders
                 * @type {Object}
                 */
                $scope.requestHeaders = {};

                /**
                 * @property listeners
                 * @type {Object}
                 */
                $scope.listeners = {

                    /**
                     * Invoked once the HTTP request has been successfully completed.
                     *
                     * @method success
                     * @param httpRequest {XMLHttpRequest}
                     * @return {void}
                     */
                    success: function success(httpRequest) {

                        httpRequest.upload.onload = function onLoad() {

                            $scope.$apply(function apply() {

                                $scope.forEachFile($scope.FILE_TYPES.VALID, function forEach(model) {

                                    // Advance the status of the file to that of an uploaded file.
                                    model.type = $scope.FILE_TYPES.UPLOADED;

                                });

                            });

                        };

                    }

                };

                /**
                 * Utility method for iterating over files of a given type.
                 *
                 * @method forEachFile
                 * @param type {Number}
                 * @param callbackFn {Function}
                 * @return {void}
                 */
                $scope.forEachFile = function forEachFile(type, callbackFn) {

                    $angular.forEach($scope.filterFiles($scope.FILE_TYPES.VALID), function forEach(model) {
                        callbackFn(model);
                    });

                };

                /**
                 * @method registerFile
                 * @param file {File}
                 * @return {Function}
                 */
                $scope.registerFile = function registerFile(file) {

                    /**
                     * @method registerFileType
                     * @param type {Number}
                     * @return {void}
                     */
                    return function registerFileType(type) {

                        // If the developer didn't specify the type then we'll assume it's a valid file
                        // that they're adding.
                        type = type || $scope.FILE_TYPES.VALID;

                        // Create the model and then register the file.
                        var model = { file: file, type: type, added: new $window.Date(), mimeType: file.type,
                                      extension: $scope.getExtension(file) };
                        $scope.files.push(model);

                    }

                };

                /**
                 * @method filterFiles
                 * @param type {Number}
                 * @return {Array}
                 */
                $scope.filterFiles = function filterFiles(type) {

                    return $scope.files.filter(function filter(file) {
                        return type & file.type;
                    });

                };

                /**
                 * @method getExtension
                 * @param file {File}
                 * @return {String}
                 */
                $scope.getExtension = function getExtension(file) {
                    return file.name.split('.').pop().trim().toLowerCase();
                };

                /**
                 * @method traverseFiles
                 * @param files {FileList}
                 * @return {void}
                 */
                $scope.traverseFiles = function traverseFiles(files) {

                    $scope.$apply(function apply() {

                        for (var index = 0, numFiles = files.length; index < numFiles; index++) {

                            var file      = files[index],
                                extension = $scope.getExtension(file),
                                type      = $scope.FILE_TYPES.VALID;

                            if ($scope.extensions.indexOf(extension) === -1) {

                                // Invalid extension which we must reject!
                                type = $scope.FILE_TYPES.INVALID

                            }

                            // Finally we'll register the file using the type that has been deduced.
                            $scope.registerFile(file)(type);

                        }

                    });

                };

                /**
                 * @method uploadFiles
                 * @return {void}
                 */
                $scope.uploadFiles = function uploadFiles() {

                    var httpRequest = new $window.XMLHttpRequest(),
                        formData    = new $window.FormData(),
                        queuedFiles = $scope.filterFiles($scope.FILE_TYPES.VALID);

                    // Initiate the HTTP request.
                    httpRequest.open('post', $scope.requestUrl, true);

                    // Iterate all of the valid files to append them to the previously created
                    // `formData` object.
                    $angular.forEach(queuedFiles, function forEach(model) {
                        formData.append('file', model.file);
                    });

                    if (!$scope.options.disableXFileSize) {

                        // Setup the file size of the request.
                        httpRequest.setRequestHeader('X-File-Size', $scope.getRequestLength(queuedFiles));

                    }

                    // ...And any other additional HTTP request headers.
                    $scope.addRequestHeaders(httpRequest);

                    // Configure the event listeners for the impending request.
                    $scope.listeners.success(httpRequest);

                    httpRequest.send(formData);

                };

                /**
                 * Iterate over any additional headers added by the developer and append to the current
                 * request being generated.
                 *
                 * @method addRequestHeaders
                 * @param httpRequest {XMLHttpRequest}
                 * @return {Array}
                 */
                $scope.addRequestHeaders = function addRequestHeaders(httpRequest) {

                    for (var header in $scope.requestHeaders) {

                        if ($scope.requestHeaders.hasOwnProperty(header)) {
                            httpRequest.setRequestHeader(header, $scope.requestHeaders[header]);
                        }

                    }

                    return Object.keys($scope.requestHeaders);

                };

                /**
                 * Determine the size of the request based on the files preparing to be uploaded.
                 *
                 * @method getRequestLength
                 * @param [files=[]] {Array}
                 * @return {Number}
                 * @private
                 */
                $scope.getRequestLength = function getRequestLength(files) {

                    var size = 0;

                    // Iterate over all of the files to determine the size of all valid files.
                    $angular.forEach(files || $scope.filterFiles($scope.FILE_TYPES.VALID), function forEach(model) {
                        size += model.file.size;
                    });

                    return size;

                };

                /**
                 * @method throwException
                 * @param message {String}
                 * @return {void}
                 */
                $scope.throwException = function throwException(message) {
                    throw "ngDroplet: " + message + ".";
                };

                /**
                 * Responsible for setting up the interface that allows the developer to interact
                 * with the directive from outside.
                 *
                 * @method setupDirectiveInterface
                 * @return {void}
                 */
                (function setupDirectiveInterface() {

                    /**
                     * @property directiveInterface
                     * @type {Object}
                     */
                    $scope.directiveInterface = {

                        /**
                         * @constant FILE_TYPES
                         * @type {Object}
                         */
                        FILE_TYPES: $scope.FILE_TYPES,

                        /**
                         * @method uploadFiles
                         * @return {void}
                         */
                        uploadFiles: $scope.uploadFiles,

                        /**
                         * @method addFile
                         * @param file {File}
                         * @param type {Number}
                         * @return {void}
                         */
                        addFile: function addFile(file, type) {
                            $scope.registerFile(file)(type || $scope.FILE_TYPES.VALID);
                        },

                        /**
                         * @method disableXFileSize
                         * @return {void}
                         */
                        disableXFileSize: function disableXFileSize() {
                            $scope.options.disableXFileSize = true;
                        },

                        /**
                         * @method setRequestUrl
                         * @param url {String}
                         * @return {void}
                         */
                        setRequestUrl: function setRequestUrl(url) {
                            $scope.requestUrl = url;
                        },

                        /**
                         * @method setRequestHeaders
                         * @param headers {Object}
                         * @return {void}
                         */
                        setRequestHeaders: function setRequestHeaders(headers) {
                            $scope.requestHeaders = headers;
                        },

                        /**
                         * @method getFiles
                         * @param type {Number}
                         * @return {Array}
                         */
                        getFiles: function getFiles(type) {

                            if (type) {

                                // Apply any necessary filters if a bitwise value has been supplied.
                                return $scope.filterFiles(type);

                            }

                            // Otherwise we'll yield the entire set of files.
                            return $scope.files;

                        },

                        /**
                         * @method allowedExtensions
                         * @param extensions {Array}
                         * @return {void}
                         */
                        allowedExtensions: function allowedExtensions(extensions) {

                            if (!$angular.isArray(extensions)) {

                                // Developer didn't pass an array of extensions!
                                $scope.throwException('Extensions must be an array');

                            }

                            $scope.extensions = extensions;

                        }

                    };

                    $timeout(function timeout() {

                        // Emit the event to notify any listening scopes that the interface has been attached
                        // for communicating with the directive.
                        $rootScope.$broadcast('$dropletReady');

                    });

                })();

            }],

            /**
             * @method link
             * @param scope {Object}
             * @param element {Object}
             * @return {void}
             */
            link: function link(scope, element) {

                /**
                 * @method _preventDefault
                 * @param event {Object}
                 * @return {void}
                 * @private
                 */
                var _preventDefault = function _preventDefault(event) {

                    event.preventDefault();
                    event.stopPropagation();

                };

                // Events that merely need their default behaviour quelling.
                element.bind('dragover dragenter dragleave', _preventDefault);

                // Bind to the "drop" event which will contain the items the user dropped
                // onto the element.
                element.bind('drop', function onDrop(event) {

                    _preventDefault(event);
                    scope.traverseFiles(event.dataTransfer.files);

                });

            }

        }

    }]).directive('dropletPreview', ['$window', function DropletPreviewDirective($window) {

        return {

            /**
             * @property scope
             * @type {Object}
             */
            scope: {
                model: '=ngModel'
            },

            /**
             * @property restrict
             * @type {String}
             */
            restrict: 'E',

            /**
             * @property replace
             * @type {Boolean}
             */
            replace: true,

            /**
             * @property template
             * @type {String}
             */
            template: '

{{model.file.name}}

',

            /**
             * @method controller
             * @param $scope {Object}
             * @return {void}
             */
            controller: ['$scope', function controller($scope) {

                /**
                 * @method isImage
                 * @param file {File}
                 * @return {Boolean}
                 */
                $scope.isImage = function isImage(file) {
                    return !!file.type.match(/^image\//i);
                };

            }],

            /**
             * @method link
             * @param scope {Object}
             * @return {void}
             */
            link: function link(scope) {

                /**
                 * @property imageData
                 * @type {String}
                 */
                scope.imageData = '';

                // Instantiate the file reader for reading the image data.
                var fileReader = new $window.FileReader();

                /**
                 * @method onload
                 * @return {void}
                 */
                fileReader.onload = function onload(event) {

                    scope.$apply(function apply() {

                        // Voila! Define the image data.
                        scope.imageData = event.target.result;

                    });

                };

                if (scope.isImage(scope.model.file)) {

                    // Initialise the loading of the image into the file reader.
                    fileReader.readAsDataURL(scope.model.file);

                }

            }

        }

    }]);

})(window.angular);<commit_msg>Added useArray option<commit_after>(function($angular) {

    "use strict";

    // The truest wisdom is a resolute determination...
    $angular.module('ngDroplet', []).directive('droplet', ['$rootScope', '$window', '$timeout',

    function DropletDirective($rootScope, $window, $timeout) {

        return {

            /**
             * @property restrict
             * @type {String}
             */
            restrict: 'EA',

            /**
             * @property template
             * @type {String}
             */
            template: '<droplet-preview ng-repeat="file in filterFiles(FILE_TYPES.VALID)" ng-model="file"></droplet-preview>',

            /**
             * @property require
             * @type {String}
             */
            require: '?ngModel',

            /**
             * @property scope
             * @type {Object}
             */
            scope: {
                directiveInterface: '=ngModel'
            },

            /**
             * @method controller
             * @param $scope {Object}
             * @return {void}
             */
            controller: ['$scope', function DropletController($scope) {

                /**
                 * @constant FILE_TYPES
                 * @type {Object}
                 */
                $scope.FILE_TYPES = { VALID: 1, INVALID: 2, DELETED: 4, UPLOADED: 8 };

                /**
                 * @property files
                 * @type {Array}
                 */
                $scope.files = [];

                /**
                 * @property options
                 * @type {Object}
                 */
                $scope.options = {
                    disableXFileSize: false,
                    useArray: true
                };

                /**
                 * @property extensions
                 * @type {Array}
                 */
                $scope.extensions = [];

                /**
                 * @property requestUrl
                 * @type {String}
                 */
                $scope.requestUrl = '';

                /**
                 * @property requestHeaders
                 * @type {Object}
                 */
                $scope.requestHeaders = {};

                /**
                 * @property listeners
                 * @type {Object}
                 */
                $scope.listeners = {

                    /**
                     * Invoked once the HTTP request has been successfully completed.
                     *
                     * @method success
                     * @param httpRequest {XMLHttpRequest}
                     * @return {void}
                     */
                    success: function success(httpRequest) {

                        httpRequest.upload.onload = function onLoad() {

                            $scope.$apply(function apply() {

                                $scope.forEachFile($scope.FILE_TYPES.VALID, function forEach(model) {

                                    // Advance the status of the file to that of an uploaded file.
                                    model.type = $scope.FILE_TYPES.UPLOADED;

                                });

                            });

                        };

                    }

                };

                /**
                 * Utility method for iterating over files of a given type.
                 *
                 * @method forEachFile
                 * @param type {Number}
                 * @param callbackFn {Function}
                 * @return {void}
                 */
                $scope.forEachFile = function forEachFile(type, callbackFn) {

                    $angular.forEach($scope.filterFiles($scope.FILE_TYPES.VALID), function forEach(model) {
                        callbackFn(model);
                    });

                };

                /**
                 * @method registerFile
                 * @param file {File}
                 * @return {Function}
                 */
                $scope.registerFile = function registerFile(file) {

                    /**
                     * @method registerFileType
                     * @param type {Number}
                     * @return {void}
                     */
                    return function registerFileType(type) {

                        // If the developer didn't specify the type then we'll assume it's a valid file
                        // that they're adding.
                        type = type || $scope.FILE_TYPES.VALID;

                        // Create the model and then register the file.
                        var model = { file: file, type: type, added: new $window.Date(), mimeType: file.type,
                                      extension: $scope.getExtension(file) };
                        $scope.files.push(model);

                    }

                };

                /**
                 * @method filterFiles
                 * @param type {Number}
                 * @return {Array}
                 */
                $scope.filterFiles = function filterFiles(type) {

                    return $scope.files.filter(function filter(file) {
                        return type & file.type;
                    });

                };

                /**
                 * @method getExtension
                 * @param file {File}
                 * @return {String}
                 */
                $scope.getExtension = function getExtension(file) {
                    return file.name.split('.').pop().trim().toLowerCase();
                };

                /**
                 * @method traverseFiles
                 * @param files {FileList}
                 * @return {void}
                 */
                $scope.traverseFiles = function traverseFiles(files) {

                    $scope.$apply(function apply() {

                        for (var index = 0, numFiles = files.length; index < numFiles; index++) {

                            var file      = files[index],
                                extension = $scope.getExtension(file),
                                type      = $scope.FILE_TYPES.VALID;

                            if ($scope.extensions.indexOf(extension) === -1) {

                                // Invalid extension which we must reject!
                                type = $scope.FILE_TYPES.INVALID

                            }

                            // Finally we'll register the file using the type that has been deduced.
                            $scope.registerFile(file)(type);

                        }

                    });

                };

                /**
                 * @method uploadFiles
                 * @return {void}
                 */
                $scope.uploadFiles = function uploadFiles() {

                    var httpRequest  = new $window.XMLHttpRequest(),
                        formData     = new $window.FormData(),
                        queuedFiles  = $scope.filterFiles($scope.FILE_TYPES.VALID),
                        fileProperty = $scope.options.useArray ? 'file[]' : 'file';

                    // Initiate the HTTP request.
                    httpRequest.open('post', $scope.requestUrl, true);

                    // Iterate all of the valid files to append them to the previously created
                    // `formData` object.
                    $angular.forEach(queuedFiles, function forEach(model) {
                        formData.append(fileProperty, model.file);
                    });

                    if (!$scope.options.disableXFileSize) {

                        // Setup the file size of the request.
                        httpRequest.setRequestHeader('X-File-Size', $scope.getRequestLength(queuedFiles));

                    }

                    // ...And any other additional HTTP request headers.
                    $scope.addRequestHeaders(httpRequest);

                    // Configure the event listeners for the impending request.
                    $scope.listeners.success(httpRequest);

                    httpRequest.send(formData);

                };

                /**
                 * Iterate over any additional headers added by the developer and append to the current
                 * request being generated.
                 *
                 * @method addRequestHeaders
                 * @param httpRequest {XMLHttpRequest}
                 * @return {Array}
                 */
                $scope.addRequestHeaders = function addRequestHeaders(httpRequest) {

                    for (var header in $scope.requestHeaders) {

                        if ($scope.requestHeaders.hasOwnProperty(header)) {
                            httpRequest.setRequestHeader(header, $scope.requestHeaders[header]);
                        }

                    }

                    return Object.keys($scope.requestHeaders);

                };

                /**
                 * Determine the size of the request based on the files preparing to be uploaded.
                 *
                 * @method getRequestLength
                 * @param [files=[]] {Array}
                 * @return {Number}
                 * @private
                 */
                $scope.getRequestLength = function getRequestLength(files) {

                    var size = 0;

                    // Iterate over all of the files to determine the size of all valid files.
                    $angular.forEach(files || $scope.filterFiles($scope.FILE_TYPES.VALID), function forEach(model) {
                        size += model.file.size;
                    });

                    return size;

                };

                /**
                 * @method throwException
                 * @param message {String}
                 * @return {void}
                 */
                $scope.throwException = function throwException(message) {
                    throw "ngDroplet: " + message + ".";
                };

                /**
                 * Responsible for setting up the interface that allows the developer to interact
                 * with the directive from outside.
                 *
                 * @method setupDirectiveInterface
                 * @return {void}
                 */
                (function setupDirectiveInterface() {

                    /**
                     * @property directiveInterface
                     * @type {Object}
                     */
                    $scope.directiveInterface = {

                        /**
                         * @constant FILE_TYPES
                         * @type {Object}
                         */
                        FILE_TYPES: $scope.FILE_TYPES,

                        /**
                         * @method uploadFiles
                         * @return {void}
                         */
                        uploadFiles: $scope.uploadFiles,

                        /**
                         * @method addFile
                         * @param file {File}
                         * @param type {Number}
                         * @return {void}
                         */
                        addFile: function addFile(file, type) {
                            $scope.registerFile(file)(type || $scope.FILE_TYPES.VALID);
                        },

                        /**
                         * @method disableXFileSize
                         * @return {void}
                         */
                        disableXFileSize: function disableXFileSize() {
                            $scope.options.disableXFileSize = true;
                        },

                        /**
                         * @method useArray
                         * @param value {Boolean}
                         * @return {void}
                         */
                        useArray: function useArray(value) {
                            $scope.options.useArray = !!value;
                        },

                        /**
                         * @method setRequestUrl
                         * @param url {String}
                         * @return {void}
                         */
                        setRequestUrl: function setRequestUrl(url) {
                            $scope.requestUrl = url;
                        },

                        /**
                         * @method setRequestHeaders
                         * @param headers {Object}
                         * @return {void}
                         */
                        setRequestHeaders: function setRequestHeaders(headers) {
                            $scope.requestHeaders = headers;
                        },

                        /**
                         * @method getFiles
                         * @param type {Number}
                         * @return {Array}
                         */
                        getFiles: function getFiles(type) {

                            if (type) {

                                // Apply any necessary filters if a bitwise value has been supplied.
                                return $scope.filterFiles(type);

                            }

                            // Otherwise we'll yield the entire set of files.
                            return $scope.files;

                        },

                        /**
                         * @method allowedExtensions
                         * @param extensions {Array}
                         * @return {void}
                         */
                        allowedExtensions: function allowedExtensions(extensions) {

                            if (!$angular.isArray(extensions)) {

                                // Developer didn't pass an array of extensions!
                                $scope.throwException('Extensions must be an array');

                            }

                            $scope.extensions = extensions;

                        }

                    };

                    $timeout(function timeout() {

                        // Emit the event to notify any listening scopes that the interface has been attached
                        // for communicating with the directive.
                        $rootScope.$broadcast('$dropletReady');

                    });

                })();

            }],

            /**
             * @method link
             * @param scope {Object}
             * @param element {Object}
             * @return {void}
             */
            link: function link(scope, element) {

                /**
                 * @method _preventDefault
                 * @param event {Object}
                 * @return {void}
                 * @private
                 */
                var _preventDefault = function _preventDefault(event) {

                    event.preventDefault();
                    event.stopPropagation();

                };

                // Events that merely need their default behaviour quelling.
                element.bind('dragover dragenter dragleave', _preventDefault);

                // Bind to the "drop" event which will contain the items the user dropped
                // onto the element.
                element.bind('drop', function onDrop(event) {

                    _preventDefault(event);
                    scope.traverseFiles(event.dataTransfer.files);

                });

            }

        }

    }]).directive('dropletPreview', ['$window', function DropletPreviewDirective($window) {

        return {

            /**
             * @property scope
             * @type {Object}
             */
            scope: {
                model: '=ngModel'
            },

            /**
             * @property restrict
             * @type {String}
             */
            restrict: 'E',

            /**
             * @property replace
             * @type {Boolean}
             */
            replace: true,

            /**
             * @property template
             * @type {String}
             */
            template: '

{{model.file.name}}

',

            /**
             * @method controller
             * @param $scope {Object}
             * @return {void}
             */
            controller: ['$scope', function controller($scope) {

                /**
                 * @method isImage
                 * @param file {File}
                 * @return {Boolean}
                 */
                $scope.isImage = function isImage(file) {
                    return !!file.type.match(/^image\//i);
                };

            }],

            /**
             * @method link
             * @param scope {Object}
             * @return {void}
             */
            link: function link(scope) {

                /**
                 * @property imageData
                 * @type {String}
                 */
                scope.imageData = '';

                // Instantiate the file reader for reading the image data.
                var fileReader = new $window.FileReader();

                /**
                 * @method onload
                 * @return {void}
                 */
                fileReader.onload = function onload(event) {

                    scope.$apply(function apply() {

                        // Voila! Define the image data.
                        scope.imageData = event.target.result;

                    });

                };

                if (scope.isImage(scope.model.file)) {

                    // Initialise the loading of the image into the file reader.
                    fileReader.readAsDataURL(scope.model.file);

                }

            }

        }

    }]);

})(window.angular);<|endoftext|>


<commit_before>requirejs.config({
    app_name: 'lab',
    paths: {
      'api': 'api',
      'components': 'components',
      'app': 'app'
  },
  shim: {
        'api': {
            exports: 'api',
        },
        'components': {
            exports: 'components'
        }
    }
});

require(['components', 'api', 'app'], function(comp, api, app){
    'use strict';

	app.init(comp, api);
});
<commit_msg>config.js added events.js, lodash and jQuery<commit_after>requirejs.config({
    app_name: 'lab',
    paths: {
      'api': 'api',
      'components': 'components',
      'app': 'app',
      'events': 'events',
      // vendor
      'jquery': '../bower_components/jquery/dist/jquery.min',
      'lodash': '../bower_components/lodash/lodash.min'
  },
  shim: {
        'api': {
            exports: 'api',
        },
        'components': {
            exports: 'components'
        },
        'events': {
            exports: 'events'
        },
        'jquery': {
            exports: 'jquery'
        },
        'lodash': {
            exports: 'lodash'
        }
    }
});

require(['jquery', 'lodash', 'components', 'api', 'events', 'app'], function($, _, comp, api, events, app){
    'use strict';

	app.init($, _, comp, api, events);
});
<|endoftext|>


<commit_before>import Delta from 'rich-text/lib/delta';
import DeltaOp from 'rich-text/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import Block, { bubbleFormats } from '../blots/block';
import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';

class Editor {
  constructor(scroll, emitter) {
    this.scroll = scroll;
    this.emitter = emitter;
    this.emitter.on(Emitter.events.SCROLL_UPDATE, this.update.bind(this, null));
    this.delta = this.getDelta();
    this.enable();
  }

  applyDelta(delta, source = Emitter.sources.API) {
    this.updating = true;
    let consumeNextNewline = false;
    let scrollLength = this.scroll.length();
    delta.ops.reduce((index, op) => {
      if (typeof op.delete === 'number') {
        this.scroll.deleteAt(index, op.delete);
        scrollLength -= op.delete;
        return index;
      }
      let length = op.retain || op.insert.length || 1;
      let attributes = handleOldList(op.attributes || {});
      if (op.insert != null) {
        [op, attributes] = handleOldEmbed(op, attributes);
        if (typeof op.insert === 'string') {
          let text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
          length = text.length;
          if (text.endsWith('\n') && consumeNextNewline) {
            consumeNextNewline = false;
            text = text.slice(0, -1);
          }
          if (index >= scrollLength && !text.endsWith('\n')) {
            consumeNextNewline = true;
          }
          this.scroll.insertAt(index, text);
          let [line, offset] = this.scroll.line(index);
          let formats = extend({}, bubbleFormats(line));
          if (line instanceof Block) {
            let [leaf, ] = line.descendant(Parchment.Leaf, offset);
            formats = extend(formats, bubbleFormats(leaf));
          }
          attributes = DeltaOp.attributes.diff(formats, attributes) || {};
        } else if (typeof op.insert === 'object') {
          let key = Object.keys(op.insert)[0];  // There should only be one key
          if (key != null) {
            this.scroll.insertAt(index, key, op.insert[key]);
          } else {
            return index;
          }
        }
        scrollLength += length;
      }
      Object.keys(attributes).forEach((name) => {
        this.scroll.formatAt(index, length, name, attributes[name]);
      });
      return index + length;
    }, 0);
    this.updating = false;
    return this.update(delta, source);
  }

  deleteText(index, length, source = Emitter.sources.API) {
    this.scroll.deleteAt(index, length);
    return this.update(new Delta().retain(index).delete(length), source);
  }

  enable(enabled = true) {
    this.scroll.domNode.setAttribute('contenteditable', enabled);
  }

  formatLine(index, length, formats = {}, source = Emitter.sources.API) {
    this.scroll.update();
    Object.keys(formats).forEach((format) => {
      let lines = this.scroll.lines(index, Math.max(length, 1));
      let lengthRemaining = length;
      lines.forEach((line, i) => {
        let lineLength = line.length();
        if (!(line instanceof CodeBlock)) {
          line.format(format, formats[format]);
        } else {
          let codeIndex = index - line.offset(this.scroll);
          let codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1;
          line.formatAt(codeIndex, codeLength, format, formats[format]);
        }
        lengthRemaining -= lineLength;
      });
    });
    this.scroll.optimize();
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  formatText(index, length, formats = {}, source = Emitter.sources.API) {
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  getContents(index, length) {
    return this.delta.slice(index, index + length);
  }

  getDelta() {
    return this.scroll.lines().reduce((delta, line) => {
      return delta.concat(line.delta());
    }, new Delta());
  }

  getFormat(index, length = 0) {
    let lines = [], leaves = [];
    if (length === 0) {
      this.scroll.path(index).forEach(function(path) {
        let [blot, offset] = path;
        if (blot instanceof Block) {
          lines.push(blot);
        } else if (blot instanceof Parchment.Leaf) {
          leaves.push(blot);
        }
      });
    } else {
      lines = this.scroll.lines(index, length);
      leaves = this.scroll.descendants(Parchment.Leaf, index, length);
    }
    let formatsArr = [lines, leaves].map(function(blots) {
      if (blots.length === 0) return {};
      let formats = bubbleFormats(blots.shift());
      while (Object.keys(formats).length > 0) {
        let blot = blots.shift();
        if (blot == null) return formats;
        formats = combineFormats(bubbleFormats(blot), formats);
      }
      return formats;
    });
    return extend.apply(extend, formatsArr);
  }

  getText(index, length) {
    return this.getContents(index, length).ops.map(function(op) {
      return (typeof op.insert === 'string') ? op.insert : '';
    }).join('');
  }

  insertEmbed(index, embed, value, source = Emitter.sources.API) {
    this.scroll.insertAt(index, embed, value);
    return this.update(new Delta().retain(index).insert({ [embed]: value }), source);
  }

  insertText(index, text, formats = {}, source = Emitter.sources.API) {
    text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    this.scroll.insertAt(index, text);
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, text.length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).insert(text, clone(formats)), source)
  }

  isBlank() {
    if (this.scroll.children.length == 0) return true;
    if (this.scroll.children.length > 1) return false;
    let child = this.scroll.children.head;
    return child.length() <= 1 && Object.keys(child.formats()).length == 0;
  }

  removeFormat(index, length, source) {
    let text = this.getText(index, length);
    let [line, offset] = this.scroll.line(index + length);
    let suffixLength = 0, suffix = new Delta();
    if (line != null) {
      if (!(line instanceof CodeBlock)) {
        suffixLength = line.length() - offset;
      } else {
        suffixLength = line.newlineIndex(offset) - offset + 1;
      }
      suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n');
    }
    let contents = this.getContents(index, length + suffixLength);
    let diff = contents.diff(new Delta().insert(text).concat(suffix));
    let delta = new Delta().retain(index).concat(diff);
    return this.applyDelta(delta, source);
  }

  update(change, source = Emitter.sources.USER) {
    if (this.updating) return;
    let oldDelta = this.delta;
    this.delta = this.getDelta();
    if (!change || !equal(oldDelta.compose(change), this.delta)) {
      change = oldDelta.diff(this.delta);
    }
    if (change.length() > 0) {
      let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
    return change;
  }
}

function combineFormats(formats, combined) {
  return Object.keys(combined).reduce(function(merged, name) {
    if (formats[name] == null) return merged;
    if (combined[name] === formats[name]) {
      merged[name] = combined[name];
    } else if (Array.isArray(combined[name])) {
      if (combined[name].indexOf(formats[name]) < 0) {
        merged[name] = combined[name].concat([formats[name]]);
      }
    } else {
      merged[name] = [combined[name], formats[name]];
    }
    return merged;
  }, {});
}

function handleOldEmbed(op, attributes) {
  if (op.insert === 1) {
    attributes = clone(attributes);
    op = {
      insert: { image: attributes.image },
      attributes: attributes
    };
    delete attributes['image'];
  }
  return [op, attributes];
}

function handleOldList(attributes) {
  if (attributes['list'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'ordered';
  } else if (attributes['bullet'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'bullet';
  }
  return attributes;
}

export default Editor;
<commit_msg>combine handling old delta<commit_after>import Delta from 'rich-text/lib/delta';
import DeltaOp from 'rich-text/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import Block, { bubbleFormats } from '../blots/block';
import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';

class Editor {
  constructor(scroll, emitter) {
    this.scroll = scroll;
    this.emitter = emitter;
    this.emitter.on(Emitter.events.SCROLL_UPDATE, this.update.bind(this, null));
    this.delta = this.getDelta();
    this.enable();
  }

  applyDelta(delta, source = Emitter.sources.API) {
    this.updating = true;
    let consumeNextNewline = false;
    let scrollLength = this.scroll.length();
    delta = normalizeDelta(delta);
    delta.ops.reduce((index, op) => {
      if (typeof op.delete === 'number') {
        this.scroll.deleteAt(index, op.delete);
        return index;
      }
      let length = op.retain || op.insert.length || 1;
      let attributes = op.attributes || {};
      if (op.insert != null) {
        if (typeof op.insert === 'string') {
          let text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
          length = text.length;
          if (text.endsWith('\n') && consumeNextNewline) {
            consumeNextNewline = false;
            text = text.slice(0, -1);
          }
          if (index >= scrollLength && !text.endsWith('\n')) {
            consumeNextNewline = true;
          }
          this.scroll.insertAt(index, text);
          let [line, offset] = this.scroll.line(index);
          let formats = extend({}, bubbleFormats(line));
          if (line instanceof Block) {
            let [leaf, ] = line.descendant(Parchment.Leaf, offset);
            formats = extend(formats, bubbleFormats(leaf));
          }
          attributes = DeltaOp.attributes.diff(formats, attributes) || {};
        } else if (typeof op.insert === 'object') {
          let key = Object.keys(op.insert)[0];  // There should only be one key
          if (key != null) {
            this.scroll.insertAt(index, key, op.insert[key]);
          } else {
            return index;
          }
        }
        scrollLength += length;
      }
      Object.keys(attributes).forEach((name) => {
        this.scroll.formatAt(index, length, name, attributes[name]);
      });
      return index + length;
    }, 0);
    this.updating = false;
    return this.update(delta, source);
  }

  deleteText(index, length, source = Emitter.sources.API) {
    this.scroll.deleteAt(index, length);
    return this.update(new Delta().retain(index).delete(length), source);
  }

  enable(enabled = true) {
    this.scroll.domNode.setAttribute('contenteditable', enabled);
  }

  formatLine(index, length, formats = {}, source = Emitter.sources.API) {
    this.scroll.update();
    Object.keys(formats).forEach((format) => {
      let lines = this.scroll.lines(index, Math.max(length, 1));
      let lengthRemaining = length;
      lines.forEach((line, i) => {
        let lineLength = line.length();
        if (!(line instanceof CodeBlock)) {
          line.format(format, formats[format]);
        } else {
          let codeIndex = index - line.offset(this.scroll);
          let codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1;
          line.formatAt(codeIndex, codeLength, format, formats[format]);
        }
        lengthRemaining -= lineLength;
      });
    });
    this.scroll.optimize();
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  formatText(index, length, formats = {}, source = Emitter.sources.API) {
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  getContents(index, length) {
    return this.delta.slice(index, index + length);
  }

  getDelta() {
    return this.scroll.lines().reduce((delta, line) => {
      return delta.concat(line.delta());
    }, new Delta());
  }

  getFormat(index, length = 0) {
    let lines = [], leaves = [];
    if (length === 0) {
      this.scroll.path(index).forEach(function(path) {
        let [blot, offset] = path;
        if (blot instanceof Block) {
          lines.push(blot);
        } else if (blot instanceof Parchment.Leaf) {
          leaves.push(blot);
        }
      });
    } else {
      lines = this.scroll.lines(index, length);
      leaves = this.scroll.descendants(Parchment.Leaf, index, length);
    }
    let formatsArr = [lines, leaves].map(function(blots) {
      if (blots.length === 0) return {};
      let formats = bubbleFormats(blots.shift());
      while (Object.keys(formats).length > 0) {
        let blot = blots.shift();
        if (blot == null) return formats;
        formats = combineFormats(bubbleFormats(blot), formats);
      }
      return formats;
    });
    return extend.apply(extend, formatsArr);
  }

  getText(index, length) {
    return this.getContents(index, length).ops.map(function(op) {
      return (typeof op.insert === 'string') ? op.insert : '';
    }).join('');
  }

  insertEmbed(index, embed, value, source = Emitter.sources.API) {
    this.scroll.insertAt(index, embed, value);
    return this.update(new Delta().retain(index).insert({ [embed]: value }), source);
  }

  insertText(index, text, formats = {}, source = Emitter.sources.API) {
    text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    this.scroll.insertAt(index, text);
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, text.length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).insert(text, clone(formats)), source)
  }

  isBlank() {
    if (this.scroll.children.length == 0) return true;
    if (this.scroll.children.length > 1) return false;
    let child = this.scroll.children.head;
    return child.length() <= 1 && Object.keys(child.formats()).length == 0;
  }

  removeFormat(index, length, source) {
    let text = this.getText(index, length);
    let [line, offset] = this.scroll.line(index + length);
    let suffixLength = 0, suffix = new Delta();
    if (line != null) {
      if (!(line instanceof CodeBlock)) {
        suffixLength = line.length() - offset;
      } else {
        suffixLength = line.newlineIndex(offset) - offset + 1;
      }
      suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n');
    }
    let contents = this.getContents(index, length + suffixLength);
    let diff = contents.diff(new Delta().insert(text).concat(suffix));
    let delta = new Delta().retain(index).concat(diff);
    return this.applyDelta(delta, source);
  }

  update(change, source = Emitter.sources.USER) {
    if (this.updating) return;
    let oldDelta = this.delta;
    this.delta = this.getDelta();
    if (!change || !equal(oldDelta.compose(change), this.delta)) {
      change = oldDelta.diff(this.delta);
    }
    if (change.length() > 0) {
      let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
    return change;
  }
}

function combineFormats(formats, combined) {
  return Object.keys(combined).reduce(function(merged, name) {
    if (formats[name] == null) return merged;
    if (combined[name] === formats[name]) {
      merged[name] = combined[name];
    } else if (Array.isArray(combined[name])) {
      if (combined[name].indexOf(formats[name]) < 0) {
        merged[name] = combined[name].concat([formats[name]]);
      }
    } else {
      merged[name] = [combined[name], formats[name]];
    }
    return merged;
  }, {});
}

function normalizeDelta(delta) {
  return delta.ops.reduce(function(delta, op) {
    if (op.insert === 1) {
      let attributes = clone(op.attributes);
      delete attributes['image'];
      return delta.insert({ image: op.attributes.image }, attributes);
    }
    if (op.attributes != null && (op.attributes.list || op.attributes.bullet)) {
      op = clone(op);
      if (op.attributes.list) {
        op.attributes.list = 'ordered';
      } else {
        op.attributes.list = 'bullet';
        delete op.attributes.bullet;
      }
    }
    return delta.push(op);
  }, new Delta());
}

export default Editor;
<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2011 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Functions for injecting Blockly into a web page.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.inject');

goog.require('Blockly.Css');
goog.require('Blockly.Options');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.DragSurfaceSvg');
goog.require('Blockly.DropDownDiv');
goog.require('goog.dom');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');

/**
 * Radius of stack glow, in px.
 * @type {number}
 * @const
 */
Blockly.STACK_GLOW_RADIUS = 1;

/**
 * Radius of replacement glow, in px.
 * @type {number}
 * @const
 */
Blockly.REPLACEMENT_GLOW_RADIUS = 2;

/**
 * Inject a Blockly editor into the specified container element (usually a div).
 * @param {!Element|string} container Containing element, or its ID,
 *     or a CSS selector.
 * @param {Object=} opt_options Optional dictionary of options.
 * @return {!Blockly.Workspace} Newly created main workspace.
 */
Blockly.inject = function(container, opt_options) {
  if (goog.isString(container)) {
    container = document.getElementById(container) ||
        document.querySelector(container);
  }
  // Verify that the container is in document.
  if (!goog.dom.contains(document, container)) {
    throw 'Error: container is not in current document.';
  }
  var options = new Blockly.Options(opt_options || {});
  var subContainer = goog.dom.createDom('div', 'injectionDiv');
  container.appendChild(subContainer);

  var svg = Blockly.createDom_(subContainer, options);
  var dragSurface = new Blockly.DragSurfaceSvg(subContainer);
  dragSurface.createDom();
  var workspace = Blockly.createMainWorkspace_(svg, options, dragSurface);

  Blockly.init_(workspace);
  workspace.markFocused();
  Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused);
  Blockly.svgResize(workspace);
  return workspace;
};

/**
 * Create the SVG image.
 * @param {!Element} container Containing element.
 * @param {!Blockly.Options} options Dictionary of options.
 * @return {!Element} Newly created SVG image.
 * @private
 */
Blockly.createDom_ = function(container, options) {
  // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
  // out content in RTL mode.  Therefore Blockly forces the use of LTR,
  // then manually positions content in RTL as needed.
  container.setAttribute('dir', 'LTR');
  // Closure can be trusted to create HTML widgets with the proper direction.
  goog.ui.Component.setDefaultRightToLeft(options.RTL);

  // Load CSS.
  Blockly.Css.inject(options.hasCss, options.pathToMedia);

  // Build the SVG DOM.
  var svg = Blockly.createSvgElement('svg', {
    'xmlns': 'http://www.w3.org/2000/svg',
    'xmlns:html': 'http://www.w3.org/1999/xhtml',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
    'version': '1.1',
    'class': 'blocklySvg'
  }, container);
  var defs = Blockly.createSvgElement('defs', {}, svg);
  var rnd = String(Math.random()).substring(2);

  // Using a dilate distorts the block shape.
  // Instead use a gaussian blur, and then set all alpha to 1 with a transfer.
  var stackGlowFilter = Blockly.createSvgElement('filter',
      {'id': 'blocklyStackGlowFilter',
        'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
  options.stackGlowBlur = Blockly.createSvgElement('feGaussianBlur',
      {'in': 'SourceGraphic',
      'stdDeviation': Blockly.STACK_GLOW_RADIUS}, stackGlowFilter);
  // Set all gaussian blur pixels to 1 opacity before applying flood
  var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, stackGlowFilter);
  Blockly.createSvgElement('feFuncA',
      {'type': 'table', 'tableValues': '0' + ' 1'.repeat(16)}, componentTransfer);
  // Color the highlight
  Blockly.createSvgElement('feFlood',
      {'flood-color': Blockly.Colours.stackGlow,
       'flood-opacity': Blockly.Colours.stackGlowOpacity, 'result': 'outColor'}, stackGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'outColor', 'in2': 'outBlur',
       'operator': 'in', 'result': 'outGlow'}, stackGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, stackGlowFilter);

  // Filter for replacement marker
  var replacementGlowFilter = Blockly.createSvgElement('filter',
      {'id': 'blocklyReplacementGlowFilter',
        'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
  Blockly.createSvgElement('feGaussianBlur',
      {'in': 'SourceGraphic',
      'stdDeviation': Blockly.REPLACEMENT_GLOW_RADIUS}, replacementGlowFilter);
  // Set all gaussian blur pixels to 1 opacity before applying flood
  var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, replacementGlowFilter);
  Blockly.createSvgElement('feFuncA',
      {'type': 'table', 'tableValues': '0' + ' 1'.repeat(16)}, componentTransfer);
  // Color the highlight
  Blockly.createSvgElement('feFlood',
      {'flood-color': Blockly.Colours.replacementGlow,
       'flood-opacity': Blockly.Colours.replacementGlowOpacity, 'result': 'outColor'}, replacementGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'outColor', 'in2': 'outBlur',
       'operator': 'in', 'result': 'outGlow'}, replacementGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, replacementGlowFilter);

  var disabledPattern = Blockly.createSvgElement('pattern',
      {'id': 'blocklyDisabledPattern' + rnd,
       'patternUnits': 'userSpaceOnUse',
       'width': 10, 'height': 10}, defs);
  Blockly.createSvgElement('rect',
      {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
  Blockly.createSvgElement('path',
      {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
  options.disabledPatternId = disabledPattern.id;

  var gridPattern = Blockly.createSvgElement('pattern',
      {'id': 'blocklyGridPattern' + rnd,
       'patternUnits': 'userSpaceOnUse'}, defs);
  if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
    Blockly.createSvgElement('line',
        {'stroke': options.gridOptions['colour']},
        gridPattern);
    if (options.gridOptions['length'] > 1) {
      Blockly.createSvgElement('line',
          {'stroke': options.gridOptions['colour']},
          gridPattern);
    }
    // x1, y1, x1, x2 properties will be set later in updateGridPattern_.
  }
  options.gridPattern = gridPattern;
  return svg;
};

/**
 * Create a main workspace and add it to the SVG.
 * @param {!Element} svg SVG element with pattern defined.
 * @param {!Blockly.Options} options Dictionary of options.
 * @param {!Blockly.DragSurfaceSvg} dragSurface Drag surface SVG for the workspace.
 * @return {!Blockly.Workspace} Newly created main workspace.
 * @private
 */
Blockly.createMainWorkspace_ = function(svg, options, dragSurface) {
  options.parentWorkspace = null;
  var mainWorkspace = new Blockly.WorkspaceSvg(options, dragSurface);
  mainWorkspace.scale = options.zoomOptions.startScale;
  svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
  // A null translation will also apply the correct initial scale.
  mainWorkspace.translate(0, 0);
  mainWorkspace.markFocused();

  if (!options.readOnly && !options.hasScrollbars) {
    var workspaceChanged = function() {
      if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
        var metrics = mainWorkspace.getMetrics();
        var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
        var edgeTop = metrics.viewTop + metrics.absoluteTop;
        if (metrics.contentTop < edgeTop ||
            metrics.contentTop + metrics.contentHeight >
            metrics.viewHeight + edgeTop ||
            metrics.contentLeft <
                (options.RTL ? metrics.viewLeft : edgeLeft) ||
            metrics.contentLeft + metrics.contentWidth > (options.RTL ?
                metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
          // One or more blocks may be out of bounds.  Bump them back in.
          var MARGIN = 25;
          var blocks = mainWorkspace.getTopBlocks(false);
          for (var b = 0, block; block = blocks[b]; b++) {
            var blockXY = block.getRelativeToSurfaceXY();
            var blockHW = block.getHeightWidth();
            // Bump any block that's above the top back inside.
            var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
            if (overflowTop > 0) {
              block.moveBy(0, overflowTop);
            }
            // Bump any block that's below the bottom back inside.
            var overflowBottom =
                edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
            if (overflowBottom < 0) {
              block.moveBy(0, overflowBottom);
            }
            // Bump any block that's off the left back inside.
            var overflowLeft = MARGIN + edgeLeft -
                blockXY.x - (options.RTL ? 0 : blockHW.width);
            if (overflowLeft > 0) {
              block.moveBy(overflowLeft, 0);
            }
            // Bump any block that's off the right back inside.
            var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
                blockXY.x + (options.RTL ? blockHW.width : 0);
            if (overflowRight < 0) {
              block.moveBy(overflowRight, 0);
            }
          }
        }
      }
    };
    mainWorkspace.addChangeListener(workspaceChanged);
  }
  // The SVG is now fully assembled.
  Blockly.svgResize(mainWorkspace);
  Blockly.WidgetDiv.createDom();
  Blockly.DropDownDiv.createDom();
  Blockly.Tooltip.createDom();
  return mainWorkspace;
};

/**
 * Initialize Blockly with various handlers.
 * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
 * @private
 */
Blockly.init_ = function(mainWorkspace) {
  var options = mainWorkspace.options;
  var svg = mainWorkspace.getParentSvg();

  // Supress the browser's context menu.
  Blockly.bindEvent_(svg, 'contextmenu', null,
      function(e) {
        if (!Blockly.isTargetInput_(e)) {
          e.preventDefault();
        }
      });

  var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null,
       function() {
         Blockly.hideChaff(true);
         Blockly.svgResize(mainWorkspace);
       });
  mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);

  Blockly.inject.bindDocumentEvents_();

  if (options.languageTree) {
    if (mainWorkspace.toolbox_) {
      mainWorkspace.toolbox_.init(mainWorkspace);
    } else if (mainWorkspace.flyout_) {
      // Build a fixed flyout with the root blocks.
      mainWorkspace.flyout_.init(mainWorkspace);
      mainWorkspace.flyout_.show(options.languageTree.childNodes);
      mainWorkspace.flyout_.scrollToStart();
      // Translate the workspace to avoid the fixed flyout.
      if (options.horizontalLayout) {
        mainWorkspace.scrollY = mainWorkspace.flyout_.height_;
        if (options.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
          mainWorkspace.scrollY *= -1;
        }
      } else {
        mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
        if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
          mainWorkspace.scrollX *= -1;
        }
      }
      mainWorkspace.translate(mainWorkspace.scrollX, mainWorkspace.scrollY);
    }
  }

  if (options.hasScrollbars) {
    mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
    mainWorkspace.scrollbar.resize();
  }

  // Load the sounds.
  if (options.hasSounds) {
    mainWorkspace.loadAudio_(
        [options.pathToMedia + 'click.wav'], 'click');
    mainWorkspace.loadAudio_(
        [options.pathToMedia + 'delete.wav'], 'delete');

    // Bind temporary hooks that preload the sounds.
    var soundBinds = [];
    var unbindSounds = function() {
      while (soundBinds.length) {
        Blockly.unbindEvent_(soundBinds.pop());
      }
      mainWorkspace.preloadAudio_();
    };
    // Android ignores any sound not loaded as a result of a user action.
    soundBinds.push(
        Blockly.bindEvent_(document, 'mousemove', null, unbindSounds));
    soundBinds.push(
        Blockly.bindEvent_(document, 'touchstart', null, unbindSounds));
  }
};

/**
 * Bind document events, but only once.  Destroying and reinjecting Blockly
 * should not bind again.
 * Bind events for scrolling the workspace.
 * Most of these events should be bound to the SVG's surface.
 * However, 'mouseup' has to be on the whole document so that a block dragged
 * out of bounds and released will know that it has been released.
 * Also, 'keydown' has to be on the whole document since the browser doesn't
 * understand a concept of focus on the SVG image.
 * @private
 */
Blockly.inject.bindDocumentEvents_ = function() {
  if (!Blockly.documentEventsBound_) {
    Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_);
    Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
    Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
    // Don't use bindEvent_ for document's mouseup since that would create a
    // corresponding touch handler that would squeltch the ability to interact
    // with non-Blockly elements.
    document.addEventListener('mouseup', Blockly.onMouseUp_, false);
    // Some iPad versions don't fire resize after portrait to landscape change.
    if (goog.userAgent.IPAD) {
      Blockly.bindEvent_(window, 'orientationchange', document, function() {
        // TODO(#397): Fix for multiple blockly workspaces.
        Blockly.svgResize(Blockly.getMainWorkspace());
      });
    }
  }
  Blockly.documentEventsBound_ = true;
};

/**
 * Modify the block tree on the existing toolbox.
 * @param {Node|string} tree DOM tree of blocks, or text representation of same.
 */
Blockly.updateToolbox = function(tree) {
  console.warn('Deprecated call to Blockly.updateToolbox, ' +
               'use workspace.updateToolbox instead.');
  Blockly.getMainWorkspace().updateToolbox(tree);
};
<commit_msg>Decompose out sound loading, and call bindEvent with opt_noCaptureIdentifier=true, as google/blockly does.<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2011 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview Functions for injecting Blockly into a web page.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.inject');

goog.require('Blockly.Css');
goog.require('Blockly.Options');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.DragSurfaceSvg');
goog.require('Blockly.DropDownDiv');
goog.require('goog.dom');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');

/**
 * Radius of stack glow, in px.
 * @type {number}
 * @const
 */
Blockly.STACK_GLOW_RADIUS = 1;

/**
 * Radius of replacement glow, in px.
 * @type {number}
 * @const
 */
Blockly.REPLACEMENT_GLOW_RADIUS = 2;

/**
 * Inject a Blockly editor into the specified container element (usually a div).
 * @param {!Element|string} container Containing element, or its ID,
 *     or a CSS selector.
 * @param {Object=} opt_options Optional dictionary of options.
 * @return {!Blockly.Workspace} Newly created main workspace.
 */
Blockly.inject = function(container, opt_options) {
  if (goog.isString(container)) {
    container = document.getElementById(container) ||
        document.querySelector(container);
  }
  // Verify that the container is in document.
  if (!goog.dom.contains(document, container)) {
    throw 'Error: container is not in current document.';
  }
  var options = new Blockly.Options(opt_options || {});
  var subContainer = goog.dom.createDom('div', 'injectionDiv');
  container.appendChild(subContainer);

  var svg = Blockly.createDom_(subContainer, options);
  var dragSurface = new Blockly.DragSurfaceSvg(subContainer);
  dragSurface.createDom();
  var workspace = Blockly.createMainWorkspace_(svg, options, dragSurface);

  Blockly.init_(workspace);
  workspace.markFocused();
  Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused);
  Blockly.svgResize(workspace);
  return workspace;
};

/**
 * Create the SVG image.
 * @param {!Element} container Containing element.
 * @param {!Blockly.Options} options Dictionary of options.
 * @return {!Element} Newly created SVG image.
 * @private
 */
Blockly.createDom_ = function(container, options) {
  // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
  // out content in RTL mode.  Therefore Blockly forces the use of LTR,
  // then manually positions content in RTL as needed.
  container.setAttribute('dir', 'LTR');
  // Closure can be trusted to create HTML widgets with the proper direction.
  goog.ui.Component.setDefaultRightToLeft(options.RTL);

  // Load CSS.
  Blockly.Css.inject(options.hasCss, options.pathToMedia);

  // Build the SVG DOM.
  var svg = Blockly.createSvgElement('svg', {
    'xmlns': 'http://www.w3.org/2000/svg',
    'xmlns:html': 'http://www.w3.org/1999/xhtml',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
    'version': '1.1',
    'class': 'blocklySvg'
  }, container);
  var defs = Blockly.createSvgElement('defs', {}, svg);
  var rnd = String(Math.random()).substring(2);

  // Using a dilate distorts the block shape.
  // Instead use a gaussian blur, and then set all alpha to 1 with a transfer.
  var stackGlowFilter = Blockly.createSvgElement('filter',
      {'id': 'blocklyStackGlowFilter',
        'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
  options.stackGlowBlur = Blockly.createSvgElement('feGaussianBlur',
      {'in': 'SourceGraphic',
      'stdDeviation': Blockly.STACK_GLOW_RADIUS}, stackGlowFilter);
  // Set all gaussian blur pixels to 1 opacity before applying flood
  var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, stackGlowFilter);
  Blockly.createSvgElement('feFuncA',
      {'type': 'table', 'tableValues': '0' + ' 1'.repeat(16)}, componentTransfer);
  // Color the highlight
  Blockly.createSvgElement('feFlood',
      {'flood-color': Blockly.Colours.stackGlow,
       'flood-opacity': Blockly.Colours.stackGlowOpacity, 'result': 'outColor'}, stackGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'outColor', 'in2': 'outBlur',
       'operator': 'in', 'result': 'outGlow'}, stackGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, stackGlowFilter);

  // Filter for replacement marker
  var replacementGlowFilter = Blockly.createSvgElement('filter',
      {'id': 'blocklyReplacementGlowFilter',
        'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
  Blockly.createSvgElement('feGaussianBlur',
      {'in': 'SourceGraphic',
      'stdDeviation': Blockly.REPLACEMENT_GLOW_RADIUS}, replacementGlowFilter);
  // Set all gaussian blur pixels to 1 opacity before applying flood
  var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, replacementGlowFilter);
  Blockly.createSvgElement('feFuncA',
      {'type': 'table', 'tableValues': '0' + ' 1'.repeat(16)}, componentTransfer);
  // Color the highlight
  Blockly.createSvgElement('feFlood',
      {'flood-color': Blockly.Colours.replacementGlow,
       'flood-opacity': Blockly.Colours.replacementGlowOpacity, 'result': 'outColor'}, replacementGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'outColor', 'in2': 'outBlur',
       'operator': 'in', 'result': 'outGlow'}, replacementGlowFilter);
  Blockly.createSvgElement('feComposite',
      {'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, replacementGlowFilter);

  var disabledPattern = Blockly.createSvgElement('pattern',
      {'id': 'blocklyDisabledPattern' + rnd,
       'patternUnits': 'userSpaceOnUse',
       'width': 10, 'height': 10}, defs);
  Blockly.createSvgElement('rect',
      {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
  Blockly.createSvgElement('path',
      {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
  options.disabledPatternId = disabledPattern.id;

  var gridPattern = Blockly.createSvgElement('pattern',
      {'id': 'blocklyGridPattern' + rnd,
       'patternUnits': 'userSpaceOnUse'}, defs);
  if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
    Blockly.createSvgElement('line',
        {'stroke': options.gridOptions['colour']},
        gridPattern);
    if (options.gridOptions['length'] > 1) {
      Blockly.createSvgElement('line',
          {'stroke': options.gridOptions['colour']},
          gridPattern);
    }
    // x1, y1, x1, x2 properties will be set later in updateGridPattern_.
  }
  options.gridPattern = gridPattern;
  return svg;
};

/**
 * Create a main workspace and add it to the SVG.
 * @param {!Element} svg SVG element with pattern defined.
 * @param {!Blockly.Options} options Dictionary of options.
 * @param {!Blockly.DragSurfaceSvg} dragSurface Drag surface SVG for the workspace.
 * @return {!Blockly.Workspace} Newly created main workspace.
 * @private
 */
Blockly.createMainWorkspace_ = function(svg, options, dragSurface) {
  options.parentWorkspace = null;
  var mainWorkspace = new Blockly.WorkspaceSvg(options, dragSurface);
  mainWorkspace.scale = options.zoomOptions.startScale;
  svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
  // A null translation will also apply the correct initial scale.
  mainWorkspace.translate(0, 0);
  mainWorkspace.markFocused();

  if (!options.readOnly && !options.hasScrollbars) {
    var workspaceChanged = function() {
      if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
        var metrics = mainWorkspace.getMetrics();
        var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
        var edgeTop = metrics.viewTop + metrics.absoluteTop;
        if (metrics.contentTop < edgeTop ||
            metrics.contentTop + metrics.contentHeight >
            metrics.viewHeight + edgeTop ||
            metrics.contentLeft <
                (options.RTL ? metrics.viewLeft : edgeLeft) ||
            metrics.contentLeft + metrics.contentWidth > (options.RTL ?
                metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
          // One or more blocks may be out of bounds.  Bump them back in.
          var MARGIN = 25;
          var blocks = mainWorkspace.getTopBlocks(false);
          for (var b = 0, block; block = blocks[b]; b++) {
            var blockXY = block.getRelativeToSurfaceXY();
            var blockHW = block.getHeightWidth();
            // Bump any block that's above the top back inside.
            var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
            if (overflowTop > 0) {
              block.moveBy(0, overflowTop);
            }
            // Bump any block that's below the bottom back inside.
            var overflowBottom =
                edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
            if (overflowBottom < 0) {
              block.moveBy(0, overflowBottom);
            }
            // Bump any block that's off the left back inside.
            var overflowLeft = MARGIN + edgeLeft -
                blockXY.x - (options.RTL ? 0 : blockHW.width);
            if (overflowLeft > 0) {
              block.moveBy(overflowLeft, 0);
            }
            // Bump any block that's off the right back inside.
            var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
                blockXY.x + (options.RTL ? blockHW.width : 0);
            if (overflowRight < 0) {
              block.moveBy(overflowRight, 0);
            }
          }
        }
      }
    };
    mainWorkspace.addChangeListener(workspaceChanged);
  }
  // The SVG is now fully assembled.
  Blockly.svgResize(mainWorkspace);
  Blockly.WidgetDiv.createDom();
  Blockly.DropDownDiv.createDom();
  Blockly.Tooltip.createDom();
  return mainWorkspace;
};

/**
 * Initialize Blockly with various handlers.
 * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
 * @private
 */
Blockly.init_ = function(mainWorkspace) {
  var options = mainWorkspace.options;
  var svg = mainWorkspace.getParentSvg();

  // Supress the browser's context menu.
  Blockly.bindEvent_(svg, 'contextmenu', null,
      function(e) {
        if (!Blockly.isTargetInput_(e)) {
          e.preventDefault();
        }
      });

  var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null,
       function() {
         Blockly.hideChaff(true);
         Blockly.svgResize(mainWorkspace);
       });
  mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);

  Blockly.inject.bindDocumentEvents_();

  if (options.languageTree) {
    if (mainWorkspace.toolbox_) {
      mainWorkspace.toolbox_.init(mainWorkspace);
    } else if (mainWorkspace.flyout_) {
      // Build a fixed flyout with the root blocks.
      mainWorkspace.flyout_.init(mainWorkspace);
      mainWorkspace.flyout_.show(options.languageTree.childNodes);
      mainWorkspace.flyout_.scrollToStart();
      // Translate the workspace to avoid the fixed flyout.
      if (options.horizontalLayout) {
        mainWorkspace.scrollY = mainWorkspace.flyout_.height_;
        if (options.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
          mainWorkspace.scrollY *= -1;
        }
      } else {
        mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
        if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
          mainWorkspace.scrollX *= -1;
        }
      }
      mainWorkspace.translate(mainWorkspace.scrollX, mainWorkspace.scrollY);
    }
  }

  if (options.hasScrollbars) {
    mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
    mainWorkspace.scrollbar.resize();
  }

  // Load the sounds.
  if (options.hasSounds) {
    Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace);
  }
};

/**
 * Bind document events, but only once.  Destroying and reinjecting Blockly
 * should not bind again.
 * Bind events for scrolling the workspace.
 * Most of these events should be bound to the SVG's surface.
 * However, 'mouseup' has to be on the whole document so that a block dragged
 * out of bounds and released will know that it has been released.
 * Also, 'keydown' has to be on the whole document since the browser doesn't
 * understand a concept of focus on the SVG image.
 * @private
 */
Blockly.inject.bindDocumentEvents_ = function() {
  if (!Blockly.documentEventsBound_) {
    Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_);
    Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
    Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
    // Don't use bindEvent_ for document's mouseup since that would create a
    // corresponding touch handler that would squeltch the ability to interact
    // with non-Blockly elements.
    document.addEventListener('mouseup', Blockly.onMouseUp_, false);
    // Some iPad versions don't fire resize after portrait to landscape change.
    if (goog.userAgent.IPAD) {
      Blockly.bindEvent_(window, 'orientationchange', document, function() {
        // TODO(#397): Fix for multiple blockly workspaces.
        Blockly.svgResize(Blockly.getMainWorkspace());
      });
    }
  }
  Blockly.documentEventsBound_ = true;
};

/**
 * Load sounds for the given workspace.
 * @param {string} pathToMedia The path to the media directory.
 * @param {!Blockly.Workspace} workspace The workspace to load sounds for.
 * @private
 */
Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
  workspace.loadAudio_(
      [pathToMedia + 'click.wav'], 'click');
  workspace.loadAudio_(
      [pathToMedia + 'delete.wav'], 'delete');

  // Bind temporary hooks that preload the sounds.
  var soundBinds = [];
  var unbindSounds = function() {
    while (soundBinds.length) {
      Blockly.unbindEvent_(soundBinds.pop());
    }
    workspace.preloadAudio_();
  };
  // Android ignores any sound not loaded as a result of a user action.
  soundBinds.push(
      Blockly.bindEvent_(document, 'mousemove', null, unbindSounds, true));
  soundBinds.push(
      Blockly.bindEvent_(document, 'touchstart', null, unbindSounds, true));
};

/**
 * Modify the block tree on the existing toolbox.
 * @param {Node|string} tree DOM tree of blocks, or text representation of same.
 */
Blockly.updateToolbox = function(tree) {
  console.warn('Deprecated call to Blockly.updateToolbox, ' +
               'use workspace.updateToolbox instead.');
  Blockly.getMainWorkspace().updateToolbox(tree);
};
<|endoftext|>


<commit_before>'use strict'

module.exports = [
  {
    user: '@michal.brasna',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@jan.pavlovsky',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@ottokovarik',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@radim',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@tomas.pokorny',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@radek.carda',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@zdenek.cejka',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@mafo',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@martin.kadlec',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@adamsobotka',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  }
]
<commit_msg>Add more people<commit_after>'use strict'

module.exports = [
  {
    user: '@michal.brasna',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@jan.pavlovsky',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@ottokovarik',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@radim',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@tomas.pokorny',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@radek.carda',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@zdenek.cejka',
    services: ['peters', 'pub', 'tankovna', 'u-sani', 'meat-market', 'lokal-hamburk']
  },
  {
    user: '@mafo',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@martin.kadlec',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@adamsobotka',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  },
  {
    user: '@tomasbauer',
    services: ['pub', 'tankovna', 'lokal-hamburk']
  }
]
<|endoftext|>


<commit_before>var _ = require('lodash');
var path = require('path');

var base = require('./options.base.js');

module.exports = _.defaults({
  'agreement-version' : 2,
  auth: {
    restrict: function (req, res, next) {
      //Restrict API
      var loginOnly = ['/api/1.0/dojos/stats',
                       '/api/1.0/dojo_create',
                       '/api/1.0/dojos/my_dojos',
                       '/api/1.0/dojos/bulk_update',
                       '/api/1.0/dojos/bulk_delete',
                       '/api/1.0/dojos/save_dojo_lead',
                       '/api/1.0/load_setup_dojo_steps'
                      ];

      function restrictAccess(res) {
        res.writeHead(401);
        res.end(JSON.stringify({ok:false,why:'restricted'}));
      }

      if(_.contains(loginOnly, req.url)) {
        if(req.user) return next();
        return restrictAccess(res);
      }

      return next();

    },
    redirect:{
      restrict: '/'
    },
    sendemail: false,
    email: {
      code: {
        register: 'auth-register',
        create_reset: 'auth-create-reset'
      },
      subject: {
        register: 'Welcome to CoderDojo!',
        create_reset: 'CoderDojo Password Reset'
      },
      content: {
        resetlinkprefix: 'http://' + '127.0.0.1' + ':8000/reset',
        confirmlinkprefix: 'http://' + '127.0.0.1' + ':8000/confirm'
      }
    }
  },

  mail: {
    folder: path.resolve(__dirname + '/../email-templates'),
    mail: {
      from:'youremail@example.com'
    },
    config: {
      service: 'Gmail',
      auth: {
        user: 'youremail@example.com',
        pass: 'yourpass'
      }
    }
  },

  client: [
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10301, pin: 'role:cd-dojos,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10302, pin: 'role:cd-countries,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10302, pin: 'role:cd-geonames,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-users,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-agreements,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-profiles,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10305, pin: 'role:cd-badges,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:user,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10305, pin: 'role:cd-badges,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10306, pin: 'role:cd-events,cmd:*'}
  ],

  redis: {
    "host": process.env.TARGETIP || (process.env.DOCKER_HOST ? require('url').parse(process.env.DOCKER_HOST).hostname : '127.0.0.1'),
    "port": 6379 // this isn't optioned
  }

}, base);
<commit_msg>Fix options.development.js<commit_after>var _ = require('lodash');
var path = require('path');

var base = require('./options.base.js');

module.exports = _.defaults({
  'agreement-version' : 2,
  auth: {
    restrict: ['/dashboard'],
    redirect:{
      restrict: '/'
    },
    sendemail: false,
    email: {
      code: {
        register: 'auth-register',
        create_reset: 'auth-create-reset'
      },
      subject: {
        register: 'Welcome to CoderDojo!',
        create_reset: 'CoderDojo Password Reset'
      },
      content: {
        resetlinkprefix: 'http://' + '127.0.0.1' + ':8000/reset',
        confirmlinkprefix: 'http://' + '127.0.0.1' + ':8000/confirm'
      }
    }
  },

  mail: {
    folder: path.resolve(__dirname + '/../email-templates'),
    mail: {
      from:'youremail@example.com'
    },
    config: {
      service: 'Gmail',
      auth: {
        user: 'youremail@example.com',
        pass: 'yourpass'
      }
    }
  },

  client: [
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10301, pin: 'role:cd-dojos,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10302, pin: 'role:cd-countries,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10302, pin: 'role:cd-geonames,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-users,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-agreements,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:cd-profiles,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10305, pin: 'role:cd-badges,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10303, pin: 'role:user,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10305, pin: 'role:cd-badges,cmd:*'},
    {type: 'web', host: process.env.TARGETIP || '127.0.0.1', port: 10306, pin: 'role:cd-events,cmd:*'}
  ],

  redis: {
    "host": process.env.TARGETIP || (process.env.DOCKER_HOST ? require('url').parse(process.env.DOCKER_HOST).hostname : '127.0.0.1'),
    "port": 6379 // this isn't optioned
  }

}, base);
<|endoftext|>