<commit_before>/*global define, Raphael*/
'use strict';
define(['jquery', 'underscore', 'backbone', 'helpers/raphael_support', 'templates', 'bootstrap', 'views/osd-detail-view', 'models/application-model', 'raphael', 'marionette'], function($, _, Backbone, Rs, JST, bs, View, Models) {
    var OSDVisualization = Backbone.Marionette.ItemView.extend({
        template: JST['app/scripts/templates/viz.ejs'],
        serializeData: function() {
            return {};
        },
        originX: 0,
        originY: 0,
        step: 40,
        timer: null,
        ui: {
            viz: '.viz',
            detail: '.detail tbody',
            spinner: '.icon-spinner'
        },
        events: {
            'click .viz': 'clickHandler'
        },
        collectionEvents: {
            'add': 'addOSD',
            'remove': 'removeOSD',
            'change': 'updateOSD',
        },
        updateCollection: function() {
            if (this.App.Config['delta-osd-api'] && this.collection.length > 0) {
                this.collection.update.apply(this.collection);
            } else {
                this.collection.fetch();
            }
        },
        initialize: function() {
            this.App = Backbone.Marionette.getOption(this, 'App');
            this.width = 17 * this.step;
            this.height = 11 * this.step;
            this.w = 720;
            this.h = 520;
            _.bindAll(this);
            var self = this;
            this.keyHandler = _.debounce(this.keyHandler, 250, true);
            this.listenTo(this.App.vent, 'keyup', this.keyHandler);
            this.listenTo(this.App.vent, 'osd:update', this.updateCollection);
            this.listenTo(this.collection, 'request', function() {
                self.ui.spinner.css('visibility', 'visible');
            });
            this.listenTo(this.collection, 'sync error', function() {
                self.ui.spinner.css('visibility', 'hidden');
            });
            this.listenTo(this.collection, 'reset', this.resetViews);
        },
        resetViews: function(collection, options) {
            _.each(options.previousModels, this.cleanupModelView);
        },
        addOSD: function(m) {
            this.moveCircle(m);
        },
        cleanupModelView: function(m) {
            if (m.views) {
                var circle = m.views.circle;
                circle.animate({
                    'opacity': 0,
                    'r': 0
                }, 250, 'easeIn', function() {
                    circle.remove();
                });
                m.views.text.remove();
                m.views = null;
            }
        },
        removeOSD: function(m) {
            this.collection.remove(m);
            this.cleanupModelView(m);
        },
        updateOSD: function(m) {
            m.set(m.attributes);
        },
        drawGrid: function(d) {
            var path = Rs.calcGrid(this.originX, this.originY, this.width, this.height, this.step);
            var path1 = this.r.path('M0,0').attr({
                'stroke-width': 1,
                'stroke': '#5e6a71',
                'opacity': 0.40
            });
            this.drawLegend(this.r, 285, 475);
            var anim = Raphael.animation({
                path: path,
                callback: d.resolve
            }, 250);
            path1.animate(anim);
        },
        startPosition: [{
            x: 40,
            y: 40
        }, {
            x: 600,
            y: 40
        }, {
            x: 600,
            y: 400
        }, {
            x: 40,
            y: 400
        }, ],
        moveCircle: function(model, index) {
            var start = this.startPosition[Math.floor(Math.random() * 4)];
            var pos = Rs.calcPosition(index, this.originX, this.originY, this.width, this.height, this.step);
            this.animateCircleTraversal(this.r, start.x, start.y, 8, pos.nx, pos.ny, model);
        },
        calculatePositions: function() {
            this.collection.each(this.moveCircle);
            return $.Deferred().resolve();
        },
        legendCircle: function(r, originX, originY, percent) {
            // Helper method to draw circles for use as legends beneath viz.
            var srctext = ['down', 'up/out', 'up/in'];
            var srcstate = [{
                up: 0,
                'in': 0
            }, {
                up: 1,
                'in': 0
            }, {
                up: 1,
                'in': 1
            }];
            var i = Math.round(1 / percent) - 1;
            var m = new Models.OSDModel(_.extend(srcstate[i], {
                capacity: 1024,
                used: percent * 1024
            }));
            var c = r.circle(originX, originY, 16 * m.getPercentage()).attr({
                fill: m.getColor(),
                stroke: 'none',
                'cursor': 'default',
                opacity: 0
            });
            var aFn = Raphael.animation({
                opacity: 1
            }, 250, 'easeOut');
            var text = srctext[i];
            r.text(originX, originY + 23, text).attr({
                'cursor': 'default',
                'font-size': '12px',
                'font-family': 'ApexSansLight'
            });
            return c.animate(aFn);
        },
        drawLegend: function(r, originX, originY) {
            // Calls legend circle to place in viz.
            var xp = originX,
                i;
            for (i = 1; i <= 3; i += 1, xp += 50) {
                this.legendCircle(r, xp, originY, i / 3);
            }
        },
        animateCircleTraversal: function(r, originX, originY, radius, destX, destY, model) {
            var c = r.circle(originX, originY, 20 * model.getPercentage()).attr({
                fill: model.getColor(),
                stroke: 'none'
            });
            c.data('modelid', model.id);
            var t;
            var aFn = Raphael.animation({
                cx: destX,
                cy: originY
            }, 250, 'easeOut', function() {
                c.animate({
                    cx: destX,
                    cy: destY
                }, 333, 'easeIn', function() {
                    t = r.text(destX, destY - 1, model.id).attr({
                        font: '',
                        stroke: '',
                        fill: '',
                        style: ''
                    });
                    t.data('modelid', model.id);
                    model.views = {
                        circle: c,
                        text: t
                    };
                });
            });
            return c.animate(aFn);
        },
        simulateUsedChanges: function() {
            var maxRed = 2;
            this.collection.each(function(m) {
                var up = true;
                var _in = Math.random();
                _in = _in < 0.95;
                if (!_in && (Math.random() > 0.6) && maxRed > 0) {
                    maxRed -= 1;
                    up = false;
                    //console.log(m.id + ' setting to down');
                }
                m.set({
                    'up': up ? 1 : 0,
                    'in': _in ? 1 : 0
                });
            });
            this.App.vent.trigger('updateTotals');
            this.App.vent.trigger('status:healthwarn');
        },
        resetChanges: function() {
            this.collection.each(function(m) {
                m.set({
                    'up': 1,
                    'in': 1
                });
            });
            this.App.vent.trigger('updateTotals');
            this.App.vent.trigger('status:healthok');
        },
        startSimulation: function() {
            var self = this;
            this.timer = setTimeout(function() {
                self.simulateUsedChanges();
                self.timer = self.startSimulation();
            }, 3000);
            return this.timer;
        },
        stopSimulation: function() {
            clearTimeout(this.timer);
            this.timer = null;
        },
        render: function() {
            Backbone.Marionette.ItemView.prototype.render.apply(this);
            this.r = window.Raphael(this.ui.viz[0], this.w, this.h);
            this.detailPanel = new View({
                App: this.App,
                el: this.ui.detail
            });
            var d = $.Deferred();
            $.when(this.drawGrid(d));
            var p = d.promise();
            p.then(this.calculatePositions);
            return p;
        },
        keyHandler: function(evt) {
            evt.preventDefault();
            if (!evt.keyCode) {
                return;
            }
            var keyCode = evt.keyCode;
            if (keyCode === 82) {
                this.resetChanges();
                return;
            }
            if (keyCode === 85) {
                this.simulateUsedChanges();
                return;
            }
            if (keyCode === 32) {
                var $spinner = $('.icon-spinner');
                if (this.timer === null) {
                    this.startSimulation();
                    $spinner.closest('i').addClass('.icon-spin').show();
                } else {
                    this.stopSimulation();
                    $spinner.closest('i').removeClass('.icon-spin').hide();
                }
            }
        },
        clickHandler: function(evt) {
            if (evt.target.nodeName === 'tspan' || evt.target.nodeName === 'circle') {
                var x = evt.clientX;
                var y = evt.clientY;
                //console.log(x + ' / ' + y);
                var el = this.r.getElementByPoint(x, y);
                //console.log(el);
                if (el) {
                    var id = el.data('modelid');
                    //console.log(id);
                    if (id) {
                        // ignore circles and tspans without data
                        this.detailPanel.model.set(this.collection.get(id).attributes);
                    }
                    return;
                }
            }
        }
    });
    return OSDVisualization;
});
<commit_msg>Fix addOSD, missing index value<commit_after>/*global define, Raphael*/
'use strict';
define(['jquery', 'underscore', 'backbone', 'helpers/raphael_support', 'templates', 'bootstrap', 'views/osd-detail-view', 'models/application-model', 'raphael', 'marionette'], function($, _, Backbone, Rs, JST, bs, View, Models) {
    var OSDVisualization = Backbone.Marionette.ItemView.extend({
        template: JST['app/scripts/templates/viz.ejs'],
        serializeData: function() {
            return {};
        },
        originX: 0,
        originY: 0,
        step: 40,
        timer: null,
        ui: {
            viz: '.viz',
            detail: '.detail tbody',
            spinner: '.icon-spinner'
        },
        events: {
            'click .viz': 'clickHandler'
        },
        collectionEvents: {
            'add': 'addOSD',
            'remove': 'removeOSD',
            'change': 'updateOSD',
        },
        updateCollection: function() {
            if (this.App.Config['delta-osd-api'] && this.collection.length > 0) {
                this.collection.update.apply(this.collection);
            } else {
                this.collection.fetch();
            }
        },
        initialize: function() {
            this.App = Backbone.Marionette.getOption(this, 'App');
            this.width = 17 * this.step;
            this.height = 11 * this.step;
            this.w = 720;
            this.h = 520;
            _.bindAll(this);
            var self = this;
            this.keyHandler = _.debounce(this.keyHandler, 250, true);
            this.listenTo(this.App.vent, 'keyup', this.keyHandler);
            this.listenTo(this.App.vent, 'osd:update', this.updateCollection);
            this.listenTo(this.collection, 'request', function() {
                self.ui.spinner.css('visibility', 'visible');
            });
            this.listenTo(this.collection, 'sync error', function() {
                self.ui.spinner.css('visibility', 'hidden');
            });
            this.listenTo(this.collection, 'reset', this.resetViews);
        },
        resetViews: function(collection, options) {
            _.each(options.previousModels, this.cleanupModelView);
        },
        addOSD: function(m) {
            this.moveCircle(m, this.collection.indexOf(m)-1);
        },
        cleanupModelView: function(m) {
            if (m.views) {
                var circle = m.views.circle;
                circle.animate({
                    'opacity': 0,
                    'r': 0
                }, 250, 'easeIn', function() {
                    circle.remove();
                });
                m.views.text.remove();
                m.views = null;
            }
        },
        removeOSD: function(m) {
            this.collection.remove(m);
            this.cleanupModelView(m);
        },
        updateOSD: function(m) {
            m.set(m.attributes);
        },
        drawGrid: function(d) {
            var path = Rs.calcGrid(this.originX, this.originY, this.width, this.height, this.step);
            var path1 = this.r.path('M0,0').attr({
                'stroke-width': 1,
                'stroke': '#5e6a71',
                'opacity': 0.40
            });
            this.drawLegend(this.r, 285, 475);
            var anim = Raphael.animation({
                path: path,
                callback: d.resolve
            }, 250);
            path1.animate(anim);
        },
        startPosition: [{
            x: 40,
            y: 40
        }, {
            x: 600,
            y: 40
        }, {
            x: 600,
            y: 400
        }, {
            x: 40,
            y: 400
        }, ],
        moveCircle: function(model, index) {
            var start = this.startPosition[Math.floor(Math.random() * 4)];
            var pos = Rs.calcPosition(index, this.originX, this.originY, this.width, this.height, this.step);
            this.animateCircleTraversal(this.r, start.x, start.y, 8, pos.nx, pos.ny, model);
        },
        calculatePositions: function() {
            this.collection.each(this.moveCircle);
            return $.Deferred().resolve();
        },
        legendCircle: function(r, originX, originY, percent) {
            // Helper method to draw circles for use as legends beneath viz.
            var srctext = ['down', 'up/out', 'up/in'];
            var srcstate = [{
                up: 0,
                'in': 0
            }, {
                up: 1,
                'in': 0
            }, {
                up: 1,
                'in': 1
            }];
            var i = Math.round(1 / percent) - 1;
            var m = new Models.OSDModel(_.extend(srcstate[i], {
                capacity: 1024,
                used: percent * 1024
            }));
            var c = r.circle(originX, originY, 16 * m.getPercentage()).attr({
                fill: m.getColor(),
                stroke: 'none',
                'cursor': 'default',
                opacity: 0
            });
            var aFn = Raphael.animation({
                opacity: 1
            }, 250, 'easeOut');
            var text = srctext[i];
            r.text(originX, originY + 23, text).attr({
                'cursor': 'default',
                'font-size': '12px',
                'font-family': 'ApexSansLight'
            });
            return c.animate(aFn);
        },
        drawLegend: function(r, originX, originY) {
            // Calls legend circle to place in viz.
            var xp = originX,
                i;
            for (i = 1; i <= 3; i += 1, xp += 50) {
                this.legendCircle(r, xp, originY, i / 3);
            }
        },
        animateCircleTraversal: function(r, originX, originY, radius, destX, destY, model) {
            var c = r.circle(originX, originY, 20 * model.getPercentage()).attr({
                fill: model.getColor(),
                stroke: 'none'
            });
            c.data('modelid', model.id);
            var t;
            var aFn = Raphael.animation({
                cx: destX,
                cy: originY
            }, 250, 'easeOut', function() {
                c.animate({
                    cx: destX,
                    cy: destY
                }, 333, 'easeIn', function() {
                    t = r.text(destX, destY - 1, model.id).attr({
                        font: '',
                        stroke: '',
                        fill: '',
                        style: ''
                    });
                    t.data('modelid', model.id);
                    model.views = {
                        circle: c,
                        text: t
                    };
                });
            });
            return c.animate(aFn);
        },
        simulateUsedChanges: function() {
            var maxRed = 2;
            this.collection.each(function(m) {
                var up = true;
                var _in = Math.random();
                _in = _in < 0.95;
                if (!_in && (Math.random() > 0.6) && maxRed > 0) {
                    maxRed -= 1;
                    up = false;
                    //console.log(m.id + ' setting to down');
                }
                m.set({
                    'up': up ? 1 : 0,
                    'in': _in ? 1 : 0
                });
            });
            this.App.vent.trigger('updateTotals');
            this.App.vent.trigger('status:healthwarn');
        },
        resetChanges: function() {
            this.collection.each(function(m) {
                m.set({
                    'up': 1,
                    'in': 1
                });
            });
            this.App.vent.trigger('updateTotals');
            this.App.vent.trigger('status:healthok');
        },
        startSimulation: function() {
            var self = this;
            this.timer = setTimeout(function() {
                self.simulateUsedChanges();
                self.timer = self.startSimulation();
            }, 3000);
            return this.timer;
        },
        stopSimulation: function() {
            clearTimeout(this.timer);
            this.timer = null;
        },
        render: function() {
            Backbone.Marionette.ItemView.prototype.render.apply(this);
            this.r = window.Raphael(this.ui.viz[0], this.w, this.h);
            this.detailPanel = new View({
                App: this.App,
                el: this.ui.detail
            });
            var d = $.Deferred();
            $.when(this.drawGrid(d));
            var p = d.promise();
            p.then(this.calculatePositions);
            return p;
        },
        keyHandler: function(evt) {
            evt.preventDefault();
            if (!evt.keyCode) {
                return;
            }
            var keyCode = evt.keyCode;
            if (keyCode === 82) {
                this.resetChanges();
                return;
            }
            if (keyCode === 85) {
                this.simulateUsedChanges();
                return;
            }
            if (keyCode === 32) {
                var $spinner = $('.icon-spinner');
                if (this.timer === null) {
                    this.startSimulation();
                    $spinner.closest('i').addClass('.icon-spin').show();
                } else {
                    this.stopSimulation();
                    $spinner.closest('i').removeClass('.icon-spin').hide();
                }
            }
        },
        clickHandler: function(evt) {
            if (evt.target.nodeName === 'tspan' || evt.target.nodeName === 'circle') {
                var x = evt.clientX;
                var y = evt.clientY;
                //console.log(x + ' / ' + y);
                var el = this.r.getElementByPoint(x, y);
                //console.log(el);
                if (el) {
                    var id = el.data('modelid');
                    //console.log(id);
                    if (id) {
                        // ignore circles and tspans without data
                        this.detailPanel.model.set(this.collection.get(id).attributes);
                    }
                    return;
                }
            }
        }
    });
    return OSDVisualization;
});
<|endoftext|>


<commit_before>import itemList from './item-list-store.js'; // eslint-disable-line

const { getters, mutations, actions } = itemList;

describe('Components: counter-list', () => {
  let state;
  let context;
  beforeEach(() => {
    state = {
      items: [],
    };
    context = {
      state,
      dispatch() {},
      commit() {},
    };
    jest.spyOn(context, 'dispatch');
    jest.spyOn(context, 'commit');
  });

  describe('Getters', () => {
    it('items should return items', () => {
      expect(getters.items(state)).toEqual(state.items);
    });

    it('nrOfItems should return amount of items', () => {
      expect(getters.nrOfItems(state)).toEqual(0);
      state.items = [1, 2, 3];
      expect(getters.nrOfItems(state)).toEqual(3);
    });
  });

  describe('Actions', () => {
    it('addItem should commit mutation', () => {
      actions.addItem(context);

      expect(context.commit).toBeCalledWith('addItem');
    });

    it('removeItem should commit mutation', () => {
      actions.removeItem(context, 42);

      expect(context.commit).toBeCalledWith('removeItem', 42);
    });
  });

  describe('Mutations', () => {
    it('addItem should add a counter group', () => {
      mutations.addItem(state);

      expect(state.items.length).toEqual(1);
    });

    it('removeItem should remove a counter group', () => {
      state.items.push({ name: '', id: 0 });
      mutations.removeItem(state, 0);

      expect(state.items.length).toEqual(0);
    });
  });
});
<commit_msg>Switched spyOn to jest.fn()<commit_after>import itemList from './item-list-store.js'; // eslint-disable-line

const { getters, mutations, actions } = itemList;

describe('Components: counter-list', () => {
  let state;
  let context;
  beforeEach(() => {
    state = {
      items: [],
    };
    context = {
      state,
      dispatch: jest.fn(),
      commit: jest.fn(),
    };
  });

  describe('Getters', () => {
    it('items should return items', () => {
      expect(getters.items(state)).toEqual(state.items);
    });

    it('nrOfItems should return amount of items', () => {
      expect(getters.nrOfItems(state)).toEqual(0);
      state.items = [1, 2, 3];
      expect(getters.nrOfItems(state)).toEqual(3);
    });
  });

  describe('Actions', () => {
    it('addItem should commit mutation', () => {
      actions.addItem(context);

      expect(context.commit).toBeCalledWith('addItem');
    });

    it('removeItem should commit mutation', () => {
      actions.removeItem(context, 42);

      expect(context.commit).toBeCalledWith('removeItem', 42);
    });
  });

  describe('Mutations', () => {
    it('addItem should add a counter group', () => {
      mutations.addItem(state);

      expect(state.items.length).toEqual(1);
    });

    it('removeItem should remove a counter group', () => {
      state.items.push({ name: '', id: 0 });
      mutations.removeItem(state, 0);

      expect(state.items.length).toEqual(0);
    });
  });
});
<|endoftext|>


<commit_before>(function(){
	'use strict';
	angular.module('lui.services')
	.service('luiNotify', ['$log', 'notify', function($log, notify){
		var template =
			"
 " +
			"
{{$classes[0] | uppercase | translate}}
" +
			"
" +
			"	{{$message|translate}}" +
			"
" +
			"
";

		var service = {};
		service.error = function(error, position){
			$log.error(error);
			notify({
				startTop: 40, // to not get above the banner
				duration: 20000,
				template: template,
				position: position || 'center',
				message:'ERR_' + error.cause,
				classes:['error']
			});
		}

		return service;
	}]);
})();
<commit_msg>luisNotify [fix] minor bugs with the template and the fact that color 'error' does not exist anymore<commit_after>(function(){
	'use strict';
	angular.module('lui.services')
	.service('luisNotify', ['$log', 'notify', function($log, notify){
		var errorTemplate =
			"
 " +
			"
{{\"ERROR\" | translate}}
" +
			"
" +
			"	{{$message|translate}}" +
			"
" +
			"
";

		var service = {};
		service.error = function(error, position){
			$log.error(error);
			notify({
				startTop: 40, // to not get above the banner
				duration: 20000,
				template: errorTemplate,
				position: position || 'center',
				message:'ERR_' + error.cause,
				classes:['red']
			});
		};

		return service;
	}]);
})();
<|endoftext|>


<commit_before>
var marked = require( 'marked' );

module.exports = function( grunt ) {

	grunt.loadNpmTasks( 'grunt-contrib-clean' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-open' );
	grunt.loadNpmTasks( 'grunt-mocha' );

	grunt.initConfig( {

		// 경로 설정
		path: {
			src: 'src',
			doc: '../../doc',
			dist: 'dist',
			test: 'test'
		},

		clean: {
			dist: '<%= path.dist %>'
		},

		cssmin: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>/app',
				src: '**/*.css',
				dest: '<%= path.dist %>/app'
			}
		},

		uglify: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>/app',
				src: '**/*.js',
				dest: '<%= path.dist %>/app'
			}
		},

		copy: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>',
				src: [ 'cornerstone/**/*', '**/images/**/*', '**/fonts/**/*', '**/*.html', '**/*.template' ],
				dest: '<%= path.dist %>'
			}
		},

		connect: {
			options: {
				port: 9000,
				hostname: '*'
			},
			test: {
				// keepalive 옵션이 없으면 grunt task들이 모두 완료되면 서버가 종료된다.
				options: {
					base: '<%= path.test %>',
					middleware: function( connect, options ) {
						return [
							connect.static( options.base ),
							connect.directory( options.base )
						];
					}
				}
			},
			dist: {
				options: {
					base: '<%= path.dist %>',
					middleware: function( connect, options ) {
						return [
							require( 'connect-livereload' )(),
							connect.static( options.base ),
							connect.directory( options.base )
						];
					}
				}
			}
		},

		open: {
			server: {
				path: 'http://localhost:<%= connect.options.port %>'
			}
		},

		watch: {
			options: {
				livereload: true
			},
			scripts: {
				files: '<%= path.src %>/app/**/*.js',
				tasks: [ 'uglify' ]
			},
			styles: {
				files: '<%= path.src %>/app/**/*.css',
				tasks: [ 'cssmin' ]
			},
			docs: {
				files: '<%= path.doc %>/**/*.md',
				tasks: [ 'markdown' ]
			},
			rest: {
				files: [ '<%= path.src %>/cornerstone/**/*', '<%= path.src %>/**/images/**/*', '<%= path.src %>/**/fonts/**/*', '<%= path.src %>/**/*.html', '<%= path.src %>/**/*.template' ],
				tasks: [ 'copy' ]
			}
		},

		mocha: {
			all: {
				options: {
					run: true,
					urls: [ 'http://localhost:<%= connect.options.port %>/index.html' ]
				}
			}
		}
	} );

	grunt.registerTask( 'markdown', 'markdown 파일들을 HTML 정보가 포함된 json 파일로 변환한다.', function() {

		// Markdown 파일 목록 구하기
		var files = grunt.file.expand( grunt.config.get( 'path.doc' ) + '/**/*.md' );
		var documents = [];

		for ( var i in files ) {
			// 파일의 내용을 읽어서
			var mdContent = grunt.file.read( files[ i ], { encoding: 'utf-8' } );
			// HTML로 변환 후
			var htmlContent = marked( mdContent );
			// 메타 정보와 함께 객체에 저장
			documents.push( { id: i, content: htmlContent } );
		}

		// 객체를 JSON 형식으로 파일에 저장한다.
		grunt.file.write( grunt.config.get( 'path.dist' ) + '/data/documents.json', JSON.stringify( documents ), { encoding: 'utf-8' } );
		grunt.log.ok();
	} );

	grunt.registerTask( 'build', [ 'clean', 'cssmin', 'uglify', 'markdown', 'copy' ] );
	grunt.registerTask( 'test', [ 'build', 'connect:test', 'mocha' ] );
	grunt.registerTask( 'server', [ 'build', 'connect:dist', 'open', 'watch' ] );

	// Default task
	grunt.registerTask( 'default', [ 'build' ] );
};
<commit_msg>개발자 가이드: 빌드 시에 md 파일의 메타정보를 읽어서 JSON 파일 생성<commit_after>
var marked = require( 'marked' );

module.exports = function( grunt ) {

	grunt.loadNpmTasks( 'grunt-contrib-clean' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-open' );
	grunt.loadNpmTasks( 'grunt-mocha' );

	grunt.initConfig( {

		// 경로 설정
		path: {
			src: 'src',
			doc: '../../doc',
			dist: 'dist',
			test: 'test'
		},

		clean: {
			dist: '<%= path.dist %>'
		},

		cssmin: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>/app',
				src: '**/*.css',
				dest: '<%= path.dist %>/app'
			}
		},

		uglify: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>/app',
				src: '**/*.js',
				dest: '<%= path.dist %>/app'
			}
		},

		copy: {
			dist: {
				expand: true,
				cwd: '<%= path.src %>',
				src: [ 'cornerstone/**/*', '**/images/**/*', '**/fonts/**/*', '**/*.html', '**/*.template' ],
				dest: '<%= path.dist %>'
			}
		},

		connect: {
			options: {
				port: 9000,
				hostname: '*'
			},
			test: {
				// keepalive 옵션이 없으면 grunt task들이 모두 완료되면 서버가 종료된다.
				options: {
					base: '<%= path.test %>',
					middleware: function( connect, options ) {
						return [
							connect.static( options.base ),
							connect.directory( options.base )
						];
					}
				}
			},
			dist: {
				options: {
					base: '<%= path.dist %>',
					middleware: function( connect, options ) {
						return [
							require( 'connect-livereload' )(),
							connect.static( options.base ),
							connect.directory( options.base )
						];
					}
				}
			}
		},

		open: {
			server: {
				path: 'http://localhost:<%= connect.options.port %>'
			}
		},

		watch: {
			options: {
				livereload: true
			},
			scripts: {
				files: '<%= path.src %>/app/**/*.js',
				tasks: [ 'uglify' ]
			},
			styles: {
				files: '<%= path.src %>/app/**/*.css',
				tasks: [ 'cssmin' ]
			},
			docs: {
				files: '<%= path.doc %>/**/*.md',
				tasks: [ 'markdown' ]
			},
			rest: {
				files: [ '<%= path.src %>/cornerstone/**/*', '<%= path.src %>/**/images/**/*', '<%= path.src %>/**/fonts/**/*', '<%= path.src %>/**/*.html', '<%= path.src %>/**/*.template' ],
				tasks: [ 'copy' ]
			}
		},

		mocha: {
			all: {
				options: {
					run: true,
					urls: [ 'http://localhost:<%= connect.options.port %>/index.html' ]
				}
			}
		}
	} );

	grunt.registerTask( 'markdown', 'markdown 파일들을 HTML 정보가 포함된 json 파일로 변환한다.', function() {

		// Markdown 파일 목록 구하기
		var files = grunt.file.expand( grunt.config.get( 'path.doc' ) + '/**/*.md' );
		var documents = [];

		for ( var i in files ) {
			// 파일의 내용을 읽어서
			var mdContent = grunt.file.read( files[ i ], { encoding: 'utf-8' } );
			try {
				// 메타데이터를 읽고
				var meta = JSON.parse( mdContent.match( /<!--([\s\S]+?)-->/ )[ 1 ] );
				// HTML로 변환 후
				meta[ 'content' ] = marked( mdContent );
				// 메타 정보와 함께 객체에 저장
				documents.push( meta );
			}
			catch( e ) {
				// 메타데이터가 없거나 JSON 형식이 아니면 Skip
			}
		}

		// 객체를 JSON 형식으로 파일에 저장한다.
		grunt.file.write( grunt.config.get( 'path.dist' ) + '/data/documents.json', JSON.stringify( documents ), { encoding: 'utf-8' } );
		grunt.log.ok();
	} );

	grunt.registerTask( 'build', [ 'clean', 'cssmin', 'uglify', 'markdown', 'copy' ] );
	grunt.registerTask( 'test', [ 'build', 'connect:test', 'mocha' ] );
	grunt.registerTask( 'server', [ 'build', 'connect:dist', 'open', 'watch' ] );

	// Default task
	grunt.registerTask( 'default', [ 'build' ] );
};
<|endoftext|>


<commit_before>/*
 * carapace.js: Top-level include for the haibu-carapace module.
 *
 * (C) 2011 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var events = require('eventemitter2'),
    net = require('net'),
    fs = require('fs'),
    path = require('path');
    dnode = require('dnode'),
    evref = require('../build/default/evref');

module.exports = carapace = new events.EventEmitter2();
for (var k in carapace) {
  carapace[k] = carapace[k];
}

//
// Plugins list
//
carapace.plugins = {};

var plugins = fs.readdirSync(path.join(__dirname, '../lib/plugins'));
plugins.forEach(function (name) {
  carapace.plugins[name.replace(/\.js$/, '')] = path.join(__dirname, '../lib/plugins', name);
});

//
// Internal state for managing various carapace operations:
// * carapace.running: Value indicating if the target script has started.
// * carapace.listening: Value indicating if the carapace `dnode` server has started.
//
carapace.running = false;
carapace.listening = false;

//
//
//
//
carapace.listen = function create (target, callback) {
  if (carapace.listening) {
    throw new Error('Cannot start carapace server that is already running');
  }

  if (!target || (typeof target !== 'string' && typeof target !== 'number')) {
    throw new Error('Cannot start carapace server without a port or socket path.');
  }

  carapace.server = net.createServer();
  carapace.server.listen(target, function () {
    var bridge = new dnode(carapace);
    bridge.listen(carapace.server);
    carapace.server.on('connection', function (conn) {
      evref.unref();
      conn.on('close', function () {
        evref.ref();
        carapace.emit('carapace:connection:close', conn);
      });
      carapace.emit('carapace:connection:start', conn);
    });

    carapace.emit('carapace:ready');

    if (callback) {
      callback();
    }
  });

  carapace.listening = true;
  return carapace;
};

carapace.use = function (plugins, callback) {

  plugins.forEach(function (plugin) {
    try {
      require(plugin)(carapace);
      carapace.emit('carapace:plugin:loaded', plugin);
    }
    catch (ex) {
      carapace.emit('carapace:plugin:error', plugin, ex);
    }
  });

  if (callback) {
    callback();
  }

  return carapace;
};

carapace.run = function (argv, callback) {
  var error;

  if (carapace.running) {
    error = new Error('Cannot spawn a new script, one is already running.');

    return callback
      ? callback(error)
      : carapace.emit('error', error);
  }

  evref.unref();
  carapace.running = true;

  argv[0] = fs.realpathSync(require.resolve(path.join(process.cwd(), argv[0])));

  for (var i = 0; i < argv.length;) {
    var item = argv[i];
    process.argv[++i] = item;
  }

  process.argv.splice(i, i - process.argv.length);
  require('module').Module._cache = {};

  //
  // Next tick to prevent a leak from arguments
  //
  process.nextTick(function () {
    require('module').Module.runMain();
  });

  carapace.emit('carapace:running');

  if (callback) {
    callback();
  }

  return carapace;
};
<commit_msg>[merge] path.resolve will automatically use cwd<commit_after>/*
 * carapace.js: Top-level include for the haibu-carapace module.
 *
 * (C) 2011 Nodejitsu Inc.
 * MIT LICENCE
 *
 */

var events = require('eventemitter2'),
    net = require('net'),
    fs = require('fs'),
    path = require('path');
    dnode = require('dnode'),
    evref = require('../build/default/evref');

var carapace = module.exports = new events.EventEmitter2();

//
// Copy over prototype methods from carapace so that
// `dnode` will pick up on them in IPC.
//
for (var k in carapace) {
  carapace[k] = carapace[k];
}

//
// Plugins list exposed through path names so that they
// can be later required by `carapace.plugin()` or `carapace:plugin` events.
//
carapace.plugins = {};
fs.readdirSync(path.join(__dirname, '../lib/plugins')).forEach(function (name) {
  carapace.plugins[name.replace(/\.js$/, '')] = path.join(__dirname, '../lib/plugins', name);
});

//
// Internal state for managing various carapace operations:
// * carapace.running: Value indicating if the target script has started.
// * carapace.listening: Value indicating if the carapace `dnode` server has started.
//
carapace.running = false;
carapace.listening = false;

//
// ### function listen (target, callback)
// #### @target {string|number} Socket path or port to listen on.
// #### @callback {function} Continuation to respond to when complete.
// Starts the carapace server on the specified `target` socket or port.
//
carapace.listen = function listen (target, callback) {
  if (carapace.listening) {
    throw new Error('Cannot start carapace server that is already running');
  }

  if (!target || (typeof target !== 'string' && typeof target !== 'number')) {
    throw new Error('Cannot start carapace server without a port or socket path.');
  }

  carapace.server = net.createServer();
  carapace.server.listen(target, function () {
    //
    // Create the `dnode` bridge for the carapace.
    //
    carapace.bridge = new dnode(carapace);
    carapace.bridge.listen(carapace.server);

    carapace.server.on('connection', function (conn) {
      //
      // Dereference `libev` so that this connection does not
      // keep the event loop alive.
      //
      evref.unref();

      conn.on('close', function () {
        evref.ref();
        carapace.emit('carapace:connection:close', conn);
      });

      carapace.emit('carapace:connection:start', conn);
    });

    carapace.emit('carapace:ready');

    if (callback) {
      callback();
    }
  });

  carapace.listening = true;
  return carapace;
};

//
// ### function use (plugins, callback)
// #### @plugins {string|Array} List (or single) plugin add to the carapace.
// #### @callback {function} Continuation to respond to when complete.
// Enables the specified `plugins` in the carapace.
//
carapace.use = function (plugins, callback) {
  if (!Array.isArray(plugins)) {
    plugins = [plugins];
  }

  plugins.forEach(function (plugin) {
    try {
      require(plugin)(carapace);
      carapace.emit('carapace:plugin:loaded', plugin);
    }
    catch (ex) {
      carapace.emit('carapace:plugin:error', plugin, ex);
    }
  });

  if (callback) {
    callback();
  }

  return carapace;
};

//
// ### function run (argv, callback)
// #### @argv {Array} Arguments to rewrite into process.argv
// #### @callback {function} Continuation to respond to when complete.
// Runs the script in `argv[0]` with the rest of the arguments specified
// in `argv` by transparently rewriting the current `process.argv`.
//
carapace.run = function (argv, callback) {
  var error;

  if (carapace.running) {
    error = new Error('Cannot spawn a new script, one is already running.');

    return callback
      ? callback(error)
      : carapace.emit('error', error);
  }

  if (argv[0][0] !== '/') {
    //
    // If the path supplied is not absolute then prepend
    // `process.cwd()` to it.
    //
    argv[0] = path.join(process.cwd(), argv[0]);
  }

  //
  // Dereference `libev` so that this connection
  // does not keep the event loop alive
  //
  evref.unref();

  carapace.running = true;

  argv[0] = fs.realpathSync(require.resolve(argv[0]));

  for (var i = 0; i < argv.length;) {
    var item = argv[i];
    process.argv[++i] = item;
  }

  process.argv.splice(i, i - process.argv.length);
  require('module').Module._cache = {};

  //
  // Next tick to prevent a leak from arguments
  //
  process.nextTick(function () {
    require('module').Module.runMain();
  });

  carapace.emit('carapace:running');

  if (callback) {
    callback();
  }

  return carapace;
};
<|endoftext|>


<commit_before>import BuilderUtils from '../../../utils/builder';
import _ from 'lodash';

export default [
  {
    key: 'multiple',
    ignore: true,
  },
  {
    type: 'datagrid',
    input: true,
    label: 'Values',
    key: 'values',
    tooltip: 'The radio button values that can be picked for this field. Values are text submitted with the form data. Labels are text that appears next to the radio buttons on the form.',
    weight: 10,
    reorder: true,
    defaultValue: [{ label: '', value: '' }],
    components: [
      {
        label: 'Label',
        key: 'label',
        input: true,
        type: 'textfield',
      },
      {
        label: 'Value',
        key: 'value',
        input: true,
        type: 'textfield',
        allowCalculateOverride: true,
        calculateValue: { _camelCase: [{ var: 'row.label' }] },
      },
      {
        type: 'select',
        input: true,
        weight: 180,
        label: 'Shortcut',
        key: 'shortcut',
        tooltip: 'The shortcut key for this option.',
        dataSrc: 'custom',
        valueProperty: 'value',
        customDefaultValue: () => '',
        template: '{{ item.label }}',
        data: {
          custom(context) {
            return BuilderUtils.getAvailableShortcuts(
              _.get(context, 'instance.options.editForm', {}),
              _.get(context, 'instance.options.editComponent', {})
            );
          },
        },
      },
    ],
  },
  {
    type: 'select',
    input: true,
    label: 'Storage Type',
    key: 'dataType',
    clearOnHide: true,
    tooltip: 'The type to store the data. If you select something other than autotype, it will force it to that type.',
    weight: 12,
    template: '{{ item.label }}',
    dataSrc: 'values',
    data: {
      values: [
        { label: 'Autotype', value: 'auto' },
        { label: 'String', value: 'string' },
        { label: 'Number', value: 'number' },
        { label: 'Boolean', value: 'boolean' },
        { label: 'Object', value: 'object' },
      ],
    },
  },
];
<commit_msg>Adding validate required to value for radio configuration.<commit_after>import BuilderUtils from '../../../utils/builder';
import _ from 'lodash';

export default [
  {
    key: 'multiple',
    ignore: true,
  },
  {
    type: 'datagrid',
    input: true,
    label: 'Values',
    key: 'values',
    tooltip: 'The radio button values that can be picked for this field. Values are text submitted with the form data. Labels are text that appears next to the radio buttons on the form.',
    weight: 10,
    reorder: true,
    defaultValue: [{ label: '', value: '' }],
    components: [
      {
        label: 'Label',
        key: 'label',
        input: true,
        type: 'textfield',
      },
      {
        label: 'Value',
        key: 'value',
        input: true,
        type: 'textfield',
        allowCalculateOverride: true,
        calculateValue: { _camelCase: [{ var: 'row.label' }] },
        validate: {
          required: true
        }
      },
      {
        type: 'select',
        input: true,
        weight: 180,
        label: 'Shortcut',
        key: 'shortcut',
        tooltip: 'The shortcut key for this option.',
        dataSrc: 'custom',
        valueProperty: 'value',
        customDefaultValue: () => '',
        template: '{{ item.label }}',
        data: {
          custom(context) {
            return BuilderUtils.getAvailableShortcuts(
              _.get(context, 'instance.options.editForm', {}),
              _.get(context, 'instance.options.editComponent', {})
            );
          },
        },
      },
    ],
  },
  {
    type: 'select',
    input: true,
    label: 'Storage Type',
    key: 'dataType',
    clearOnHide: true,
    tooltip: 'The type to store the data. If you select something other than autotype, it will force it to that type.',
    weight: 12,
    template: '{{ item.label }}',
    dataSrc: 'values',
    data: {
      values: [
        { label: 'Autotype', value: 'auto' },
        { label: 'String', value: 'string' },
        { label: 'Number', value: 'number' },
        { label: 'Boolean', value: 'boolean' },
        { label: 'Object', value: 'object' },
      ],
    },
  },
];
<|endoftext|>


<commit_before>import atkPlugin from 'plugins/atkPlugin';

export default class fileUpload extends atkPlugin {

  main() {
    const that = this;

    this.textInput = this.$el.find('input[type="text"]');

    this.fileInput = this.$el.find('input[type="file"]');
    this.action = $('#' + this.settings.action);
    this.actionContent = this.action.html();

    this.bar = this.$el.find('.progress')
      .progress({
        text : {
          active: '{percent}%',
          success: this.settings.completeLabel,
        }
      })
      .hide();

    // Open file dialog on focus.
    if (this.settings.hasFocus) {
      this.textInput.on('focus', function(e) {
        if (!e.target.value) {
          that.fileInput.click();
        }
      });
    }

    // add event handler to action button.
    this.action.on('click', function(e) {
      if (!that.textInput.val()) {
        that.fileInput.click();
      } else {
        // When upload is complete a js action can be send to set an id
        // to the uploaded file via the jQuery data property.
        // Check if that id exist and send it with
        // delete callback, If not, default to file name.
        let id = that.$el.data().fileId;
        if (id === '' || typeof id === 'undefined') {
          id = that.textInput.val();
        }
        that.doFileDelete(id);
      }
    });

    // add event handler to file input.
    this.fileInput.on('change', function(e) {
      if (e.target.files.length > 0) {
        that.textInput.val( e.target.files[0].name);
        that.doFileUpload(e.target.files[0]);
      }
    })
  }

  /**
   * Set the action button html content.
   * Set the input text content.
   */
  setState(mode) {
    const that = this;

    switch (mode) {
      case 'delete':
        this.action.html(this.getEraseContent);
        setTimeout(function() {
          that.bar.hide('fade');
        }, 1000);
        break;
      case 'upload':
        this.action.html(this.actionContent);
        this.textInput.val('');
        this.fileInput.val('');
        break;
    }
  }

  /**
   * Do the actual file uploading process.
   *
   * @param file
   */
  doFileUpload(file) {

    const that = this;
    const fileName = file.name;

    // if submit button id is set, then disable submit
    // during upload.
    if (this.settings.submit) {
      $('#'+this.settings.submit).addClass('disabled');
    }

    let formData = new FormData();
    formData.append('file', file);
    formData.append('action', 'upload');

    that.bar.progress('reset');
    that.bar.show();

    this.$el.api({
      on: 'now',
      url: this.settings.uri,
      cache: false,
      processData: false,
      contentType: false,
      data: formData,
      method: 'POST',
      obj: this.$el,
      xhr: function () {
        let xhr = new window.XMLHttpRequest();
        xhr.upload.addEventListener("progress", function (evt) {
          if (evt.lengthComputable) {
            let percentComplete = evt.loaded / evt.total;
            that.bar.progress('set percent', parseInt(percentComplete * 100));
          }
        }, false);
        return xhr;
      },
      onComplete: function(response, content) {
        if (response.success) {
          that.setState('delete');
        }
        if (that.settings.submit) {
          $('#'+that.settings.submit).removeClass('disabled');
        }
      }
    });
  }

  /**
   * Callback server for file delete.
   *
   * @param fileName
   */
  doFileDelete(fileName) {

    const that = this;

    this.$el.api({
      on: 'now',
      url: this.settings.uri,
      data: {'action': 'delete', 'f_name': fileName},
      method: 'POST',
      obj: this.$el,
      onComplete: function(response, content) {
        if (response.success) {
          that.setState('upload');
        }
      }
    });
  }

  /**
   * Return the html content for erase action button.
   *
   * @returns {string}
   */
  getEraseContent() {
    return `<i class="red remove icon" style=""></i>`;
  }
}

fileUpload.DEFAULTS = {
  uri: null,
  uri_options: {},
  hasFocus: true,
  action: null,
  completeLabel: '100%',
  submit: null
};
<commit_msg>fix progress bar label<commit_after>import atkPlugin from 'plugins/atkPlugin';

export default class fileUpload extends atkPlugin {

  main() {
    const that = this;

    this.textInput = this.$el.find('input[type="text"]');

    this.fileInput = this.$el.find('input[type="file"]');
    this.action = $('#' + this.settings.action);
    this.actionContent = this.action.html();

    this.bar = this.$el.find('.progress')
      .progress({
        text : {
          percent: '{percent}%',
          active: '{percent}%',
        }
      })
      .hide();

    // Open file dialog on focus.
    if (this.settings.hasFocus) {
      this.textInput.on('focus', function(e) {
        if (!e.target.value) {
          that.fileInput.click();
        }
      });
    }

    // add event handler to action button.
    this.action.on('click', function(e) {
      if (!that.textInput.val()) {
        that.fileInput.click();
      } else {
        // When upload is complete a js action can be send to set an id
        // to the uploaded file via the jQuery data property.
        // Check if that id exist and send it with
        // delete callback, If not, default to file name.
        let id = that.$el.data().fileId;
        if (id === '' || typeof id === 'undefined') {
          id = that.textInput.val();
        }
        that.doFileDelete(id);
      }
    });

    // add event handler to file input.
    this.fileInput.on('change', function(e) {
      if (e.target.files.length > 0) {
        that.textInput.val( e.target.files[0].name);
        that.doFileUpload(e.target.files[0]);
      }
    })
  }

  /**
   * Set the action button html content.
   * Set the input text content.
   */
  setState(mode) {
    const that = this;

    switch (mode) {
      case 'delete':
        this.action.html(this.getEraseContent);
        setTimeout(function() {
          that.bar.progress('reset');
          that.bar.hide('fade');
        }, 1000);
        break;
      case 'upload':
        this.action.html(this.actionContent);
        this.textInput.val('');
        this.fileInput.val('');
        break;
    }
  }

  /**
   * Do the actual file uploading process.
   *
   * @param file
   */
  doFileUpload(file) {

    const that = this;
    //const fileName = file.name;

    // if submit button id is set, then disable submit
    // during upload.
    if (this.settings.submit) {
      $('#'+this.settings.submit).addClass('disabled');
    }

    let formData = new FormData();
    formData.append('file', file);
    formData.append('action', 'upload');

    that.bar.show();

    this.$el.api({
      on: 'now',
      url: this.settings.uri,
      cache: false,
      processData: false,
      contentType: false,
      data: formData,
      method: 'POST',
      obj: this.$el,
      xhr: function () {
        let xhr = new window.XMLHttpRequest();
        xhr.upload.addEventListener("progress", function (evt) {
          if (evt.lengthComputable) {
            let percentComplete = evt.loaded / evt.total;
            console.log(percentComplete);
            that.bar.progress('set percent', parseInt(percentComplete * 100));
          }
        }, false);
        return xhr;
      },
      onComplete: function(response, content) {
        if (response.success) {
          that.bar.progress('set label', that.settings.completeLabel);
          that.setState('delete');
        }
        if (that.settings.submit) {
          $('#'+that.settings.submit).removeClass('disabled');
        }
      }
    });
  }

  /**
   * Callback server for file delete.
   *
   * @param fileName
   */
  doFileDelete(fileName) {

    const that = this;

    this.$el.api({
      on: 'now',
      url: this.settings.uri,
      data: {'action': 'delete', 'f_name': fileName},
      method: 'POST',
      obj: this.$el,
      onComplete: function(response, content) {
        if (response.success) {
          that.setState('upload');
        }
      }
    });
  }

  /**
   * Return the html content for erase action button.
   *
   * @returns {string}
   */
  getEraseContent() {
    return `<i class="red remove icon" style=""></i>`;
  }
}

fileUpload.DEFAULTS = {
  uri: null,
  uri_options: {},
  hasFocus: true,
  action: null,
  completeLabel: '100%',
  submit: null
};
<|endoftext|>


<commit_before>// run as node scripts/admin/bump-all-version-numbers.js <packageNames>

var fs = require("fs");
var _ = require("../../packages/underscore/underscore.js")._;

var packageNames = _.rest(process.argv, 2);

_.each(packageNames, function (name) {
  // name = "packages/" + name + "/package.js";

  var content = fs.readFileSync(name, {encoding: "utf-8"});

  match = content.match(/\d+\.\d+\.\d+-pre\.\d+/);
  if (match) {
    var versionNumber = match[0];
    var s = versionNumber.split(".");
    s[3] = "rc.0";
    var incremented = s.join(".");

    content = content.replace(versionNumber, incremented);
    //console.log(incremented);
    fs.writeFileSync(name, content);
  }
});
<commit_msg>Improve script<commit_after>// run as node scripts/admin/bump-all-version-numbers.js <packageNames>

var fs = require("fs");
var _ = require("../../packages/underscore/underscore.js")._;

var packageNames = _.rest(process.argv, 2);

_.each(packageNames, function (name) {
  // name = "packages/" + name + "/package.js";

  var content = fs.readFileSync(name, {encoding: "utf-8"});

  match = content.match(/\d+\.\d+\.\d+-pre\.\d+/);
  if (match) {
    var versionNumber = match[0];
    var s = versionNumber.split(".");
    s[2] = "rc.0";
    s = s.slice(0, 3);
    var incremented = s.join(".");

    content = content.replace(versionNumber, incremented);
    //console.log(incremented);
    fs.writeFileSync(name, content);
  }
});
<|endoftext|>


<commit_before>import React from 'react'
import multiDownload from 'multi-download'

import { showModalCalc, newCalcId } from '../actions'
import { adminNames, graphCols } from '../constants'
import { getMapId } from '../utils/map-id'
import { shortenNumber } from '../utils/format'
import { t } from '../utils/i18n'

import BarChart from './charts/bar-chart'

const Results = React.createClass({
  propTypes: {
    dispatch: React.PropTypes.func,
    dataSelection: React.PropTypes.object,
    queryParams: React.PropTypes.object,
    data: React.PropTypes.object,
    mapType: React.PropTypes.string
  },

  deleteThis: function () {
    return (

    )
  },

  render: function () {
    let d = this.props.data
    if (d === null) {
      return this.deleteThis()
    }

    let adminName
    d.id.length === 2
      ? adminName = t(d.id)
      : adminName = `${t(d.id)}, ${t(d.id.substring(0, 2))}`

    const risk = this.props.dataSelection.risk.getActive().value
    const metric = this.props.dataSelection.metric.getActive().key
    const admin = this.props.dataSelection.admin.getActive().key

    let yTitle = t('Billions (US$)')
    let xTitle = t('Return Period')
    let valDenominator = 1000000000
    if (admin === 'admin1') {
      yTitle = t('Millions (US$)')
      valDenominator = 1000000
    }

    const rps = graphCols[getMapId(this.props.dataSelection).slice(0, 5)]
    const suffix = this.props.mapType === 'relative' && metric === 'loss' ? '_R' : ''
    const data = rps.map((rp) => {
      const value = d[`LS_${risk}_${rp}${suffix}`] ? d[`LS_${risk}_${rp}${suffix}`] : 0
      return {value: Number((value / valDenominator).toFixed(2)), name: rp}
    })

    let margin = {
      top: 16,
      left: 50,
      right: 16,
      bottom: 56
    }

    return (

{adminName}

{t('Exposure')}

{t('GDP')}

${shortenNumber(d.EX_GD, 2, false)}

{t('Building Stock Exposure')}

${shortenNumber(d.EX_BS, 2, false)}

{t('loss')}

{t('Average Annual Loss')}

                      ${shortenNumber(d[`LS_${risk}_AAL`], 2, false)}

                    <BarChart
                      data={data}
                      margin={margin}
                      yTitle={yTitle}
                      xTitle={xTitle}
                    />

                <button className='button button_results' onClick={this.handleDownload}>{t('Download Country Profile PDF')}</button>

          <button onClick={() =>
            this.props.dispatch(showModalCalc()) &&
            this.props.dispatch(newCalcId(d.id))
          } className='button button__map button--full'>{t('Launch cost and benefit calculator')}</button>

    )
  },

  handleDownload: function () {
    multiDownload([`assets/data/pdfs/${this.props.data.id}.pdf`])
  }
})

export default Results
<commit_msg>Make tests pass<commit_after>import React from 'react'
import multiDownload from 'multi-download'

import { showModalCalc, newCalcId } from '../actions'
import { graphCols } from '../constants'
import { getMapId } from '../utils/map-id'
import { shortenNumber } from '../utils/format'
import { t } from '../utils/i18n'

import BarChart from './charts/bar-chart'

const Results = React.createClass({
  propTypes: {
    dispatch: React.PropTypes.func,
    dataSelection: React.PropTypes.object,
    queryParams: React.PropTypes.object,
    data: React.PropTypes.object,
    mapType: React.PropTypes.string
  },

  deleteThis: function () {
    return (

    )
  },

  render: function () {
    let d = this.props.data
    if (d === null) {
      return this.deleteThis()
    }

    let adminName
    d.id.length === 2
      ? adminName = t(d.id)
      : adminName = `${t(d.id)}, ${t(d.id.substring(0, 2))}`

    const risk = this.props.dataSelection.risk.getActive().value
    const metric = this.props.dataSelection.metric.getActive().key
    const admin = this.props.dataSelection.admin.getActive().key

    let yTitle = t('Billions (US$)')
    let xTitle = t('Return Period')
    let valDenominator = 1000000000
    if (admin === 'admin1') {
      yTitle = t('Millions (US$)')
      valDenominator = 1000000
    }

    const rps = graphCols[getMapId(this.props.dataSelection).slice(0, 5)]
    const suffix = this.props.mapType === 'relative' && metric === 'loss' ? '_R' : ''
    const data = rps.map((rp) => {
      const value = d[`LS_${risk}_${rp}${suffix}`] ? d[`LS_${risk}_${rp}${suffix}`] : 0
      return {value: Number((value / valDenominator).toFixed(2)), name: rp}
    })

    let margin = {
      top: 16,
      left: 50,
      right: 16,
      bottom: 56
    }

    return (

{adminName}

{t('Exposure')}

{t('GDP')}

${shortenNumber(d.EX_GD, 2, false)}

{t('Building Stock Exposure')}

${shortenNumber(d.EX_BS, 2, false)}

{t('loss')}

{t('Average Annual Loss')}

                      ${shortenNumber(d[`LS_${risk}_AAL`], 2, false)}

                    <BarChart
                      data={data}
                      margin={margin}
                      yTitle={yTitle}
                      xTitle={xTitle}
                    />

                <button className='button button_results' onClick={this.handleDownload}>{t('Download Country Profile PDF')}</button>

          <button onClick={() =>
            this.props.dispatch(showModalCalc()) &&
            this.props.dispatch(newCalcId(d.id))
          } className='button button__map button--full'>{t('Launch cost and benefit calculator')}</button>

    )
  },

  handleDownload: function () {
    multiDownload([`assets/data/pdfs/${this.props.data.id}.pdf`])
  }
})

export default Results
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import { AsyncStorage, StyleSheet, Text, View } from 'react-native';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import OneSignal from 'react-native-onesignal';
import Button from 'react-native-button';
import { authActions, brandActions } from 'goommerce-redux';
import _ from 'lodash';

import Icon from '../components/Icon';

const Profile = React.createClass({
  statics: {
    rightButton: ({ dispatch }) => {
      const logout = bindActionCreators(authActions.logout, dispatch);
      const signout = () => OneSignal.configure({
        onIdsAvailable({ userId, pushToken }) {
          logout(pushToken && userId).then(
            () => AsyncStorage.removeItem('bearer')
          );
        },
      });
      return (
        <Button onPress={signout}>
          <View style={{ padding: 5 }}>
            <Icon name='log-out' size={23} color='white' />
          </View>
        </Button>
      );
    },
  },
  componentDidMount() {
    const { loadBrand, brandId } = this.props;
    loadBrand(brandId);
  },
  render() {
    const { brand } = this.props;
    return (
      <View style={styles.container}>
        <View style={styles.section}>
          <Text style={styles.name} numberOfLines={1}>{_.get(brand, 'name.ko')}</Text>
          <Text style={styles.main}>{'http://www.linkshops.com/'}</Text>
          <Text style={styles.main}>{_.toLower(_.get(brand, 'pathname'))}</Text>
          <View style={{ height: 45 }} />
          <Text style={{ fontSize: 16, fontWeight: 'bold', color: '#4C4C4C' }}>{_.get(brand, 'data.location.building.name.ko')}</Text>
          <Text style={styles.main}>{_.get(brand, 'data.location.floor')} {_.get(brand, 'data.location.flatNumber')}</Text>
        </View>
        <View style={styles.section}>
          <Text style={styles.footer}>친절하고 빠른 서비스 / 이용문의</Text>
          <Text style={styles.footer}>02-2272-1122</Text>
          <Text style={styles.footer}>카카오톡: @링크샵스 판매자 센터</Text>
        </View>
      </View>
    );
  }
});

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'space-between',
    marginTop: 90,
    marginBottom: 40,
  },
  section: {
    alignItems: 'center',
  },
  signoutContainer: {
    backgroundColor: '#1fcbf6',
    borderRadius: 6,
    marginTop: 20,
    overflow:'hidden',
    paddingHorizontal: 60,
    paddingVertical: 10,
  },
  name: {
    fontSize: 36,
    fontWeight: 'bold',
    color: '#4C4C4C',
  },
  main: {
    fontSize: 11,
    color: '#4C4C4C',
  },
  footer: {
    fontSize: 11,
    fontWeight: 'bold',
    color: '#999999',
  },
});

export default connect(
  (state, ownProps) => {
    const { key } = brandActions.loadBrand(ownProps.brandId);
    return { brand: state.brand[key] };
  }, brandActions
)(Profile);
<commit_msg>change logout button to text<commit_after>'use strict';

import React from 'react';
import { AsyncStorage, Platform, StyleSheet, Text, View } from 'react-native';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import OneSignal from 'react-native-onesignal';
import Button from 'react-native-button';
import { authActions, brandActions } from 'goommerce-redux';
import _ from 'lodash';

import Icon from '../components/Icon';

const Profile = React.createClass({
  statics: {
    rightButton: ({ dispatch }) => {
      const logout = bindActionCreators(authActions.logout, dispatch);
      const signout = () => OneSignal.configure({
        onIdsAvailable({ userId, pushToken }) {
          logout(pushToken && userId).then(
            () => AsyncStorage.removeItem('bearer')
          );
        },
      });
      return (
        <Button
          style={{ fontSize: 11, color: 'white', margin: 5, paddingVertical: 2 }}
          containerStyle={{ marginTop: Platform.OS === 'android' ? 5 : 3 }}
          onPress={signout}
        >
          로그아웃
        </Button>
      );
    },
  },
  componentDidMount() {
    const { loadBrand, brandId } = this.props;
    loadBrand(brandId);
  },
  render() {
    const { brand } = this.props;
    return (
      <View style={styles.container}>
        <View style={styles.section}>
          <Text style={styles.name} numberOfLines={1}>{_.get(brand, 'name.ko')}</Text>
          <Text style={styles.main}>{'http://www.linkshops.com/'}</Text>
          <Text style={styles.main}>{_.toLower(_.get(brand, 'pathname'))}</Text>
          <View style={{ height: 45 }} />
          <Text style={{ fontSize: 16, fontWeight: 'bold', color: '#4C4C4C' }}>{_.get(brand, 'data.location.building.name.ko')}</Text>
          <Text style={styles.main}>{_.get(brand, 'data.location.floor')} {_.get(brand, 'data.location.flatNumber')}</Text>
        </View>
        <View style={styles.section}>
          <Text style={styles.footer}>친절하고 빠른 서비스 / 이용문의</Text>
          <Text style={styles.footer}>02-2272-1122</Text>
          <Text style={styles.footer}>카카오톡: @링크샵스 판매자 센터</Text>
        </View>
      </View>
    );
  }
});

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'space-between',
    marginTop: 90,
    marginBottom: 40,
  },
  section: {
    alignItems: 'center',
  },
  signoutContainer: {
    backgroundColor: '#1fcbf6',
    borderRadius: 6,
    marginTop: 20,
    overflow:'hidden',
    paddingHorizontal: 60,
    paddingVertical: 10,
  },
  name: {
    fontSize: 36,
    fontWeight: 'bold',
    color: '#4C4C4C',
  },
  main: {
    fontSize: 11,
    color: '#4C4C4C',
  },
  footer: {
    fontSize: 11,
    fontWeight: 'bold',
    color: '#999999',
  },
});

export default connect(
  (state, ownProps) => {
    const { key } = brandActions.loadBrand(ownProps.brandId);
    return { brand: state.brand[key] };
  }, brandActions
)(Profile);
<|endoftext|>


<commit_before>function load() {
    function responseText() {
        var translated = JSON.parse(this.responseText).responseData.translatedText;
        console.log(translated);

        var MessageObject = Parse.Object.extend("Message");
        var messageObject = new MessageObject();
        messageObject.save({
            sendingNumber: TwilioNumber,
            BodyNotTranslated: data,
            BodyTranslated: translated
        });

        sendMessageToServer('+12144035793', translated); // after translation
    }
    var data = document.getElementById("input-section--message-content").value; // this will be the text before translation
    var url = "https://api.mymemory.translated.net/get?q=" + data +
        "&langpair=es|en";

    var oReq = new XMLHttpRequest();
    oReq.addEventListener('load', responseText);
    oReq.open("get", url, true);
    oReq.send();
}

// { Phonenumber : nunmber, origionaltext : data, translatedtext: data}

// {phonenumber: number,
//   conversation:{
//     [message:{
//       sent/received

//     }]

//   }

var data, tb, tBox = {
    vars: {
        isOpen: false,
        domObj: $("#translation-section-container"),
        translateButton: $("#translate-button"),
        checkButton: $("#translate-check-button"),
        sendButton: $("#translate-send-button"),
        cancelButton: $("#translate-cancel-button")
    },
    init: function () {
        tb = this.vars;
        this.bindUIElements();
        tb.sendMessage = this.sendMessage;
    },
    sendMessage: function () {

        data = $('#input-section--message-content').val();
        console.log(data);
        var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=es|en";
        console.log(url);

        var oReq = new XMLHttpRequest();
        oReq.addEventListener('load', function () {
            console.log(JSON.parse(this.responseText).responseData.translatedText);
            sendMessageToServer(OtherNumber, JSON.parse(this.response).responseData.translatedText);

            var elem = '
 \

 \
								' + data +' \

 \
							 \
								? \
							 \

'

    		  $('#message-conversation').append(elem);

    		  var MessageObject = Parse.Object.extend("Message");
    		  var messageObject = new MessageObject();
    		  messageObject.save({sendingNumber: TwilioNumber, LanguageOne: JSON.parse(this.response).responseData.translatedText, LanguageTwo: data});

        });
        oReq.open("get", url, true);
        oReq.send();

        $('#input-section--message-content').val('');
    },
    bindUIElements: function () {
        tb.translateButton.on('click', function () {
            tb.domObj.addClass("open");
            tb.isOpen = true;

            data = $('#input-section--message-content').val();

            var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=en|es";
            console.log(url);

            var oReq = new XMLHttpRequest();
            oReq.addEventListener('load', function(){
                console.log(JSON.parse(this.responseText).responseData.translatedText);

                $('#translation-section--message-content').html(JSON.parse(this.responseText).responseData.translatedText);
            });
            oReq.open("get", url, true);
            oReq.send();
        });

        tb.checkButton.on('click', function () {
            tb.domObj.addClass("open");
            tb.isOpen = true;

            data = $('#input-section--message-content').val();

            var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=es|en";
            console.log(url);

            var oReq = new XMLHttpRequest();
            oReq.addEventListener('load', function(){
                console.log(JSON.parse(this.responseText).responseData.translatedText);

                $('#translation-section--message-content').html(JSON.parse(this.responseText).responseData.translatedText);
            });
            oReq.open("get", url, true);
            oReq.send();
        });

        tb.cancelButton.on('click', function () {
            tb.domObj.removeClass("open");
            tb.isOpen = false;
        });

        tb.cancelButton.on('click', function () {
            tb.domObj.removeClass("open");
            tb.isOpen = false;
        });

        tb.sendButton.on('click', function () {
            tb.sendMessage();
        });

        $(document).keyup(function (e) {
            if (e.keyCode == 27 && tb.isOpen) {
                tb.domObj.removeClass("open");
                tb.isOpen = false;
            }
        });

    }
};

tBox.init();

var menu, menuApp = {
    vars: {
        menuButton: $("#translate-change-lang-button"),
        menu: $("#language-menu"),
        mainWrapper: $("#main-wrapper"),
        menuList: $(".language-menu--list-item")
    },
    init: function () {
        menu = this.vars;
        this.bindUIElements();
    },
    bindUIElements: function () {
        menu.menuButton.click(function () {
            menu.mainWrapper.addClass("menu-open ");
            menu.menu.addClass("menu-open ");
        });
        menu.menuList.click(function(){
            specifiedLanguage = $(this).attr("id");
            menu.mainWrapper.removeClass("menu-open ");
            menu.menu.removeClass("menu-open ");
        });
    }
}
menuApp.init();
<commit_msg>window scrolls when elem added to dom<commit_after>function load() {
    function responseText() {
        var translated = JSON.parse(this.responseText).responseData.translatedText;
        console.log(translated);

        var MessageObject = Parse.Object.extend("Message");
        var messageObject = new MessageObject();
        messageObject.save({
            sendingNumber: TwilioNumber,
            BodyNotTranslated: data,
            BodyTranslated: translated
        });

        sendMessageToServer('+12144035793', translated); // after translation
    }
    var data = document.getElementById("input-section--message-content").value; // this will be the text before translation
    var url = "https://api.mymemory.translated.net/get?q=" + data +
        "&langpair=es|en";

    var oReq = new XMLHttpRequest();
    oReq.addEventListener('load', responseText);
    oReq.open("get", url, true);
    oReq.send();
}

// { Phonenumber : nunmber, origionaltext : data, translatedtext: data}

// {phonenumber: number,
//   conversation:{
//     [message:{
//       sent/received

//     }]

//   }

var data, tb, tBox = {
    vars: {
        isOpen: false,
        domObj: $("#translation-section-container"),
        translateButton: $("#translate-button"),
        checkButton: $("#translate-check-button"),
        sendButton: $("#translate-send-button"),
        cancelButton: $("#translate-cancel-button")
    },
    init: function () {
        tb = this.vars;
        this.bindUIElements();
        tb.sendMessage = this.sendMessage;
    },
    sendMessage: function () {

        data = $('#input-section--message-content').val();
        console.log(data);
        var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=es|en";
        console.log(url);

        var oReq = new XMLHttpRequest();
        oReq.addEventListener('load', function () {
            console.log(JSON.parse(this.responseText).responseData.translatedText);
            sendMessageToServer(OtherNumber, JSON.parse(this.response).responseData.translatedText);

            var elem = '
 \

 \
								' + data +' \

 \
							 \
								? \
							 \

';

    		  $('#message-conversation').append(elem);
    		  $("html, body").animate({ scrollTop: $(document).height() }, 1000);

    		  var MessageObject = Parse.Object.extend("Message");
    		  var messageObject = new MessageObject();
    		  messageObject.save({sendingNumber: TwilioNumber, LanguageOne: JSON.parse(this.response).responseData.translatedText, LanguageTwo: data});

        });
        oReq.open("get", url, true);
        oReq.send();

        $('#input-section--message-content').val('');
    },
    bindUIElements: function () {
        tb.translateButton.on('click', function () {
            tb.domObj.addClass("open");
            tb.isOpen = true;

            data = $('#input-section--message-content').val();

            var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=en|es";
            console.log(url);

            var oReq = new XMLHttpRequest();
            oReq.addEventListener('load', function(){
                console.log(JSON.parse(this.responseText).responseData.translatedText);

                $('#translation-section--message-content').html(JSON.parse(this.responseText).responseData.translatedText);
            });
            oReq.open("get", url, true);
            oReq.send();
        });

        tb.checkButton.on('click', function () {
            tb.domObj.addClass("open");
            tb.isOpen = true;

            data = $('#input-section--message-content').val();

            var url = "https://api.mymemory.translated.net/get?q=" + data + "&langpair=es|en";
            console.log(url);

            var oReq = new XMLHttpRequest();
            oReq.addEventListener('load', function(){
                console.log(JSON.parse(this.responseText).responseData.translatedText);

                $('#translation-section--message-content').html(JSON.parse(this.responseText).responseData.translatedText);
            });
            oReq.open("get", url, true);
            oReq.send();
        });

        tb.cancelButton.on('click', function () {
            tb.domObj.removeClass("open");
            tb.isOpen = false;
        });

        tb.cancelButton.on('click', function () {
            tb.domObj.removeClass("open");
            tb.isOpen = false;
        });

        tb.sendButton.on('click', function () {
            tb.sendMessage();
        });

        $(document).keyup(function (e) {
            if (e.keyCode == 27 && tb.isOpen) {
                tb.domObj.removeClass("open");
                tb.isOpen = false;
            }
        });

    }
};

tBox.init();

var menu, menuApp = {
    vars: {
        menuButton: $("#translate-change-lang-button"),
        menu: $("#language-menu"),
        mainWrapper: $("#main-wrapper"),
        menuList: $(".language-menu--list-item")
    },
    init: function () {
        menu = this.vars;
        this.bindUIElements();
    },
    bindUIElements: function () {
        menu.menuButton.click(function () {
            menu.mainWrapper.addClass("menu-open ");
            menu.menu.addClass("menu-open ");
        });
        menu.menuList.click(function(){
            specifiedLanguage = $(this).attr("id");
            menu.mainWrapper.removeClass("menu-open ");
            menu.menu.removeClass("menu-open ");
        });
    }
}
menuApp.init();
<|endoftext|>


<commit_before>(function()
{
    'use strict';

    angular
        .module('lumx.utils.utils')
        .service('LxUtils', LxUtils);

    function LxUtils()
    {
        var service = this;

        service.generateUUID = generateUUID;

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

        function generateUUID()
        {
            var d = new Date().getTime();

            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
            {
                var r = (d + Math.random() * 16) % 16 | 0;
                d = Math.floor(d / 16);
                return (c == 'x' ? r : (r & 0x3 | 0x8))
                    .toString(16);
            });

            return uuid.toUpperCase();
        }
    }
})();<commit_msg>feat: add a debounce function<commit_after>(function()
{
    'use strict';

    angular
        .module('lumx.utils.utils')
        .service('LxUtils', LxUtils);

    function LxUtils()
    {
        var service = this;

        service.debounce = debounce;
        service.generateUUID = generateUUID;

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

        // http://underscorejs.org/#debounce
        function debounce(func, wait, immediate)
        {
            var timeout, args, context, timestamp, result;

            wait = wait || 500;

            var later = function()
            {
                var last = Date.now - timestamp;

                if (last < wait && last >= 0)
                {
                    timeout = setTimeout(later, wait - last);
                }
                else
                {
                    timeout = null;
                    if (!immediate)
                    {
                        result = func.apply(context, args);
                        if (!timeout)
                        {
                            context = args = null;
                        }
                    }
                }
            };

            return function() {
                context = this;
                args = arguments;
                timestamp = Date.now;
                var callNow = immediate && !timeout;
                if (!timeout)
                {
                    timeout = setTimeout(later, wait);
                }
                if (callNow)
                {
                    result = func.apply(context, args);
                    context = args = null;
                }

                return result;
            };
        }

        function generateUUID()
        {
            var d = new Date().getTime();

            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
            {
                var r = (d + Math.random() * 16) % 16 | 0;
                d = Math.floor(d / 16);
                return (c == 'x' ? r : (r & 0x3 | 0x8))
                    .toString(16);
            });

            return uuid.toUpperCase();
        }
    }
})();<|endoftext|>


<commit_before>'use strict';
const config = require('./config');
const fetch = require('node-fetch');
const formatter = require('./formatter');
const { MongoClient } = require('mongodb');

const DEFAULT_DOCUMENTS_LIMIT = 5;

let _mongoDb;

function hello(msg) {
    return msg.reply.text('✋');
}

async function news(msg) {
    return getDocumentsAndReply(msg);
}

async function tech(msg) {
    return getDocumentsAndReply(msg, { tags: 'tech' });
}

async function finances(msg) {
    return getDocumentsAndReply(msg, { tags: 'finances' });
}

async function world(msg) {
    return getDocumentsAndReply(msg, { tags: 'world' });
}

async function tags(msg) {
    const tagsMatch = msg.text.match(/\/tags\s*([\w\s]+)?/u);
    if (!tagsMatch) {
        return msg.reply.text('☝ You should provide tags', { asReply: true });
    }

    const tagsString = tagsMatch[1].replace(/\s+/g, ',');
    return getDocumentsAndReply(msg, { tags: tagsString });
}

async function text(msg) {
    const text = msg.text.replace(/\/tags\s*([\w\s.-]+)?/u, '$1');
    if (!text) {
        return msg.reply.text('☝ You should provide text', { asReply: true });
    }

    return getDocumentsAndReply(msg, { text });
}

async function getDocumentsAndReply(msg, params) {
    const res = await fetch(await buildUrl(msg, 'get-documents', params));
    const responseText = await res.text();

    const data = JSON.parse(responseText);

    const done = formatter.formatDocuments(data).map(msg.reply.text, msg.reply);
    done.push(saveLastDate(msg, data));

    return Promise.all(done);
}

async function saveLastDate(msg, data) {
    if (!config.store_data || msg.text.startsWith('/text')) {
        return;
    }

    let lastDate = '';
    for (let doc of data) {
        if (doc.published > lastDate) {
            lastDate = doc.published;
        }
    }

    if (!lastDate) {
        return;
    }

    const db = await getMongo();
    const collection = db.collection('last_published');

    await collection.createIndex({ key: 1 }, { unique: true });

    const key = getLastDateKey(msg);
    return collection.updateOne({ key }, { key, val: lastDate }, { upsert: true });
}

async function getLastDate(msg) {
    if (!config.store_data || msg.text.startsWith('/text')) {
        return '';
    }

    const db = await getMongo();
    const collection = db.collection('last_published');

    const key = getLastDateKey(msg);
    const data = await collection.findOne({ key });

    return data.val;
}

function getLastDateKey(msg) {
    const command = /(\/\w+)/.exec(msg.text)[1];
    return `${msg.from.id}:${msg.from.username}:${command}`;
}

async function getMongo() {
    if (!_mongoDb) {
        _mongoDb = await MongoClient.connect(`mongodb://${config.mongo_host}:${config.mongo_port}/${config.mongo_db}`);
    }
    return _mongoDb;
}

function wrapHandler(handler) {
    return async function wrapper(msg) {
        try {
            return await handler.call(this, msg);
        } catch (err) {
            return msg.reply.text(`😿 We have an error:\n${err}`);
        }
    }
}

async function buildUrl(msg, handler, params = {}) {
    const user = msg.from;

    Object.assign(params, {
        limit: DEFAULT_DOCUMENTS_LIMIT,
        lang: user.language_code,
        from: 'telegram',
        'user-id': user.id,
        'user-login': user.username,
    });

    const limitMatch = msg.text.match(/\/[\w\s]+\s*(\d+)/u);
    if (limitMatch) {
        params.limit = limitMatch[1];
    }

    const unreadOnly = msg.text.includes('!ur');
    if (unreadOnly) {
        params['from-date'] = await getLastDate(msg);
    }

    const paramsString = Object.entries(params).map(([name, value]) => {
        return encodeURIComponent(name) + '=' + encodeURIComponent(value);
    }).join('&');

    return `${config.api_url}/${handler}?${paramsString}`;
}

module.exports = Object.entries({
    hello,
    news,
    tech,
    finances,
    world,
    tags,
    text,
}).reduce((res, [name, handler]) => {
    res[name] = wrapHandler(handler);
    return res;
}, {});
<commit_msg>Add basic help<commit_after>'use strict';
const config = require('./config');
const fetch = require('node-fetch');
const formatter = require('./formatter');
const { MongoClient } = require('mongodb');

const DEFAULT_DOCUMENTS_LIMIT = 5;

let _mongoDb;

const handlers = {
    hello,
    news,
    tech,
    finances,
    world,
    tags,
    text,
};

function start(msg) {
    return hello(msg);
}

function hello(msg) {
    return msg.reply.text('✋');
}

function help(msg) {
    const commandsList = Object.keys(handlers).map(name => '/' + name).join('\n');

    return msg.reply.text(`👌 Available commands:\n${commandsList}`);
}

async function news(msg) {
    return getDocumentsAndReply(msg);
}

async function tech(msg) {
    return getDocumentsAndReply(msg, { tags: 'tech' });
}

async function finances(msg) {
    return getDocumentsAndReply(msg, { tags: 'finances' });
}

async function world(msg) {
    return getDocumentsAndReply(msg, { tags: 'world' });
}

async function tags(msg) {
    const tagsMatch = msg.text.match(/\/tags\s*([\w\s]+)?/u);
    if (!tagsMatch) {
        return msg.reply.text('☝ You should provide tags', { asReply: true });
    }

    const tagsString = tagsMatch[1].replace(/\s+/g, ',');
    return getDocumentsAndReply(msg, { tags: tagsString });
}

async function text(msg) {
    const text = msg.text.replace(/\/tags\s*([\w\s.-]+)?/u, '$1');
    if (!text) {
        return msg.reply.text('☝ You should provide text', { asReply: true });
    }

    return getDocumentsAndReply(msg, { text });
}

async function getDocumentsAndReply(msg, params) {
    const res = await fetch(await buildUrl(msg, 'get-documents', params));
    const responseText = await res.text();

    const data = JSON.parse(responseText);

    const done = formatter.formatDocuments(data).map(msg.reply.text, msg.reply);
    done.push(saveLastDate(msg, data));

    return Promise.all(done);
}

async function saveLastDate(msg, data) {
    if (!config.store_data || msg.text.startsWith('/text')) {
        return;
    }

    let lastDate = '';
    for (let doc of data) {
        if (doc.published > lastDate) {
            lastDate = doc.published;
        }
    }

    if (!lastDate) {
        return;
    }

    const db = await getMongo();
    const collection = db.collection('last_published');

    await collection.createIndex({ key: 1 }, { unique: true });

    const key = getLastDateKey(msg);
    return collection.updateOne({ key }, { key, val: lastDate }, { upsert: true });
}

async function getLastDate(msg) {
    if (!config.store_data || msg.text.startsWith('/text')) {
        return '';
    }

    const db = await getMongo();
    const collection = db.collection('last_published');

    const key = getLastDateKey(msg);
    const data = await collection.findOne({ key });

    return data.val;
}

function getLastDateKey(msg) {
    const command = /(\/\w+)/.exec(msg.text)[1];
    return `${msg.from.id}:${msg.from.username}:${command}`;
}

async function getMongo() {
    if (!_mongoDb) {
        _mongoDb = await MongoClient.connect(`mongodb://${config.mongo_host}:${config.mongo_port}/${config.mongo_db}`);
    }
    return _mongoDb;
}

function wrapHandler(handler) {
    return async function wrapper(msg) {
        try {
            return await handler.call(this, msg);
        } catch (err) {
            return msg.reply.text(`😿 We have an error:\n${err}`);
        }
    }
}

async function buildUrl(msg, handler, params = {}) {
    const user = msg.from;

    Object.assign(params, {
        limit: DEFAULT_DOCUMENTS_LIMIT,
        lang: user.language_code,
        from: 'telegram',
        'user-id': user.id,
        'user-login': user.username,
    });

    const limitMatch = msg.text.match(/\/[\w\s]+\s*(\d+)/u);
    if (limitMatch) {
        params.limit = limitMatch[1];
    }

    const unreadOnly = msg.text.includes('!ur');
    if (unreadOnly) {
        params['from-date'] = await getLastDate(msg);
    }

    const paramsString = Object.entries(params).map(([name, value]) => {
        return encodeURIComponent(name) + '=' + encodeURIComponent(value);
    }).join('&');

    return `${config.api_url}/${handler}?${paramsString}`;
}

module.exports = Object.entries(
    Object.assign({ start, help }, handlers)
).reduce((res, [name, handler]) => {
    res[name] = wrapHandler(handler);
    return res;
}, {});
<|endoftext|>