<commit_before>/**
*
* Docbase engine
* Appbase
* Henrique Sa, Feb '15
* MIT license
*
*/

!(function ($, angular){

    var jWindow = $(window);
    var angApp;

    var exports = this;
    var Docbase = exports.Docbase = {};
    var Events = Docbase.events = {};
    var Route = Docbase.route = {};

    /**
    * Github offers an API with a very strict, non-increasable limit for the client side.
    * Docbase uses this API to make a map of your markdown files, and then fetches
    * them through get requests to GitHub Pages, not the API. As there is a small chance
    * the GitHub quota will be exceeded before the docs are mapped, keep a map file (JSON)
    * in the root of your site so that can be fetched.
    * Only use HTML5 mode if you're hosting it yourself, requires server config.
    */

    Docbase.methods = ['file', 'github'];

    Docbase.run = function(options) {
        var defaults = {
            method: 'github',
            path: 'docs',
            map: {
                file: 'map.json',
                path: 'docs'
            },
            github: {
                /*user: 'user',
                repo: 'repo',*/
                path: '/',
                branch: 'gh-pages',
                editGithubBtn: true
            },
            html5mode: false,
            indexType: 'html',
            indexSrc: 'html/main.html',
            flatdocHtml: 'html/flatdoc.html',
            angularAppName: 'docbaseApp'
        };

        options = $.extend({}, defaults, options);

        if(options.method === 'github') {
            if(!options.github.user || !options.github.repo) {
                throw 'Missing GitHub user/repo info.';
            }
        }

        // Removes trailing '/'s.
        Docbase.methods.forEach(function(method){
            var properties = options[method];
            Object.keys(properties).forEach(function(key){
                properties[key] = cutTrailingSlashes(properties[key]);
            });
        });
        options.map.path = cutTrailingSlashes(options.map.path);
        options.path = cutTrailingSlashes(options.path);

        Docbase.options = options;

        Events.bind();

        angApp = angular
            .module(options.angularAppName, ['ngRoute'])
            .controller('URLCtrl',
                ['$scope', '$routeParams', '$location', '$timeout', '$anchorScroll', Route.URLCtrl]
            )
            .controller('MainCtrl', ['$location', Route.mainCtrl])
            .config(['$routeProvider', '$locationProvider', Route.config])
            .run(
                ['$rootScope', '$location', '$routeParams', '$anchorScroll',
                '$route', Route.anchorConfig]
            );

        Docbase[options.method](options[options.method]);
    }

    Docbase.github = function(options) {
        githubTree(options, function(error, map){
            if(error) {
                Docbase.file(Docbase.options.map);
            } else if(checkSchema(map)){
                Docbase.map = map;
                jWindow.trigger('mapped');
                Events.bind();
            } else {
                throw 'GitHub tree mapping error.';
            }
        });
    }

    Docbase.file = function(options) {
        $.get(options.path + '/' + options.file)
        .success(function(map){
            if(checkSchema(map)){
                var v = Object.keys(map);
                if(v.length &&  map[v[0]][0].files.length && map[v[0]][0].files[0].name){
                    Docbase.map = map;
                    jWindow.trigger('mapped');
                    Events.bind();
                } else {
                    throw 'Map does not have a file entry. Check the documentation';
                }
            } else {
                throw 'Map file schema error. Check the documentation.';
            }
        })
        .error(function(error){
            throw error;
        });
    }

    Events.switchBind = function(state){
        jWindow[state]('flatdoc:ready', Events.parseTitle);
        jWindow[state]('ajaxError', Events.ajaxError);
    }

    Events.bind = function(){
        Events.switchBind('on');
    };

    Events.unbind = function(){
        Events.switchBind('off');
    }

    Events.ready = function(){
        jWindow.trigger('docbase:ready');
    }

    Events.ajaxError = function(event, request){
        if(request.status === 403 && Docbase.options.method === 'github') {
            console.error('Github API quota exceeded.');
        }
    }

    /**
    * Parses title object, looking for specs such as three columns.
    * Simply make your fiirst markdown title an object to customize it.
    * Use double quotes on the markdown.
    * Example: {"title": "Actual title", "threeColumns": false}
    */
    Events.parseTitle = function(){
        var element = $('[role~="flatdoc-content"] h1:first');
        var menuTitle = $( '#' + element.attr('id') + '-link' );

        var content = element.html();
        try {
            content = content.replace(/\u201D/g, '"');
            content = content.replace(/\u201C/g, '"');
            content = JSON.parse(content);

            element.html(content.title);
            menuTitle.html(content.title);

            if(content.threeColumns) {
                $('body').removeClass('no-literate');
            } else {
                $('body').addClass('no-literate');
            }

        } catch (e) {
            // No JSON object found, keep title as-is, but disable three-collums
            $('body').addClass('no-literate');
        };

        Events.ready();
    };

    Route.config = function($routeProvider, $location, $rootScope, $anchorScroll){
        var flatdocURL = Docbase.options.flatdocHtml;
        var mainURL = Docbase.options.indexSrc;

        if(Docbase.options.indexType === 'markdown') {
            mainURL = flatdocURL;
        }

        $routeProvider
        .when('/:version/:folder/:file', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl'
        })
        .when('/:version/:folder', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl'
        })
        .when('/:version', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl'
        })
        .when('/', {
            templateUrl: mainURL,
            controller: 'MainCtrl'
        })
        .otherwise({
            redirectTo: '/'
        });

        $location.html5Mode(Docbase.options.html5mode);
    }

    Route.anchorConfig = function($rootScope, $location, $routeParams, $anchorScroll, $route){

        /**
        * Hack to prevent route reload when hash is changed.
        * Makes sure only the hash was change and intercepts the event.
        */

        $rootScope.$on('$locationChangeStart', function(evnt, newRoute, oldRoute){
            var firstRoute = newRoute.split('#');
            var hash = firstRoute[firstRoute.length-1];

            firstRoute.splice(firstRoute.length-1, 1);
            firstRoute = firstRoute.join('#');

            var secondRoute = oldRoute.split('#');
            secondRoute.splice(secondRoute.length === 2 ? 2 : secondRoute.length-1, 1);
            secondRoute = secondRoute.join('#');

            if(firstRoute === secondRoute && (newRoute !== oldRoute)) {

                $location.hash(hash);
                var lastRoute = $route.current;
                var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                    $route.current = lastRoute;
                    unbind();
                });
                $anchorScroll();
            }
        });

        /**
        * Initial scroll to hash on page load.
        */

        $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
            jWindow.on('docbase:ready', function(){
                $anchorScroll();
            });
        });
    }

    Route.fetch = function(file){
        var options = Docbase.options;

        Flatdoc.run({
          fetcher: Flatdoc.file(options.path + file + '.md')
        });
    };

    // Route.github = function(path){
    //     var options = Docbase.options.github;
    //     var ghRepo = options.user + '/' + options.repo;
    //     var ghPath = options.path + path + '.md';
    //     var branch = options.branch;

    //     Flatdoc.run({
    //         fetcher: Flatdoc.github(ghRepo, ghPath, branch)
    //     });
    // };

    Route.URLCtrl = function($scope, $routeParams, $location, $timeout, $anchorScroll){
        $scope.loading = true;

        jWindow.on('docbase:ready', function(){
            $timeout(function(){
                $scope.loading = false;
            });
        });

        if(Docbase.map) {
            mapLoaded();
        } else {
            jWindow.on('mapped', mapLoaded);
        }

        function mapLoaded(){
            var map = Docbase.map;
            var currentVersion = $routeParams.version;
            var versions = Object.keys(map);

            var location = Route.updatePath($routeParams);

            $timeout(function(){
                $scope.versions = versions;
                $scope.currentVersion = currentVersion || versions[versions.length-1];
                $scope.map = map;
            });

            $location.path(location.path);

            if(!location.fail){
                $timeout(function(){
                    var options = Docbase.options;
                    var gh = Docbase.options.github;

                    var url = 'https://github.com/'
                    + gh.user + '/' + gh.repo
                    + '/tree/' + gh.branch + '/'
                    + options.path + location.path + '.md';

                    $scope.github = url;
                });

                Route.fetch(location.path);

                // jWindow.on('docbase:ready', function(){
                //     $location.hash($routeParams.scrollTo);
                //     $anchorScroll();
                // });

            } else {
                $scope.github = false;
            }
        }
    };

    Route.mainCtrl = function($location){
        if(Docbase.options.indexType === 'markdown') {
            var path = Docbase.options.indexSrc;
            if(endsWith(path, '.md')){
                path = path.substring(0, path.length-3);
            }
            if(path.charAt(0) !== '/'){
                path = '/' + path;
            }
            $location.path(path);
        }
    };

    Route.updatePath = function(params){
        var map = Docbase.map;
        var version = params.version;
        var folder = params.folder;
        var file = params.file;

        if(!map[version]){
            console.error('Version not mapped.');
            return {path: '/', fail: true};
        }

        var mapFolder;
        if(folder){
            mapFolder = map[version].filter(function(folders){
                return folders.name === folder;
            });
            if(!mapFolder.length){
                console.error('Folder not mapped.');
                return {path: '/' + version, fail: true};
            }
        }
        if(file){
            var mapFile = mapFolder[0].files.filter(function(files){
                return files.name === file;
            });
            if(!mapFile.length){
                console.error('File not mapped.');
                return {path: '/' + version + '/' + file, fail: true};
            }
        }

        folder = folder || map[version][0].name;
        var folderObj = map[version].filter(function(each){
            return each.name === folder;
        })[0];
        file = file || folderObj.files[0].name;

        var path = '/' + version + '/' + folder + '/' + file;

        return {path: path, fail: false};
    };

    function cutTrailingSlashes(value){
        if(!angular.isString(value)){
            return value;
        }

        value = value.charAt(0) === '/' ? value.substring(1) : value;
        return endsWith(value, '/') ? value.substring(0, value.length-1) : value;
    }

    function checkSchema(map){
        return validate = schema({
            '*': Array.of(schema({
                name: String,
                label: String,
                files: Array.of(schema({
                    name: String,
                    label: String
                }))
            }))
        })(map);
    }

    function githubTree(options, callback){
        var full_path = options.path;

        var path = full_path.split('/');
        var deleted = path.splice(path.length-1, 1);
        path.join('/');
        deleted = deleted[0];

        var baseurl = 'https://api.github.com/repos/'
                    + options.user + '/' + options.repo + '/';

        var url = baseurl  + 'contents/' + path;

        $.get(url, {ref: options.branch})
        .success(function(data){
            var sha = data.filter(function(each){
                return each.name === deleted;
            })[0].sha;

            $.get(baseurl + 'git/trees/' + sha + '?recursive=1')
            .success(function(tree) {
                tree = tree.tree.filter(function(each){
                    return endsWith(each.path, '.md');
                });

                var map = {};

                tree.forEach(function(each){
                    var sub_path = each.path.split('/');
                    /* assuming sub_path[0] is the version,
                     * sub_path[1] is the folder,
                     * and sub_path[2] is the file.
                     */
                    if(sub_path.length >= 3){
                        var version = sub_path[0];
                        var folder = sub_path[1];
                        var file = sub_path[2].substring(0, sub_path[2].length-3);

                        // Version is new
                        if(!map[version]){
                            map[version] = [];
                        }

                        // Folder is new
                        if( !map[version].filter(function(a){ return a.name === folder }).length ) {
                            map[version].push({ label: folder, name: folder, files: [] });
                        }

                        // Add file
                        map[version].forEach(function(each){
                            if(each.name === folder)
                                each.files.push({name: file, label: file});
                        });
                    }
                });

                callback(null, map);

            })
            .error(function(error){
                callback(error);
            });
        })
        .error(function(error){
            callback(error);
        });
    }

    /**
    * endsWith polyfill, from MDN
    * Created by Ripter, last edited by Mathias Bynens
    */
    function endsWith(subjectString, searchString, position){
      if(position === undefined || position > subjectString.length){
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.indexOf(searchString, position);
      return lastIndex !== -1 && lastIndex === position;
    }
})(window.jQuery, window.angular);<commit_msg>Bower update<commit_after>/**
*
* Docbase engine
* Appbase
* Henrique Sa, Feb '15
* MIT license
*
*/

!(function ($, angular){

    var jWindow = $(window);
    var angApp;

    var exports = this;
    var Docbase = exports.Docbase = {};
    var Events = Docbase.events = {};
    var Route = Docbase.route = {};

    /**
    * Github offers an API with a very strict, non-increasable limit for the client side.
    * Docbase uses this API to make a map of your markdown files, and then fetches
    * them through get requests to GitHub Pages, not the API. As there is a small chance
    * the GitHub quota will be exceeded before the docs are mapped, keep a map file (JSON)
    * in the root of your site so that can be fetched.
    * Only use HTML5 mode if you're hosting it yourself, requires server config.
    */

    Docbase.methods = ['file', 'github'];

    Docbase.run = function(options) {
        var defaults = {
            method: 'github',
            path: 'docs',
            map: {
                file: 'map.json',
                path: 'docs'
            },
            github: {
                /*user: 'user',
                repo: 'repo',*/
                path: '/',
                branch: 'gh-pages',
                editGithubBtn: true
            },
            html5mode: false,
            indexType: 'html',
            indexSrc: 'v1/path/index.md',
            indexHtml: 'html/main.html',
            flatdocHtml: 'html/flatdoc.html',
            angularAppName: 'docbaseApp'
        };

        options = $.extend({}, defaults, options);

        if(options.method === 'github') {
            if(!options.github.user || !options.github.repo) {
                throw 'Missing GitHub user/repo info.';
            }
        }

        // Removes trailing '/'s.
        Docbase.methods.forEach(function(method){
            var properties = options[method];
            Object.keys(properties).forEach(function(key){
                properties[key] = cutTrailingSlashes(properties[key]);
            });
        });
        options.map.path = cutTrailingSlashes(options.map.path);
        options.path = cutTrailingSlashes(options.path);

        Docbase.options = options;

        Events.bind();

        angApp = angular
            .module(options.angularAppName, ['ngRoute'])
            .factory('FlatdocService', ['$q', '$route', '$location', '$anchorScroll', Route.fetch])
            .controller('URLCtrl', ['$scope', '$location', 'data', Route.URLCtrl])
            .controller('MainCtrl', ['$location', Route.mainCtrl])
            .config(['$routeProvider', '$locationProvider', Route.config])
            .run(
                ['$rootScope', '$location', '$routeParams', '$anchorScroll',
                '$route', Route.anchorConfig]
            );

        Docbase[options.method](options[options.method]);
    }

    Docbase.github = function(options) {
        githubTree(options, function(error, map){
            if(error) {
                Docbase.file(Docbase.options.map);
            } else if(checkSchema(map)){
                Docbase.map = map;
                jWindow.trigger('mapped');
                Events.bind();
            } else {
                throw 'GitHub tree mapping error.';
            }
        });
    }

    Docbase.file = function(options) {
        $.get(options.path + '/' + options.file)
        .success(function(map){
            if(checkSchema(map)){
                var v = Object.keys(map);
                if(v.length &&  map[v[0]][0].files.length && map[v[0]][0].files[0].name){
                    Docbase.map = map;
                    jWindow.trigger('mapped');
                    Events.bind();
                } else {
                    throw 'Map does not have a file entry. Check the documentation';
                }
            } else {
                throw 'Map file schema error. Check the documentation.';
            }
        })
        .error(function(error){
            throw error;
        });
    }

    Events.switchBind = function(state){
        jWindow[state]('flatdoc:ready', Events.parseTitle);
        jWindow[state]('ajaxError', Events.ajaxError);
    }

    Events.bind = function(){
        Events.switchBind('on');
    };

    Events.unbind = function(){
        Events.switchBind('off');
    }

    Events.ready = function(){
        jWindow.trigger('docbase:ready');
    }

    Events.ajaxError = function(event, request){
        if(request.status === 403 && Docbase.options.method === 'github') {
            console.error('Github API quota exceeded.');
        }
    }

    /**
    * Parses title object, looking for specs such as three columns.
    * Simply make your fiirst markdown title an object to customize it.
    * Use double quotes on the markdown.
    * Example: {"title": "Actual title", "threeColumns": false}
    */
    Events.parseTitle = function(){
        var element = $('[role~="flatdoc-content"] h1:first');
        var menuTitle = $( '#' + element.attr('id') + '-link' );

        var content = element.html();
        try {
            content = content.replace(/\u201D/g, '"');
            content = content.replace(/\u201C/g, '"');
            content = JSON.parse(content);

            element.html(content.title);
            menuTitle.html(content.title);

            if(content.threeColumns) {
                $('body').removeClass('no-literate');
            } else {
                $('body').addClass('no-literate');
            }

            Events.parsed = true;

        } catch (e) {
            // No JSON object found, keep title as-is, but disable three-collums
            if(!Events.parsed) {
                $('body').addClass('no-literate');
            }
        };

        Events.ready();
    };

    Route.config = function($routeProvider, $location, $rootScope, $anchorScroll){
        var flatdocURL = Docbase.options.flatdocHtml;
        var mainURL = Docbase.options.indexHtml;
        var resolve = {
            data: function(FlatdocService) {
                return FlatdocService.getData().then(function(data){
                    return data;
                });
            }
        }

        $routeProvider
        .when('/:version/:folder/:file', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl',
            resolve: resolve
        })
        .when('/:version/:folder', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl',
            resolve: resolve
        })
        .when('/:version', {
            templateUrl: flatdocURL,
            controller: 'URLCtrl',
            resolve: resolve
        })
        .when('/', {
            templateUrl: mainURL,
            controller: 'MainCtrl'
        })
        .otherwise({
            redirectTo: '/'
        });
        $location.html5Mode(Docbase.options.html5mode);
    }

    Route.anchorConfig = function($rootScope, $location, $routeParams, $anchorScroll, $route){

        /**
        * Hack to prevent route reload when hash is changed.
        * Makes sure only the hash was change and intercepts the event.
        */

        $rootScope.$on('$locationChangeStart', function(evnt, newRoute, oldRoute){
            var firstRoute = newRoute.split('#');
            var hash = firstRoute[firstRoute.length-1];

            firstRoute.splice(firstRoute.length-1, 1);
            firstRoute = firstRoute.join('#');

            var secondRoute = oldRoute.split('#');
            secondRoute.splice(secondRoute.length === 2 ? 2 : secondRoute.length-1, 1);
            secondRoute = secondRoute.join('#');

            if(firstRoute === secondRoute && (newRoute !== oldRoute)) {

                $location.hash(hash);
                var lastRoute = $route.current;
                var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                    $route.current = lastRoute;
                    unbind();
                });
                $anchorScroll();
            }
        });

        /**
        * Initial scroll to hash on page load.
        */

        $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
            jWindow.on('docbase:ready', function(){
                $anchorScroll();
            });
        });
    }

    Route.fetch = function($q, $route, $location, $anchorScroll){
        function fetcher() {
            var deferred = $q.defer();
            var options = Docbase.options;

            if(Docbase.map) {
                mapLoaded();
            } else {
                jWindow.on('mapped', mapLoaded);
            }

            function mapLoaded(){
                var map = Docbase.map;
                var retObj = {};
                var currentVersion = $route.current.params.version;
                var versions = Object.keys(map);
                var location = Route.updatePath($route.current.params);

                retObj.versions = versions;
                retObj.currentVersion = currentVersion || versions[versions.length-1];
                retObj.map = map;
                retObj.locationPath = location.path;

                if(!location.fail){
                    var options = Docbase.options;
                    var gh = Docbase.options.github;

                    var url = 'https://github.com/'
                    + gh.user + '/' + gh.repo
                    + '/tree/' + gh.branch + '/'
                    + options.path + location.path + '.md';

                    retObj.github = url;

                    Events.parsed = false;

                    Flatdoc.file(options.path + location.path + '.md')(function(err, markdown){
                        var data = Flatdoc.parser.parse(markdown, function(code){
                            return Flatdoc.highlighters.generic(code)
                        });
                        retObj.markdown = data;
                        deferred.resolve(retObj);
                    });

                } else {
                    retObj.github = false;
                    deferred.resolve(retObj);
                }
            }
            return deferred.promise;
        }

        return {
            getData: function(){
                return new fetcher();
            }
        }
    };

    Route.URLCtrl = function($scope, $location, data){
        $location.path(data.locationPath);

        if(!data.fail){
            $scope.versions = data.versions;
            $scope.currentVersion = data.currentVersion;
            $scope.map = data.map;
            $scope.github = data.github;

            var content = data.markdown;

            $('[role="flatdoc-content"]').html(content.content.find('>*'));
            $('[role="flatdoc-menu"]').html(Flatdoc.menuView(content.menu));

            jWindow.trigger('flatdoc:ready');
        }
    };

    Route.mainCtrl = function($location){
        if(Docbase.options.indexType === 'markdown') {
            var path = Docbase.options.indexSrc;
            if(endsWith(path, '.md')){
                path = path.substring(0, path.length-3);
            }
            if(path.charAt(0) !== '/'){
                path = '/' + path;
            }

            $location.path(path);
        }
    };

    Route.updatePath = function(params){
        var map = Docbase.map;
        var version = params.version;
        var folder = params.folder;
        var file = params.file;

        if(!map[version]){
            console.error('Version not mapped.');
            return {path: '/', fail: true};
        }

        var mapFolder;
        if(folder){
            mapFolder = map[version].filter(function(folders){
                return folders.name === folder;
            });
            if(!mapFolder.length){
                console.error('Folder not mapped.');
                return {path: '/' + version, fail: true};
            }
        }
        if(file){
            var mapFile = mapFolder[0].files.filter(function(files){
                return files.name === file;
            });
            if(!mapFile.length){
                console.error('File not mapped.');
                return {path: '/' + version + '/' + file, fail: true};
            }
        }

        folder = folder || map[version][0].name;
        var folderObj = map[version].filter(function(each){
            return each.name === folder;
        })[0];
        file = file || folderObj.files[0].name;

        var path = '/' + version + '/' + folder + '/' + file;

        return {path: path, fail: false};
    };

    function cutTrailingSlashes(value){
        if(!angular.isString(value)){
            return value;
        }

        value = value.charAt(0) === '/' ? value.substring(1) : value;
        return endsWith(value, '/') ? value.substring(0, value.length-1) : value;
    }

    function checkSchema(map){
        return validate = schema({
            '*': Array.of(schema({
                name: String,
                label: String,
                files: Array.of(schema({
                    name: String,
                    label: String
                }))
            }))
        })(map);
    }

    function githubTree(options, callback){
        var full_path = options.path;

        var path = full_path.split('/');
        var deleted = path.splice(path.length-1, 1);
        path.join('/');
        deleted = deleted[0];

        var baseurl = 'https://api.github.com/repos/'
                    + options.user + '/' + options.repo + '/';

        var url = baseurl  + 'contents/' + path;

        $.get(url, {ref: options.branch})
        .success(function(data){
            var sha = data.filter(function(each){
                return each.name === deleted;
            })[0].sha;

            $.get(baseurl + 'git/trees/' + sha + '?recursive=1')
            .success(function(tree) {
                tree = tree.tree.filter(function(each){
                    return endsWith(each.path, '.md');
                });

                var map = {};

                tree.forEach(function(each){
                    var sub_path = each.path.split('/');
                    /* assuming sub_path[0] is the version,
                     * sub_path[1] is the folder,
                     * and sub_path[2] is the file.
                     */
                    if(sub_path.length >= 3){
                        var version = sub_path[0];
                        var folder = sub_path[1];
                        var file = sub_path[2].substring(0, sub_path[2].length-3);

                        // Version is new
                        if(!map[version]){
                            map[version] = [];
                        }

                        // Folder is new
                        if( !map[version].filter(function(a){ return a.name === folder }).length ) {
                            map[version].push({ label: folder, name: folder, files: [] });
                        }

                        // Add file
                        map[version].forEach(function(each){
                            if(each.name === folder)
                                each.files.push({name: file, label: file});
                        });
                    }
                });

                callback(null, map);

            })
            .error(function(error){
                callback(error);
            });
        })
        .error(function(error){
            callback(error);
        });
    }

    /**
    * endsWith polyfill, from MDN
    * Created by Ripter, last edited by Mathias Bynens
    */
    function endsWith(subjectString, searchString, position){
      if(position === undefined || position > subjectString.length){
        position = subjectString.length;
      }
      position -= searchString.length;
      var lastIndex = subjectString.indexOf(searchString, position);
      return lastIndex !== -1 && lastIndex === position;
    }
})(window.jQuery, window.angular);<|endoftext|>


<commit_before>"use strict";

var chain = require('chain-middleware');
var Model = require('./model');

var api = new RestAPI(); // Stores our models

var restifier = module.exports = function() {
  return api.setup.apply(api, Array.prototype.slice.call(arguments, 0));
};

/**
 * Resets the restifier API. You should typically not use this.
 */
restifier.reset = function() {
  api = new RestAPI();
};

function RestAPI() {
  this.initialized = false;
  this.models = {};
}

RestAPI.prototype.setup = function() {
  if (arguments.length > 1) {
    var middlewares = [];
    middlewares.push(restifier.initialize());
    var args = Array.prototype.slice.call(arguments, 0);
    middlewares.concat(this.modelsMiddleware(args));
  }
  var mongooseModel = arguments[0];
  if (!mongooseModel) {
    return this.initialize();
  }
  return this.model(mongooseModel);
};

/**
 * Initialization middleware.
 */
RestAPI.prototype.initialize = function() {
  if (this.initialized) {
    return function(req, res, next) {
      next();
    };
  }
  this.initialized = true;
  return require('res-error')({
    log: false
  });
};

/**
 * Creates a middleware out of multiple models.
 *
 * @param models The mongoose models
 */
RestAPI.prototype.modelsMiddleware = function(models) {
  var api = this;
  models = models.map(function(mong) {
    return api.model(mong).middleware();
  });
  return chain(models);
};

/**
 * Adds a model to RestAPI.prototype.
 *
 * @param mongooseModel The mongoose model
 */
RestAPI.prototype.model = function(mongooseModel) {
  var m = new Model(this, mongooseModel);
  this.models[mongooseModel.modelName] = m;
  return m;
};
<commit_msg>Don't add initialization middleware if already initializeD<commit_after>"use strict";

var chain = require('chain-middleware');
var Model = require('./model');

var api = new RestAPI(); // Stores our models

var restifier = module.exports = function() {
  return api.setup.apply(api, Array.prototype.slice.call(arguments, 0));
};

/**
 * Resets the restifier API. You should typically not use this.
 */
restifier.reset = function() {
  api = new RestAPI();
};

function RestAPI() {
  this.initialized = false;
  this.models = {};
}

RestAPI.prototype.setup = function() {
  if (arguments.length > 1) {
    var middlewares = [];
    if (!this.initialized) {
      middlewares.push(restifier.initialize());
    }
    var args = Array.prototype.slice.call(arguments, 0);
    middlewares.concat(this.modelsMiddleware(args));
  }
  var mongooseModel = arguments[0];
  if (!mongooseModel) {
    return this.initialize();
  }
  return this.model(mongooseModel);
};

/**
 * Initialization middleware.
 */
RestAPI.prototype.initialize = function() {
  if (this.initialized) {
    return function(req, res, next) {
      next();
    };
  }
  this.initialized = true;
  return require('res-error')({
    log: false
  });
};

/**
 * Creates a middleware out of multiple models.
 *
 * @param models The mongoose models
 */
RestAPI.prototype.modelsMiddleware = function(models) {
  var api = this;
  models = models.map(function(mong) {
    return api.model(mong).middleware();
  });
  return chain(models);
};

/**
 * Adds a model to RestAPI.prototype.
 *
 * @param mongooseModel The mongoose model
 */
RestAPI.prototype.model = function(mongooseModel) {
  var m = new Model(this, mongooseModel);
  this.models[mongooseModel.modelName] = m;
  return m;
};
<|endoftext|>


<commit_before>var $overlay = $("#overlay");
var deleteClick = false; //Used icons "delete" button toggle

var txt = document.getElementById("txt");
//Array storing all different font colors

// Tick images
var $img = $("");
var $img2 = $("");
var $img3 = $("");
var $img4 = $("");
var $img5 = $("");

//Used to specify default index of the ticks in the "Customisations" lightboxes
var tick1 = 2;
var tick2 = 1;
var tick3 = 1;
var tick4 = 4;
var e;
noteNum = 0; //Default notepad index in noteArray

//Array in which the notepads are stored in
noteArray = [];

var noteDelete;
var once;
var enter;

(function() {
  $(".lightbox ul li").click(function() {
    storeData();
  });
  //Font-Style Customisation
  $("#fstyle1").click(function() {
    $("#txt").css("font-family", "Crimson Text");
    tick1 = 0;
  });
  $("#fstyle2").click(function() {
    $("#txt").css("font-family", "Open Sans");
    tick1 = 1;
  });
  $("#fstyle3").click(function() {
    $("#txt").css("font-family", "Lato, Lato-Light");
    tick1 = 2;
  });
  $("#fstyle4").click(function() {
    $("#txt").css("font-family", "Pontano Sans");
    tick1 = 3;
  });
  $("#fstyle5").click(function() {
    $("#txt").css("font-family", "PT Sans");
    tick1 = 4;
  });
  $("#fstyle6").click(function() {
    $("#txt").css("font-family", "Arvo");
    tick1 = 5;
  });
  $("#ft_style ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selected
    $("." + tick1).remove();
    $img.appendTo("#fstyle" + (tick1 + 1));
  });

  //Font-Color Customisation
  $("#fcolor1").click(function() {
    $("#txt").css("color", "#ff0000");
    tick2 = 0;
  });
  $("#fcolor2").click(function() {
    $("#txt").css("color", "#000000");
    tick2 = 1;
  });
  $("#fcolor3").click(function() {
    $("#txt").css("color", "#FFFFFF");
    tick2 = 2;
  });
  $("#fcolor4").click(function() {
    $("#txt").css("color", "#FFFF00");
    tick2 = 3;
  });
  $("#fcolor5").click(function() {
    $("#txt").css("color", "#00FF00");
    tick2 = 4;
  });
  $("#fcolor6").click(function() {
    $("#txt").css("color", "#6fdcff");
    tick2 = 5;
  });
  $("#fcolor7").keydown(function(event) {
    if (event.which == 13) {
      var color = document.getElementById("fcolor7").value;
      color = color.toUpperCase();
      if (color.charAt(0) == '#' && color.length == 7) {
        $("#txt").css("color", color);
        tick2 = 6;
      }
      else {
        console.log("Invalid color.");
      }
    }
  });
  $("#ft_color ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selcected
    $("." + tick2).remove();
    $img2.appendTo("#fcolor" + (tick2 + 1));
  });

  //Font-size Customisation
  $("#fsize1").click(function() {
    $("#txt").css("font-size", "16px");
    tick3 = 0;
  });
  $("#fsize2").click(function() {
    $("#txt").css("font-size", "14px");
    tick3 = 1;
  });
  $("#fsize3").click(function() {
    $("#txt").css("font-size", "12px");
    tick3 = 2;
  });
  $("#fsize4").keydown(function(event) {
    if (event.which == 13) {
      var size = document.getElementById("fsize4").value;
      console.log();
      if (size % 1 === 0) {
        size += "px";
        console.log(size);
        $("#txt").css("font-size", size);
        tick3 = 3;
      }
      else {
        console.log("Invalid size.");
      }
    }
  });
  $("#ft_size ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selcected
    $("." + tick3).remove();
    $img3.appendTo("#fsize" + (tick3 + 1));
  });

  //Themes Customisation
  $("#t1").click(function() {
    $("body").css("background-color", "#EBEBEB");
    $(".button").css("background-color", "#EBEBEB");
    $(".4").remove();
    $img4.appendTo(this); //Tick is added beside this option when selected
    tick4 = 0; //Index of the tick image
  });
  $("#t2").click(function() {
    $("body").css("background-color", "#FFFFFF");
    $(".button").css("background-color", "#FFFFFF");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 1;
  });
  $("#t3").click(function() {
    $("body").css("background-color", "#DDBDFB");
    $(".button").css("background-color", "#DDBDFB");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 2;
  });
  $("#t4").click(function() {
    $("body").css("background-color", "#FFAE49");
    $(".button").css("background-color", "#FFAE49");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 3;
  });
  $("#t5").click(function() {
    $("body").css("background-color", "#FFF977");
    $(".button").css("background-color", "#FFF977");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 4;
  });
  $("#t6").click(function() {
    $("body").css("background-color", "#D7EAFB");
    $(".button").css("background-color", "#D7EAFB");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 5;
  });
  $("#t7").keydown(function(event) {
    if (event.which == 13) {
      var color = document.getElementById("t7").value;
      color = color.toUpperCase();
      if (color.charAt(0) == '#' && color.length == 7) {
        $("body").css("background-color", color);
        $(".button").css("background-color", color);
        $img4.appendTo(this);
        tick4 = 6;
      }
      else {
        console.log("Invalid color.");
      }
    }
  });
  //The above section is devoted to styling when the customisation list items are selected.
  $overlay.click(function() {
    close();
  });

  function storeData() {
    //indexOf does not work for multi-dimensional arrays, had to use a for loop instead
    for (var i = 0; i < noteArray.length; i++) {
      if (noteArray[i][2] === "") { //if Notepad name is empty, it is set to "Blank"
        noteArray[i][2] = "Blank";
        $("#scroll ul li").children("p").html(noteArray[i][2]);
        storeData();
      }
    }
    if (noteArray.length === 0) { //if all the Notepads are deleted, a blank one is created
      noteArray = [
        []
      ];
      noteArray[0][0] = 1;
      noteArray[0][1] = "";
      noteArray[0][2] = "Blank";
      txt.value = "";
      localStorage.setItem("notes", JSON.stringify(noteArray));
      init2();
      $("#scroll ul li").children("p").html(noteArray[0][2]);
    }

    //Stores various pieces of data into localStorage for later use
    localStorage.setItem("noteNum", noteNum);

    //The following 3 lines of code make use of the style DOM object.
    localStorage.setItem("fstyle", txt.style.fontFamily);
    localStorage.setItem("fsize", txt.style.fontSize);
    localStorage.setItem("theme", document.body.style.backgroundColor);

    //Since there is no style fontColor object, I am using an array in its stead.
    localStorage.setItem("fcolor", txt.style.color);
    localStorage.setItem("tick1", tick1);
    localStorage.setItem("tick2", tick2);
    localStorage.setItem("tick3", tick3);
    localStorage.setItem("tick4", tick4);

    $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
    noteNum = parseInt(localStorage.getItem("noteNum"));
    //The textarea content is saved into noteArray as the content of the current Notepad
    noteArray[noteNum][1] = txt.value;
    //Notepad name is saved into noteArray
    noteArray[noteNum][2] = $("#scroll ul li:eq(" + noteNum + ")").children("p").html();
    //noteArray is saved in localStorage
    localStorage.setItem("notes", JSON.stringify(noteArray));

    var height = parseInt(txt.style.height) - 400;
    if (height < 400) height = 400;
    txt.style.height = height + "px";
  }

  function init() {
    //The following statement is set to run only once, when the extension is first opened.
    if (!Boolean(localStorage.getItem("once"))) {
      once = true;
      localStorage.setItem("once", once);
      storeData();
    }
    $('html').bind('keypress', function(e) {
      if (e.keyCode == 13) {
        if (!enter) {
          enter = true; //Allows the renaming function to occur
        }
        else if (enter) {
          e.preventDefault();
          enter = false;
          close();
        }
      }
    });
    //localStorage only returns String so noteArray has to be converted into JSON format before being reverted back to an array.
    noteArray = JSON.parse(localStorage.getItem("notes"));
    //"Plus" button functionality
    $("#plus").click(function() {
      //Pushes blank notepad details into the last row of the array.
      noteArray.push([parseInt(noteArray[noteArray.length - 1]) + 1, "", "Blank"]);
      //Adds new notepad item into "Organisation" lightbox
      $("#scroll ul").append("

" + noteArray[noteArray.length - 1][2] + "

");
    });
    $("#delete").click(function() { //When "delete" button is clicked
      deleteClick = true;
      //Notepad items turn red to indicate that the "delete" function is active
      $("#scroll ul li").css("color", "#FF0000");
    });
    $("#scroll ul").on("click", "li", function() { //Click event handler is attached to Notepad item
      e = this;
      var $liID = $(this).attr("id"); //each Notepad's ID is stored in the first column of noteArray
      if (deleteClick) { //If delete function is enabled
        deleteClick = false;
        $(this).remove(); //Notepad item removed from DOM
        $("#scroll ul li").css("color", "#bfbfbf"); //Notepad names are reverted back to their original color
        //indexOf() does not work for multi-dimensional array.
        for (var i2 = 0; i2 < noteArray.length; i2++) {
          if (noteArray[i2][0] == $liID) {
            noteDelete = i2; //noteDelete is equal to the noteArray index of the selected Notepad
            break;
          }
        }
        noteArray.splice(noteDelete, 1); //The row of the selected notepad is removed from noteArray
        if (noteDelete < noteNum) { //If the deleted notepad is situated before the one that is in use (in terms of array index)
          noteNum--;
        } else if (noteNum == noteDelete && noteArray.length !== 0) { //If the deleted notepad is the one that is in use
          noteNum = 0;
          txt.value = noteArray[0][1];
          $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
        }
        storeData();
      } else {
        for (var i = 0; i < noteArray.length; i++) { //indexOf does not work for multi-dimensional arrays
          if (noteArray[i][0] == $liID) {
            noteNum = i; //noteNum is equal to the index of the array that is in use
            $img5.appendTo("#scroll ul li:eq(" + noteNum + ")"); //Tick image is added beside the selectd notepad
            localStorage.setItem("noteNum", noteNum);
            txt.value = noteArray[noteNum][1]; //value of textarea is set to the content of the current notepad
          }
        }
        if (!enter && !deleteClick) { //If delete and enter button were not clicked
          close();
        }
        if (enter) { //If enter button was clicked
          $(this).children("p").attr("contenteditable", "true");
          storeData();
        }
      }
    });

    //Main initiation block
    noteNum = parseInt(localStorage.getItem("noteNum"));
    txt.value = noteArray[noteNum][1];

    //Customisations
    txt.style.fontFamily = localStorage.getItem("fstyle");
    txt.style.fontSize = localStorage.getItem("fsize");
    txt.style.color = localStorage.getItem("fcolor");
    document.body.style.backgroundColor = localStorage.getItem("theme");

    tick1 = localStorage.getItem("tick1");
    tick2 = localStorage.getItem("tick2");
    tick3 = localStorage.getItem("tick3");
    tick4 = localStorage.getItem("tick4");

    $img.appendTo("#ft_style ul li:eq(" + tick1 + ")"); //nth-child method deprecated
    $img2.appendTo("#ft_color ul li:eq(" + tick2 + ")");
    $img3.appendTo("#ft_size ul li:eq(" + tick3 + ")");
    $img4.appendTo("#themes ul li:eq(" + tick4 + ")");
    //Adds all notepad items into the "Organisation" lightbox when the extension is first opened
  }

  function init2() {
    for (num = 0; noteArray !== null && noteArray.length > 0 && num < noteArray.length; num++) {
      $("#scroll ul").append("

");
      $("#scroll ul li:eq(" + num + ")").children("p").html(noteArray[num][2]);
    }
    tick5 = localStorage.getItem("tick5");
    $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
    txt.onkeyup = storeData;
    storeData();
  }

  //Most of the code content below was taken from other sources
  //"Download" button functionality
  $("#download").click(function() {
    var savedText = txt.value;
    var textBlob = new Blob([savedText], {
      type: 'text/plain; charset=UTF-8'
    });
    var downloadLink = document.createElement("a");
    downloadLink.download = noteArray[noteNum][2];
    downloadLink.innerHTML = "HiddenLink";
    downloadLink.click();
    window.URL = window.URL || window.webkitURL;
    downloadLink.href = window.URL.createObjectURL(textBlob);
    downloadLink.onclick = destroyLink;
    downloadLink.style.display = "none";
    document.body.appendChild(downloadLink);
    downloadLink.click();
  });

  function close() {
    $(".lightbox").hide(400);
    $("#overlay").hide();
    if (e !== null) {
      $(e).children("p").attr("contenteditable", "false");
    }
    enter = false;
    storeData();
  }

  function destroyLink(event) {
    document.body.removeChild(event.target);
  }
  init();
  init2();
}());
<commit_msg>Removed the errors.<commit_after>var $overlay = $("#overlay");
var deleteClick = false; //Used icons "delete" button toggle

var txt = document.getElementById("txt");
//Array storing all different font colors

// Tick images
var $img = $("");
var $img2 = $("");
var $img3 = $("");
var $img4 = $("");
var $img5 = $("");

//Used to specify default index of the ticks in the "Customisations" lightboxes
var tick1 = 2;
var tick2 = 1;
var tick3 = 1;
var tick4 = 4;
var e;
noteNum = 0; //Default notepad index in noteArray

//Array in which the notepads are stored in
noteArray = [];

var noteDelete;
var once;
var enter;

(function() {
  $(".lightbox ul li").click(function() {
    storeData();
  });
  //Font-Style Customisation
  $("#fstyle1").click(function() {
    $("#txt").css("font-family", "Crimson Text");
    tick1 = 0;
  });
  $("#fstyle2").click(function() {
    $("#txt").css("font-family", "Open Sans");
    tick1 = 1;
  });
  $("#fstyle3").click(function() {
    $("#txt").css("font-family", "Lato, Lato-Light");
    tick1 = 2;
  });
  $("#fstyle4").click(function() {
    $("#txt").css("font-family", "Pontano Sans");
    tick1 = 3;
  });
  $("#fstyle5").click(function() {
    $("#txt").css("font-family", "PT Sans");
    tick1 = 4;
  });
  $("#fstyle6").click(function() {
    $("#txt").css("font-family", "Arvo");
    tick1 = 5;
  });
  $("#ft_style ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selected
    $("." + tick1).remove();
    $img.appendTo("#fstyle" + (tick1 + 1));
  });

  //Font-Color Customisation
  $("#fcolor1").click(function() {
    $("#txt").css("color", "#ff0000");
    tick2 = 0;
  });
  $("#fcolor2").click(function() {
    $("#txt").css("color", "#000000");
    tick2 = 1;
  });
  $("#fcolor3").click(function() {
    $("#txt").css("color", "#FFFFFF");
    tick2 = 2;
  });
  $("#fcolor4").click(function() {
    $("#txt").css("color", "#FFFF00");
    tick2 = 3;
  });
  $("#fcolor5").click(function() {
    $("#txt").css("color", "#00FF00");
    tick2 = 4;
  });
  $("#fcolor6").click(function() {
    $("#txt").css("color", "#6fdcff");
    tick2 = 5;
  });
  $("#fcolor7").keydown(function(event) {
    if (event.which == 13) {
      var color = document.getElementById("fcolor7").value;
      color = color.toUpperCase();
      if (color.charAt(0) == '#' && color.length == 7) {
        $("#txt").css("color", color);
        tick2 = 6;
      }
      else {
        console.log("Invalid color.");
      }
    }
  });
  $("#ft_color ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selcected
    $("." + tick2).remove();
    $img2.appendTo("#fcolor" + (tick2 + 1));
  });

  //Font-size Customisation
  $("#fsize1").click(function() {
    $("#txt").css("font-size", "16px");
    tick3 = 0;
  });
  $("#fsize2").click(function() {
    $("#txt").css("font-size", "14px");
    tick3 = 1;
  });
  $("#fsize3").click(function() {
    $("#txt").css("font-size", "12px");
    tick3 = 2;
  });
  $("#fsize4").keydown(function(event) {
    if (event.which == 13) {
      var size = document.getElementById("fsize4").value;
      console.log();
      if (size % 1 === 0) {
        size += "px";
        console.log(size);
        $("#txt").css("font-size", size);
        tick3 = 3;
      }
      else {
        console.log("Invalid size.");
      }
    }
  });
  $("#ft_size ul li").click(function() {
    //The existing tick image is removed. New tick image is added beside the option that is selcected
    $("." + tick3).remove();
    $img3.appendTo("#fsize" + (tick3 + 1));
  });

  //Themes Customisation
  $("#t1").click(function() {
    $("body").css("background-color", "#EBEBEB");
    $(".button").css("background-color", "#EBEBEB");
    $(".4").remove();
    $img4.appendTo(this); //Tick is added beside this option when selected
    tick4 = 0; //Index of the tick image
  });
  $("#t2").click(function() {
    $("body").css("background-color", "#FFFFFF");
    $(".button").css("background-color", "#FFFFFF");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 1;
  });
  $("#t3").click(function() {
    $("body").css("background-color", "#DDBDFB");
    $(".button").css("background-color", "#DDBDFB");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 2;
  });
  $("#t4").click(function() {
    $("body").css("background-color", "#FFAE49");
    $(".button").css("background-color", "#FFAE49");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 3;
  });
  $("#t5").click(function() {
    $("body").css("background-color", "#FFF977");
    $(".button").css("background-color", "#FFF977");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 4;
  });
  $("#t6").click(function() {
    $("body").css("background-color", "#D7EAFB");
    $(".button").css("background-color", "#D7EAFB");
    $(".4").remove();
    $img4.appendTo(this);
    tick4 = 5;
  });
  $("#t7").keydown(function(event) {
    if (event.which == 13) {
      var color = document.getElementById("t7").value;
      color = color.toUpperCase();
      color = color.trim().toUpperCase();
      if (color.charAt(0) == '#' && color.length == 7) {
        $("body").css("background-color", color);
        $(".button").css("background-color", color);
        $img4.appendTo(this);
        tick4 = 6;
      }
      else {
        console.log("Invalid color.");
      }
    }
  });
  //The above section is devoted to styling when the customisation list items are selected.
  $overlay.click(function() {
    close();
  });

  function storeData() {
    //indexOf does not work for multi-dimensional arrays, had to use a for loop instead
    for (var i = 0; i < noteArray.length; i++) {
      if (noteArray[i][2] === "") { //if Notepad name is empty, it is set to "Blank"
        noteArray[i][2] = "Blank";
        $("#scroll ul li").children("p").html(noteArray[i][2]);
        storeData();
      }
    }
    if (noteArray.length === 0) { //if all the Notepads are deleted, a blank one is created
      noteArray = [
        []
      ];
      noteArray[0][0] = 1;
      noteArray[0][1] = "";
      noteArray[0][2] = "Blank";
      txt.value = "";
      localStorage.setItem("notes", JSON.stringify(noteArray));
      init2();
      $("#scroll ul li").children("p").html(noteArray[0][2]);
    }

    //Stores various pieces of data into localStorage for later use
    localStorage.setItem("noteNum", noteNum);

    //The following 3 lines of code make use of the style DOM object.
    localStorage.setItem("fstyle", txt.style.fontFamily);
    localStorage.setItem("fsize", txt.style.fontSize);
    localStorage.setItem("theme", document.body.style.backgroundColor);

    //Since there is no style fontColor object, I am using an array in its stead.
    localStorage.setItem("fcolor", txt.style.color);
    localStorage.setItem("tick1", tick1);
    localStorage.setItem("tick2", tick2);
    localStorage.setItem("tick3", tick3);
    localStorage.setItem("tick4", tick4);

    $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
    noteNum = parseInt(localStorage.getItem("noteNum"));
    //The textarea content is saved into noteArray as the content of the current Notepad
    noteArray[noteNum][1] = txt.value;
    //Notepad name is saved into noteArray
    noteArray[noteNum][2] = $("#scroll ul li:eq(" + noteNum + ")").children("p").html();
    //noteArray is saved in localStorage
    localStorage.setItem("notes", JSON.stringify(noteArray));

    var height = parseInt(txt.style.height) - 400;
    if (height < 400) height = 400;
    txt.style.height = height + "px";
  }

  function init() {
    //The following statement is set to run only once, when the extension is first opened.
    if (!Boolean(localStorage.getItem("once"))) {
      once = true;
      localStorage.setItem("once", once);
      storeData();
    }
    $('html').bind('keypress', function(e) {
      if (e.keyCode == 13) {
        if (!enter) {
          enter = true; //Allows the renaming function to occur
        }
        else if (enter) {
          e.preventDefault();
          enter = false;
          close();
        }
      }
    });
    //localStorage only returns String so noteArray has to be converted into JSON format before being reverted back to an array.
    noteArray = JSON.parse(localStorage.getItem("notes"));
    //"Plus" button functionality
    $("#plus").click(function() {
      //Pushes blank notepad details into the last row of the array.
      noteArray.push([parseInt(noteArray[noteArray.length - 1]) + 1, "", "Blank"]);
      //Adds new notepad item into "Organisation" lightbox
      $("#scroll ul").append("

" + noteArray[noteArray.length - 1][2] + "

");
    });
    $("#delete").click(function() { //When "delete" button is clicked
      deleteClick = true;
      //Notepad items turn red to indicate that the "delete" function is active
      $("#scroll ul li").css("color", "#FF0000");
    });
    $("#scroll ul").on("click", "li", function() { //Click event handler is attached to Notepad item
      e = this;
      var $liID = $(this).attr("id"); //each Notepad's ID is stored in the first column of noteArray
      if (deleteClick) { //If delete function is enabled
        deleteClick = false;
        $(this).remove(); //Notepad item removed from DOM
        $("#scroll ul li").css("color", "#bfbfbf"); //Notepad names are reverted back to their original color
        //indexOf() does not work for multi-dimensional array.
        for (var i2 = 0; i2 < noteArray.length; i2++) {
          if (noteArray[i2][0] == $liID) {
            noteDelete = i2; //noteDelete is equal to the noteArray index of the selected Notepad
            break;
          }
        }
        noteArray.splice(noteDelete, 1); //The row of the selected notepad is removed from noteArray
        if (noteDelete < noteNum) { //If the deleted notepad is situated before the one that is in use (in terms of array index)
          noteNum--;
        } else if (noteNum == noteDelete && noteArray.length !== 0) { //If the deleted notepad is the one that is in use
          noteNum = 0;
          txt.value = noteArray[0][1];
          $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
        }
        storeData();
      } else {
        for (var i = 0; i < noteArray.length; i++) { //indexOf does not work for multi-dimensional arrays
          if (noteArray[i][0] == $liID) {
            noteNum = i; //noteNum is equal to the index of the array that is in use
            $img5.appendTo("#scroll ul li:eq(" + noteNum + ")"); //Tick image is added beside the selectd notepad
            localStorage.setItem("noteNum", noteNum);
            txt.value = noteArray[noteNum][1]; //value of textarea is set to the content of the current notepad
          }
        }
        if (!enter && !deleteClick) { //If delete and enter button were not clicked
          close();
        }
        if (enter) { //If enter button was clicked
          $(this).children("p").attr("contenteditable", "true");
          storeData();
        }
      }
    });

    //Main initiation block
    noteNum = parseInt(localStorage.getItem("noteNum"));
    txt.value = noteArray[noteNum][1];

    //Customisations
    txt.style.fontFamily = localStorage.getItem("fstyle");
    txt.style.fontSize = localStorage.getItem("fsize");
    txt.style.color = localStorage.getItem("fcolor");
    document.body.style.backgroundColor = localStorage.getItem("theme");

    tick1 = localStorage.getItem("tick1");
    tick2 = localStorage.getItem("tick2");
    tick3 = localStorage.getItem("tick3");
    tick4 = localStorage.getItem("tick4");

    $img.appendTo("#ft_style ul li:eq(" + tick1 + ")"); //nth-child method deprecated
    $img2.appendTo("#ft_color ul li:eq(" + tick2 + ")");
    $img3.appendTo("#ft_size ul li:eq(" + tick3 + ")");
    $img4.appendTo("#themes ul li:eq(" + tick4 + ")");
    //Adds all notepad items into the "Organisation" lightbox when the extension is first opened
  }

  function init2() {
    for (num = 0; noteArray !== null && noteArray.length > 0 && num < noteArray.length; num++) {
      $("#scroll ul").append("

");
      $("#scroll ul li:eq(" + num + ")").children("p").html(noteArray[num][2]);
    }
    tick5 = localStorage.getItem("tick5");
    $img5.appendTo("#scroll ul li:eq(" + noteNum + ")");
    txt.onkeyup = storeData;
    storeData();
  }

  //Most of the code content below was taken from other sources
  //"Download" button functionality
  $("#download").click(function() {
    var savedText = txt.value;
    var textBlob = new Blob([savedText], {
      type: 'text/plain; charset=UTF-8'
    });
    var downloadLink = document.createElement("a");
    downloadLink.download = noteArray[noteNum][2];
    downloadLink.innerHTML = "HiddenLink";
    downloadLink.click();
    window.URL = window.URL || window.webkitURL;
    downloadLink.href = window.URL.createObjectURL(textBlob);
    downloadLink.onclick = destroyLink;
    downloadLink.style.display = "none";
    document.body.appendChild(downloadLink);
    downloadLink.click();
  });

  function close() {
    $(".lightbox").hide(400);
    $("#overlay").hide();
    if (e !== null) {
      $(e).children("p").attr("contenteditable", "false");
    }
    enter = false;
    storeData();
  }

  function destroyLink(event) {
    document.body.removeChild(event.target);
  }
  init();
  init2();
}());
<|endoftext|>


<commit_before>(function() {
	var ThreeUVModifierPlugin = E2.plugins.three_uv_modifier = function(core, node) {
		Plugin.apply(this, arguments)

		this.desc = 'Adjust a texture\'s UV coordinates'

		this.input_slots = [{
			name: 'texture',
			dt: core.datatypes.TEXTURE
		}, {
			name: 'u offset',
			dt: core.datatypes.FLOAT,
			def: 0.0
		},  {
			name: 'v offset',
			dt: core.datatypes.FLOAT,
			def: 0.0
		}, {
			name: 'u repeat',
			dt: core.datatypes.FLOAT,
			def: 1.0
		},  {
			name: 'v repeat',
			dt: core.datatypes.FLOAT,
			def: 1.0
		}]

		this.output_slots = [{
			name: 'texture',
			dt: core.datatypes.TEXTURE
		}]
	}

	ThreeUVModifierPlugin.prototype = Object.create(Plugin.prototype)

	ThreeUVModifierPlugin.prototype.reset = function() {
		this.texture = undefined
		this.uOffset = 0
		this.vOffset = 0
		this.uRepeat = 1
		this.vRepeat = 1

		this.dirty = false
	}

	ThreeUVModifierPlugin.prototype.update_input = function(slot, data) {
		if (slot.index === 0 && data) { // texture
			if (data.image === THREE.Texture.DEFAULT_IMAGE) {
				// store a reference - if the map is not there, the texture
				// hasn't loaded - force the node to update until one is loaded
				this.pendingTexture = data
				this.always_update = true
			}
			else {
				this.texture = data.clone()
			}
		}
		else if (slot.index === 1) { // u offset
			this.uOffset = data
			this.dirty = true
		}
		else if (slot.index === 2) { // v offset
			this.vOffset = data
			this.dirty = true
		}
		else if (slot.index === 3) { // u repeat
			this.uRepeat = data
			this.dirty = true
		}
		else if (slot.index === 4) { // v repeat
			this.vRepeat = data
			this.dirty = true
		}
	}

	ThreeUVModifierPlugin.prototype.state_changed = function(ui) {
		if (ui) {
			return
		}
	}

	ThreeUVModifierPlugin.prototype.update_state = function() {
		if (this.pendingTexture && this.pendingTexture.image !== THREE.Texture.DEFAULT_IMAGE) {
			this.texture = this.pendingTexture.clone()
			this.always_update = false
			this.pendingTexture = undefined
			this.dirty = true
		}

		if (this.dirty && this.texture) {
			this.texture.offset.set(this.uOffset, this.vOffset)
			this.texture.repeat.set(this.uRepeat, this.vRepeat)
			this.texture.needsUpdate = true

			this.dirty = false
		}
	}

	ThreeUVModifierPlugin.prototype.update_output = function() {
		return this.texture
	}

})()<commit_msg>set dirtiness correctly - the texture wasn't getting updated if it was already loaded<commit_after>(function() {
	var ThreeUVModifierPlugin = E2.plugins.three_uv_modifier = function(core, node) {
		Plugin.apply(this, arguments)

		this.desc = 'Adjust a texture\'s UV coordinates'

		this.input_slots = [{
			name: 'texture',
			dt: core.datatypes.TEXTURE
		}, {
			name: 'u offset',
			dt: core.datatypes.FLOAT,
			def: 0.0
		},  {
			name: 'v offset',
			dt: core.datatypes.FLOAT,
			def: 0.0
		}, {
			name: 'u repeat',
			dt: core.datatypes.FLOAT,
			def: 1.0
		},  {
			name: 'v repeat',
			dt: core.datatypes.FLOAT,
			def: 1.0
		}]

		this.output_slots = [{
			name: 'texture',
			dt: core.datatypes.TEXTURE
		}]
	}

	ThreeUVModifierPlugin.prototype = Object.create(Plugin.prototype)

	ThreeUVModifierPlugin.prototype.reset = function() {
		this.texture = undefined
		this.uOffset = 0
		this.vOffset = 0
		this.uRepeat = 1
		this.vRepeat = 1

		this.dirty = false
	}

	ThreeUVModifierPlugin.prototype.update_input = function(slot, data) {
		if (slot.index === 0 && data) { // texture
			if (data.image === THREE.Texture.DEFAULT_IMAGE) {
				// store a reference - if the map is not there, the texture
				// hasn't loaded - force the node to update until one is loaded
				this.pendingTexture = data
				this.always_update = true
			}
			else {
				this.texture = data.clone()
				this.dirty = true
			}
		}
		else if (slot.index === 1) { // u offset
			this.uOffset = data
			this.dirty = true
		}
		else if (slot.index === 2) { // v offset
			this.vOffset = data
			this.dirty = true
		}
		else if (slot.index === 3) { // u repeat
			this.uRepeat = data
			this.dirty = true
		}
		else if (slot.index === 4) { // v repeat
			this.vRepeat = data
			this.dirty = true
		}
	}

	ThreeUVModifierPlugin.prototype.state_changed = function(ui) {
		if (ui) {
			return
		}
	}

	ThreeUVModifierPlugin.prototype.update_state = function() {
		if (this.pendingTexture && this.pendingTexture.image !== THREE.Texture.DEFAULT_IMAGE) {
			this.texture = this.pendingTexture.clone()
			this.always_update = false
			this.pendingTexture = undefined
			this.dirty = true
		}

		if (this.dirty && this.texture) {
			this.texture.offset.set(this.uOffset, this.vOffset)
			this.texture.repeat.set(this.uRepeat, this.vRepeat)
			this.texture.needsUpdate = true

			this.dirty = false
		}
	}

	ThreeUVModifierPlugin.prototype.update_output = function() {
		return this.texture
	}

})()<|endoftext|>


<commit_before>(function () {
  var inNode = false
    , inBrowser = false;

  if (typeof window === 'undefined') {
    inNode = true;
  } else {
    inBrowser = true;
  }

  /**
   * Library dependencies.
   */

  var EventEmitter = (inNode ? process.EventEmitter : window.EventEmitter)
    , slice = Array.prototype.slice
    , concat = Array.prototype.concat;

  /**
   * Apply the same callback to every given event.
   *
   * @api public
   */

  EventEmitter.prototype.every = function every () {
    var args = slice.call(arguments, 0)
      , fn = args.pop()
      , i = args.length;

    while (i--) {
      this.on(args[i], fn);
    }

    return this;
  };

  /**
   * Small utility to use a object to assign EventEmitters.
   *
   * @param {Object} events
   * @api public
   */

  EventEmitter.prototype.multiple = function multiple (events) {
    for (var event in events) {
      if (events.hasOwnProperty(event)) {
        this.on(event, events[event]);
      }
    }

    return this;
  };

  /**
   * Checks if the event is already added to the event emitter. Useful to prevent
   * memory leaks.
   *
   * @param {String} event name of the event
   * @param {Function} fn callback
   * @returns {Boolean} do we have the event or not
   * @api public
   */

  EventEmitter.prototype.has = function has (event, fn) {
    if (event && this._events && this._events[event]) {
      // optimize the case of one listener
      if (typeof this._events[event] == 'function')
        return fn === this._events[event];

      this._events[event].indexOf(fn) >= 0;
    }

    return false;
  };

  /**
   * Sets an idle timer for an event. If the event has not been emitted
   * within the time frame specified, the callback is executed with the
   * supplied arguments.
   *
   * @param {String} event name of the event
   * @param {Number} timeout number of ms of idle time allowed
   * @param {Function} fn callback
   * @param {Object} arg1 first of any arguments to include
   * @api public
   */

  EventEmitter.prototype.idle = function idle (event, timeout, fn) {
    var args = slice.call(arguments, 0).slice(3)
      , self = this;

    /**
     * Callback wrapper.
     *
     * @api private
     */

    function callback () {
      self.removeListener(event, self.idle);

      var nargs = args.slice(0);
      nargs.unshift(event);

      fn.apply(self, nargs);
    }

    var timerId = setTimeout(callback, timeout);

    // remove the timeout and reset the idle timer in case of event
    this.once(event, function removeTimout () {
      clearTimeout(timerId);

      args.unshift(fn);
      args.unshift(timeout);
      args.unshift(event);

      self.idle.apply(self, args);
    });

    return this;
  };

  /**
   * Delay the execution of the emitting of the event.
   *
   * @param {String} event
   * @param {Number} timeout
   * @api public
   */

  EventEmitter.prototype.delay = function delay (event, timeout) {
    var self = this
      , args = slice.call(arguments, 0);

    // remove the time argument from the arguments array
    args.splice(1, 1);

    setTimeout(function timeout () {
      self.emit.apply(self, args);
    }, timeout);

    return this;
  };

  /**
   * Defer execution until the call stack is empty and the event has been
   * progressed to the next tick.
   *
   * It follows the same argument structure as regular emit, it's just asynchronous
   * and at the begin of the next call stack.
   *
   * @param {String} event event name
   * @param {Function} fn callback
   * @api public
   */

  EventEmitter.prototype.defer = function defer () {
    var self = this
      , args = arguments;

    if (inNode) {
      process.nextTick(function nextTick () {
        self.emit.apply(self, args);
      });
    } else {
      setTimeout(function nextTick () {
        self.emit.apply(self, args);
      }, 0);
    }

    return this;
  };

  /**
   * Provide aliases to common associated methods.
   */

  EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
  EventEmitter.prototype.removeEventListener = EventEmitter.prototype.removeListener;
  EventEmitter.prototype.addEventListener = EventEmitter.prototype.addListener;

  /**
   * The following methods are hidden behind a preference lock as they might
   * introduce a slight performance hit, so if you don't use this functionality
   * it will not affect your application.
   *
   * @param {Boolean} emit enable emit override
   * @api public
   */

  if (inNode) {
    exports = module.exports = function preferencelocked (emit) {
      if (!emit) return;

      /**
       * Backup of the existing event emitter.
       *
       * @type {Function}
       * @api public
       */

      var emit = EventEmitter.prototype.emit;

      /**
       * Override the emit method so we can implement a catch all method.
       *
       * @param {String} event name of the event
       * @returns {Boolean} true if the event was emitted, false it wasn't
       * @api public
       */

      EventEmitter.prototype.emit = function emitter (event) {
        // the build in emit returns true or false it was able to emit the event to
        // a listener
        if (!emit.apply(this, arguments)) {
          if (event !== 'newListener') {
            emit.apply(this, concat.apply(['uncaught'], arguments));
          }

          return false;
        }

        return true;
      };
    };

    /**
     * Library version.
     *
     * @type {String}
     * @api public
     */

    exports.version = '0.0.5';
  }

})();<commit_msg>Small fixes to the has method, and some jshinting<commit_after>(function () {
  var inNode = false
    , inBrowser = false;

  if (typeof window === 'undefined') {
    inNode = true;
  } else {
    inBrowser = true;
  }

  /**
   * Library dependencies.
   */

  var EventEmitter = (inNode ? process.EventEmitter : window.EventEmitter)
    , slice = Array.prototype.slice
    , concat = Array.prototype.concat;

  /**
   * Apply the same callback to every given event.
   *
   * @api public
   */

  EventEmitter.prototype.every = function every () {
    var args = slice.call(arguments, 0)
      , fn = args.pop()
      , i = args.length;

    while (i--) {
      this.on(args[i], fn);
    }

    return this;
  };

  /**
   * Small utility to use a object to assign EventEmitters.
   *
   * @param {Object} events
   * @api public
   */

  EventEmitter.prototype.multiple = function multiple (events) {
    for (var event in events) {
      if (events.hasOwnProperty(event)) {
        this.on(event, events[event]);
      }
    }

    return this;
  };

  /**
   * Checks if the event is already added to the event emitter. Useful to prevent
   * memory leaks.
   *
   * @param {String} event name of the event
   * @param {Function} fn callback
   * @returns {Boolean} do we have the event or not
   * @api public
   */

  EventEmitter.prototype.has = function has (event, fn) {
    if (event && this._events && this._events[event]) {
      // optimize the case of one listener
      if (typeof this._events[event] == 'function')
        return fn === this._events[event];

      return this._events[event].indexOf(fn) >= 0;
    }

    return false;
  };

  /**
   * Sets an idle timer for an event. If the event has not been emitted
   * within the time frame specified, the callback is executed with the
   * supplied arguments.
   *
   * @param {String} event name of the event
   * @param {Number} timeout number of ms of idle time allowed
   * @param {Function} fn callback
   * @param {Object} arg1 first of any arguments to include
   * @api public
   */

  EventEmitter.prototype.idle = function idle (event, timeout, fn) {
    var args = slice.call(arguments, 0).slice(3)
      , self = this;

    /**
     * Callback wrapper.
     *
     * @api private
     */

    function callback () {
      self.removeListener(event, self.idle);

      var nargs = args.slice(0);
      nargs.unshift(event);

      fn.apply(self, nargs);
    }

    var timerId = setTimeout(callback, timeout);

    // remove the timeout and reset the idle timer in case of event
    this.once(event, function removeTimout () {
      clearTimeout(timerId);

      args.unshift(fn);
      args.unshift(timeout);
      args.unshift(event);

      self.idle.apply(self, args);
    });

    return this;
  };

  /**
   * Delay the execution of the emitting of the event.
   *
   * @param {String} event
   * @param {Number} timeout
   * @api public
   */

  EventEmitter.prototype.delay = function delay (event, timeout) {
    var self = this
      , args = slice.call(arguments, 0);

    // remove the time argument from the arguments array
    args.splice(1, 1);

    setTimeout(function timeout () {
      self.emit.apply(self, args);
    }, timeout);

    return this;
  };

  /**
   * Defer execution until the call stack is empty and the event has been
   * progressed to the next tick.
   *
   * It follows the same argument structure as regular emit, it's just asynchronous
   * and at the begin of the next call stack.
   *
   * @param {String} event event name
   * @param {Function} fn callback
   * @api public
   */

  EventEmitter.prototype.defer = function defer () {
    var self = this
      , args = arguments;

    if (inNode) {
      process.nextTick(function nextTick () {
        self.emit.apply(self, args);
      });
    } else {
      setTimeout(function nextTick () {
        self.emit.apply(self, args);
      }, 0);
    }

    return this;
  };

  /**
   * Provide aliases to common associated methods.
   */

  EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
  EventEmitter.prototype.removeEventListener = EventEmitter.prototype.removeListener;
  EventEmitter.prototype.addEventListener = EventEmitter.prototype.addListener;

  /**
   * The following methods are hidden behind a preference lock as they might
   * introduce a slight performance hit, so if you don't use this functionality
   * it will not affect your application.
   *
   * @param {Boolean} emit enable emit override
   * @api public
   */

  if (inNode) {
    exports = module.exports = function preferencelocked (emit) {
      if (!emit) return;

      /**
       * Backup of the existing event emitter.
       *
       * @type {Function}
       * @api public
       */

      var emitter = EventEmitter.prototype.emit;

      /**
       * Override the emit method so we can implement a catch all method.
       *
       * @param {String} event name of the event
       * @returns {Boolean} true if the event was emitted, false it wasn't
       * @api public
       */

      EventEmitter.prototype.emit = function emit (event) {
        // the build in emit returns true or false it was able to emit the event to
        // a listener
        if (!emitter.apply(this, arguments)) {
          if (event !== 'newListener') {
            emitter.apply(this, concat.apply(['uncaught'], arguments));
          }

          return false;
        }

        return true;
      };
    };

    /**
     * Library version.
     *
     * @type {String}
     * @api public
     */

    exports.version = '0.0.5';
  }

})();
<|endoftext|>


<commit_before>/**
* ==  background ==
*
*   @Author:
*       mutarock, mutarock@gmail.com
**/

// default option value
var g_default_value = {
    'link_up': 0,
    'link_down': 0,
    'link_right': 0,
    'link_left': 0,
    'text_up': 0,
    'text_down': 0,
    'text_right': 0,
    'text_left': 0,
    'image_up': 0,
    'image_down': 0,
    'image_right': 0,
    'image_left': 0,
    'text_up_engine': 0,
    'text_down_engine': 0,
    'text_right_engine': 0,
    'text_left_engine': 0,
    'link_gesture_mode': 0,
    'text_gesture_mode': 0,
    'image_gesture_mode': 0
};

// build in search engine
var g_build_in_seach_engines = [{
            "name" : "google",
            "url" : "http://www.google.com/search?hl=en&q=%s"
        }, {
            "name" : "yahoo",
            "url" : "http://search.yahoo.com/search?fr=crmas&p=%s"
        }, {
            "name" : "wiki",
            "url" : "http://en.wikipedia.org/w/index.php?search=%s"
        }];

// background.js startup
window.onload = function() {
    initailize();
    console.log("onload");
};

// 初始化
function initailize() {
    console.log("initailize");
    for(var i in g_default_value) {
        if(localStorage[i] == undefined) {
            localStorage[i] = g_default_value[i];
        }
    }
}

function openNewTab(tabData) {
	//console.log("Background.js");

	chrome.tabs.query({'currentWindow': true}, function(tabs){
		//console.log(tabs.length);
		var newIndex = tabs.length;
		var activeMode = activeOrNot(tabData);

		console.log(activeMode);
		if (activeMode == 99) {
			return;

		} else if (activeMode == 0) {
			tabData.isActive = true;
			tabData.newIndex = newIndex;
			openTab(tabData);

		} else if (activeMode == 1) {
			tabData.isActive = false;
			tabData.newIndex = newIndex;
			openTab(tabData);

		} else if (activeMode == 2) {
			updateCurrTab(tabData);

		} else if (activeMode == 3) {
			highLightCurrTab(tabData);

		} else {
			// Impossible.......

		}

		// if (!tabData.isUrl)
		// 	setSearchUrl(tabData);

		// // open tab
		// chrome.tabs.create({
		// 					"url" : tabData.url,
		// 					"active" : isActive,
		// 					"index" : newIndex
		// });

	});
}

function openTab(tabData) {
	if (!tabData.isUrl)
		setSearchUrl(tabData);

	// open tab
	chrome.tabs.create({
					"url" : tabData.url,
					"active" : tabData.isActive,
					"index" : tabData.newIndex
	});

}

function updateCurrTab(tabData) {
	if (!tabData.isUrl)
		setSearchUrl(tabData);

	chrome.tabs.getSelected(null, function (tab) {
		chrome.tabs.update(tab.id, {url: tabData.url});
	});

}

function highLightCurrTab(tabData) {
	// if (window.find) {

	// 	var string = String(tabData.url);
	// 	console.log(string);
	// 	var found = window.find(string, false, false, false, true, false, false);
	// 	if (!found) {
	// 		alert ("The following text was not found:\n" + string);
	// 	}
	// }
}

/****************************/
/*            UP            */
/*     \      |      /      */
/*      \  3  |  5  /     R */
/* L   1 \    |    / 7    I */
/* E -------------------- G */
/* F   2 /    |    \ 8    H */
/* T    /  4  |  6  \     T */
/*     /      |      \      */
/*           DOWN           */
/****************************/
function activeOrNot(tabData) {

	if (!tabData.isUrl) {  // text

        var mode = localStorage['text_gesture_mode'];
        if(mode == 0) {
            if(tabData.dirID % 2 == 0) {
                return localStorage['text_down'];
            } else {
                return localStorage['text_up'];
            }

        } else {mode == 1} {
            switch(tabData.dirID) {
                case 3:
                case 5:
                    return localStorage['text_up'];

                case 4:
                case 6:
                    return localStorage['text_down'];

                case 7:
                case 8:
                    return localStorage['text_right'];

                case 1:
                case 2:
                    return localStorage['text_left'];

            }

        } else {
            return 99;
        }

	}else {  // url or image
		switch(tabData.dirID) {
			case 0:
				return localStorage['link_up'];

			case 1:
				return localStorage['link_down'];

			case 2:
				return localStorage['link_right'];

			case 3:
				return localStorage['link_left'];

		}
	}

}

function setSearchUrl(tabData) {
	var enginesIndex;

	switch(tabData.dirID) {
		case 0:
			enginesIndex = localStorage['text_up_engine'];
			break;

		case 1:
			enginesIndex = localStorage['text_down_engine'];
			break;

		case 2:
			enginesIndex = localStorage['text_right_engine'];
			break;

		case 3:
			enginesIndex = localStorage['text_left_engine'];
			break;
	}

	tabData.url = g_build_in_seach_engines[enginesIndex].url.replace(
				"%s", tabData.url);
}

chrome.runtime.onMessage.addListener(
	function(request, sender, sendResponse) {
		console.log(sender.tab ?
					"from a content script:" + sender.tab.url :
					"from the extension");

		if (request.greeting == "openNewTab") {
			openNewTab(request.data);
			sendResponse({farewell: "goodbye"});
		}
});<commit_msg>fix syntax error<commit_after>/**
* ==  background ==
*
*   @Author:
*       mutarock, mutarock@gmail.com
**/

// default option value
var g_default_value = {
    'link_up': 0,
    'link_down': 0,
    'link_right': 0,
    'link_left': 0,
    'text_up': 0,
    'text_down': 0,
    'text_right': 0,
    'text_left': 0,
    'image_up': 0,
    'image_down': 0,
    'image_right': 0,
    'image_left': 0,
    'text_up_engine': 0,
    'text_down_engine': 0,
    'text_right_engine': 0,
    'text_left_engine': 0,
    'link_gesture_mode': 0,
    'text_gesture_mode': 0,
    'image_gesture_mode': 0
};

// build in search engine
var g_build_in_seach_engines = [{
            "name" : "google",
            "url" : "http://www.google.com/search?hl=en&q=%s"
        }, {
            "name" : "yahoo",
            "url" : "http://search.yahoo.com/search?fr=crmas&p=%s"
        }, {
            "name" : "wiki",
            "url" : "http://en.wikipedia.org/w/index.php?search=%s"
        }];

// background.js startup
window.onload = function() {
    initailize();
    console.log("onload");
};

// 初始化
function initailize() {
    console.log("initailize");
    for(var i in g_default_value) {
        if(localStorage[i] == undefined) {
            localStorage[i] = g_default_value[i];
        }
    }
}

function openNewTab(tabData) {
	//console.log("Background.js");

	chrome.tabs.query({'currentWindow': true}, function(tabs){
		//console.log(tabs.length);
		var newIndex = tabs.length;
		var activeMode = activeOrNot(tabData);

		console.log(activeMode);
		if (activeMode == 99) {
			return;

		} else if (activeMode == 0) {
			tabData.isActive = true;
			tabData.newIndex = newIndex;
			openTab(tabData);

		} else if (activeMode == 1) {
			tabData.isActive = false;
			tabData.newIndex = newIndex;
			openTab(tabData);

		} else if (activeMode == 2) {
			updateCurrTab(tabData);

		} else if (activeMode == 3) {
			highLightCurrTab(tabData);

		} else {
			// Impossible.......

		}

		// if (!tabData.isUrl)
		// 	setSearchUrl(tabData);

		// // open tab
		// chrome.tabs.create({
		// 					"url" : tabData.url,
		// 					"active" : isActive,
		// 					"index" : newIndex
		// });

	});
}

function openTab(tabData) {
	if (!tabData.isUrl)
		setSearchUrl(tabData);

	// open tab
	chrome.tabs.create({
					"url" : tabData.url,
					"active" : tabData.isActive,
					"index" : tabData.newIndex
	});

}

function updateCurrTab(tabData) {
	if (!tabData.isUrl)
		setSearchUrl(tabData);

	chrome.tabs.getSelected(null, function (tab) {
		chrome.tabs.update(tab.id, {url: tabData.url});
	});

}

function highLightCurrTab(tabData) {
	// if (window.find) {

	// 	var string = String(tabData.url);
	// 	console.log(string);
	// 	var found = window.find(string, false, false, false, true, false, false);
	// 	if (!found) {
	// 		alert ("The following text was not found:\n" + string);
	// 	}
	// }
}

/****************************/
/*            UP            */
/*     \      |      /      */
/*      \  3  |  5  /     R */
/* L   1 \    |    / 7    I */
/* E -------------------- G */
/* F   2 /    |    \ 8    H */
/* T    /  4  |  6  \     T */
/*     /      |      \      */
/*           DOWN           */
/****************************/
function activeOrNot(tabData) {

	if (!tabData.isUrl) {  // text

        var mode = localStorage['text_gesture_mode'];
        if(mode == 0) {
            if(tabData.dirID % 2 == 0) {
                return localStorage['text_down'];
            } else {
                return localStorage['text_up'];
            }

        } else if(mode == 1) {
            switch(tabData.dirID) {
                case 3:
                case 5:
                    return localStorage['text_up'];

                case 4:
                case 6:
                    return localStorage['text_down'];

                case 7:
                case 8:
                    return localStorage['text_right'];

                case 1:
                case 2:
                    return localStorage['text_left'];

            }

        } else {
            return 99;
        }

	}else {  // url or image
		switch(tabData.dirID) {
			case 0:
				return localStorage['link_up'];

			case 1:
				return localStorage['link_down'];

			case 2:
				return localStorage['link_right'];

			case 3:
				return localStorage['link_left'];

		}
	}

}

function setSearchUrl(tabData) {
	var enginesIndex;

	switch(tabData.dirID) {
		case 0:
			enginesIndex = localStorage['text_up_engine'];
			break;

		case 1:
			enginesIndex = localStorage['text_down_engine'];
			break;

		case 2:
			enginesIndex = localStorage['text_right_engine'];
			break;

		case 3:
			enginesIndex = localStorage['text_left_engine'];
			break;
	}

	tabData.url = g_build_in_seach_engines[enginesIndex].url.replace(
				"%s", tabData.url);
}

chrome.runtime.onMessage.addListener(
	function(request, sender, sendResponse) {
		console.log(sender.tab ?
					"from a content script:" + sender.tab.url :
					"from the extension");

		if (request.greeting == "openNewTab") {
			openNewTab(request.data);
			sendResponse({farewell: "goodbye"});
		}
});<|endoftext|>


<commit_before>/* eslint-disable max-classes-per-file */

'use strict';

class BaseCache extends Array {

    constructor (maxSize = undefined) {
        super ()
        Object.defineProperty (this, 'maxSize', {
            __proto__: null, // make it invisible
            value: maxSize,
            writable: true,
        })
    }

    clear () {
        this.length = 0
    }
}

class ArrayCache extends BaseCache {

    constructor (maxSize = undefined) {
        super (maxSize);
        Object.defineProperty (this, 'newUpdatesBySymbol', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'clearUpdatesBySymbol', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'allNewUpdates', {
            __proto__: null, // make it invisible
            value: 0,
            writable: true,
        })
        Object.defineProperty (this, 'clearAllUpdates', {
            __proto__: null, // make it invisible
            value: false,
            writable: true,
        })
    }

    getLimit (symbol, limit) {
        let newUpdatesValue = undefined

        if (symbol === undefined) {
            newUpdatesValue =  this.allNewUpdates
            this.clearAllUpdates = true
        } else {
            newUpdatesValue = this.newUpdatesBySymbol[symbol];
            this.clearUpdatesBySymbol[symbol] = true
        }

        if (newUpdatesValue === undefined) {
            return limit
        } else if (limit !== undefined) {
            return Math.min (newUpdatesValue, limit)
        } else {
            return newUpdatesValue;
        }
    }

    append (item) {
        // maxSize may be 0 when initialized by a .filter() copy-construction
        if (this.maxSize && (this.length === this.maxSize)) {
            this.shift ()
        }
        this.push (item)
        if (this.clearUpdatesBySymbol[item.symbol]) {
            this.clearUpdatesBySymbol[item.symbol] = false
            this.newUpdatesBySymbol[item.symbol] = 0
        }
        if (this.clearAllUpdates) {
            this.clearAllUpdates = false
            this.allNewUpdates = 0
        }
        this.newUpdatesBySymbol[item.symbol] = (this.newUpdatesBySymbol[item.symbol] || 0) + 1
        this.allNewUpdates = (this.allNewUpdates || 0) + 1
    }
}

class ArrayCacheByTimestamp extends BaseCache {

    constructor (maxSize = undefined) {
        super (maxSize)
        Object.defineProperty (this, 'hashmap', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'sizeTracker', {
            __proto__: null, // make it invisible
            value: new Set (),
            writable: true,
        })
        Object.defineProperty (this, 'newUpdates', {
            __proto__: null, // make it invisible
            value: 0,
            writable: true,
        })
        Object.defineProperty (this, 'clearUpdates', {
            __proto__: null, // make it invisible
            value: false,
            writable: true,
        })
    }

    getLimit (symbol, limit) {
        this.clearUpdates = true
        if (limit === undefined) {
            return this.newUpdates
        }
        return Math.min (this.newUpdates, limit)
    }

    append (item) {
        if (item[0] in this.hashmap) {
            const reference = this.hashmap[item[0]]
            if (reference !== item) {
                for (const prop in item) {
                    reference[prop] = item[prop]
                }
            }
        } else {
            this.hashmap[item[0]] = item
            if (this.maxSize && (this.length === this.maxSize)) {
                const deleteReference = this.shift ()
                delete this.hashmap[deleteReference[0]]
            }
            this.push (item)
        }
        if (this.clearUpdates) {
            this.clearUpdates = false
            this.sizeTracker.clear ()
        }
        this.sizeTracker.add (item[0])
        this.newUpdates = this.sizeTracker.size
    }
}

class ArrayCacheBySymbolById extends ArrayCache {

    constructor (maxSize = undefined) {
        super (maxSize)
        Object.defineProperty (this, 'hashmap', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
    }

    append (item) {
        const byId = this.hashmap[item.symbol] = this.hashmap[item.symbol] || {}
        if (item.id in byId) {
            const reference = byId[item.id]
            if (reference !== item) {
                for (const prop in item) {
                    reference[prop] = item[prop]
                }
            }
            item = reference
            const index = this.findIndex ((x) => x.id === item.id)
            // move the order to the end of the array
            this.splice (index, 1)
        } else {
            byId[item.id] = item
        }
        if (this.maxSize && (this.length === this.maxSize)) {
            const deleteReference = this.shift ()
            delete this.hashmap[deleteReference.symbol][deleteReference.id]
        }
        this.push (item)
        if (this.clearUpdatesBySymbol[item.symbol]) {
            this.clearUpdatesBySymbol[item.symbol] = false
            this.newUpdatesBySymbol[item.symbol] = 0
        }
        if (this.clearAllUpdates) {
            this.clearAllUpdates = false
            this.allNewUpdates = 0
        }
        this.newUpdatesBySymbol[item.symbol] = (this.newUpdatesBySymbol[item.symbol] || 0) + 1
        this.allNewUpdates = (this.allNewUpdates || 0) + 1
    }
}

module.exports = {
    ArrayCache,
    ArrayCacheByTimestamp,
    ArrayCacheBySymbolById,
}
<commit_msg>implementation v2<commit_after>/* eslint-disable max-classes-per-file */

'use strict';

class BaseCache extends Array {

    constructor (maxSize = undefined) {
        super ()
        Object.defineProperty (this, 'maxSize', {
            __proto__: null, // make it invisible
            value: maxSize,
            writable: true,
        })
    }

    clear () {
        this.length = 0
    }
}

class ArrayCache extends BaseCache {

    constructor (maxSize = undefined) {
        super (maxSize);
        Object.defineProperty (this, 'newUpdatesBySymbol', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'clearUpdatesBySymbol', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'allNewUpdates', {
            __proto__: null, // make it invisible
            value: 0,
            writable: true,
        })
        Object.defineProperty (this, 'clearAllUpdates', {
            __proto__: null, // make it invisible
            value: false,
            writable: true,
        })
    }

    getLimit (symbol, limit) {
        let newUpdatesValue = undefined

        if (symbol === undefined) {
            newUpdatesValue = this.allNewUpdates
            this.clearAllUpdates = true
        } else {
            newUpdatesValue = (symbol in this.newUpdatesBySymbol) ? this.newUpdatesBySymbol[symbol].size () : 0;
            this.clearUpdatesBySymbol[symbol] = true
        }

        if (newUpdatesValue === undefined) {
            return limit
        } else if (limit !== undefined) {
            return Math.min (newUpdatesValue, limit)
        } else {
            return newUpdatesValue;
        }
    }

    append (item) {
        // maxSize may be 0 when initialized by a .filter() copy-construction
        if (this.maxSize && (this.length === this.maxSize)) {
            this.shift ()
        }
        this.push (item)
        if (this.clearUpdatesBySymbol[item.symbol]) {
            this.clearUpdatesBySymbol[item.symbol] = false
            this.newUpdatesBySymbol[item.symbol].clear ()
        }
        if (this.clearAllUpdates) {
            this.clearAllUpdates = false
            this.allNewUpdates = 0
        }
        this.newUpdatesBySymbol[item.symbol].add (item.id)
        this.allNewUpdates = (this.allNewUpdates || 0) + 1
    }
}

class ArrayCacheByTimestamp extends BaseCache {

    constructor (maxSize = undefined) {
        super (maxSize)
        Object.defineProperty (this, 'hashmap', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
        Object.defineProperty (this, 'sizeTracker', {
            __proto__: null, // make it invisible
            value: new Set (),
            writable: true,
        })
        Object.defineProperty (this, 'newUpdates', {
            __proto__: null, // make it invisible
            value: 0,
            writable: true,
        })
        Object.defineProperty (this, 'clearUpdates', {
            __proto__: null, // make it invisible
            value: false,
            writable: true,
        })
    }

    getLimit (symbol, limit) {
        this.clearUpdates = true
        if (limit === undefined) {
            return this.newUpdates
        }
        return Math.min (this.newUpdates, limit)
    }

    append (item) {
        if (item[0] in this.hashmap) {
            const reference = this.hashmap[item[0]]
            if (reference !== item) {
                for (const prop in item) {
                    reference[prop] = item[prop]
                }
            }
        } else {
            this.hashmap[item[0]] = item
            if (this.maxSize && (this.length === this.maxSize)) {
                const deleteReference = this.shift ()
                delete this.hashmap[deleteReference[0]]
            }
            this.push (item)
        }
        if (this.clearUpdates) {
            this.clearUpdates = false
            this.sizeTracker.clear ()
        }
        this.sizeTracker.add (item[0])
        this.newUpdates = this.sizeTracker.size
    }
}

class ArrayCacheBySymbolById extends ArrayCache {

    constructor (maxSize = undefined) {
        super (maxSize)
        Object.defineProperty (this, 'hashmap', {
            __proto__: null, // make it invisible
            value: {},
            writable: true,
        })
    }

    append (item) {
        const byId = this.hashmap[item.symbol] = this.hashmap[item.symbol] || {}
        if (item.id in byId) {
            const reference = byId[item.id]
            if (reference !== item) {
                for (const prop in item) {
                    reference[prop] = item[prop]
                }
            }
            item = reference
            const index = this.findIndex ((x) => x.id === item.id)
            // move the order to the end of the array
            this.splice (index, 1)
        } else {
            byId[item.id] = item
        }
        if (this.maxSize && (this.length === this.maxSize)) {
            const deleteReference = this.shift ()
            delete this.hashmap[deleteReference.symbol][deleteReference.id]
        }
        this.push (item)
        if (this.clearUpdatesBySymbol[item.symbol]) {
            this.clearUpdatesBySymbol[item.symbol] = false
            this.newUpdatesBySymbol[item.symbol] = 0
        }
        if (this.clearAllUpdates) {
            this.clearAllUpdates = false
            this.allNewUpdates = 0
        }
        this.newUpdatesBySymbol[item.symbol] = (this.newUpdatesBySymbol[item.symbol] || 0) + 1
        this.allNewUpdates = (this.allNewUpdates || 0) + 1
    }
}

module.exports = {
    ArrayCache,
    ArrayCacheByTimestamp,
    ArrayCacheBySymbolById,
}
<|endoftext|>