<commit_before>/**
 * @ngdoc service
 * @name refigure.collections.services:collections
 * @description
 * Collection service
 */
(function (angular) {
    'use strict';

    var _apiUrl = '/api';
    var _itemDescriptionLength = 55;

    angular
        .module('refigure.collections', [])
        .factory('collections', serviceFunc);

    serviceFunc.$inject = ['$http', 'auth'];

    function serviceFunc($http, auth) {
        //noinspection UnnecessaryLocalVariableJS
        var exports = {
            mostVisited: mostVisited,
            search: search,
            myCollections: myCollections,
            remove: remove,
            get: get,
            save: save,
            toggleFlag: toggleFlag,
            statistics: statistics
        };

        return exports;

        ////////////////

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#mostVisited
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Loads most visited collection
         */
        function mostVisited(opts) {
            return $http
                .get(_apiUrl + '/most-visited-metapublications', {
                    params: {
                        limit: opts.limit
                    }
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemsUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#get
         * @methodOf refigure.collections.services:collections
         * @param {String} ID Metapublication ID
         * @returns {Promise} promise
         * @description
         * Gets collection
         */
        function get(ID) {
            return $http
                .get(_apiUrl + '/metapublication/' + ID)
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#save
         * @methodOf refigure.collections.services:collections
         * @param {Object} collection Refigure
         * @returns {Promise} promise
         * @description
         * Saves collection
         */
        function save(collection) {
            return $http
                .put(_apiUrl + '/metapublication/', collection)
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#search
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Searches collection
         */
        function search(opts) {
            return $http
                .get(_apiUrl + '/metapublications', {
                    params: opts
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    items.results = itemsUIData(items.results);
                    console.log(items);
                    return items;
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#remove
         * @methodOf refigure.collections.services:collections
         * @param {String} id id of element to delete
         * @returns {Object} promise
         * @description
         * Removes collection
         */
        function remove(id) {
            return $http.delete(_apiUrl + '/metapublication/' + id);
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#myCollections
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Searches collection
         */
        function myCollections(opts) {
            return $http
                .get(_apiUrl + '/my-metapublications', {
                    params: opts
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    items.results = itemsUIData(items.results);
                    console.log(items);
                    return items;
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#toggleFlag
         * @methodOf refigure.collections.services:collections
         * @param {String} ID Metapublication ID
         * @returns {Object} promise
         * @description
         * Switches Flagged state
         */
        function toggleFlag(ID) {
            return $http
                .put(_apiUrl + '/metapublication-flag', {
                    ID: ID
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#statistics
         * @methodOf refigure.collections.services:collections
         * @returns {Object} promise
         * @description
         * Refigure statistics for dashboard
         */
        function statistics() {
            return $http
                .get(_apiUrl + '/statistics')
                .then(function (resp) {
                    return utils.get(resp, 'data.data');
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#itemsUIData
         * @methodOf refigure.collections.services:collections
         * @param {Array=} items Search query options
         * @returns {Object} promise
         * @description
         * Re-map data for UI
         */
        function itemsUIData(items) {
            return items.map(function (_item) {
                return itemUIData(_item);
            });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#itemUIData
         * @methodOf refigure.collections.services:collections
         * @param {Object=} item Search query options
         * @returns {Object} promise
         * @description
         * Re-map data for UI
         */
        function itemUIData(item) {
            item = item.Metapublication;
            item.$ui = {};

            var uiData = item.$ui;

            uiData.title = item.Title;
            uiData.description = (item.Description || '').substring(0, _itemDescriptionLength);
            if (item.Description && uiData.description !== item.Description) {
                uiData.description += '...';
            }

            auth.setUsrNames(item.User);

            if (!item.FiguresCount) {
                item.FiguresCount = item.Figures.length;
            }

            if (item.Keywords) {
                item.KeywordsChips = item.Keywords.split(/(?:(?:&[^;]+;)|\s|\||,|;)+/);
            }

            uiData.img = {};
            // $ui.img.src
            if (!utils.empty(item.Figures)) {
                var first = item.Figures[0];
                uiData.img.src = first.URL;
            } else {
                uiData.img.src = 'img/no-image.png';
            }
            return item;
        }
    }
})(window.angular);
<commit_msg>+ filter for keyword chips<commit_after>/**
 * @ngdoc service
 * @name refigure.collections.services:collections
 * @description
 * Collection service
 */
(function (angular) {
    'use strict';

    var _apiUrl = '/api';
    var _itemDescriptionLength = 55;

    angular
        .module('refigure.collections', [])
        .factory('collections', serviceFunc);

    serviceFunc.$inject = ['$http', 'auth'];

    function serviceFunc($http, auth) {
        //noinspection UnnecessaryLocalVariableJS
        var exports = {
            mostVisited: mostVisited,
            search: search,
            myCollections: myCollections,
            remove: remove,
            get: get,
            save: save,
            toggleFlag: toggleFlag,
            statistics: statistics
        };

        return exports;

        ////////////////

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#mostVisited
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Loads most visited collection
         */
        function mostVisited(opts) {
            return $http
                .get(_apiUrl + '/most-visited-metapublications', {
                    params: {
                        limit: opts.limit
                    }
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemsUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#get
         * @methodOf refigure.collections.services:collections
         * @param {String} ID Metapublication ID
         * @returns {Promise} promise
         * @description
         * Gets collection
         */
        function get(ID) {
            return $http
                .get(_apiUrl + '/metapublication/' + ID)
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collections#save
         * @methodOf refigure.collections.services:collections
         * @param {Object} collection Refigure
         * @returns {Promise} promise
         * @description
         * Saves collection
         */
        function save(collection) {
            return $http
                .put(_apiUrl + '/metapublication/', collection)
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    return itemUIData(items);
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#search
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Searches collection
         */
        function search(opts) {
            return $http
                .get(_apiUrl + '/metapublications', {
                    params: opts
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    items.results = itemsUIData(items.results);
                    console.log(items);
                    return items;
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#remove
         * @methodOf refigure.collections.services:collections
         * @param {String} id id of element to delete
         * @returns {Object} promise
         * @description
         * Removes collection
         */
        function remove(id) {
            return $http.delete(_apiUrl + '/metapublication/' + id);
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#myCollections
         * @methodOf refigure.collections.services:collections
         * @param {Object=} opts Search query options
         * @returns {Object} promise
         * @description
         * Searches collection
         */
        function myCollections(opts) {
            return $http
                .get(_apiUrl + '/my-metapublications', {
                    params: opts
                })
                .then(function (res) {
                    var items = utils.get(res, 'data.data');
                    items.results = itemsUIData(items.results);
                    console.log(items);
                    return items;
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#toggleFlag
         * @methodOf refigure.collections.services:collections
         * @param {String} ID Metapublication ID
         * @returns {Object} promise
         * @description
         * Switches Flagged state
         */
        function toggleFlag(ID) {
            return $http
                .put(_apiUrl + '/metapublication-flag', {
                    ID: ID
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#statistics
         * @methodOf refigure.collections.services:collections
         * @returns {Object} promise
         * @description
         * Refigure statistics for dashboard
         */
        function statistics() {
            return $http
                .get(_apiUrl + '/statistics')
                .then(function (resp) {
                    return utils.get(resp, 'data.data');
                });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#itemsUIData
         * @methodOf refigure.collections.services:collections
         * @param {Array=} items Search query options
         * @returns {Object} promise
         * @description
         * Re-map data for UI
         */
        function itemsUIData(items) {
            return items.map(function (_item) {
                return itemUIData(_item);
            });
        }

        /**
         * @ngdocs method
         * @name refigure.collections.services:collection#itemUIData
         * @methodOf refigure.collections.services:collections
         * @param {Object=} item Search query options
         * @returns {Object} promise
         * @description
         * Re-map data for UI
         */
        function itemUIData(item) {
            item = item.Metapublication;
            item.$ui = {};

            var uiData = item.$ui;

            uiData.title = item.Title;
            uiData.description = (item.Description || '').substring(0, _itemDescriptionLength);
            if (item.Description && uiData.description !== item.Description) {
                uiData.description += '...';
            }

            auth.setUsrNames(item.User);

            if (!item.FiguresCount) {
                item.FiguresCount = item.Figures.length;
            }

            if (item.Keywords) {
                var tmpChips = item.Keywords.split(/(?:(?:&[^;]+;)|\s|\||,|;)+/);
                tmpChips = tmpChips.filter(function (el) {
                    return el;
                });
                item.KeywordsChips = tmpChips
            }

            uiData.img = {};
            // $ui.img.src
            if (!utils.empty(item.Figures)) {
                var first = item.Figures[0];
                uiData.img.src = first.URL;
            } else {
                uiData.img.src = 'img/no-image.png';
            }
            return item;
        }
    }
})(window.angular);
<|endoftext|>


<commit_before>var browserSync = require('browser-sync'),
    config = require('./config.json'),
    bs,
    url = require('url'),
    Q = require('q');

function deferredEmit(socket, event, data, timeout) {
  var deferred = Q.defer();
  var t1;
  if (timeout) {
    t1 = setTimeout(function(){
      deferred.reject();
    }, timeout);
  }
  socket.emit(event, data, function(confirmation){
    if (t1) {
      clearTimeout(t1);
    }
    deferred.resolve();
  });
  return deferred.promise;
}

process.on('message', function(message) {
  if (message.type === 'init') {
    var parsedUrl = url.parse(message.url);
    bs = browserSync.init(null, {
      proxy: message.url,
      host: '192.168.56.101',
      startPath: parsedUrl.path,
      browser: 'disable',
      https: true,
      ghostMode: {
        clicks: true,
        location: true,
        forms: true,
        scroll: true
      }
    });

    bs.events.on('init', function(api) {
      process.send({type: 'browserSyncInit', browserSync: api.options.urls.external});
    });
  } else if (message.type === 'location') {
    // bs.io.sockets.emit('location', {url: message.url});
    var promises = [];
    bs.io.sockets.sockets.forEach(function(socket) {
      promises.push(deferredEmit(socket, 'location', {url: message.url}, message.timeout || 5000));
    });
    Q.all(promises).fin(function(){
      // all promises finished.
      if (message.completeMessageType) {
        process.send({type: message.completeMessageType});
      }
    });
  } else {
    browserSync.exit();
  }
});
<commit_msg>Remove debug<commit_after>var browserSync = require('browser-sync'),
    config = require('./config.json'),
    bs,
    url = require('url'),
    Q = require('q');

function deferredEmit(socket, event, data, timeout) {
  var deferred = Q.defer();
  var t1;
  if (timeout) {
    t1 = setTimeout(function(){
      deferred.reject();
    }, timeout);
  }
  socket.emit(event, data, function(confirmation){
    if (t1) {
      clearTimeout(t1);
    }
    deferred.resolve();
  });
  return deferred.promise;
}

process.on('message', function(message) {
  if (message.type === 'init') {
    var parsedUrl = url.parse(message.url);
    bs = browserSync.init(null, {
      proxy: message.url,
      startPath: parsedUrl.path,
      browser: 'disable',
      https: true,
      ghostMode: {
        clicks: true,
        location: true,
        forms: true,
        scroll: true
      }
    });

    bs.events.on('init', function(api) {
      process.send({type: 'browserSyncInit', browserSync: api.options.urls.external});
    });
  } else if (message.type === 'location') {
    // bs.io.sockets.emit('location', {url: message.url});
    var promises = [];
    bs.io.sockets.sockets.forEach(function(socket) {
      promises.push(deferredEmit(socket, 'location', {url: message.url}, message.timeout || 5000));
    });
    Q.all(promises).fin(function(){
      // all promises finished.
      if (message.completeMessageType) {
        process.send({type: message.completeMessageType});
      }
    });
  } else {
    browserSync.exit();
  }
});
<|endoftext|>


<commit_before>var colors      = require("colors");
var STATE       = require("./models/GameState");
var lobbyManager = require("./interface/lobbyManager");
var apeManager = require("./game/apeManager");
var gameState = require("./gameState");
var ROLE        = require("./models/ROLE");

//Used to update the game/lobby on intervals
var updateInterval;

var startGame = function() {
    var theApe = parseInt(Math.random()*gameState.players.length);
    for(var i=0;i<gameState.players.length;i++){
        if(i === theApe){
            gameState.players[i].setApe();
            gameState.players[i].socket.emit("player_role",ROLE.APE);

            //Wait for the ape to give us level info
            console.log("Game is ready, waiting for Ape.".cyan + " (" + gameState.players[i].name + ")");
            gameState.players[i].socket.on("level_start",startLevel);
        } else {
            gameState.players[i].setJailer();
            gameState.players[i].socket.emit("player_role",ROLE.JAILER);
        }
    }
};
exports.startGame = startGame;

var startLevel = function(levelNumber, traps){
    if(gameState.state !== STATE.GAME){
        //First time
        gameState.state = STATE.GAME;
        clearInterval(updateInterval);
        updateInterval = setInterval(updateGame,100);
    }
    console.log("Starting level!".cyan);

    //Update the traps
    gameState.traps = traps;
    socketHandler.io.emit("level_start", 1);
}
exports.startLevel = startLevel;

var endGame = function() {
    clearInterval(updateInterval);
    socketHandler.io.emit("game_end",true);
    for(var i=0;i<gameState.players.length;i++){
        gameState.players[i].role = ROLE.NONE;
        gameState.players[i].gameState = {};
    }
};
exports.endGame = endGame;

var updateGame = function() {
    //Update only jailers
    for(var i=0;i<gameState.players.length;i++){
        if(gameState.players[i].role === ROLE.JAILER){
            console.log("updating game");
            gameState.players[i].socket.emit("game_state",{
                state: gameState.state,
                players: gameState.players
            });
        }
    }
};
exports.updateGame = updateGame;<commit_msg>gotta include socketHandler<commit_after>var colors      = require("colors");
var STATE       = require("./models/GameState");
var lobbyManager = require("./interface/lobbyManager");
var apeManager = require("./game/apeManager");
var gameState = require("./gameState");
var ROLE        = require("./models/ROLE");
var socketHandler = require("./socketHandler");

//Used to update the game/lobby on intervals
var updateInterval;

var startGame = function() {
    var theApe = parseInt(Math.random()*gameState.players.length);
    for(var i=0;i<gameState.players.length;i++){
        if(i === theApe){
            gameState.players[i].setApe();
            gameState.players[i].socket.emit("player_role",ROLE.APE);

            //Wait for the ape to give us level info
            console.log("Game is ready, waiting for Ape.".cyan + " (" + gameState.players[i].name + ")");
            gameState.players[i].socket.on("level_start",startLevel);
        } else {
            gameState.players[i].setJailer();
            gameState.players[i].socket.emit("player_role",ROLE.JAILER);
        }
    }
};
exports.startGame = startGame;

var startLevel = function(levelNumber, traps){
    if(gameState.state !== STATE.GAME){
        //First time
        gameState.state = STATE.GAME;
        clearInterval(updateInterval);
        updateInterval = setInterval(updateGame,100);
    }
    console.log("Starting level!".cyan);

    //Update the traps
    gameState.traps = traps;
    socketHandler.io.emit("level_start", 1);
}
exports.startLevel = startLevel;

var endGame = function() {
    clearInterval(updateInterval);
    socketHandler.io.emit("game_end",true);
    for(var i=0;i<gameState.players.length;i++){
        gameState.players[i].role = ROLE.NONE;
        gameState.players[i].gameState = {};
    }
};
exports.endGame = endGame;

var updateGame = function() {
    //Update only jailers
    for(var i=0;i<gameState.players.length;i++){
        if(gameState.players[i].role === ROLE.JAILER){
            console.log("updating game");
            gameState.players[i].socket.emit("game_state",{
                state: gameState.state,
                players: gameState.players
            });
        }
    }
};
exports.updateGame = updateGame;<|endoftext|>


<commit_before>import { WebApp } from 'meteor/webapp';
import { Meteor } from 'meteor/meteor';

WebApp.connectHandlers.use('/robots.txt', (req, res) => {
  let robotsTxt = `User-agent: * \n`;
  robotsTxt += `Crawl-deslay: 4 \n`;
  robotsTxt += `Disallow: /companyInfo* \n`;
  robotsTxt += `Disallow: /productInfo* \n`;
  robotsTxt += `Disallow: /userInfo* \n`;

  robotsTxt += `\n`;
  robotsTxt += `sitemap: https://${Meteor.settings.public.websiteInfo.domainName}/sitemap/sitemap-index.xml \n`;

  res.setHeader('Cache-Control', 'public, max-age=604800');
  res.end(robotsTxt);
});
<commit_msg>更新 robots 內的規則<commit_after>import { WebApp } from 'meteor/webapp';
import { Meteor } from 'meteor/meteor';

WebApp.connectHandlers.use('/robots.txt', (req, res) => {
  let robotsTxt = `User-agent: * \n`;
  robotsTxt += `Disallow: /companyInfo* \n`;
  robotsTxt += `Disallow: /productInfo* \n`;
  robotsTxt += `Disallow: /userInfo* \n`;
  robotsTxt += `Disallow: /company/edit/* \n`;
  robotsTxt += `Disallow: /foundation/view/* \n`;
  robotsTxt += `Disallow: /violation?offset=* \n`;
  robotsTxt += `Disallow: /violation/view/* \n`;
  robotsTxt += `Disallow: /violation/report* \n`;
  robotsTxt += `Disallow: /fscLogs* \n`;
  robotsTxt += `Crawl-deslay: 4 \n`;

  robotsTxt += `\n`;
  robotsTxt += `sitemap: https://${Meteor.settings.public.websiteInfo.domainName}/sitemap/sitemap-index.xml \n`;

  res.setHeader('Cache-Control', 'public, max-age=604800');
  res.end(robotsTxt);
});
<|endoftext|>


<commit_before>/*global gettext, get_format, quickElement, removeChildren*/
/*
calendar.js - Calendar functions by Adrian Holovaty
depends on core.js for utility functions like removeChildren or quickElement
*/

(function() {
    'use strict';
    // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions
    var CalendarNamespace = {
        monthsOfYear: gettext('January February March April May June July August September October November December').split(' '),
        daysOfWeek: gettext('S M T W T F S').split(' '),
        firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')),
        isLeapYear: function(year) {
            return (((year % 4) === 0) && ((year % 100) !== 0 ) || ((year % 400) === 0));
        },
        getDaysInMonth: function(month, year) {
            var days;
            if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) {
                days = 31;
            }
            else if (month === 4 || month === 6 || month === 9 || month === 11) {
                days = 30;
            }
            else if (month === 2 && CalendarNamespace.isLeapYear(year)) {
                days = 29;
            }
            else {
                days = 28;
            }
            return days;
        },
        draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999
            var today = new Date();
            var todayDay = today.getDate();
            var todayMonth = today.getMonth() + 1;
            var todayYear = today.getFullYear();
            var todayClass = '';

            // Use UTC functions here because the date field does not contain time
            // and using the UTC function variants prevent the local time offset
            // from altering the date, specifically the day field.  For example:
            //
            // ```
            // var x = new Date('2013-10-02');
            // var day = x.getDate();
            // ```
            //
            // The day variable above will be 1 instead of 2 in, say, US Pacific time
            // zone.
            var isSelectedMonth = false;
            if (typeof selected !== 'undefined') {
                isSelectedMonth = (selected.getUTCFullYear() === year && (selected.getUTCMonth() + 1) === month);
            }

            month = parseInt(month);
            year = parseInt(year);
            var calDiv = document.getElementById(div_id);
            removeChildren(calDiv);
            var calTable = document.createElement('table');
            quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month - 1] + ' ' + year);
            var tableBody = quickElement('tbody', calTable);

            // Draw days-of-week header
            var tableRow = quickElement('tr', tableBody);
            for (var i = 0; i < 7; i++) {
                quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]);
            }

            var startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay();
            var days = CalendarNamespace.getDaysInMonth(month, year);

            var nonDayCell;

            // Draw blanks before first of month
            tableRow = quickElement('tr', tableBody);
            for (i = 0; i < startingPos; i++) {
                nonDayCell = quickElement('td', tableRow, ' ');
                nonDayCell.className = "nonday";
            }

            // Draw days of month
            var currentDay = 1;
            for (i = startingPos; currentDay <= days; i++) {
                if (i % 7 === 0 && currentDay !== 1) {
                    tableRow = quickElement('tr', tableBody);
                }
                if ((currentDay === todayDay) && (month === todayMonth) && (year === todayYear)) {
                    todayClass = 'today';
                } else {
                    todayClass = '';
                }

                // use UTC function; see above for explanation.
                if (isSelectedMonth && currentDay === selected.getUTCDate()) {
                    if (todayClass !== '') {
                        todayClass += " ";
                    }
                    todayClass += "selected";
                }

                var cell = quickElement('td', tableRow, '', 'class', todayClass);

                quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '(' + year + ',' + month + ',' + currentDay + '));');
                currentDay++;
            }

            // Draw blanks after end of month (optional, but makes for valid code)
            while (tableRow.childNodes.length < 7) {
                nonDayCell = quickElement('td', tableRow, ' ');
                nonDayCell.className = "nonday";
            }

            calDiv.appendChild(calTable);
        }
    };

    // Calendar -- A calendar instance
    function Calendar(div_id, callback, selected) {
        // div_id (string) is the ID of the element in which the calendar will
        //     be displayed
        // callback (string) is the name of a JavaScript function that will be
        //     called with the parameters (year, month, day) when a day in the
        //     calendar is clicked
        this.div_id = div_id;
        this.callback = callback;
        this.today = new Date();
        this.currentMonth = this.today.getMonth() + 1;
        this.currentYear = this.today.getFullYear();
        if (typeof selected !== 'undefined') {
            this.selected = selected;
        }
    }
    Calendar.prototype = {
        drawCurrent: function() {
            CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected);
        },
        drawDate: function(month, year, selected) {
            this.currentMonth = month;
            this.currentYear = year;

            if(selected) {
                this.selected = selected;
            }

            this.drawCurrent();
        },
        drawPreviousMonth: function() {
            if (this.currentMonth === 1) {
                this.currentMonth = 12;
                this.currentYear--;
            }
            else {
                this.currentMonth--;
            }
            this.drawCurrent();
        },
        drawNextMonth: function() {
            if (this.currentMonth === 12) {
                this.currentMonth = 1;
                this.currentYear++;
            }
            else {
                this.currentMonth++;
            }
            this.drawCurrent();
        },
        drawPreviousYear: function() {
            this.currentYear--;
            this.drawCurrent();
        },
        drawNextYear: function() {
            this.currentYear++;
            this.drawCurrent();
        }
    };
    window.Calendar = Calendar;
})();
<commit_msg>Update tmp/admin/js/calendar.js<commit_after><|endoftext|>


<commit_before>/**
 * Admin
 *
 * for admin pages
 */
var Admin = typeof Admin == 'undefined' ? {} : Admin;

/**
 * Navigation
 *
 * @return void
 */
Admin.navigation = function () {
  var $sidebar = $('#sidebar-menu');
  var $topLevelMenus = $('#sidebar-menu > li > .hasChild');

  // no item is current, fallback to current controller index
  var $current = $('.sidebar .current');
  if ($current.length == 0) {
    var selector = _.template('a.sidebar-item[href^="<%= basePath %>admin/' +
      '<%= params.plugin %>/' +
      '<%= params.controller %>"]:first');
    if ($(selector(Croogo)).addClass('current').length == 0) {
      var selector = _.template(
        'a.sidebar-item[href="<%= basePath %>admin/' + '<%= params.plugin %>"]');
      $(selector(Croogo)).addClass('current');
    }
  }
  // traverse parent elements and mark as current
  $($current.selector).parentsUntil('.sidebar', 'ul').each(function () {
    $(this).siblings('a.sidebar-item').addClass('current')
  });
  if (window.innerWidth >= 979) {
    $topLevelMenus.parent().find('> .current').next('ul').toggle();
  }

  var dropdownOpen = function () {
    $(this)
      .addClass('dropdown-open')
      .removeClass('dropdown-close')
      .siblings('.sidebar-item')
      .addClass('dropdown-open')
      .removeClass('dropdown-close');
  };

  var dropdownClose = function () {
    $(this)
      .addClass('dropdown-close')
      .removeClass('dropdown-open')
      .siblings('.sidebar-item')
      .addClass('dropdown-close')
      .removeClass('dropdown-open');
  };

  $topLevelMenus.on('click blur', function (e) {
    var $this = $(this);
    var $ul = $(this).next('ul');
    var sidebarWidth = $sidebar.width();

    if (e.type == 'blur' && window.innerWidth > 979) {
      return;
    }

    if ($ul.is(':visible')) {

      var onComplete = function () {
        dropdownClose.call($ul.get(0));
        $ul.css({'margin-left': sidebarWidth + 'px', 'margin-top': 'inherit'})
      }

      if (window.innerWidth <= 979) {
        $ul.hide('fade', 'fast', onComplete);
      } else {
        $ul.slideUp('fast', onComplete);
      }
    } else {
      $topLevelMenus.siblings('ul:visible').slideUp('fast', function () {
        dropdownClose.call(this);
      });
      dropdownOpen.call(this);
      if (window.innerWidth <= 979) {
        $ul.css(
          {'position': 'absolute', 'margin-left': sidebarWidth + 1 + 'px', 'margin-top': '-42px'});
        $ul.show('fade', 'fast');
      } else {
        $ul.css({'margin-left': 0, 'position': 'relative'});
        $ul.slideDown('fast');
      }
    }
    e.stopPropagation();
    return false;
  });

  $(window).on('resize', function () {
    $('#sidebar-menu > li ul:visible').each(function () {
      $(this).toggle();
      dropdownClose.call(this);
    });
  });
}

/**
 * Document ready
 *
 * @return void
 */
$(document).ready(function () {
  Admin.navigation();
});
<commit_msg>sidebar: Fix issue in small display due to jquery easing name change<commit_after>/**
 * Admin
 *
 * for admin pages
 */
var Admin = typeof Admin == 'undefined' ? {} : Admin;

/**
 * Navigation
 *
 * @return void
 */
Admin.navigation = function () {
  var $sidebar = $('#sidebar-menu');
  var $topLevelMenus = $('#sidebar-menu > li > .hasChild');

  // no item is current, fallback to current controller index
  var $current = $('.sidebar .current');
  if ($current.length == 0) {
    var selector = _.template('a.sidebar-item[href^="<%= basePath %>admin/' +
      '<%= params.plugin %>/' +
      '<%= params.controller %>"]:first');
    if ($(selector(Croogo)).addClass('current').length == 0) {
      var selector = _.template(
        'a.sidebar-item[href="<%= basePath %>admin/' + '<%= params.plugin %>"]');
      $(selector(Croogo)).addClass('current');
    }
  }
  // traverse parent elements and mark as current
  $($current.selector).parentsUntil('.sidebar', 'ul').each(function () {
    $(this).siblings('a.sidebar-item').addClass('current')
  });
  if (window.innerWidth >= 979) {
    $topLevelMenus.parent().find('> .current').next('ul').toggle();
  }

  var dropdownOpen = function () {
    $(this)
      .addClass('dropdown-open')
      .removeClass('dropdown-close')
      .siblings('.sidebar-item')
      .addClass('dropdown-open')
      .removeClass('dropdown-close');
  };

  var dropdownClose = function () {
    $(this)
      .addClass('dropdown-close')
      .removeClass('dropdown-open')
      .siblings('.sidebar-item')
      .addClass('dropdown-close')
      .removeClass('dropdown-open');
  };

  $topLevelMenus.on('click blur', function (e) {
    var $this = $(this);
    var $ul = $(this).next('ul');
    var sidebarWidth = $sidebar.width();

    if (e.type == 'blur' && window.innerWidth > 979) {
      return;
    }

    if ($ul.is(':visible')) {

      var onComplete = function () {
        dropdownClose.call($ul.get(0));
        $ul.css({'margin-left': sidebarWidth + 'px', 'margin-top': 'inherit'})
      }

      if (window.innerWidth <= 979) {
        $ul.removeAttr('z-index').fadeOut('fast');
      } else {
        $ul.slideUp('fast', onComplete);
      }
    } else {
      $topLevelMenus.siblings('ul:visible').slideUp('fast', function () {
        dropdownClose.call(this);
      });
      dropdownOpen.call(this);
      if (window.innerWidth <= 979) {
        $ul.css(
          {'position': 'absolute', 'margin-left': sidebarWidth + 1 + 'px', 'margin-top': '-42px'});
        $ul.css({'z-index': 99}).fadeIn('fast');
      } else {
        $ul.css({'margin-left': 0, 'position': 'relative'});
        $ul.slideDown('fast');
      }
    }
    e.stopPropagation();
    return false;
  });

  $(window).on('resize', function () {
    $('#sidebar-menu > li ul:visible').each(function () {
      $(this).toggle();
      dropdownClose.call(this);
    });
  });
}

/**
 * Document ready
 *
 * @return void
 */
$(document).ready(function () {
  Admin.navigation();
});
<|endoftext|>


<commit_before>/* BOM management functions.
 * Requires follwing files to be loaded first:
 * - api.js
 * - part.js
 * - modals.js
 */

function reloadBomTable(table, options) {

    table.bootstrapTable('refresh');
}

function downloadBom(options = {}) {

    var modal = options.modal || "#modal-form";

    var content = `
        Select file format

        <select id='bom-format' class='select'>
            <option value='csv'>CSV</option>
            <option value='tsv'>TSV</option>
            <option value='xls'>XLS</option>
            <option value='xlsx'>XLSX</option>
            <option value='ods'>ODS</option>
            <option value='yaml'>YAML</option>
            <option value='json'>JSON</option>
            <option value='xml'>XML</option>
            <option value='html'>HTML</option>
        </select>

    `;

    openModal({
        modal: modal,
        title: "Export Bill of Materials",
        submit_text: "Download",
        close_text: "Cancel",
    });

    modalSetContent(modal, content);

    modalEnable(modal, true);

    $(modal).on('click', '#modal-form-submit', function() {
        $(modal).modal('hide');

        var format = $(modal).find('#bom-format :selected').val();

        if (options.url) {
            var url = options.url + "?format=" + format;

            location.href = url;
        }
    });
}

function loadBomTable(table, options) {
    /* Load a BOM table with some configurable options.
     *
     * Following options are available:
     * editable      - Should the BOM table be editable?
     * bom_url       - Address to request BOM data from
     * part_url      - Address to request Part data from
     * parent_id     - Parent ID of the owning part
     *
     * BOM data are retrieved from the server via AJAX query
     */

    // Construct the table columns

    var cols = [
        {
            field: 'pk',
            title: 'ID',
            visible: false,
        },
        {
            checkbox: true,
            title: 'Select',
            searchable: false,
            sortable: false,
        },
    ];

    // Part column
    cols.push(
        {
            field: 'sub_part_detail.full_name',
            title: 'Part',
            sortable: true,
            formatter: function(value, row, index, field) {
                return imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url);
            }
        }
    );

    // Part description
    cols.push(
        {
            field: 'sub_part_detail.description',
            title: 'Description',
        }
    );

    // Part quantity
    cols.push(
        {
            field: 'quantity',
            title: 'Required',
            searchable: false,
            sortable: true,
            formatter: function(value, row, index, field) {
                var text = value;

                if (row.overage) {
                    text += " (+" + row.overage + ")    ";
                }

                return text;
            }
        }
    );

    if (!options.editable) {
        cols.push(
        {
            field: 'sub_part_detail.available_stock',
            title: 'Available',
            searchable: false,
            sortable: true,
            formatter: function(value, row, index, field) {
                var text = "";

                if (row.quantity < row.sub_part_detail.available_stock)
                {
                    text = "" + value + "";
                }
                else
                {
                    if (!value) {
                        value = 'No Stock';
                    }
                    text = "" + value + "";
                }

                return renderLink(text, row.sub_part_detail.url + "stock/");
            }
        });

        cols.push(
        {
            field: 'price_range',
            title: 'Price',
            sortable: true,
            formatter: function(value, row, index, field) {
                if (value) {
                    return value;
                } else {
                    return "No pricing available";
                }
            }
        });

    }

    // Part notes
    cols.push(
        {
            field: 'note',
            title: 'Notes',
            searchable: true,
            sortable: true,
        }
    );

    if (options.editable) {
        cols.push({
            formatter: function(value, row, index, field) {
                var bEdit = "<button title='Edit BOM Item' class='btn btn-primary bom-edit-button btn-sm' type='button' url='/part/bom/" + row.pk + "/edit'></button>";
                var bDelt = "<button title='Delete BOM Item' class='btn btn-danger bom-delete-button btn-sm' type='button' url='/part/bom/" + row.pk + "/delete'></button>";

                return "
" + bEdit + bDelt + "
";
            }
        });
    }

    // Configure the table (bootstrap-table)

    table.bootstrapTable({
        sortable: true,
        search: true,
        formatNoMatches: function() { return "No BOM items found"; },
        clickToSelect: true,
        queryParams: function(p) {
            return {
                part: options.parent_id,
                ordering: 'name',
        }
    },
    columns: cols,
    url: options.bom_url
});

    // In editing mode, attached editables to the appropriate table elements
    if (options.editable) {

        table.on('click', '.bom-delete-button', function() {
            var button = $(this);

            launchModalForm(button.attr('url'), {
                                success: function() {
                                    reloadBomTable(table);
                                }
            });
        });

        table.on('click', '.bom-edit-button', function() {
            var button = $(this);

            launchModalForm(button.attr('url'), {
                                success: function() {
                                    reloadBomTable(table);
                                }
            });
        });
    }
}<commit_msg>Display total part quantity in BOM footer<commit_after>/* BOM management functions.
 * Requires follwing files to be loaded first:
 * - api.js
 * - part.js
 * - modals.js
 */

function reloadBomTable(table, options) {

    table.bootstrapTable('refresh');
}

function downloadBom(options = {}) {

    var modal = options.modal || "#modal-form";

    var content = `
        Select file format

        <select id='bom-format' class='select'>
            <option value='csv'>CSV</option>
            <option value='tsv'>TSV</option>
            <option value='xls'>XLS</option>
            <option value='xlsx'>XLSX</option>
            <option value='ods'>ODS</option>
            <option value='yaml'>YAML</option>
            <option value='json'>JSON</option>
            <option value='xml'>XML</option>
            <option value='html'>HTML</option>
        </select>

    `;

    openModal({
        modal: modal,
        title: "Export Bill of Materials",
        submit_text: "Download",
        close_text: "Cancel",
    });

    modalSetContent(modal, content);

    modalEnable(modal, true);

    $(modal).on('click', '#modal-form-submit', function() {
        $(modal).modal('hide');

        var format = $(modal).find('#bom-format :selected').val();

        if (options.url) {
            var url = options.url + "?format=" + format;

            location.href = url;
        }
    });
}

function loadBomTable(table, options) {
    /* Load a BOM table with some configurable options.
     *
     * Following options are available:
     * editable      - Should the BOM table be editable?
     * bom_url       - Address to request BOM data from
     * part_url      - Address to request Part data from
     * parent_id     - Parent ID of the owning part
     *
     * BOM data are retrieved from the server via AJAX query
     */

    // Construct the table columns

    var cols = [
        {
            field: 'pk',
            title: 'ID',
            visible: false,
        },
        {
            checkbox: true,
            title: 'Select',
            searchable: false,
            sortable: false,
        },
    ];

    // Part column
    cols.push(
        {
            field: 'sub_part_detail.full_name',
            title: 'Part',
            sortable: true,
            formatter: function(value, row, index, field) {
                return imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url);
            }
        }
    );

    // Part description
    cols.push(
        {
            field: 'sub_part_detail.description',
            title: 'Description',
        }
    );

    // Part quantity
    cols.push(
        {
            field: 'quantity',
            title: 'Required',
            searchable: false,
            sortable: true,
            formatter: function(value, row, index, field) {
                var text = value;

                if (row.overage) {
                    text += " (+" + row.overage + ")    ";
                }

                return text;
            },
            footerFormatter: function(data) {
                var quantity = 0;

                data.forEach(function(item) {
                    quantity += item.quantity;
                });

                return quantity;
            },
        }
    );

    if (!options.editable) {
        cols.push(
        {
            field: 'sub_part_detail.available_stock',
            title: 'Available',
            searchable: false,
            sortable: true,
            formatter: function(value, row, index, field) {
                var text = "";

                if (row.quantity < row.sub_part_detail.available_stock)
                {
                    text = "" + value + "";
                }
                else
                {
                    if (!value) {
                        value = 'No Stock';
                    }
                    text = "" + value + "";
                }

                return renderLink(text, row.sub_part_detail.url + "stock/");
            }
        });

        cols.push(
        {
            field: 'price_range',
            title: 'Price',
            sortable: true,
            formatter: function(value, row, index, field) {
                if (value) {
                    return value;
                } else {
                    return "No pricing available";
                }
            }
        });

    }

    // Part notes
    cols.push(
        {
            field: 'note',
            title: 'Notes',
            searchable: true,
            sortable: true,
        }
    );

    if (options.editable) {
        cols.push({
            formatter: function(value, row, index, field) {
                var bEdit = "<button title='Edit BOM Item' class='btn btn-primary bom-edit-button btn-sm' type='button' url='/part/bom/" + row.pk + "/edit'></button>";
                var bDelt = "<button title='Delete BOM Item' class='btn btn-danger bom-delete-button btn-sm' type='button' url='/part/bom/" + row.pk + "/delete'></button>";

                return "
" + bEdit + bDelt + "
";
            }
        });
    }

    // Configure the table (bootstrap-table)

    table.bootstrapTable({
        sortable: true,
        search: true,
        formatNoMatches: function() { return "No BOM items found"; },
        clickToSelect: true,
        showFooter: true,
        queryParams: function(p) {
            return {
                part: options.parent_id,
                ordering: 'name',
        }
    },
    columns: cols,
    url: options.bom_url
});

    // In editing mode, attached editables to the appropriate table elements
    if (options.editable) {

        table.on('click', '.bom-delete-button', function() {
            var button = $(this);

            launchModalForm(button.attr('url'), {
                                success: function() {
                                    reloadBomTable(table);
                                }
            });
        });

        table.on('click', '.bom-edit-button', function() {
            var button = $(this);

            launchModalForm(button.attr('url'), {
                                success: function() {
                                    reloadBomTable(table);
                                }
            });
        });
    }
}<|endoftext|>


<commit_before>/* eslint-disable no-underscore-dangle */

/**
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function():*} handler
 */
export function on (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = Array.isArray(type) ? type : [type];

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.addEventListener(eventType, handler);

            if (typeof node._listeners === "undefined")
            {
                node._listeners = {};
            }

            if (typeof node._listeners[eventType] === "undefined")
            {
                node._listeners[eventType] = [];
            }

            node._listeners[eventType].push(handler);
        }
    }
}

/**
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function():*} handler
 */
export function off (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = Array.isArray(type) ? type : [type];

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.removeEventListener(eventType, handler);

            if (typeof node._listeners !== "undefined" && typeof node._listeners[eventType] !== "undefined")
            {
                const index = node._listeners[eventType].indexOf(handler);

                if (-1 !== index)
                {
                    node._listeners[eventType].splice(index, 1);
                }
            }
        }
    }
}
<commit_msg>Docs + once()<commit_after>/* eslint-disable no-underscore-dangle */

/**
 * Registers an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function on (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = Array.isArray(type) ? type : [type];

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.addEventListener(eventType, handler);

            if (typeof node._listeners === "undefined")
            {
                node._listeners = {};
            }

            if (typeof node._listeners[eventType] === "undefined")
            {
                node._listeners[eventType] = [];
            }

            node._listeners[eventType].push(handler);
        }
    }
}

/**
 * Removes an event listener for the given events
 *
 * @param {HTMLElement|HTMLElement[]} element
 * @param {string|string[]} type
 * @param {function(*):*} handler
 */
export function off (element, type, handler)
{
    const list = Array.isArray(element) ? element : [element];
    const types = Array.isArray(type) ? type : [type];

    for (let i = 0; i < list.length; i++)
    {
        for (let j = 0; j < types.length; j++)
        {
            const node = list[i];
            const eventType = types[j];

            node.removeEventListener(eventType, handler);

            if (typeof node._listeners !== "undefined" && typeof node._listeners[eventType] !== "undefined")
            {
                const index = node._listeners[eventType].indexOf(handler);

                if (-1 !== index)
                {
                    node._listeners[eventType].splice(index, 1);
                }
            }
        }
    }
}

/**
 * Registers an event listener, that it automatically is removed after it was executed once.
 *
 * Returns the intermediate function, so that the event listener can be removed:
 *
 *      const intermediate = once(element, event, handler);
 *      off(element, event, intermediate);
 *
 * @param {HTMLElement} element
 * @param {string} type
 * @param {function(*):*} handler
 * @return {function():*}
 */
export function once (element, type, handler)
{
    const intermediate = (...args) => {
        handler(...args);
        off(element, type, intermediate);
    };
    on(element, type, intermediate);

    return intermediate;
}
<|endoftext|>


<commit_before>window.CodeKindaSmart = {
  Models: {},
  Collections: {},
  Views: {},
  Routers: {},
  initialize: function() {
    alert('Hello from Backbone!');
  }
};

$(document).ready(function(){
  CodeKindaSmart.initialize();
});
<commit_msg>removes the alert from backbone<commit_after>window.CodeKindaSmart = {
  Models: {},
  Collections: {},
  Views: {},
  Routers: {},
  initialize: function() {

  }
};

$(document).ready(function(){
  CodeKindaSmart.initialize();
});
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.rebuildFilenameList = () => {
    const filenames = cd.filenames();
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    //
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    //
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    cd.rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    cd.rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFileContent = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const   newFile  = fileOps.find('#new');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>refactoring; move non-public js functions to bottom of file<commit_after>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.rebuildFilenameList = () => {
    const filenames = cd.filenames();
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    cd.rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    cd.rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFileContent = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const   newFile  = fileOps.find('#new');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>//= require d3
//= require c3
//= require c3ext

// TODO: This module is complex enough to require its own tests. Rewrite it using Ecma6 class syntax and
// write tests for this feature after consul has been migrated to Rails 5.1
(function(){
  'use strict';

  var ProposalGraph = function(url) {
    this.url = url;
    this.successfulProposalDataUrl = null;
    this.proposalAchievementsUrl = null;
    this.targetId = null;
    this.groupBy = null;
    this.proposalSuccess = null;
    this.maximumValue = 0;
    this.progressLabel = 'Progress';
    this.supportsLabel = 'Supports';
    this.successLabel = 'Success';
    this.goals = null;
    this.achievements = null;
    this.xColumnValues = null;
    this.progressColumnValues = null;
    this.resourcesUrl = null;
  };

  ProposalGraph.prototype.refresh = function() {
    this.refreshGoals()
      .then(this.refreshData.bind(this))
      .then(this.refreshSuccessfulData.bind(this))
      .then(this.refreshAchievements.bind(this))
      .done(this.draw.bind(this));
  };

  ProposalGraph.prototype.refreshGoals = function () {
    return $.ajax({
      url: this.resourcesUrl,
      cache: false,
      success: function(data) {
        this.parseGoals(data);
      }.bind(this)
    });
  };

  ProposalGraph.prototype.parseGoals = function(data) {
    var i, l;

    this.goals = [];
    for (i = 0, l = data.length; i < l; i += 1) {
      this.goals.push({
        value: data[i].required_supports,
        text: data[i].title
      });
    }
  };

  ProposalGraph.prototype.refreshData = function () {
    return $.ajax({
      url: this.url,
      cache: false,
      success: function (data) {
        this.parseData(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseData = function(data) {
    var key;

    this.xColumnValues = [ ];
    this.progressColumnValues =  [ this.progressLabel ];

    for (key in data) {
      if (data.hasOwnProperty(key)) {
        this.xColumnValues.push(key);
        this.progressColumnValues.push(data[key]);

        if (this.maximumValue < data[key]) {
          this.maximumValue = data[key];
        }
      }
    }
  };

  ProposalGraph.prototype.refreshSuccessfulData = function() {
    return $.ajax({
      url: this.successfulProposalDataUrl,
      cache: false,
      success: function (data) {
        this.parseSuccessfulProposalData(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseSuccessfulProposalData = function(data) {
    var key;

    this.successfulColumnValues = [ this.successLabel ];

    for (key in data) {
      if (data.hasOwnProperty(key)) {
        this.addXColumnValue(key);
        this.successfulColumnValues.push(data[key]);
      }
    }
  };

  ProposalGraph.prototype.refreshAchievements = function() {
    return $.ajax({
      url: this.proposalAchievementsUrl,
      cache: false,
      success: function (data) {
        this.parseAchievements(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseAchievements = function(data) {
    var group;

    this.achievements = [];
    for (group in data) {
      if (data.hasOwnProperty(group)) {
        this.addXColumnValue(group);
        this.achievements.push({
          value: this.formatGroup(group),
          text: data[group].title,
          position: 'start'
        });
      }
    }
  };

  ProposalGraph.prototype.addXColumnValue = function (value) {
    if (this.xColumnValues.indexOf(value) === -1) {
      this.xColumnValues.push(value);
    }
  };

  ProposalGraph.prototype.draw = function() {
    var colors = {},
        maximumValue = this.maximumValue === 0 ? this.proposalSuccess : Math.round(this.maximumValue * 1.10);

    this.formatXColumnValues();

    colors[this.progressColumnValues[0]] = '#004a83';
    colors[this.successfulColumnValues[0]] = '#ff7f0e';

    c3.generate({
      bindto: '#' + this.targetId,
      data: {
        x: 'x',
        columns: [
          this.xColumnValues,
          this.progressColumnValues,
          this.successfulColumnValues
        ],
        colors: colors
      },
      axis: {
        y: {
          tick: {
            values: this.tickYValues()
          },
          min: (this.maximumValue === 0 ? Math.round(this.proposalSuccess * 0.10) : 0),
          max: maximumValue,
          label: {
            text: this.supportsLabel,
            position: 'outer-middle'
          }
        },
        x: {
          type: 'category',
          tick: {
            values: this.tickXValues(),
            centered: true
          }
        }
      },
      grid: {
        y: {
          lines: this.goals
        },
        x: {
          lines: this.achievements
        }
      },
      zoom: {
        enabled: true
      }
    });
  };

  ProposalGraph.prototype.tickYValues = function () {
    var i,
        tick = [0],
        maximumValue = this.maximumValue === 0 ? this.proposalSuccess : Math.round(this.maximumValue * 1.10),
        step = maximumValue <= 10 ? 1 : Math.round(maximumValue / 10);

    for (i = step; i < maximumValue; i += step) {
      tick.push(i);
    }

    tick.push(maximumValue);

    return tick;
  };

  ProposalGraph.prototype.tickXValues = function () {
    var i,
        l,
        tick = [],
        step = this.xColumnValues.length < 13 ? 1 : Math.round((this.xColumnValues.length - 1) / 12);

    if (this.xColumnValues.length > 1) {
      tick.push(0);

      for(i = step, l = this.xColumnValues.length - 1; i < l; i += step) {
        tick.push(i);
      }
    }

    return tick;
  };

  ProposalGraph.prototype.formatXColumnValues = function () {
    var i, l, parts;

    this.xColumnValues = this.xColumnValues.sort();

    if (this.isDailyGrouped()) {
      for (i = 0, l = this.xColumnValues.length; i < l; i += 1) {
        this.xColumnValues[i] = this.formatGroup(this.xColumnValues[i]);
      }
    }

    this.xColumnValues.unshift('x');
  };

  ProposalGraph.prototype.formatGroup = function(group) {
    if (this.isDailyGrouped()) {
      var parts = group.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
      return parts[2] + "/" + parts[3];
    }

    return group;
  }

  ProposalGraph.prototype.isDailyGrouped = function() {
    return this.groupBy === undefined || this.groupBy === '' || this.groupBy === null
  };

  $(document).ready(function () {
    $('[data-proposal-graph-url]').each(function () {
      var graph = new ProposalGraph($(this).data('proposal-graph-url'));
      graph.successfulProposalDataUrl = $(this).data('successful-proposal-graph-url');
      graph.proposalAchievementsUrl = $(this).data('proposal-achievements-url');
      graph.targetId = $(this).attr('id');
      graph.groupBy = $(this).data('proposal-graph-group-by');
      graph.progressLabel = $(this).data('proposal-graph-progress-label');
      graph.supportsLabel = $(this).data('proposal-graph-supports-label');
      graph.successLabel = $(this).data('proposal-graph-success-label');
      graph.proposalSuccess = parseInt($(this).data('proposal-success'), 10);
      graph.resourcesUrl = $(this).data('proposal-resources-url');

      graph.refresh();
    });
  });
})();
<commit_msg>Enhacements in proposal progress graph<commit_after>//= require d3
//= require c3
//= require c3ext

// TODO: This module is complex enough to require its own tests. Rewrite it using Ecma6 class syntax and
// write tests for this feature after consul has been migrated to Rails 5.1
(function(){
  'use strict';

  var ProposalGraph = function(url) {
    this.url = url;
    this.successfulProposalDataUrl = null;
    this.proposalAchievementsUrl = null;
    this.targetId = null;
    this.groupBy = null;
    this.proposalSuccess = null;
    this.maximumValue = 0;
    this.progressLabel = 'Progress';
    this.supportsLabel = 'Supports';
    this.successLabel = 'Success';
    this.goals = null;
    this.achievements = null;
    this.xColumnValues = null;
    this.progressColumnValues = null;
    this.resourcesUrl = null;
  };

  ProposalGraph.prototype.refresh = function() {
    this.refreshGoals()
      .then(this.refreshData.bind(this))
      .then(this.refreshSuccessfulData.bind(this))
      .then(this.refreshAchievements.bind(this))
      .done(this.draw.bind(this));
  };

  ProposalGraph.prototype.refreshGoals = function () {
    return $.ajax({
      url: this.resourcesUrl,
      cache: false,
      success: function(data) {
        this.parseGoals(data);
      }.bind(this)
    });
  };

  ProposalGraph.prototype.parseGoals = function(data) {
    var i, l;

    this.goals = [];
    for (i = 0, l = data.length; i < l; i += 1) {
      this.goals.push({
        value: data[i].required_supports,
        text: data[i].title
      });
    }
  };

  ProposalGraph.prototype.refreshData = function () {
    return $.ajax({
      url: this.url,
      cache: false,
      success: function (data) {
        this.parseData(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseData = function(data) {
    var key;

    this.xColumnValues = [ ];
    this.progressColumnValues =  [ this.progressLabel ];

    for (key in data) {
      if (data.hasOwnProperty(key)) {
        this.xColumnValues.push(key);
        this.progressColumnValues.push(data[key]);

        if (this.maximumValue < data[key]) {
          this.maximumValue = data[key];
        }
      }
    }
  };

  ProposalGraph.prototype.refreshSuccessfulData = function() {
    return $.ajax({
      url: this.successfulProposalDataUrl,
      cache: false,
      success: function (data) {
        this.parseSuccessfulProposalData(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseSuccessfulProposalData = function(data) {
    var key;

    this.successfulColumnValues = [ this.successLabel ];

    for (key in data) {
      if (data.hasOwnProperty(key)) {
        this.addXColumnValue(key);
        this.successfulColumnValues.push(data[key]);
      }
    }
  };

  ProposalGraph.prototype.refreshAchievements = function() {
    return $.ajax({
      url: this.proposalAchievementsUrl,
      cache: false,
      success: function (data) {
        this.parseAchievements(data);
      }.bind(this),
      data: {
        group_by: this.groupBy
      }
    });
  };

  ProposalGraph.prototype.parseAchievements = function(data) {
    var group;

    this.achievements = [];
    for (group in data) {
      if (data.hasOwnProperty(group)) {
        this.addXColumnValue(group);
        this.achievements.push({
          value: this.formatGroup(group),
          text: data[group].title,
          position: 'start'
        });
      }
    }
  };

  ProposalGraph.prototype.addXColumnValue = function (value) {
    if (this.xColumnValues.indexOf(value) === -1) {
      this.xColumnValues.push(value);
    }
  };

  ProposalGraph.prototype.draw = function() {
    var colors = {},
        maximumValue = this.maximumValue === 0 ? this.proposalSuccess : Math.round(this.maximumValue * 1.10);

    this.formatXColumnValues();

    colors[this.progressColumnValues[0]] = '#004a83';
    colors[this.successfulColumnValues[0]] = '#ff7f0e';

    c3.generate({
      bindto: '#' + this.targetId,
      data: {
        x: 'x',
        columns: [
          this.xColumnValues,
          this.progressColumnValues,
          this.successfulColumnValues
        ],
        colors: colors,
        color: function (color, d) {
          var achievement;

          if (d.id === this.successfulColumnValues[0] || !d.hasOwnProperty('x')) {
            return color;
          }

          achievement = this.achievements.find(function (element) {
            return element.value === this.xColumnValues[d.index + 1];
          }.bind(this));

          if (achievement !== undefined) {
            return '#ff0000';
          }

          return color;
        }.bind(this)
      },
      axis: {
        y: {
          tick: {
            values: this.tickYValues()
          },
          min: (this.maximumValue === 0 ? Math.round(this.proposalSuccess * 0.10) : 0),
          max: maximumValue,
          label: {
            text: this.supportsLabel,
            position: 'outer-middle'
          }
        },
        x: {
          type: 'category',
          tick: {
            values: this.tickXValues(),
            centered: true
          }
        }
      },
      grid: {
        y: {
          lines: this.goals
        }
      },
      zoom: {
        enabled: true
      },
      tooltip: {
        format: {
          title: function (d) {
            var achievement = this.achievements.find(function (element) {
              return element.value === this.xColumnValues[d + 1];
            }.bind(this));

            if (achievement !== undefined) {
              return this.xColumnValues[d + 1] + ': ' + achievement.text;
            }

            return this.xColumnValues[d + 1];
          }.bind(this)
        }
      }
    });
  };

  ProposalGraph.prototype.tickYValues = function () {
    var i,
        tick = [0],
        maximumValue = this.maximumValue === 0 ? this.proposalSuccess : Math.round(this.maximumValue * 1.10),
        step = maximumValue <= 10 ? 1 : Math.round(maximumValue / 10);

    for (i = step; i < maximumValue; i += step) {
      tick.push(i);
    }

    tick.push(maximumValue);

    return tick;
  };

  ProposalGraph.prototype.tickXValues = function () {
    var i,
        l,
        tick = [],
        step = this.xColumnValues.length < 13 ? 1 : Math.round((this.xColumnValues.length - 1) / 12);

    if (this.xColumnValues.length > 1) {
      tick.push(0);

      for(i = step, l = this.xColumnValues.length - 1; i < l; i += step) {
        tick.push(i);
      }
    }

    return tick;
  };

  ProposalGraph.prototype.formatXColumnValues = function () {
    var i, l, parts;

    this.xColumnValues = this.xColumnValues.sort();

    if (this.isDailyGrouped()) {
      for (i = 0, l = this.xColumnValues.length; i < l; i += 1) {
        this.xColumnValues[i] = this.formatGroup(this.xColumnValues[i]);
      }
    }

    this.xColumnValues.unshift('x');
  };

  ProposalGraph.prototype.formatGroup = function(group) {
    if (this.isDailyGrouped()) {
      var parts = group.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
      return parts[2] + "/" + parts[3];
    }

    return group;
  }

  ProposalGraph.prototype.isDailyGrouped = function() {
    return this.groupBy === undefined || this.groupBy === '' || this.groupBy === null
  };

  $(document).ready(function () {
    $('[data-proposal-graph-url]').each(function () {
      var graph = new ProposalGraph($(this).data('proposal-graph-url'));
      graph.successfulProposalDataUrl = $(this).data('successful-proposal-graph-url');
      graph.proposalAchievementsUrl = $(this).data('proposal-achievements-url');
      graph.targetId = $(this).attr('id');
      graph.groupBy = $(this).data('proposal-graph-group-by');
      graph.progressLabel = $(this).data('proposal-graph-progress-label');
      graph.supportsLabel = $(this).data('proposal-graph-supports-label');
      graph.successLabel = $(this).data('proposal-graph-success-label');
      graph.proposalSuccess = parseInt($(this).data('proposal-success'), 10);
      graph.resourcesUrl = $(this).data('proposal-resources-url');

      graph.refresh();
    });
  });
})();
<|endoftext|>


<commit_before>define(
    [
        "angular",
        "./dataset-list-controllers",
        "./dataset-list-services"
    ],
    function (angular, controllers) {
        "use strict";

        var datasetListRoutes = angular.module("datasetList.routes", ["narthex.common", "dashboard.services"]);
        datasetListRoutes.config(
            [
                "$routeProvider", "userResolve",
                function ($routeProvider, userResolve) {
                    $routeProvider.when(
                        "/", {
                            templateUrl: "/narthex/assets/templates/dataset-list.html",
                            controller: controllers.DatasetListCtrl,
                            resolve: userResolve,
                            reloadOnSearch: false
                        }
                    );
                }
            ]
        );

        var narthexDatasetList = angular.module("narthex.datasetList", [
            "ngRoute",
            "datasetList.routes",
            "datasetList.services",
            "narthex.common"
        ]);

        narthexDatasetList.controller('DatasetEntryCtrl', controllers.DatasetEntryCtrl);

        var config = function config($rootScopeProvider) {
            $rootScopeProvider.digestTtl(15);
        };

        config.$inject = ['$rootScopeProvider'];
        narthexDatasetList.config(config);
        return narthexDatasetList;
    });

<commit_msg>Remove reference to dashboard services.<commit_after>define(
    [
        "angular",
        "./dataset-list-controllers",
        "./dataset-list-services"
    ],
    function (angular, controllers) {
        "use strict";

        var datasetListRoutes = angular.module("datasetList.routes", ["narthex.common"]);
        datasetListRoutes.config(
            [
                "$routeProvider", "userResolve",
                function ($routeProvider, userResolve) {
                    $routeProvider.when(
                        "/", {
                            templateUrl: "/narthex/assets/templates/dataset-list.html",
                            controller: controllers.DatasetListCtrl,
                            resolve: userResolve,
                            reloadOnSearch: false
                        }
                    );
                }
            ]
        );

        var narthexDatasetList = angular.module("narthex.datasetList", [
            "ngRoute",
            "datasetList.routes",
            "datasetList.services",
            "narthex.common"
        ]);

        narthexDatasetList.controller('DatasetEntryCtrl', controllers.DatasetEntryCtrl);

        var config = function config($rootScopeProvider) {
            $rootScopeProvider.digestTtl(15);
        };

        config.$inject = ['$rootScopeProvider'];
        narthexDatasetList.config(config);
        return narthexDatasetList;
    });

<|endoftext|>