<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: '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', ['$scope', '$location', '$timeout', 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;

                $.get(Docbase.options.map.path + '/' + Docbase.options.map.file)
                .success(function(fileMap){
                    var ghMap = Docbase.map;
                    var fileMapVer = Object.keys(fileMap);

                    fileMapVer.forEach(function(fileVer){
                        if( ghMap[fileVer] ){
                            ghMap[fileVer].forEach(function(category){
                                var categoryM = fileMap[fileVer].filter(function(_category){
                                    return _category.name === category.name;
                                })[0];
                                if(categoryM && categoryM.label) {
                                    category.label = categoryM.label;

                                    category.files.forEach(function(file){
                                        var fileM = categoryM.files.filter(function(_file){
                                            return _file.name === file.name;
                                        })[0];
                                        if(fileM && fileM.label) file.label = fileM.label;
                                    });
                                }

                            });
                        }
                    });
                    jWindow.trigger('mapped');
                    Events.bind();
                })
                .error(function(error){
                    // no map available for labels
                    jWindow.trigger('mapped');
                    Events.bind();
                });
            } else {
                throw 'GitHub tree mapping error.';
            }
        });

    }

    Docbase.file = function(options) {
        $.get(options.path + '/' + options.src)
        .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.ready);
        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.');
        }
    }

    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();
                $('.content').find('pre code').each(function() {
                    $(this).addClass("prettyprint");
                });
                prettyPrint();
            });
        });
    }

    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){
                        markdown = markdown.split('\n');
                        var obj = markdown.shift();
                        obj = obj.replace(/\u201D/g, '"');
                        obj = obj.replace(/\u201C/g, '"');

                        try {
                            obj = JSON.parse(obj);
                        } catch(e) {
                            markdown.unshift(obj);
                            obj = { 'threeColumns': false };
                        }

                        markdown = markdown.join('\n');

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

                        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($scope, $location, $timeout){
        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);
        } else {
            jWindow.on('mapped', function(){
                $timeout(function(){
                    $scope.map = Docbase.map;
                    $scope.versions = Object.keys($scope.map);
                    $scope.currentVersion = $scope.versions[0];
                });
            });
        }
    };

    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>Fix mainCtrl on mapped bug and code reidentation<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', ['$scope', '$location', '$timeout', 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;

                $.get(Docbase.options.map.path + '/' + Docbase.options.map.file)
                    .success(function(fileMap) {
                        var ghMap = Docbase.map;
                        var fileMapVer = Object.keys(fileMap);

                        fileMapVer.forEach(function(fileVer) {
                            if (ghMap[fileVer]) {
                                ghMap[fileVer].forEach(function(category) {
                                    var categoryM = fileMap[fileVer].filter(function(_category) {
                                        return _category.name === category.name;
                                    })[0];
                                    if (categoryM && categoryM.label) {
                                        category.label = categoryM.label;

                                        category.files.forEach(function(file) {
                                            var fileM = categoryM.files.filter(function(_file) {
                                                return _file.name === file.name;
                                            })[0];
                                            if (fileM && fileM.label) file.label = fileM.label;
                                        });
                                    }

                                });
                            }
                        });
                        jWindow.trigger('mapped');
                        Events.bind();
                    })
                    .error(function(error) {
                        // no map available for labels
                        jWindow.trigger('mapped');
                        Events.bind();
                    });
            } else {
                throw 'GitHub tree mapping error.';
            }
        });

    }

    Docbase.file = function(options) {
        $.get(options.path + '/' + options.src)
            .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.ready);
        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.');
        }
    }

    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();
                $('.content').find('pre code').each(function() {
                    $(this).addClass("prettyprint");
                });
                prettyPrint();
            });
        });
    }

    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) {
                        markdown = markdown.split('\n');
                        var obj = markdown.shift();
                        obj = obj.replace(/\u201D/g, '"');
                        obj = obj.replace(/\u201C/g, '"');

                        try {
                            obj = JSON.parse(obj);
                        } catch (e) {
                            markdown.unshift(obj);
                            obj = {
                                'threeColumns': false
                            };
                        }

                        markdown = markdown.join('\n');

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

                        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($scope, $location, $timeout) {
        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);
        } else {
            var onMapped = function() {
                $timeout(function() {
                    $scope.map = Docbase.map;
                    $scope.versions = Object.keys($scope.map);
                    $scope.currentVersion = $scope.versions[0];
                });
            };
            if (Docbase.map) {
                onMapped();
            } else {
                jWindow.on('mapped', onMapped);
            }
        }
    };

    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>// This function might be passed either an OpenLayers.LonLat (so has
// lon and lat) or an OpenLayers.Geometry.Point (so has x and y)
function fixmystreet_update_pin(lonlat) {
    lonlat.transform(
        fixmystreet.map.getProjectionObject(),
        new OpenLayers.Projection("EPSG:4326")
    );
    document.getElementById('fixmystreet.latitude').value = lonlat.lat || lonlat.y;
    document.getElementById('fixmystreet.longitude').value = lonlat.lon || lonlat.x;
}

function fixmystreet_activate_drag() {
    fixmystreet.drag = new OpenLayers.Control.DragFeature( fixmystreet.markers, {
        onComplete: function(feature, e) {
            fixmystreet_update_pin( feature.geometry.clone() );
        }
    } );
    fixmystreet.map.addControl( fixmystreet.drag );
    fixmystreet.drag.activate();
}

function fms_markers_list(pins, transform) {
    var cols = { 'red':'R', 'green':'G', 'blue':'B', 'purple':'P' };
    var markers = [];
    for (var i=0; i<pins.length; i++) {
        var pin = pins[i];
        var loc = new OpenLayers.Geometry.Point(pin[1], pin[0]);
        if (transform) {
            // The Strategy does this for us, so don't do it in that case.
            loc.transform(
                new OpenLayers.Projection("EPSG:4326"),
                fixmystreet.map.getProjectionObject()
            );
        }
        var marker = new OpenLayers.Feature.Vector(loc, {
            type: cols[pin[2]],
            id: pin[3],
            title: pin[4] || ''
        });
        markers.push( marker );
    }
    return markers;
}

function fixmystreet_onload() {
    if ( fixmystreet.area ) {
        var area = new OpenLayers.Layer.Vector("KML", {
            strategies: [ new OpenLayers.Strategy.Fixed() ],
            protocol: new OpenLayers.Protocol.HTTP({
                url: "/mapit/area/" + fixmystreet.area + ".kml?simplify_tolerance=0.0001",
                format: new OpenLayers.Format.KML()
            })
        });
        fixmystreet.map.addLayer(area);
        area.events.register('loadend', null, function(a,b,c) {
            var bounds = area.getDataExtent();
            if (bounds) { fixmystreet.map.zoomToExtent( bounds ); }
        });
    }

    var pin_layer_options = {
        styleMap: new OpenLayers.StyleMap({
            'default': new OpenLayers.Style({
                externalGraphic: "/i/pin${type}.png",
                graphicTitle: "${title}",
                graphicWidth: 44,
                graphicHeight: 58,
                graphicOpacity: 1,
                graphicXOffset: -22,
                graphicYOffset: -58,
                backgroundGraphic: "/i/pin-shadow.png",
                backgroundWidth: 50,
                backgroundHeight: 19,
                backgroundXOffset: -7,
                backgroundYOffset: -19,
                graphicZIndex: 11,
                backgroundGraphicZIndex: 10
            })
        })
    };
    if (fixmystreet.page == 'around') {
        fixmystreet.bbox_strategy = new OpenLayers.Strategy.BBOX();
        pin_layer_options.strategies = [ fixmystreet.bbox_strategy ];
        pin_layer_options.protocol = new OpenLayers.Protocol.HTTP({
            url: '/ajax',
            params: fixmystreet.all_pins ? { all_pins: 1 } : { },
            format: new OpenLayers.Format.FixMyStreet()
        });
    }
    fixmystreet.markers = new OpenLayers.Layer.Vector("Pins", pin_layer_options);

    var markers = fms_markers_list( fixmystreet.pins, true );
    fixmystreet.markers.addFeatures( markers );
    if (fixmystreet.page == 'around' || fixmystreet.page == 'reports' || fixmystreet.page == 'my') {
        fixmystreet.markers.events.register( 'featureselected', fixmystreet.markers, function(evt) {
            if (evt.feature.attributes.id) {
                window.location = '/report/' + evt.feature.attributes.id;
            }
            OpenLayers.Event.stop(evt);
        });
        var select = new OpenLayers.Control.SelectFeature( fixmystreet.markers );
        fixmystreet.map.addControl( select );
        select.activate();
    } else if (fixmystreet.page == 'new') {
        fixmystreet_activate_drag();
    }
    fixmystreet.map.addLayer(fixmystreet.markers);

    if ( fixmystreet.zoomToBounds ) {
        var bounds = fixmystreet.markers.getDataExtent();
        if (bounds) { fixmystreet.map.zoomToExtent( bounds ); }
    }

    $('#hide_pins_link').click(function(e) {
        e.preventDefault();
        var showhide = [
            'Show pins', 'Hide pins',
            'Dangos pinnau', 'Cuddio pinnau',
            "Vis nåler", "Gjem nåler"
        ];
        for (var i=0; i<showhide.length; i+=2) {
            if (this.innerHTML == showhide[i]) {
                fixmystreet.markers.setVisibility(true);
                this.innerHTML = showhide[i+1];
            } else if (this.innerHTML == showhide[i+1]) {
                fixmystreet.markers.setVisibility(false);
                this.innerHTML = showhide[i];
            }
        }
    });

    $('#all_pins_link').click(function(e) {
        e.preventDefault();
        fixmystreet.markers.setVisibility(true);
        var texts = [
            'en', 'Include stale reports', 'Hide stale reports',
            'nb', 'Inkluder utdaterte problemer', 'Skjul utdaterte rapporter',
            'cy', 'Cynnwys hen adroddiadau', 'Cuddio hen adroddiadau'
        ];
        for (var i=0; i<texts.length; i+=3) {
            if (this.innerHTML == texts[i+1]) {
                this.innerHTML = texts[i+2];
                fixmystreet.markers.protocol.options.params = { all_pins: 1 };
                fixmystreet.markers.refresh( { force: true } );
                lang = texts[i];
            } else if (this.innerHTML == texts[i+2]) {
                this.innerHTML = texts[i+1];
                fixmystreet.markers.protocol.options.params = { };
                fixmystreet.markers.refresh( { force: true } );
                lang = texts[i];
            }
        }
        if (lang == 'cy') {
            document.getElementById('hide_pins_link').innerHTML = 'Cuddio pinnau';
        } else if (lang == 'nb') {
            document.getElementById('hide_pins_link').innerHTML = 'Gjem nåler';
        } else {
            document.getElementById('hide_pins_link').innerHTML = 'Hide pins';
        }
    });

}

$(function(){

    var perm = new OpenLayers.Control.Permalink();
    set_map_config(perm);

    fixmystreet.map = new OpenLayers.Map("map", {
        controls: fixmystreet.controls,
        displayProjection: new OpenLayers.Projection("EPSG:4326")
    });

    fixmystreet.layer_options = OpenLayers.Util.extend({
        zoomOffset: fixmystreet.zoomOffset,
        transitionEffect: 'resize',
        numZoomLevels: fixmystreet.numZoomLevels
    }, fixmystreet.layer_options);
    var layer = new fixmystreet.map_type("", fixmystreet.layer_options);
    fixmystreet.map.addLayer(layer);

    if (!fixmystreet.map.getCenter()) {
        var centre = new OpenLayers.LonLat( fixmystreet.longitude, fixmystreet.latitude );
        centre.transform(
            new OpenLayers.Projection("EPSG:4326"),
            fixmystreet.map.getProjectionObject()
        );
        fixmystreet.map.setCenter(centre, fixmystreet.zoom || 3);
    }

    if (document.getElementById('mapForm')) {
        var click = new OpenLayers.Control.Click();
        fixmystreet.map.addControl(click);
        click.activate();
    }

    /* To let permalink not be caught by the Click layer, answer found
     * at http://www.mail-archive.com/users@openlayers.org/msg12958.html
     * Not sure why you can't use eventListeners or events.register...
     */
    OpenLayers.Event.observe( perm.element, "click", function(e) {
        OpenLayers.Event.stop(e);
        location.href = OpenLayers.Event.element(e).href;
        return false;
    });

    $(window).hashchange(function(){
        if (location.hash) { return; }
        // Okay, back to around view.
        fixmystreet.bbox_strategy.activate();
        fixmystreet.markers.refresh( { force: true } );
        fixmystreet.drag.deactivate();
        $('#side-form').hide();
        $('#side').show();
        $('#sub_map_links').show();
        fixmystreet.page = 'around';
    });

    // Vector layers must be added onload as IE sucks
    if ($.browser.msie) {
        $(window).load(fixmystreet_onload);
    } else {
        fixmystreet_onload();
    }
});

/* Overridding the buttonDown function of PanZoom so that it does
   zoomTo(0) rather than zoomToMaxExtent()
*/
OpenLayers.Control.PanZoomFMS = OpenLayers.Class(OpenLayers.Control.PanZoom, {
    buttonDown: function (evt) {
        if (!OpenLayers.Event.isLeftClick(evt)) {
            return;
        }

        switch (this.action) {
            case "panup":
                this.map.pan(0, -this.getSlideFactor("h"));
                break;
            case "pandown":
                this.map.pan(0, this.getSlideFactor("h"));
                break;
            case "panleft":
                this.map.pan(-this.getSlideFactor("w"), 0);
                break;
            case "panright":
                this.map.pan(this.getSlideFactor("w"), 0);
                break;
            case "zoomin":
                this.map.zoomIn();
                break;
            case "zoomout":
                this.map.zoomOut();
                break;
            case "zoomworld":
                this.map.zoomTo(0);
                break;
        }

        OpenLayers.Event.stop(evt);
    }
});

/* Overriding Permalink so that it can pass the correct zoom to OSM */
OpenLayers.Control.PermalinkFMS = OpenLayers.Class(OpenLayers.Control.Permalink, {
    updateLink: function() {
        var separator = this.anchor ? '#' : '?';
        var href = this.base;
        if (href.indexOf(separator) != -1) {
            href = href.substring( 0, href.indexOf(separator) );
        }

        href += separator + OpenLayers.Util.getParameterString(this.createParams(null, this.map.getZoom()+fixmystreet.zoomOffset));
        // Could use mlat/mlon here as well if we are on a page with a marker
        if (this.anchor && !this.element) {
            window.location.href = href;
        }
        else {
            this.element.href = href;
        }
    }
});

/* Pan data handler */
OpenLayers.Format.FixMyStreet = OpenLayers.Class(OpenLayers.Format.JSON, {
    read: function(json, filter) {
        if (typeof json == 'string') {
            obj = OpenLayers.Format.JSON.prototype.read.apply(this, [json, filter]);
        } else {
            obj = json;
        }
        var current, current_near;
        if (typeof(obj.current) != 'undefined' && (current = document.getElementById('current'))) {
            current.innerHTML = obj.current;
        }
        if (typeof(obj.current_near) != 'undefined' && (current_near = document.getElementById('current_near'))) {
            current_near.innerHTML = obj.current_near;
        }
        var markers = fms_markers_list( obj.pins, false );
        return markers;
    },
    CLASS_NAME: "OpenLayers.Format.FixMyStreet"
});

/* Click handler */
OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
    defaultHandlerOptions: {
        'single': true,
        'double': false,
        'pixelTolerance': 0,
        'stopSingle': false,
        'stopDouble': false
    },

    initialize: function(options) {
        this.handlerOptions = OpenLayers.Util.extend(
            {}, this.defaultHandlerOptions);
        OpenLayers.Control.prototype.initialize.apply(
            this, arguments
        );
        this.handler = new OpenLayers.Handler.Click(
            this, {
                'click': this.trigger
            }, this.handlerOptions);
    },

    trigger: function(e) {
        var lonlat = fixmystreet.map.getLonLatFromViewPortPx(e.xy);
        if (fixmystreet.page == 'new') {
            /* Already have a purple pin */
            fixmystreet.markers.features[0].move(lonlat);
        } else {
            var markers = fms_markers_list( [ [ lonlat.lat, lonlat.lon, 'purple' ] ], false );
            fixmystreet.bbox_strategy.deactivate();
            fixmystreet.markers.removeAllFeatures();
            fixmystreet.markers.addFeatures( markers );
            fixmystreet_activate_drag();
        }
        fixmystreet_update_pin(lonlat);
        // check to see if markers are visible. We click the
        // link so that it updates the text in case they go
        // back
        if ( ! fixmystreet.markers.getVisibility() ) {
            $('#hide_pins_link').click();
        }
        if (fixmystreet.page == 'new') {
            return;
        }
        $.getJSON('/report/new/ajax', {
                latitude: $('#fixmystreet\\.latitude').val(),
                longitude: $('#fixmystreet\\.longitude').val()
        }, function(data) {
            $('#councils_text').html(data.councils_text);
            $('#form_category_row').html(data.category);
            /* Need to reset this here as it gets removed when we replace
               the HTML for the dropdown */
            if ( data.has_open311 > 0 ) {
                $('#form_category').change( form_category_onchange );
            }
        });
        $('#side-form').show();
        $('#side').hide();
        $('#sub_map_links').hide();
        fixmystreet.page = 'new';
        location.hash = 'report';
    }
});

<commit_msg>Turn on Y-ordering to fix pin shadow issue.<commit_after>// This function might be passed either an OpenLayers.LonLat (so has
// lon and lat) or an OpenLayers.Geometry.Point (so has x and y)
function fixmystreet_update_pin(lonlat) {
    lonlat.transform(
        fixmystreet.map.getProjectionObject(),
        new OpenLayers.Projection("EPSG:4326")
    );
    document.getElementById('fixmystreet.latitude').value = lonlat.lat || lonlat.y;
    document.getElementById('fixmystreet.longitude').value = lonlat.lon || lonlat.x;
}

function fixmystreet_activate_drag() {
    fixmystreet.drag = new OpenLayers.Control.DragFeature( fixmystreet.markers, {
        onComplete: function(feature, e) {
            fixmystreet_update_pin( feature.geometry.clone() );
        }
    } );
    fixmystreet.map.addControl( fixmystreet.drag );
    fixmystreet.drag.activate();
}

function fms_markers_list(pins, transform) {
    var cols = { 'red':'R', 'green':'G', 'blue':'B', 'purple':'P' };
    var markers = [];
    for (var i=0; i<pins.length; i++) {
        var pin = pins[i];
        var loc = new OpenLayers.Geometry.Point(pin[1], pin[0]);
        if (transform) {
            // The Strategy does this for us, so don't do it in that case.
            loc.transform(
                new OpenLayers.Projection("EPSG:4326"),
                fixmystreet.map.getProjectionObject()
            );
        }
        var marker = new OpenLayers.Feature.Vector(loc, {
            type: cols[pin[2]],
            id: pin[3],
            title: pin[4] || ''
        });
        markers.push( marker );
    }
    return markers;
}

function fixmystreet_onload() {
    if ( fixmystreet.area ) {
        var area = new OpenLayers.Layer.Vector("KML", {
            strategies: [ new OpenLayers.Strategy.Fixed() ],
            protocol: new OpenLayers.Protocol.HTTP({
                url: "/mapit/area/" + fixmystreet.area + ".kml?simplify_tolerance=0.0001",
                format: new OpenLayers.Format.KML()
            })
        });
        fixmystreet.map.addLayer(area);
        area.events.register('loadend', null, function(a,b,c) {
            var bounds = area.getDataExtent();
            if (bounds) { fixmystreet.map.zoomToExtent( bounds ); }
        });
    }

    var pin_layer_options = {
        rendererOptions: {
            yOrdering: true
        },
        styleMap: new OpenLayers.StyleMap({
            'default': new OpenLayers.Style({
                externalGraphic: "/i/pin${type}.png",
                graphicTitle: "${title}",
                graphicWidth: 44,
                graphicHeight: 58,
                graphicOpacity: 1,
                graphicXOffset: -22,
                graphicYOffset: -58,
                backgroundGraphic: "/i/pin-shadow.png",
                backgroundWidth: 50,
                backgroundHeight: 19,
                backgroundXOffset: -7,
                backgroundYOffset: -19,
                graphicZIndex: 11,
                backgroundGraphicZIndex: 10
            })
        })
    };
    if (fixmystreet.page == 'around') {
        fixmystreet.bbox_strategy = new OpenLayers.Strategy.BBOX();
        pin_layer_options.strategies = [ fixmystreet.bbox_strategy ];
        pin_layer_options.protocol = new OpenLayers.Protocol.HTTP({
            url: '/ajax',
            params: fixmystreet.all_pins ? { all_pins: 1 } : { },
            format: new OpenLayers.Format.FixMyStreet()
        });
    }
    fixmystreet.markers = new OpenLayers.Layer.Vector("Pins", pin_layer_options);

    var markers = fms_markers_list( fixmystreet.pins, true );
    fixmystreet.markers.addFeatures( markers );
    if (fixmystreet.page == 'around' || fixmystreet.page == 'reports' || fixmystreet.page == 'my') {
        fixmystreet.markers.events.register( 'featureselected', fixmystreet.markers, function(evt) {
            if (evt.feature.attributes.id) {
                window.location = '/report/' + evt.feature.attributes.id;
            }
            OpenLayers.Event.stop(evt);
        });
        var select = new OpenLayers.Control.SelectFeature( fixmystreet.markers );
        fixmystreet.map.addControl( select );
        select.activate();
    } else if (fixmystreet.page == 'new') {
        fixmystreet_activate_drag();
    }
    fixmystreet.map.addLayer(fixmystreet.markers);

    if ( fixmystreet.zoomToBounds ) {
        var bounds = fixmystreet.markers.getDataExtent();
        if (bounds) { fixmystreet.map.zoomToExtent( bounds ); }
    }

    $('#hide_pins_link').click(function(e) {
        e.preventDefault();
        var showhide = [
            'Show pins', 'Hide pins',
            'Dangos pinnau', 'Cuddio pinnau',
            "Vis nåler", "Gjem nåler"
        ];
        for (var i=0; i<showhide.length; i+=2) {
            if (this.innerHTML == showhide[i]) {
                fixmystreet.markers.setVisibility(true);
                this.innerHTML = showhide[i+1];
            } else if (this.innerHTML == showhide[i+1]) {
                fixmystreet.markers.setVisibility(false);
                this.innerHTML = showhide[i];
            }
        }
    });

    $('#all_pins_link').click(function(e) {
        e.preventDefault();
        fixmystreet.markers.setVisibility(true);
        var texts = [
            'en', 'Include stale reports', 'Hide stale reports',
            'nb', 'Inkluder utdaterte problemer', 'Skjul utdaterte rapporter',
            'cy', 'Cynnwys hen adroddiadau', 'Cuddio hen adroddiadau'
        ];
        for (var i=0; i<texts.length; i+=3) {
            if (this.innerHTML == texts[i+1]) {
                this.innerHTML = texts[i+2];
                fixmystreet.markers.protocol.options.params = { all_pins: 1 };
                fixmystreet.markers.refresh( { force: true } );
                lang = texts[i];
            } else if (this.innerHTML == texts[i+2]) {
                this.innerHTML = texts[i+1];
                fixmystreet.markers.protocol.options.params = { };
                fixmystreet.markers.refresh( { force: true } );
                lang = texts[i];
            }
        }
        if (lang == 'cy') {
            document.getElementById('hide_pins_link').innerHTML = 'Cuddio pinnau';
        } else if (lang == 'nb') {
            document.getElementById('hide_pins_link').innerHTML = 'Gjem nåler';
        } else {
            document.getElementById('hide_pins_link').innerHTML = 'Hide pins';
        }
    });

}

$(function(){

    var perm = new OpenLayers.Control.Permalink();
    set_map_config(perm);

    fixmystreet.map = new OpenLayers.Map("map", {
        controls: fixmystreet.controls,
        displayProjection: new OpenLayers.Projection("EPSG:4326")
    });

    fixmystreet.layer_options = OpenLayers.Util.extend({
        zoomOffset: fixmystreet.zoomOffset,
        transitionEffect: 'resize',
        numZoomLevels: fixmystreet.numZoomLevels
    }, fixmystreet.layer_options);
    var layer = new fixmystreet.map_type("", fixmystreet.layer_options);
    fixmystreet.map.addLayer(layer);

    if (!fixmystreet.map.getCenter()) {
        var centre = new OpenLayers.LonLat( fixmystreet.longitude, fixmystreet.latitude );
        centre.transform(
            new OpenLayers.Projection("EPSG:4326"),
            fixmystreet.map.getProjectionObject()
        );
        fixmystreet.map.setCenter(centre, fixmystreet.zoom || 3);
    }

    if (document.getElementById('mapForm')) {
        var click = new OpenLayers.Control.Click();
        fixmystreet.map.addControl(click);
        click.activate();
    }

    /* To let permalink not be caught by the Click layer, answer found
     * at http://www.mail-archive.com/users@openlayers.org/msg12958.html
     * Not sure why you can't use eventListeners or events.register...
     */
    OpenLayers.Event.observe( perm.element, "click", function(e) {
        OpenLayers.Event.stop(e);
        location.href = OpenLayers.Event.element(e).href;
        return false;
    });

    $(window).hashchange(function(){
        if (location.hash) { return; }
        // Okay, back to around view.
        fixmystreet.bbox_strategy.activate();
        fixmystreet.markers.refresh( { force: true } );
        fixmystreet.drag.deactivate();
        $('#side-form').hide();
        $('#side').show();
        $('#sub_map_links').show();
        fixmystreet.page = 'around';
    });

    // Vector layers must be added onload as IE sucks
    if ($.browser.msie) {
        $(window).load(fixmystreet_onload);
    } else {
        fixmystreet_onload();
    }
});

/* Overridding the buttonDown function of PanZoom so that it does
   zoomTo(0) rather than zoomToMaxExtent()
*/
OpenLayers.Control.PanZoomFMS = OpenLayers.Class(OpenLayers.Control.PanZoom, {
    buttonDown: function (evt) {
        if (!OpenLayers.Event.isLeftClick(evt)) {
            return;
        }

        switch (this.action) {
            case "panup":
                this.map.pan(0, -this.getSlideFactor("h"));
                break;
            case "pandown":
                this.map.pan(0, this.getSlideFactor("h"));
                break;
            case "panleft":
                this.map.pan(-this.getSlideFactor("w"), 0);
                break;
            case "panright":
                this.map.pan(this.getSlideFactor("w"), 0);
                break;
            case "zoomin":
                this.map.zoomIn();
                break;
            case "zoomout":
                this.map.zoomOut();
                break;
            case "zoomworld":
                this.map.zoomTo(0);
                break;
        }

        OpenLayers.Event.stop(evt);
    }
});

/* Overriding Permalink so that it can pass the correct zoom to OSM */
OpenLayers.Control.PermalinkFMS = OpenLayers.Class(OpenLayers.Control.Permalink, {
    updateLink: function() {
        var separator = this.anchor ? '#' : '?';
        var href = this.base;
        if (href.indexOf(separator) != -1) {
            href = href.substring( 0, href.indexOf(separator) );
        }

        href += separator + OpenLayers.Util.getParameterString(this.createParams(null, this.map.getZoom()+fixmystreet.zoomOffset));
        // Could use mlat/mlon here as well if we are on a page with a marker
        if (this.anchor && !this.element) {
            window.location.href = href;
        }
        else {
            this.element.href = href;
        }
    }
});

/* Pan data handler */
OpenLayers.Format.FixMyStreet = OpenLayers.Class(OpenLayers.Format.JSON, {
    read: function(json, filter) {
        if (typeof json == 'string') {
            obj = OpenLayers.Format.JSON.prototype.read.apply(this, [json, filter]);
        } else {
            obj = json;
        }
        var current, current_near;
        if (typeof(obj.current) != 'undefined' && (current = document.getElementById('current'))) {
            current.innerHTML = obj.current;
        }
        if (typeof(obj.current_near) != 'undefined' && (current_near = document.getElementById('current_near'))) {
            current_near.innerHTML = obj.current_near;
        }
        var markers = fms_markers_list( obj.pins, false );
        return markers;
    },
    CLASS_NAME: "OpenLayers.Format.FixMyStreet"
});

/* Click handler */
OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
    defaultHandlerOptions: {
        'single': true,
        'double': false,
        'pixelTolerance': 0,
        'stopSingle': false,
        'stopDouble': false
    },

    initialize: function(options) {
        this.handlerOptions = OpenLayers.Util.extend(
            {}, this.defaultHandlerOptions);
        OpenLayers.Control.prototype.initialize.apply(
            this, arguments
        );
        this.handler = new OpenLayers.Handler.Click(
            this, {
                'click': this.trigger
            }, this.handlerOptions);
    },

    trigger: function(e) {
        var lonlat = fixmystreet.map.getLonLatFromViewPortPx(e.xy);
        if (fixmystreet.page == 'new') {
            /* Already have a purple pin */
            fixmystreet.markers.features[0].move(lonlat);
        } else {
            var markers = fms_markers_list( [ [ lonlat.lat, lonlat.lon, 'purple' ] ], false );
            fixmystreet.bbox_strategy.deactivate();
            fixmystreet.markers.removeAllFeatures();
            fixmystreet.markers.addFeatures( markers );
            fixmystreet_activate_drag();
        }
        fixmystreet_update_pin(lonlat);
        // check to see if markers are visible. We click the
        // link so that it updates the text in case they go
        // back
        if ( ! fixmystreet.markers.getVisibility() ) {
            $('#hide_pins_link').click();
        }
        if (fixmystreet.page == 'new') {
            return;
        }
        $.getJSON('/report/new/ajax', {
                latitude: $('#fixmystreet\\.latitude').val(),
                longitude: $('#fixmystreet\\.longitude').val()
        }, function(data) {
            $('#councils_text').html(data.councils_text);
            $('#form_category_row').html(data.category);
            /* Need to reset this here as it gets removed when we replace
               the HTML for the dropdown */
            if ( data.has_open311 > 0 ) {
                $('#form_category').change( form_category_onchange );
            }
        });
        $('#side-form').show();
        $('#side').hide();
        $('#sub_map_links').hide();
        fixmystreet.page = 'new';
        location.hash = 'report';
    }
});

<|endoftext|>


<commit_before>define([
    'agrc/widgets/map/BaseMap',
    'agrc/widgets/map/BaseMapSelector',

    'app/config',
    'app/Router',

    'dijit/Destroyable',

    'dojo/_base/declare',
    'dojo/_base/lang',
    'dojo/dom-style',
    'dojo/topic',

    'esri/geometry/Point',
    'esri/graphic',
    'esri/layers/ArcGISDynamicMapServiceLayer'
], function(
    BaseMap,
    BaseMapSelector,

    config,
    Router,

    Destroyable,

    declare,
    lang,
    domStyle,
    topic,

    Point,
    Graphic,
    ArcGISDynamicMapServiceLayer
) {
    var MC = declare([Destroyable], {
        // map: BaseMap
        map: null,

        // dLayers: Object
        //      dynamic layers
        dLayers: {},

        // childWidgets: Object[]
        childWidgets: [],

        initMap: function(mapDiv) {
            // summary:
            //      Sets up the map
            console.info('app/mapController:initMap', arguments);

            this.map = new BaseMap(mapDiv, {
                // useDefaultBaseMap: false,
                defaultBaseMap: 'Lite',
                showAttribution: false,
                center: new Point(config.initialExtent.center, {
                    wkid: 26912
                }),
                scale: config.initialExtent.scale,
                router: true
            });

            // this.childWidgets.push(
            //     new BaseMapSelector({
            //         map: this.map,
            //         id: 'claro',
            //         position: 'TL',
            //         defaultThemeLabel: 'Lite'
            //     })
            // );
            this.childWidgets.push(this.map);

            // force map to auto height
            // required for proper alignment in firefox
            domStyle.set(mapDiv, 'height', 'auto');
            this.map.resize();

            var that = this;
            this.own(
                topic.subscribe(config.topics.addLayer,
                    lang.hitch(this, 'addLayer')),
                topic.subscribe(config.topics.layer.toggleDynamicLayer,
                    lang.hitch(this, 'toggleDynamicLayer')),
                topic.subscribe(config.topics.slider.change,
                    lang.hitch(this, 'onSliderChange')),
                this.map.on('load', function () {
                    topic.subscribe('agrc.widgets.locate.FindAddress.OnFindStart',
                        lang.hitch(that.map.graphics, 'clear'));
                    setTimeout(function () {
                        new Router();
                    }, 0);
                }),
                this.map.on('click', lang.hitch(this, 'onMapClick'))
            );
        },
        addLayer: function (lyr, bottom) {
            // summary:
            //      description
            // lyr: Layer
            // bottom: Boolean
            console.log('app/mapController:addLayer', arguments);

            var index = (bottom) ? 1 : undefined;
            this.map.addLayer(lyr, index);
            this.map.addLoaderToLayer(lyr);
        },
        toggleDynamicLayer: function (layerId, show, groupName, defaultOpacity, isRadio) {
            // summary:
            //      sets the appropriate visible layers on the dynamic service
            // layerId: String
            //      The id (or id's separated by a comma)
            // show: Boolean
            // groupName: String
            // defaultOpacity: Number
            // isRadio: Boolean
            //      Determines if more than one layer can be shown at a time in a group
            console.log('app/mapController:toggleDynamicLayer', arguments);

            var dLayer;
            var that = this;
            if (!this.dLayers[groupName]) {
                dLayer = this.dLayers[groupName] = new ArcGISDynamicMapServiceLayer(config.urls.mapService, {
                    opacity: defaultOpacity || 0.5
                });
                this.map.addLayer(dLayer, 1);
                this.map.addLoaderToLayer(dLayer);
                dLayer.on('load', function () {
                    that.toggleDynamicLayer(layerId, show, groupName);
                });
                return;
            } else {
                dLayer = this.dLayers[groupName];
                if (!dLayer.loaded) {
                    dLayer.on('load', function () {
                        that.toggleDynamicLayer(layerId, show, groupName);
                    });
                    return;
                }
            }

            var toggleIds = layerId.split(',').map(function (idTxt) {
                return parseInt(idTxt, 10);
            });
            var layerIds;

            if (show) {
                if (isRadio || !dLayer.visibleLayers) {
                    layerIds = toggleIds;
                } else {
                    if (dLayer.visibleLayers.every(function (id) {
                        return toggleIds.indexOf(id) === -1;
                    })) {
                        layerIds = toggleIds.concat(dLayer.visibleLayers);
                    } else {
                        return;
                    }
                }
            } else {
                if (dLayer.visibleLayers) {
                    layerIds = dLayer.visibleLayers.filter(function (id) {
                        return toggleIds.indexOf(id) === -1;
                    });
                } else {
                    return;
                }
            }

            dLayer.setVisibleLayers(layerIds);
        },
        onSliderChange: function (newValue, groupName) {
            // summary:
            //      the user is changing the tranparency slider
            // newValue: Number
            //      0 - 100
            console.log('mapController:onSliderChange', arguments);

            this.dLayers[groupName].setOpacity(newValue/100);
        },
        onMapClick: function (evt) {
            // summary:
            //      user clicks on the map
            // evt: MapClick Event Object
            console.log('mapController:onMapClick', arguments);

            this.map.graphics.clear();
            this.map.graphics.add(new Graphic(evt.mapPoint, config.currentLocationSymbol));
            topic.publish(config.topics.mapClick, evt.mapPoint);
        },
        destroy: function () {
            // summary:
            //
            console.log('mapController:destroy', arguments);

            this.childWidgets.forEach(function (w) {
                w.destroy();
            });
        }
    });
    return new MC();
});
<commit_msg>turn on base map selector\nCloses #87<commit_after>define([
    'agrc/widgets/map/BaseMap',
    'agrc/widgets/map/BaseMapSelector',

    'app/config',
    'app/Router',

    'dijit/Destroyable',

    'dojo/_base/declare',
    'dojo/_base/lang',
    'dojo/dom-style',
    'dojo/topic',

    'esri/geometry/Point',
    'esri/graphic',
    'esri/layers/ArcGISDynamicMapServiceLayer'
], function(
    BaseMap,
    BaseMapSelector,

    config,
    Router,

    Destroyable,

    declare,
    lang,
    domStyle,
    topic,

    Point,
    Graphic,
    ArcGISDynamicMapServiceLayer
) {
    var MC = declare([Destroyable], {
        // map: BaseMap
        map: null,

        // dLayers: Object
        //      dynamic layers
        dLayers: {},

        // childWidgets: Object[]
        childWidgets: [],

        initMap: function(mapDiv) {
            // summary:
            //      Sets up the map
            console.info('app/mapController:initMap', arguments);

            this.map = new BaseMap(mapDiv, {
                useDefaultBaseMap: false,
                defaultBaseMap: 'Lite',
                showAttribution: false,
                center: new Point(config.initialExtent.center, {
                    wkid: 26912
                }),
                scale: config.initialExtent.scale,
                router: true
            });

            this.childWidgets.push(
                new BaseMapSelector({
                    map: this.map,
                    id: 'claro',
                    position: 'TL',
                    defaultThemeLabel: 'Lite'
                })
            );
            this.childWidgets.push(this.map);

            // force map to auto height
            // required for proper alignment in firefox
            domStyle.set(mapDiv, 'height', 'auto');
            this.map.resize();

            var that = this;
            this.own(
                topic.subscribe(config.topics.addLayer,
                    lang.hitch(this, 'addLayer')),
                topic.subscribe(config.topics.layer.toggleDynamicLayer,
                    lang.hitch(this, 'toggleDynamicLayer')),
                topic.subscribe(config.topics.slider.change,
                    lang.hitch(this, 'onSliderChange')),
                this.map.on('load', function () {
                    topic.subscribe('agrc.widgets.locate.FindAddress.OnFindStart',
                        lang.hitch(that.map.graphics, 'clear'));
                    setTimeout(function () {
                        new Router();
                    }, 0);
                }),
                this.map.on('click', lang.hitch(this, 'onMapClick'))
            );
        },
        addLayer: function (lyr, bottom) {
            // summary:
            //      description
            // lyr: Layer
            // bottom: Boolean
            console.log('app/mapController:addLayer', arguments);

            var index = (bottom) ? 1 : undefined;
            this.map.addLayer(lyr, index);
            this.map.addLoaderToLayer(lyr);
        },
        toggleDynamicLayer: function (layerId, show, groupName, defaultOpacity, isRadio) {
            // summary:
            //      sets the appropriate visible layers on the dynamic service
            // layerId: String
            //      The id (or id's separated by a comma)
            // show: Boolean
            // groupName: String
            // defaultOpacity: Number
            // isRadio: Boolean
            //      Determines if more than one layer can be shown at a time in a group
            console.log('app/mapController:toggleDynamicLayer', arguments);

            var dLayer;
            var that = this;
            if (!this.dLayers[groupName]) {
                dLayer = this.dLayers[groupName] = new ArcGISDynamicMapServiceLayer(config.urls.mapService, {
                    opacity: defaultOpacity || 0.5
                });
                this.map.addLayer(dLayer, 1);
                this.map.addLoaderToLayer(dLayer);
                dLayer.on('load', function () {
                    that.toggleDynamicLayer(layerId, show, groupName);
                });
                return;
            } else {
                dLayer = this.dLayers[groupName];
                if (!dLayer.loaded) {
                    dLayer.on('load', function () {
                        that.toggleDynamicLayer(layerId, show, groupName);
                    });
                    return;
                }
            }

            var toggleIds = layerId.split(',').map(function (idTxt) {
                return parseInt(idTxt, 10);
            });
            var layerIds;

            if (show) {
                if (isRadio || !dLayer.visibleLayers) {
                    layerIds = toggleIds;
                } else {
                    if (dLayer.visibleLayers.every(function (id) {
                        return toggleIds.indexOf(id) === -1;
                    })) {
                        layerIds = toggleIds.concat(dLayer.visibleLayers);
                    } else {
                        return;
                    }
                }
            } else {
                if (dLayer.visibleLayers) {
                    layerIds = dLayer.visibleLayers.filter(function (id) {
                        return toggleIds.indexOf(id) === -1;
                    });
                } else {
                    return;
                }
            }

            dLayer.setVisibleLayers(layerIds);
        },
        onSliderChange: function (newValue, groupName) {
            // summary:
            //      the user is changing the tranparency slider
            // newValue: Number
            //      0 - 100
            console.log('mapController:onSliderChange', arguments);

            this.dLayers[groupName].setOpacity(newValue/100);
        },
        onMapClick: function (evt) {
            // summary:
            //      user clicks on the map
            // evt: MapClick Event Object
            console.log('mapController:onMapClick', arguments);

            this.map.graphics.clear();
            this.map.graphics.add(new Graphic(evt.mapPoint, config.currentLocationSymbol));
            topic.publish(config.topics.mapClick, evt.mapPoint);
        },
        destroy: function () {
            // summary:
            //
            console.log('mapController:destroy', arguments);

            this.childWidgets.forEach(function (w) {
                w.destroy();
            });
        }
    });
    return new MC();
});
<|endoftext|>


<commit_before>const expect = require('chai').expect;
const { Canvas } = require('../../src/renderer');
const Shape = require('../../src/geom/shape/shape');
const Coord = require('../../src/coord/');
require('../../src/geom/shape/edge');

const div = document.createElement('div');
div.id = 'csedge';
document.body.appendChild(div);
const canvas = new Canvas({
  containerId: 'csedge',
  width: 500,
  height: 500
});
describe('#730', () => {
  it('arc direction', () => {

    const coord = new Coord.Polar({
      start: { x: 0, y: 500 },
      end: { x: 500, y: 0 },
      startAngle: 6,
      endAngle: 3,
      innerRadius: 0.3
    });
    const shapeObj = Shape.getShapeFactory('edge');
    shapeObj._coord = coord;
    const obj = {
      x: [ 0.2, 0.4, 0.6, 0.7 ],
      y: [ 0.5, 0.5, 0.5, 0.5 ]
    };
    const points = shapeObj.getShapePoints('arc', obj);
    const shape = shapeObj.drawShape('arc', {
      points,
      color: 'green',
      isInCircle: true,
      center: {
        x: 230,
        y: 250
      }
    }, canvas);
    expect(shape.attr('path').length).equal(8);
    expect(shape.attr('path')).eql(
      [
        [ 'M', 360.27450827599966, 231.34131348761517 ],
        [ 'Q', 499.99999999999994, 288.2096172921748, 134.6177879398623, 245.43810097501847 ],
        [ 'A', 165.84782027366163, 165.84782027366163, 0, 0, 0, 134.6177879398623, 245.43810097501847 ],
        [ 'A', 165.84782027366163, 165.84782027366163, 0, 0, 1, 173.7033896727621, 214.95367727554216 ],
        [ 'Q', 499.99999999999994, 288.2096172921748, 269.52359393728557, 194.29089032447982 ],
        [ 'A', 165.84782027366163, 165.84782027366163, 0, 0, 0, 269.52359393728557, 194.29089032447982 ],
        [ 'A', 165.84782027366163, 165.84782027366163, 0, 0, 1, 360.27450827599966, 231.34131348761517 ],
        [ 'Z' ]
      ]
    );
  });
});
<commit_msg>fix: modify test case accordingly<commit_after>const expect = require('chai').expect;
const { Canvas } = require('../../src/renderer');
const Shape = require('../../src/geom/shape/shape');
const Coord = require('../../src/coord/');
require('../../src/geom/shape/edge');

const div = document.createElement('div');
div.id = 'csedge';
document.body.appendChild(div);
const canvas = new Canvas({
  containerId: 'csedge',
  width: 500,
  height: 500
});
describe('#730', () => {
  it('arc direction', () => {

    const coord = new Coord.Polar({
      start: { x: 0, y: 500 },
      end: { x: 500, y: 0 },
      startAngle: 6,
      endAngle: 3,
      innerRadius: 0.3
    });
    const shapeObj = Shape.getShapeFactory('edge');
    shapeObj._coord = coord;
    const obj = {
      x: [ 0.2, 0.4, 0.6, 0.7 ],
      y: [ 0.5, 0.5, 0.5, 0.5 ]
    };
    const points = shapeObj.getShapePoints('arc', obj);
    const shape = shapeObj.drawShape('arc', {
      points,
      color: 'green',
      isInCircle: true,
      center: {
        x: 230,
        y: 250
      }
    }, canvas);
    expect(shape.attr('path').length).equal(8);
    expect(shape.attr('path')).eql(
      [
        [ 'M', 401.24050295113045, 219.29627573959306 ],
        [ 'Q', 490.52723201381275, 89.36622397684033, 178.74731436635312, 307.27359825797487 ],
        [ 'A', 163.4954289700296, 163.4954289700296, 0, 0, 1, 178.74731436635312, 307.27359825797487 ],
        [ 'A', 163.49542897002962, 163.49542897002962, 0, 0, 0, 230.10270324898926, 322.0460009231282 ],
        [ 'Q', 490.52723201381275, 89.36622397684033, 333.1723063161811, 299.8199446789916 ],
        [ 'A', 163.49542897002956, 163.49542897002956, 0, 0, 1, 333.1723063161811, 299.8199446789916 ],
        [ 'A', 163.49542897002956, 163.49542897002956, 0, 0, 0, 401.24050295113045, 219.29627573959306 ],
        [ 'Z' ]
      ]
    );
  });
});
<|endoftext|>


<commit_before>
module.exports = {

  VERSION: "Default JavaScript folding player",

  bet_request: function(game_state) {
    return 0;
  },

  showdown: function(game_state) {

  }
};
<commit_msg>return 0 replaced with return 1<commit_after>
module.exports = {

  VERSION: "Default JavaScript folding player",

  bet_request: function(game_state) {
    return 1;
  },

  showdown: function(game_state) {

  }
};
<|endoftext|>


<commit_before>if (enyo && enyo.version) {
	enyo.version["enyo-ilib"] = "2.5.2-pre.3";
}
<commit_msg>Update version string to 2.5.2-pre.5<commit_after>if (enyo && enyo.version) {
	enyo.version["enyo-ilib"] = "2.5.2-pre.5";
}
<|endoftext|>


<commit_before>var http=require("http");
var unmon={};

/* Your default stack */
var routes=unmon.routes=[];

var debug=unmon.debug=true;

var plugin=unmon.plugin=function(name){
    var mod;

    if(unmon.debug){
        console.log("Loading %s plugin",name);
    }

    try{
        mod=require("./lib/"+name);
    }catch(err){
        if(unmon.debug){
            console.error("Error loading %s",name);
            console.error(err);
        }
    }
    return mod;
};

/* route produces new routes, and pushes them to a stack */
var route=unmon.route=function(patt,f,opts){
    opts=opts||{}; // you don't have to provide options
    // make sure the discriminator is defined
    opts.disc=opts.disc||'url'; // your router parses by url by default
    opts.stack=opts.stack||routes; // you can provide an alternate stack

    if(typeof f !== 'function'){
        console.error("[ROUTE ERROR]: Second argument provided to unmon.route (%s) is not a function. ignoring...\n",f);
        return;
    }

    if(typeof patt === 'string'){
        if(unmon.debug){
            console.log("[ROUTE DEBUG]: Got a string instead of a regular expression.\n...Attempting to convert");
        }
        patt=patt
        // support 'globbing'
        .replace(/\*/g,function(esc){
            return '.'+esc;
        })
        // escape relevant characters, I'm sure this is incomplete.
        .replace(/[\\\/\-\+\?\{\}\(\)\[\]\^\$\.]/g,function(esc){
            return '\\'+esc;
        });
        if(unmon.debug){
            console.log("[ROUTE DEBUG]: Route expression is %s",patt);
        }
        patt=new RegExp(patt);
    }

    // push your new route to the stack in question
    opts.stack.push(function(req,res,Next){
        // encapsulate Next so that we don't always have to pass req and res
        var next=function(){Next(req,res);};
        if(patt.test(req[opts.disc])){
            f(req,res,next);
        }else{
            next();
        }
    });
};

/*    makeRouter accepts a stack of routes and collapses it into a single function
        which is compatible with the NodeJS http api
        of the form function(request,response)    */
var makeRouter=unmon.makeRouter=function(stack){
    return stack.reduceRight(function(b,a,i,z){
        return function(req,res){
            a(req,res,b);
        };
    });
};

/* If being run as a standalone server, load routes and launch a server */

if(require.main === module){
    console.log("Running unmon as standalone script");

    var __dirname=process.argv[1].replace(/\/[^\/]*$/,'');
    console.log(__dirname);

    /* instantiate error logging behaviour
        this catches ALL uncaught exceptions
        which is possibly a security risk if your server SHOULD crash */
    global.logError=plugin('error')();

    /* This is where you add your routes */

    /* infer appropriate caching behaviour from requested urls */
    route(/.*/,plugin('cache')());

    /* Enable CORS headers for api access */
    route(/.*/,plugin('cors')());

    /* Log all requests to file */
    route(/.*/,plugin('logger')({path:process.env.PWD+'/log/'}));

    /* Process post requests and export them as req.postdata */
    route(/.*/,plugin('posted')());

    /* Templating and serverside scripting */
    route(/.*/,plugin('koan')({
        error:logError
    }));

    /* find and serve static files */
    route(/.*/,plugin('fixed')({
        path:__dirname+'/static'
    }));

    route(/\/shared\/.*/,plugin('directive')({
        path:__dirname+'/static/'
    }));

    /* load the blag plugin for serving templated markdown files */
    route(/.*/,plugin('blag')({path:process.env.PWD+"/md/",title:" "}));

    /* 404 as a last resort */
    route(/.*/,plugin('parcel')(process.env.PWD+"/static/html/404.html"));

    // produce a new router
    var router=makeRouter(routes)

    // make the port known globally for other scripts
    global.thisServersPort=8083;

    // bind to all ipv4 addresses by default
    var addresses=[''];

    // check if you have a unique local ipv6 address
    (function(){
        global.thisServersIPV6=plugin('fc')();
        if(thisServersIPV6)
            addresses.push(thisServersIPV6[0]);
    })();

    // instantiate and launch a server for each address in addresses
    var servers=addresses.map(function(addy){
        var server=http.createServer(router);
        server.listen(thisServersPort,addy,function(){
            console.log("unmon is running on %s:%s",addy,thisServersPort);
        });
        return server;
    });

}else{
    /*    If this module is being loaded as a library by another file
            just export the router functions */
    console.log("Exporting unmon as a library.");
    module.exports=unmon;
}
<commit_msg>unmon.js : removed unnecessary logs from startup routine<commit_after>var http=require("http");
var unmon={};

/* Your default stack */
var routes=unmon.routes=[];

var debug=unmon.debug=true;

var plugin=unmon.plugin=function(name){
    var mod;

    if(unmon.debug){
        console.log("Loading %s plugin",name);
    }

    try{
        mod=require("./lib/"+name);
    }catch(err){
        if(unmon.debug){
            console.error("Error loading %s",name);
            console.error(err);
        }
    }
    return mod;
};

/* route produces new routes, and pushes them to a stack */
var route=unmon.route=function(patt,f,opts){
    opts=opts||{}; // you don't have to provide options
    // make sure the discriminator is defined
    opts.disc=opts.disc||'url'; // your router parses by url by default
    opts.stack=opts.stack||routes; // you can provide an alternate stack

    if(typeof f !== 'function'){
        console.error("[ROUTE ERROR]: Second argument provided to unmon.route (%s) is not a function. ignoring...\n",f);
        return;
    }

    if(typeof patt === 'string'){
        if(unmon.debug){
            console.log("[ROUTE DEBUG]: Got a string instead of a regular expression.\n...Attempting to convert");
        }
        patt=patt
        // support 'globbing'
        .replace(/\*/g,function(esc){
            return '.'+esc;
        })
        // escape relevant characters, I'm sure this is incomplete.
        .replace(/[\\\/\-\+\?\{\}\(\)\[\]\^\$\.]/g,function(esc){
            return '\\'+esc;
        });
        if(unmon.debug){
            console.log("[ROUTE DEBUG]: Route expression is %s",patt);
        }
        patt=new RegExp(patt);
    }

    // push your new route to the stack in question
    opts.stack.push(function(req,res,Next){
        if(typeof Next !== 'function'){
            Next=function(req,res){
                res.statusCode=404;
                res.end('404');
            };
        }

        // encapsulate Next so that we don't always have to pass req and res
        var next=function(){Next(req,res);};
        if(patt.test(req[opts.disc])){
            f(req,res,next);
        }else{
            next();
        }
    });
};

/*    makeRouter accepts a stack of routes and collapses it into a single function
        which is compatible with the NodeJS http api
        of the form function(request,response)    */
var makeRouter=unmon.makeRouter=function(stack){
    stack=stack||unmon.routes;
    return stack.reduceRight(function(b,a,i,z){
        return function(req,res){
            a(req,res,b);
        };
    },[]);
};

var createServer=unmon.createServer=function(callback){
    callback=callback||makeRouter();
    return http.createServer(callback);
};

/* If being run as a standalone server, load routes and launch a server */

if(require.main === module){
    // Running unmon as standalone script

    var __dirname=process.argv[1].replace(/\/[^\/]*$/,'');
    console.log(__dirname);

    /* instantiate error logging behaviour
        this catches ALL uncaught exceptions
        which is possibly a security risk if your server SHOULD crash */
    global.logError=plugin('error')();

    /* This is where you add your routes */

    /* infer appropriate caching behaviour from requested urls */
    route(/.*/,plugin('cache')());

    /* Enable CORS headers for api access */
    route(/.*/,plugin('cors')());

    /* Log all requests to file */
    route(/.*/,plugin('logger')({path:process.env.PWD+'/log/'}));

    /* Process post requests and export them as req.postdata */
    route(/.*/,plugin('posted')());

    /* Templating and serverside scripting */
    route(/.*/,plugin('koan')({
        error:logError
    }));

    /* find and serve static files */
    route(/.*/,plugin('fixed')({
        path:__dirname+'/static'
    }));

    route(/\/shared\/.*/,plugin('directive')({
        path:__dirname+'/static/'
    }));

    /* load the blag plugin for serving templated markdown files */
    route(/.*/,plugin('blag')({path:process.env.PWD+"/md/",title:" "}));

    /* 404 as a last resort */
    route(/.*/,plugin('parcel')(process.env.PWD+"/static/html/404.html"));

    // produce a new router
    var router=makeRouter(routes)

    // make the port known globally for other scripts
    global.thisServersPort=8083;

    // bind to all ipv4 addresses by default
    var addresses=[''];

    // check if you have a unique local ipv6 address
    (function(){
        global.thisServersIPV6=plugin('fc')();
        if(thisServersIPV6)
            addresses.push(thisServersIPV6[0]);
    })();

    // instantiate and launch a server for each address in addresses
    var servers=addresses.map(function(addy){
        var server=http.createServer(router);
        server.listen(thisServersPort,addy,function(){
            console.log("unmon is running on %s:%s",addy,thisServersPort);
        });
        return server;
    });

}else{
    /*    If this module is being loaded as a library by another file
            just export the router functions */
    module.exports=unmon;
}
<|endoftext|>