<commit_before>/**
 * An Openframe extension which loops through a users collection on a timed duration.
 */

var pjson = require('./package.json'),
    debug = require('debug')('openframe:slideshow'),
    Extension = require('openframe-extension');

// default duration in minutes
var DEFAULT_DURATION = 1;

/**
 * Extension initialization method.
 *
 * Called when the extension (and its dependencies) have been installed.
 *
 * @param  {object} OF An interface provided to extensions giving limitted access to the frame environment
 */
module.exports = new Extension({
    init: function(OF) {
        // do your extension thing
        debug('=======>   Openframe-Slideshow initialized!   <=======');
        /**
         * Extensions also have access to the global event system
         */
        var pubsub = this.pubsub;

        /**
         * Extensions also have access to the Swagger REST client (https://github.com/swagger-api/swagger-js)
         * See openframe.io/explorer for API docs, or openframe.io/explorer/swagger.json for the swagger definition
         * which shows the available methods as 'operationId'
         */
        var rest = this.rest;

        /**
         * Reference to the frame model wrapper, allowing plugin to update frame data.
         * (frame.state is the model data)
         */
        var frame = this.frame;

        var duration = getDuration(frame.state, frame.state.current_artwork);
        debug('duration', duration);

        var timer = setTimeout(getNextFromCollection, duration);

        /**
         * Use the REST API to fetch the collection, then select a random artwork to display.
         */
        function getNextFromCollection() {
            debug('getNextFromCollection', duration);

            // get the logged-in user's primary collection
            rest.OpenframeUser.OpenframeUser_prototype_get_created_artwork({
                id: 'current'
            }).then(function(data) {
                console.log(data);
                var artworkList = data.obj,
                    len = artworkList.length,
                    currentArtworkId = frame.state.current_artwork && frame.state.current_artwork.id,
                    nextArtwork;

                debug('currentArtworkId', currentArtworkId);

                // cur idx
                var idx = artworkList.findIndex(function(el) {
                    return currentArtworkId === el.id;
                });

                debug('idx', idx);

                idx = idx === -1 ? 0 : idx;

                // next idx
                if (idx < len - 1) {
                    idx += 1;
                } else {
                    idx = 0;
                }

                nextArtwork = artworkList[idx];
                debug('nextArtwork', nextArtwork);

                // allow artwork-specific durations
                var duration = getDuration(frame.state, nextArtwork);
                debug('duration', duration);

                // frame.state is the plain JS object representing the frame's state...
                frame.state.currentArtworkId = nextArtwork.id;

                // then save the frame to the server. when the db is updated on the server, the server
                // will trigger a 'frame updated' event, which will in turn update this frame, forcing
                // the artwork to change to the new one we just set
                frame.save()
                    .then(function() {
                        debug('Success...');
                        timer = setTimeout(getNextFromCollection, duration);
                    })
                    .catch(function(err) {
                        debug('ERROR: ', err);
                    });
            }).catch(function(err) {
                debug('ERROR: ', err);
            });
        }

        function getDuration(frameState, artwork) {
            var duration;
            if (artwork.settings && artwork.settings[pjson.name] && artwork.settings[pjson.name].duration) {
                duration = minToMillis(artwork.settings[pjson.name].duration);
            } else if (frameState.settings && frameState.settings[pjson.name] && frameState.settings[pjson.name].duration) {
                duration = minToMillis(frameState.settings[pjson.name].duration);
            } else {
                duration = minToMillis(DEFAULT_DURATION);
            }
            return duration;
        }

        function minToMillis(min) {
            return min * 60000;
        }
    }
});
<commit_msg>Add default settings<commit_after>/**
 * An Openframe extension which loops through a users collection on a timed duration.
 */

var pjson = require('./package.json'),
    debug = require('debug')('openframe:slideshow'),
    Extension = require('openframe-extension');

// default duration in minutes
var DEFAULT_DURATION = 1;

/**
 * Extension initialization method.
 *
 * Called when the extension (and its dependencies) have been installed.
 *
 * @param  {object} OF An interface provided to extensions giving limitted access to the frame environment
 */
module.exports = new Extension({
    init: function(OF) {
        // do your extension thing
        debug('=======>   Openframe-Slideshow initialized!   <=======');
        /**
         * Extensions also have access to the global event system
         */
        var pubsub = this.pubsub;

        /**
         * Extensions also have access to the Swagger REST client (https://github.com/swagger-api/swagger-js)
         * See openframe.io/explorer for API docs, or openframe.io/explorer/swagger.json for the swagger definition
         * which shows the available methods as 'operationId'
         */
        var rest = this.rest;

        /**
         * Reference to the frame model wrapper, allowing plugin to update frame data.
         * (frame.state is the model data)
         */
        var frame = this.frame;

        var duration = getDuration(frame.state, frame.state.current_artwork);
        debug('duration', duration);

        var timer = setTimeout(getNextFromCollection, duration);

        /**
         * Use the REST API to fetch the collection, then select a random artwork to display.
         */
        function getNextFromCollection() {
            debug('getNextFromCollection', duration);

            // get the logged-in user's primary collection
            rest.OpenframeUser.OpenframeUser_prototype_get_created_artwork({
                id: 'current'
            }).then(function(data) {
                console.log(data);
                var artworkList = data.obj,
                    len = artworkList.length,
                    currentArtworkId = frame.state.current_artwork && frame.state.current_artwork.id,
                    nextArtwork;

                debug('currentArtworkId', currentArtworkId);

                // cur idx
                var idx = artworkList.findIndex(function(el) {
                    return currentArtworkId === el.id;
                });

                debug('idx', idx);

                idx = idx === -1 ? 0 : idx;

                // next idx
                if (idx < len - 1) {
                    idx += 1;
                } else {
                    idx = 0;
                }

                nextArtwork = artworkList[idx];
                debug('nextArtwork', nextArtwork);

                // allow artwork-specific durations
                var duration = getDuration(frame.state, nextArtwork);
                debug('duration', duration);

                // frame.state is the plain JS object representing the frame's state...
                frame.state.currentArtworkId = nextArtwork.id;

                // then save the frame to the server. when the db is updated on the server, the server
                // will trigger a 'frame updated' event, which will in turn update this frame, forcing
                // the artwork to change to the new one we just set
                frame.save()
                    .then(function() {
                        debug('Success...');
                        timer = setTimeout(getNextFromCollection, duration);
                    })
                    .catch(function(err) {
                        debug('ERROR: ', err);
                    });
            }).catch(function(err) {
                debug('ERROR: ', err);
            });
        }

        function getDuration(frameState, artwork) {
            var duration;
            if (artwork.settings && artwork.settings[pjson.name] && artwork.settings[pjson.name].duration) {
                duration = minToMillis(artwork.settings[pjson.name].duration);
            } else if (frameState.settings && frameState.settings[pjson.name] && frameState.settings[pjson.name].duration) {
                duration = minToMillis(frameState.settings[pjson.name].duration);
            } else {
                duration = minToMillis(DEFAULT_DURATION);
            }
            return duration;
        }

        function minToMillis(min) {
            return min * 60000;
        }
    },

    settings: {
        duration: 1,
        enabled: true
    }
});
<|endoftext|>


<commit_before>describe('Popover: ', function() {
  beforeEach(function() {
    loadFixtures('popover_fixture.html');
    locastyle.popover.init();
  });

  afterEach(function() {
      locastyle.popover.destroyPopover();
  });
  describe('Popover creation', function() {

    it('Should create one popover for each trigger', function() {
      var elems     = document.querySelectorAll('[data-ls-module="popover"]').length;
      var popovers = document.querySelectorAll('.ls-popover').length;
      expect(elems).toEqual(popovers);
    });

  });

  describe('Popover behavior', function() {

    it('Should show a popover on click event', function() {
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('click');
      var display = document.getElementsByClassName('ls-popover')[0].style.display;
      expect( display  ).toEqual('block');
    });

    it('Should show and close a popover on repeated click events', function() {
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('click');
      $popoverTrigger.trigger('click');
      expect( $popover.eq(0).css('display')  ).toEqual('none');
    });

    it('Should show a popover on hover event', function() {
      var $popoverTrigger = $('#popoverhover');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('mouseenter');
      expect( $popover.eq(2).css('display')  ).toEqual('block');
    });

    it('Should show and close a popover on repeated hover events', function() {
      var $popoverTrigger = $('#popoverhover');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('mouseenter');
      $popoverTrigger.trigger('mouseleave');
      expect( $popover.eq(2).css('display')  ).toEqual('none');
    });

  });

  describe('[unbind] When init is called multiple times', function () {

    it('should bind events on popover elements only one time', function () {
      locastyle.init();
      locastyle.init();
      locastyle.init();
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('#ls-popover-' + $popoverTrigger.data('uniqueId'));
      $popover.hide();
      $popoverTrigger.trigger('click');
      $popoverTrigger.trigger('click');
      expect( $popover ).not.toBeVisible();
    });
  });

  describe('change component on small screens', function () {
    it('should not create a popover in small screens', function () {
      locastyle.breakpointClass = 'ls-screen-sm';
      locastyle.popover.destroyPopover();
      locastyle.popover.init();
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('#ls-popover-' + $popoverTrigger.data('uniqueId'));
      expect( $popover[0] ).toBeUndefined();
    });

    it('should open popover as modal in small screens', function () {
      locastyle.breakpointClass = 'ls-screen-sm';
      locastyle.popover.init();
      var $popoverTrigger = $('#popoverclick');
      expect( $('.ls-modal')[0] ).not.toBeUndefined();
    });

  });

});
<commit_msg>[tests-fix] simplifying the test<commit_after>describe('Popover: ', function() {
  beforeEach(function() {
    loadFixtures('popover_fixture.html');
    locastyle.popover.init();
  });

  afterEach(function() {
      locastyle.popover.destroyPopover();
  });

  describe('Popover creation', function() {

    it('Should create one popover for each trigger', function() {
      var elems     = document.querySelectorAll('[data-ls-module="popover"]').length;
      var popovers = document.querySelectorAll('.ls-popover').length;
      expect(elems).toEqual(popovers);
    });

  });

  describe('Popover behavior', function() {

    it('Should show a popover on click event', function() {
      $('.ls-popover').hide();
      $('#popoverclick').trigger("click");
      expect($('.ls-popover').css("display")).toEqual("block");
    });

    it('Should show and close a popover on repeated click events', function() {
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('click');
      $popoverTrigger.trigger('click');
      expect( $popover.eq(0).css('display')  ).toEqual('none');
    });

    it('Should show a popover on hover event', function() {
      var $popoverTrigger = $('#popoverhover');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('mouseenter');
      expect( $popover.eq(2).css('display')  ).toEqual('block');
    });

    it('Should show and close a popover on repeated hover events', function() {
      var $popoverTrigger = $('#popoverhover');
      var $popover = $('.ls-popover');
      $popover.hide();
      $popoverTrigger.trigger('mouseenter');
      $popoverTrigger.trigger('mouseleave');
      expect( $popover.eq(2).css('display')  ).toEqual('none');
    });

  });

  describe('[unbind] When init is called multiple times', function () {

    it('should bind events on popover elements only one time', function () {
      locastyle.init();
      locastyle.init();
      locastyle.init();
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('#ls-popover-' + $popoverTrigger.data('uniqueId'));
      $popover.hide();
      $popoverTrigger.trigger('click');
      $popoverTrigger.trigger('click');
      expect( $popover ).not.toBeVisible();
    });
  });

  describe('change component on small screens', function () {
    it('should not create a popover in small screens', function () {
      locastyle.breakpointClass = 'ls-screen-sm';
      locastyle.popover.destroyPopover();
      locastyle.popover.init();
      var $popoverTrigger = $('#popoverclick');
      var $popover = $('#ls-popover-' + $popoverTrigger.data('uniqueId'));
      expect( $popover[0] ).toBeUndefined();
    });

    it('should open popover as modal in small screens', function () {
      locastyle.breakpointClass = 'ls-screen-sm';
      locastyle.popover.init();
      var $popoverTrigger = $('#popoverclick');
      expect( $('.ls-modal')[0] ).not.toBeUndefined();
    });

  });

});
<|endoftext|>


<commit_before>module.exports = function(grunt) {
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        dirs: {
            bower: 'vendor/bower_components',
            css: 'assets/css',
            js: 'assets/js',
            images: 'assets/images',
            icons: 'assets/icons'
        },

        // SCSS
        sass: {
            dev: {
                options: {
                    style: 'expanded',
                    loadPath: '.'
                },
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.scss'
                }
            },
            build: {
                options: {
                    style: 'compressed',
                    loadPath: '.'
                },
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.scss'
                }
            }
        },

        // CSS autoprefixer
        autoprefixer: {
            options: {
                browsers: ['last 2 versions']
            },
            dist: {
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.css'
                }
            }
        },

        // Connect Server
        connect: {
            server: {
                options: {
                    port: 9001,
                    base: ''
                }
            }
        },

        // Concat
        concat: {
            options: {
                separator: ';',
            },
            dist: {
                src: [
                    '<%= dirs.bower %>/jquery/dist/jquery.js',
                    '<%= dirs.js %>/modal.js',
                    '<%= dirs.js %>/main.js',
                    '!<%= dirs.js %>/modernizr.js',
                    '!<%= dirs.js %>/build.js'
                ],
                dest: '<%= dirs.js %>/build.js',
            },
        },

        // JShint
        jshint: {
            all: [
                'Gruntfile.js',
                '<%= dirs.js %>/*.js',
                '!<%= dirs.js %>/modernizr.js',
                '!<%= dirs.js %>/build.js'
            ]
        },

        // HTMLhint
        htmlhint: {
            html: {
                options: {
                    'tag-pair': true
                },
                src: ['*.html']
            }
        },

        // Uglify
        uglify: {
            all: {
                files: {
                    '<%= dirs.js %>/build.js': ['<%= dirs.js %>/build.js'],
                    '<%= dirs.js %>/modernizr.js': ['<%= dirs.bower %>/modernizr/modernizr.js']
                }
            }
        },

        // Imagemin
        imagemin: {
            dynamic: {
                files: [{
                    expand: true,
                    cwd: '<%= dirs.images %>',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: '<%= dirs.images %>'
                }]
            }
        },

        // Grunticon
        grunticon: {
            icons: {
                files: [{
                    expand: true,
                    cwd: '<%= dirs.icons %>',
                    src: ['*.svg', '*.png'],
                    dest: "<%= dirs.icons %>/grunticon"
                }],
                options: {
                    cssprefix: ".icon--",
                    customselectors: {
                      "*": [".icon--$1:before"],
                      "waves": [".headline--wavy:after",".site-footer:before"]
                    }
                }
            }
        },

        // Browser Sync
        browser_sync: {
            dev: {
                bsFiles: {
                    src : 'assets/css/style.css'
                },
                options: {
                    watchTask: true,
                    server: {
                        baseDir: ""
                    },
                    ghostMode: {
                        clicks: true,
                        scroll: true,
                        links: true,
                        forms: true
                    },
                }
            }
        },

        // Watch
        watch: {
            options: {
                livereload: true
            },
            sass: {
                files: ['<%= dirs.css %>/*.scss'],
                tasks: ['sass:dev', 'autoprefixer']
            },
            images: {
                files: ['<%= dirs.images %>/*.{png,jpg,gif}'],
                tasks: ['imagemin']
            },
            icons: {
                files: ['<%= dirs.icons %>/*'],
                tasks: ['grunticon']
            },
            html: {
                files: ['*.html'],
                tasks: ['htmlhint']
            },
            scripts: {
                files: ['Gruntfile.js', '<%= dirs.js %>/*.js'],
                tasks: ['jshint', 'concat'],
                options: {
                    spawn: false
                }
            }
        },

        // Shell
        shell: {
            options: {
                stderr: true
            },
            parse: {
                command: 'php parser.php > data/github.json'
            }
        },

        // phplint
        phplint: {
          options: {
            stdout: true,
            stderr: true
          },
          files: [ '*.php', 'templates/layouts/*.php', 'templates/pages/*.php' ]
        }
    });

    grunt.registerTask('default', ['sass:build', 'autoprefixer', 'concat', 'uglify', 'grunticon', 'phplint']);
    grunt.registerTask('dev', ['connect', 'watch', 'notify']);
    grunt.registerTask('dev:sync', ['browser_sync', 'watch', 'notify']);
    grunt.registerTask('parse', ['shell:parse']);
};
<commit_msg>Update grunt-sass options<commit_after>module.exports = function(grunt) {
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        dirs: {
            bower: 'vendor/bower_components',
            css: 'assets/css',
            js: 'assets/js',
            images: 'assets/images',
            icons: 'assets/icons'
        },

        // SCSS
        sass: {
            dev: {
                options: {
                    outputStyle: 'expanded',
                    loadPath: '.'
                },
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.scss'
                }
            },
            build: {
                options: {
                    outputStyle: 'compressed',
                    loadPath: '.'
                },
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.scss'
                }
            }
        },

        // CSS autoprefixer
        autoprefixer: {
            options: {
                browsers: ['last 2 versions']
            },
            dist: {
                files: {
                    '<%= dirs.css %>/style.css': '<%= dirs.css %>/style.css'
                }
            }
        },

        // Connect Server
        connect: {
            server: {
                options: {
                    port: 9001,
                    base: ''
                }
            }
        },

        // Concat
        concat: {
            options: {
                separator: ';',
            },
            dist: {
                src: [
                    '<%= dirs.bower %>/jquery/dist/jquery.js',
                    '<%= dirs.js %>/modal.js',
                    '<%= dirs.js %>/main.js',
                    '!<%= dirs.js %>/modernizr.js',
                    '!<%= dirs.js %>/build.js'
                ],
                dest: '<%= dirs.js %>/build.js',
            },
        },

        // JShint
        jshint: {
            all: [
                'Gruntfile.js',
                '<%= dirs.js %>/*.js',
                '!<%= dirs.js %>/modernizr.js',
                '!<%= dirs.js %>/build.js'
            ]
        },

        // HTMLhint
        htmlhint: {
            html: {
                options: {
                    'tag-pair': true
                },
                src: ['*.html']
            }
        },

        // Uglify
        uglify: {
            all: {
                files: {
                    '<%= dirs.js %>/build.js': ['<%= dirs.js %>/build.js'],
                    '<%= dirs.js %>/modernizr.js': ['<%= dirs.bower %>/modernizr/modernizr.js']
                }
            }
        },

        // Imagemin
        imagemin: {
            dynamic: {
                files: [{
                    expand: true,
                    cwd: '<%= dirs.images %>',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: '<%= dirs.images %>'
                }]
            }
        },

        // Grunticon
        grunticon: {
            icons: {
                files: [{
                    expand: true,
                    cwd: '<%= dirs.icons %>',
                    src: ['*.svg', '*.png'],
                    dest: "<%= dirs.icons %>/grunticon"
                }],
                options: {
                    cssprefix: ".icon--",
                    customselectors: {
                      "*": [".icon--$1:before"],
                      "waves": [".headline--wavy:after",".site-footer:before"]
                    }
                }
            }
        },

        // Browser Sync
        browser_sync: {
            dev: {
                bsFiles: {
                    src : 'assets/css/style.css'
                },
                options: {
                    watchTask: true,
                    server: {
                        baseDir: ""
                    },
                    ghostMode: {
                        clicks: true,
                        scroll: true,
                        links: true,
                        forms: true
                    },
                }
            }
        },

        // Watch
        watch: {
            options: {
                livereload: true
            },
            sass: {
                files: ['<%= dirs.css %>/*.scss'],
                tasks: ['sass:dev', 'autoprefixer']
            },
            images: {
                files: ['<%= dirs.images %>/*.{png,jpg,gif}'],
                tasks: ['imagemin']
            },
            icons: {
                files: ['<%= dirs.icons %>/*'],
                tasks: ['grunticon']
            },
            html: {
                files: ['*.html'],
                tasks: ['htmlhint']
            },
            scripts: {
                files: ['Gruntfile.js', '<%= dirs.js %>/*.js'],
                tasks: ['jshint', 'concat'],
                options: {
                    spawn: false
                }
            }
        },

        // Shell
        shell: {
            options: {
                stderr: true
            },
            parse: {
                command: 'php parser.php > data/github.json'
            }
        },

        // phplint
        phplint: {
          options: {
            stdout: true,
            stderr: true
          },
          files: [ '*.php', 'templates/layouts/*.php', 'templates/pages/*.php' ]
        }
    });

    grunt.registerTask('default', ['sass:build', 'autoprefixer', 'concat', 'uglify', 'grunticon', 'phplint']);
    grunt.registerTask('dev', ['connect', 'watch', 'notify']);
    grunt.registerTask('dev:sync', ['browser_sync', 'watch', 'notify']);
    grunt.registerTask('parse', ['shell:parse']);
};
<|endoftext|>


<commit_before>/*
 * TODO:
 * 1) Deploy script to deploy only the app directory
 * 2) Image Compression
 * 3) JS concat
 * 4) CSS + JS Lint
 * 5) Watch for js
 * 6) For deploy, do not delete the wp-config-sample but rather do not upload it to production server
 */

module.exports = function(grunt) {

	// Set UTF-8 Charset
	grunt.file.defaultEncoding = 'utf8';

	// Load grunt packages
	require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

	// Set our instructions
	grunt.initConfig({
		// Read package.json for instructions on what packages to use
		pkg : grunt.file.readJSON('package.json'),
		copy : {
			// WordPress
			wp : {
				files : [{
					expand : true,
					cwd : 'app/wordpress',
					src : 'wp-content/**',
					dest : 'app'
				}, {
					expand : true,
					cwd : 'app/wordpress',
					src : 'wp-config-sample.php',
					dest : 'app'
				}]
			}
		},
		clean : {
			// WordPress
			wp : ['app/wp-content/plugins/hello.php', 'app/wp-content/themes/twentyten', 'app/wp-content/themes/twentyeleven',

'app/wp-content/themes/twentytwelve'],
			wpconfig: ['app/wp-config-sample.php']
		},
		compass : {
			wp : {
				options : {
					environment : 'development',
					outputStyle : 'expanded',
					relativeAssets : true,
					basePath : 'app/wp-content/themes/gutsThemeStarter',
					cssDir : 'css',
					sassDir : 'sass',
					imagesDir : 'img',
					javascriptsDir : 'js',
					fontsDir : 'font',
					noLineComments : true,
					force : true
				}
			},
			/*
			 * TODO: Update production settings
			 */
			wpproduction : {
				options : {
					environment : 'production',
					outputStyle : 'compressed',
					relativeAssets : false,
					basePath : 'app/wp-content/themes/gutsThemeStarter',
					cssDir : 'css',
					sassDir : 'sass',
					imagesDir : 'img',
					javascriptsDir : 'js',
					fontsDir : 'font',
					noLineComments : true,
					force : true
				}
			}
		},
		/*
		 * TODO: Compile js files and compress images
		 */
		watch: {
			// Watch our sass files and auto compile them
			css: {
				files: ['app/wp-content/themes/gutsThemeStarter/sass/**.*'],
				tasks: ['compass:wp'],
				options: {
					livereload: true
				}
			},
			// Watch our js files and auto compile them
			js: {
				files: ['app/wp-content/themes/gutsThemeStarter/js/main.js', 'components/**/*.js'],
				tasks: ['uglify']
			},
			// Watch our files for any changes, then automatically reload the page
			// Requires livereload chrome extension, or equivalent
			livereload: {
				files: ['app/wp-content/themes/gutsThemeStarter/css/**.*'],
				options: {
					livereload: true
				}
			}
		},
		replace: {
			wpconfig: {
				src: ['app/wp-config.php'],
				overwrite: true,
				//dest: 'app/wp-config.php',
				replacements: [{
					from: '<?php',
					to: function (matchedWord) {   // callback replacement
						//return matchedWord;
						var phpTag = matchedWord + '\n\n\n';
						var prewrap1 = "/***** Dynamicaly generated from Gruntfile.js *****/ \n"
						var prewrap2 = "/***** You may need to update the path to your project *****/ \n"
					    var wp_home = "define('WP_HOME',    'http://' . $_SERVER['SERVER_NAME'] . '/app'); \n";
					    var wp_siteurl = "define('WP_SITEURL', 'http://' . $_SERVER['SERVER_NAME'] . '/app/wordpress'); \n";
					    var wp_content_dir = "define('WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/app/wp-content'); \n";
					    var wp_content_url = "define('WP_CONTENT_URL', 'http://' . $_SERVER['SERVER_NAME'] . '/app/wp-content'); \n";
						var postwrap = "/***** End Gruntfile.js inserts *****/ \n\n\n"
					    return [phpTag + prewrap1 + prewrap2 + wp_home + wp_siteurl + wp_content_dir + wp_content_url + postwrap];
					}
				}]
			}
		}
	});

	/*
	 * Define Grunt Tasks
	 */

	// Set up wordpress, copy across wp-content and create index.php and wp-config-sample.php
	grunt.registerTask('wp', function() {
		grunt.file.write("app/index.php", '<?php define(\'WP_USE_THEMES\',true);require(dirname(__FILE__).\'/wordpress/wp-blog-header.php\');');
		grunt.task.run('copy:wp', 'clean:wp');
	});

	// Compile styles, and watch for changes
	grunt.registerTask('wpsass', ['compass:wp', 'watch:wp']);

	// Dynamically add necessary paths to wp-config.php. You will need to edit this.
	grunt.registerTask('wpconfig', function() {
		grunt.task.run('replace:wpconfig', 'clean:wpconfig');
		grunt.log.write('Guts has dynamically added new lines to the top of your wp-config.php file in the app folder. You will need to edit these lines. Please do this now./');
	});
	grunt.registerTask('wpproduction', function() {
		grunt.task.run('compass:wpproduction');
	});

	// We want to disable default grunt for now, so provide a nice message
	grunt.registerTask('default', 'My "default" task description.', function() {
		grunt.log.writeln('');
		grunt.log.writeln('********************************************');
		grunt.log.writeln('There are no default tasks in this project.');
		grunt.log.writeln('Open Gruntfile.js to see how Grunt is built.');
		grunt.log.writeln('********************************************');
	});

};
<commit_msg>upd grunt<commit_after>/*
 * TODO:
 * 1) Deploy script to deploy only the app directory
 * 2) Image Compression
 * 3) JS concat
 * 4) CSS + JS Lint
 * 5) Watch for js
 * 6) For deploy, do not delete the wp-config-sample but rather do not upload it to production server
 */

module.exports = function(grunt) {

	// Set UTF-8 Charset
	grunt.file.defaultEncoding = 'utf8';

	// Load grunt packages
	require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

	// Set our instructions
	grunt.initConfig({
		// Read package.json for instructions on what packages to use
		pkg : grunt.file.readJSON('package.json'),
		copy : {
			// WordPress
			wp : {
				files : [{
					expand : true,
					cwd : 'app/wordpress',
					src : 'wp-content/**',
					dest : 'app'
				}, {
					expand : true,
					cwd : 'app/wordpress',
					src : 'wp-config-sample.php',
					dest : 'app'
				}]
			}
		},
		clean : {
			// WordPress
			wp : ['app/wordpress/wp-config-sample.php', 'app/wp-content/plugins/hello.php', 'app/wp-content/themes/twentyten', 'app/wp-content/themes/twentyeleven', 'app/wp-content/themes/twentytwelve'],
			wpconfig: ['app/wp-config-sample.php']
		},
		compass : {
			wp : {
				options : {
					environment : 'development',
					outputStyle : 'expanded',
					relativeAssets : true,
					basePath : 'app/wp-content/themes/gutsThemeStarter',
					cssDir : 'css',
					sassDir : 'sass',
					imagesDir : 'img',
					javascriptsDir : 'js',
					fontsDir : 'font',
					noLineComments : true,
					force : true
				}
			},
			/*
			 * TODO: Update production settings
			 */
			wpproduction : {
				options : {
					environment : 'production',
					outputStyle : 'compressed',
					relativeAssets : false,
					basePath : 'app/wp-content/themes/gutsThemeStarter',
					cssDir : 'css',
					sassDir : 'sass',
					imagesDir : 'img',
					javascriptsDir : 'js',
					fontsDir : 'font',
					noLineComments : true,
					force : true
				}
			}
		},
		/*
		 * TODO: Compile js files and compress images
		 */
		watch: {
			// Watch our sass files and auto compile them
			css: {
				files: ['app/wp-content/themes/gutsThemeStarter/sass/**.*'],
				tasks: ['compass:wp'],
				options: {
					livereload: true
				}
			},
			// Watch our js files and auto compile them
			js: {
				files: ['app/wp-content/themes/gutsThemeStarter/js/main.js', 'components/**/*.js'],
				tasks: ['uglify']
			},
			// Watch our files for any changes, then automatically reload the page
			// Requires livereload chrome extension, or equivalent
			livereload: {
				files: ['app/wp-content/themes/gutsThemeStarter/css/**.*'],
				options: {
					livereload: true
				}
			}
		},
		replace: {
			wpconfig: {
				src: ['app/wp-config.php'],
				overwrite: true,
				//dest: 'app/wp-config.php',
				replacements: [{
					from: '<?php',
					to: function (matchedWord) {   // callback replacement
						//return matchedWord;
						var phpTag = matchedWord + '\n\n\n';
						var prewrap1 = "/***** Dynamicaly generated from Gruntfile.js *****/ \n"
						var prewrap2 = "/***** You may need to update the path to your project *****/ \n"
					    var wp_home = "define('WP_HOME',    'http://' . $_SERVER['SERVER_NAME'] . '/app'); \n";
					    var wp_siteurl = "define('WP_SITEURL', 'http://' . $_SERVER['SERVER_NAME'] . '/app/wordpress'); \n";
					    var wp_content_dir = "define('WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/app/wp-content'); \n";
					    var wp_content_url = "define('WP_CONTENT_URL', 'http://' . $_SERVER['SERVER_NAME'] . '/app/wp-content'); \n";
						var postwrap = "/***** End Gruntfile.js inserts *****/ \n\n\n"
					    return [phpTag + prewrap1 + prewrap2 + wp_home + wp_siteurl + wp_content_dir + wp_content_url + postwrap];
					}
				}]
			}
		}
	});

	/*
	 * Define Grunt Tasks
	 */

	// Set up wordpress, copy across wp-content and create index.php and wp-config-sample.php
	grunt.registerTask('wp', function() {
		grunt.file.write("app/index.php", '<?php define(\'WP_USE_THEMES\',true);require(dirname(__FILE__).\'/wordpress/wp-blog-header.php\');');
		grunt.task.run('copy:wp', 'clean:wp');
	});

	// Compile styles, and watch for changes
	grunt.registerTask('wpsass', ['compass:wp', 'watch:wp']);

	// Dynamically add necessary paths to wp-config.php. You will need to edit this.
	grunt.registerTask('wpconfig', function() {
		grunt.task.run('replace:wpconfig', 'clean:wpconfig');
		grunt.log.write('Guts has dynamically added new lines to the top of your wp-config.php file in the app folder. You will need to edit these lines. Please do this now./');
	});
	grunt.registerTask('wpproduction', function() {
		grunt.task.run('compass:wpproduction');
	});

	// We want to disable default grunt for now, so provide a nice message
	grunt.registerTask('default', 'My "default" task description.', function() {
		grunt.log.writeln('');
		grunt.log.writeln('********************************************');
		grunt.log.writeln('There are no default tasks in this project.');
		grunt.log.writeln('Open Gruntfile.js to see how Grunt is built.');
		grunt.log.writeln('********************************************');
	});

};
<|endoftext|>


<commit_before>import $ from 'cash-dom';
import Editor from './editor';
import { isElement, isFunction } from 'underscore';
import polyfills from 'utils/polyfills';
import PluginManager from './plugin_manager';

polyfills();

module.exports = (() => {
  const plugins = new PluginManager();
  const editors = [];
  const defaultConfig = {
    // If true renders editor on init
    autorender: 1,

    // Array of plugins to init
    plugins: [],

    // Custom options for plugins
    pluginsOpts: {}
  };

  return {
    $,

    editors,

    plugins,

    // Will be replaced on build
    version: '<# VERSION #>',

    /**
     * Initializes an editor based on passed options
     * @param {Object} config Configuration object
     * @param {string|HTMLElement} config.container Selector which indicates where render the editor
     * @param {Boolean} [config.autorender=true] If true, auto-render the content
     * @param {Array} [config.plugins=[]] Array of plugins to execute on start
     * @param {Object} [config.pluginsOpts={}] Custom options for plugins
     * @return {Editor} Editor instance
     * @example
     * var editor = grapesjs.init({
     *   container: '#myeditor',
     *   components: '
Hello world
',
     *   style: '.hello{color: red}',
     * })
     */
    init(config = {}) {
      const els = config.container;
      if (!els) throw new Error("'container' is required");
      config = { ...defaultConfig, ...config };
      config.el = isElement(els) ? els : document.querySelector(els);
      const editor = new Editor(config).init();

      // Load plugins
      config.plugins.forEach(pluginId => {
        let plugin = plugins.get(pluginId);
        const plgOptions = config.pluginsOpts[pluginId] || {};

        // Try to search in global context
        if (!plugin) {
          const wplg = window[pluginId];
          plugin = wplg && wplg.defaults ? wplg.defaults : wplg;
        }

        if (plugin) {
          plugin(editor, plgOptions);
        } else if (isFunction(pluginId)) {
          pluginId(editor, plgOptions);
        } else {
          console.warn(`Plugin ${pluginId} not found`);
        }
      });

      // Execute `onLoad` on modules once all plugins are initialized.
      // A plugin might have extended/added some custom type so this
      // is a good point to load stuff like components, css rules, etc.
      editor.getModel().loadOnStart();
      config.autorender && editor.render();
      editors.push(editor);

      return editor;
    }
  };
})();
<commit_msg>Fix `default` module name<commit_after>import $ from 'cash-dom';
import Editor from './editor';
import { isElement, isFunction } from 'underscore';
import polyfills from 'utils/polyfills';
import PluginManager from './plugin_manager';

polyfills();

module.exports = (() => {
  const plugins = new PluginManager();
  const editors = [];
  const defaultConfig = {
    // If true renders editor on init
    autorender: 1,

    // Array of plugins to init
    plugins: [],

    // Custom options for plugins
    pluginsOpts: {}
  };

  return {
    $,

    editors,

    plugins,

    // Will be replaced on build
    version: '<# VERSION #>',

    /**
     * Initializes an editor based on passed options
     * @param {Object} config Configuration object
     * @param {string|HTMLElement} config.container Selector which indicates where render the editor
     * @param {Boolean} [config.autorender=true] If true, auto-render the content
     * @param {Array} [config.plugins=[]] Array of plugins to execute on start
     * @param {Object} [config.pluginsOpts={}] Custom options for plugins
     * @return {Editor} Editor instance
     * @example
     * var editor = grapesjs.init({
     *   container: '#myeditor',
     *   components: '
Hello world
',
     *   style: '.hello{color: red}',
     * })
     */
    init(config = {}) {
      const els = config.container;
      if (!els) throw new Error("'container' is required");
      config = { ...defaultConfig, ...config };
      config.el = isElement(els) ? els : document.querySelector(els);
      const editor = new Editor(config).init();

      // Load plugins
      config.plugins.forEach(pluginId => {
        let plugin = plugins.get(pluginId);
        const plgOptions = config.pluginsOpts[pluginId] || {};

        // Try to search in global context
        if (!plugin) {
          const wplg = window[pluginId];
          plugin = wplg && wplg.default ? wplg.default : wplg;
        }

        if (plugin) {
          plugin(editor, plgOptions);
        } else if (isFunction(pluginId)) {
          pluginId(editor, plgOptions);
        } else {
          console.warn(`Plugin ${pluginId} not found`);
        }
      });

      // Execute `onLoad` on modules once all plugins are initialized.
      // A plugin might have extended/added some custom type so this
      // is a good point to load stuff like components, css rules, etc.
      editor.getModel().loadOnStart();
      config.autorender && editor.render();
      editors.push(editor);

      return editor;
    }
  };
})();
<|endoftext|>


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

  if (document.readyState === 'complete') {
    startAgateScript()
  } else {
    window.addEventListener('load', startAgateScript)
  }

  function startAgateScript() {
    insertStyles();

    const userId = /^\/\w+\/time\/sheet\/(\d+)/.exec(window.location.pathname)?.[1];

    document
      .querySelectorAll('tr.work:not(.notworkedTr)')
      .forEach((workRow) => {
        const cell = workRow.querySelector('.hourButton')?.parentElement || workRow.querySelector('td:nth-child(3)');
        const infoButton = document.createElement('button');

        infoButton.classList.add('btn', 'ams-btn', 'ams-float-right', 'ams-ml-4');
        infoButton.setAttribute('title', 'Plus d\'informations');
        infoButton.innerHTML = '';
        infoButton.addEventListener('click', () => {
          summerize(workRow).catch((e) => {
            console.error('Failed to summerize row');
            console.error(e);
          });
        });

        const checkClockingsButton = document.createElement('a');

        const rowDate = /td-(\d+-\d+-\d+)/.exec(workRow.getAttribute('id'))?.[1];

        checkClockingsButton.classList.add('btn', 'ams-btn', 'ams-float-right', 'ams-ml-4');
        checkClockingsButton.setAttribute('href', getPunchUrl(rowDate));
        checkClockingsButton.setAttribute('title', 'Voir les pointages du jour');
        checkClockingsButton.innerHTML = '';

        cell.appendChild(checkClockingsButton);
        cell.appendChild(infoButton);

        if (workRow.classList.contains('trToday')) {
          summerize(workRow).catch((e) => {
            console.error('Failed to summerize row');
            console.error(e);
          });
        }
      });

    async function summerize(workRow) {
      const clockingsCell = workRow.querySelector('.hourButton')?.parentElement || workRow.querySelector('td:nth-child(3)');

      if (!clockingsCell) { return; }

      let summary = clockingsCell.querySelector('.ams-summary');

      if (!summary) {
        summary = document.createElement('div');
        summary.classList.add('ams-summary', 'ams-float-right');
        clockingsCell.appendChild(summary);
      }

      summary.innerHTML = '';

      const rowDate = /td-(\d+-\d+-\d+)/.exec(workRow.getAttribute('id'))?.[1];

      const spinnerButton = createSpinnerButton();
      summary.appendChild(spinnerButton);

      let clockings;
      try {
        clockings = await getClockings(rowDate);
      } catch (e) {
        console.error('Failed to download clockings of current date');
        console.error(e);
      } finally {
        summary.removeChild(spinnerButton);
      }

      if (Array.isArray(clockings)) {
        clockings.forEach((clocking) => {
          summary.appendChild(createClockingButton(clocking));
        });
      }

      // We only need to calculate the end of day for the current date
      if (!workRow.classList.contains('trToday')) {
        return;
      }

      const dailyTotal = stringToMinutes(workRow.querySelector('.theoricalColumn')?.textContent);

      let regulation = 0;
      let breakTime = 0;
      let leaveTaken = false;

      workRow
        .querySelectorAll('.hourButton')
        .forEach((item) => {
          if (item.querySelector('i.fa-cutlery')) {
            breakTime += stringToMinutes(item?.textContent);
            return;
          }
          if (item.querySelector('i.icon-os-absence')) {
            leaveTaken = true;
            regulation += stringToMinutes(item?.textContent);
            return;
          }
          if (item.classList.contains('withoutValidation')) {
            regulation += stringToMinutes(item?.textContent);
          }
        });

      const minBreakTime = 45;

      if (!leaveTaken && breakTime === 0) {
        // If there are no break time, use the time between the two first clocking ranges
        // If a leave has been taken, we don't need to regulate the break time

        if (clockings.length >= 2) {
          breakTime = (clockings[1]?.clockIn - clockings[0]?.clockOut) || 0;
        }
        regulation += (breakTime - minBreakTime);
      }

      const remainingButton = document.createElement('button');
      remainingButton.classList.add('btn', 'ams-btn');
      remainingButton.setAttribute('title', 'Temps de travail restant');
      remainingButton.innerHTML = `

      `;

      const endOfDayButton = document.createElement('button');
      endOfDayButton.classList.add('btn', 'ams-btn');
      endOfDayButton.setAttribute('title', 'Fin de la journée');
      endOfDayButton.innerHTML = `

      `;

      const minToMs   = 60000;
      const worktime  = getWorktime(clockings);
      const remaining = dailyTotal - worktime - regulation;
      const endOfDay  = new Date(Date.now() + remaining * minToMs);

      remainingButton.querySelector('.text').textContent = minutesToString(remaining);
      endOfDayButton.querySelector('.text').textContent = dateToString(endOfDay);

      summary.appendChild(remainingButton);
      summary.appendChild(endOfDayButton);
    }

    function createSpinnerButton() {
      const spinnerButton = document.createElement('button');
      spinnerButton.classList.add('btn', 'ams-btn');
      spinnerButton.setAttribute('title', 'Chargement...');
      spinnerButton.innerHTML = '';
      return spinnerButton;
    }

    function createClockingButton({ clockIn, clockOut }) {
      let clockRange = 0;

      if (clockIn && clockOut) {
        clockRange = clockOut - clockIn;
      } else if (clockIn) {
        const now = new Date()
        clockRange = (now.getHours() * 60) + now.getMinutes() - clockIn;
      }

      const button = document.createElement('button');
      button.classList.add('btn', 'ams-btn-primary');
      button.setAttribute('title', minutesToString(clockRange));
      button.innerHTML = `
        ${clockIn ? minutesToString(clockIn) : '...'}

        ${clockOut ? minutesToString(clockOut) : '...'}
      `;
      return button;
    }

    /**
     * Get the total worktime based on clockings
     */
    function getWorktime (clockings) {
      let worktime = 0

      if (Array.isArray(clockings) && clockings.length > 0) {
        clockings.forEach(({ clockIn, clockOut }) => {
          if (clockIn && clockOut) {
            // For each clock-in/clock-out couple, add the period to the work time
            worktime += (clockOut - clockIn)
          } else if (clockIn) {
            // If the clock-in has no associated clock-out, add the period going from clock-in to current time
            const now = new Date()
            worktime += (now.getHours() * 60) + now.getMinutes() - clockIn
          }
        })
      }

      return worktime
    }

    /**
     * Convert a string (..h.. or -..h..) into a number of minutes
     * @param {String} str
     */
    function stringToMinutes (str) {
      const match = /^(-)?\s*(\d+)\s*[h:]\s*(\d+)$/.exec((str || '').trim())
      if (!match) { return 0 }

      const minutes = (parseInt(match[2]) * 60) + parseInt(match[3])

      return match[1] ? minutes * -1 : minutes
    }

    /**
     * Take a number of minutes and return a formated string (..h.. or -..h..)
     * @param {Integer} nbMinutes
     */
    function minutesToString (nbMinutes) {
      if (!nbMinutes && nbMinutes !== 0) { return 'N/A' }

      let hours = Math.floor(Math.abs(nbMinutes / 60)).toString().padStart(2, '0');
      let minutes = Math.abs(nbMinutes % 60).toString().padStart(2, '0');

      return nbMinutes >= 0 ? `${hours}h${minutes}` : `-${hours}h${minutes}`
    }

    /**
     * Take a date and return a formated string (..h.. or -..h..)
     * @param {Integer} date
     */
    function dateToString(date) {
      if (!date) { return 'N/A'; }

      const hours = date.getHours().toString().padStart(2, '0');
      const minutes = date.getMinutes().toString().padStart(2, '0');

      return `${hours}h${minutes}`;
    }

    function getPunchUrl(date) {
      return `${window.location.origin}/fr/time/punch/${userId}/${date}`;
    }

    /**
     * Get clockings of the given date
     */
    function getClockings(date) {
      return fetch(getPunchUrl(date))
        .then((response) => {
          if (!response.ok) {
            throw new Error(`Request failed with status ${response.status}`);
          }
          return response.text();
        })
        .then((text) => {
          const parser = new DOMParser();
          const clockingPage = parser.parseFromString(text, 'text/html');
          const clockings = [];

          const inputs = clockingPage.querySelectorAll('#punches_collection tr:not(.tr-punch-canceled) input[type=time]');

          for (let i = 0; i < inputs.length; i += 2) {
            clockings.push({
              clockIn: stringToMinutes(inputs[i]?.value),
              clockOut: stringToMinutes(inputs[i + 1]?.value),
            });
          }

          return clockings;
        });
    }

    /**
     * Insert extension specific styles in the document head
     */
    function insertStyles() {
      const style = document.createElement('style');

      style.textContent = `
        .ams-float-right
        {
          float: right;
        }

        .ams-summary > button,
        .ams-ml-4
        {
          margin-left: 4px;
        }

        .ams-btn {
          background-color: transparent;
          color: #444;
          border-color: #AAA;
          min-width: 30px;
        }
        .ams-btn-primary {
          background-color: transparent;
          color: #4583aa;
          border-color: #4583aa;
        }
        .bankHolidayTr {
          background: repeating-linear-gradient(45deg, #ffe7b8, #ffe7b8 10px, #ffffff 10px, #ffffff 20px);
        }
      `;

      document.head.appendChild(style);
    }
  }
})()<commit_msg>fix: don't alter regulation when breaktime is over 45min<commit_after>(function () {
  'use strict'

  if (document.readyState === 'complete') {
    startAgateScript()
  } else {
    window.addEventListener('load', startAgateScript)
  }

  function startAgateScript() {
    insertStyles();

    const userId = /^\/\w+\/time\/sheet\/(\d+)/.exec(window.location.pathname)?.[1];

    document
      .querySelectorAll('tr.work:not(.notworkedTr)')
      .forEach((workRow) => {
        const cell = workRow.querySelector('.hourButton')?.parentElement || workRow.querySelector('td:nth-child(3)');
        const infoButton = document.createElement('button');

        infoButton.classList.add('btn', 'ams-btn', 'ams-float-right', 'ams-ml-4');
        infoButton.setAttribute('title', 'Plus d\'informations');
        infoButton.innerHTML = '';
        infoButton.addEventListener('click', () => {
          summerize(workRow).catch((e) => {
            console.error('Failed to summerize row');
            console.error(e);
          });
        });

        const checkClockingsButton = document.createElement('a');

        const rowDate = /td-(\d+-\d+-\d+)/.exec(workRow.getAttribute('id'))?.[1];

        checkClockingsButton.classList.add('btn', 'ams-btn', 'ams-float-right', 'ams-ml-4');
        checkClockingsButton.setAttribute('href', getPunchUrl(rowDate));
        checkClockingsButton.setAttribute('title', 'Voir les pointages du jour');
        checkClockingsButton.innerHTML = '';

        cell.appendChild(checkClockingsButton);
        cell.appendChild(infoButton);

        if (workRow.classList.contains('trToday')) {
          summerize(workRow).catch((e) => {
            console.error('Failed to summerize row');
            console.error(e);
          });
        }
      });

    async function summerize(workRow) {
      const clockingsCell = workRow.querySelector('.hourButton')?.parentElement || workRow.querySelector('td:nth-child(3)');

      if (!clockingsCell) { return; }

      let summary = clockingsCell.querySelector('.ams-summary');

      if (!summary) {
        summary = document.createElement('div');
        summary.classList.add('ams-summary', 'ams-float-right');
        clockingsCell.appendChild(summary);
      }

      summary.innerHTML = '';

      const rowDate = /td-(\d+-\d+-\d+)/.exec(workRow.getAttribute('id'))?.[1];

      const spinnerButton = createSpinnerButton();
      summary.appendChild(spinnerButton);

      let clockings;
      try {
        clockings = await getClockings(rowDate);
      } catch (e) {
        console.error('Failed to download clockings of current date');
        console.error(e);
      } finally {
        summary.removeChild(spinnerButton);
      }

      if (Array.isArray(clockings)) {
        clockings.forEach((clocking) => {
          summary.appendChild(createClockingButton(clocking));
        });
      }

      // We only need to calculate the end of day for the current date
      if (!workRow.classList.contains('trToday')) {
        return;
      }

      const dailyTotal = stringToMinutes(workRow.querySelector('.theoricalColumn')?.textContent);

      let regulation = 0;
      let breakTime = 0;
      let leaveTaken = false;

      workRow
        .querySelectorAll('.hourButton')
        .forEach((item) => {
          if (item.querySelector('i.fa-cutlery')) {
            breakTime += stringToMinutes(item?.textContent);
            return;
          }
          if (item.querySelector('i.icon-os-absence')) {
            leaveTaken = true;
            regulation += stringToMinutes(item?.textContent);
            return;
          }
          if (item.classList.contains('withoutValidation')) {
            regulation += stringToMinutes(item?.textContent);
          }
        });

      const minBreakTime = 45;

      if (!leaveTaken && breakTime === 0) {
        // If there are no break time, use the time between the two first clocking ranges
        // If a leave has been taken, we don't need to regulate the break time

        if (clockings.length >= 2) {
          breakTime = (clockings[1]?.clockIn - clockings[0]?.clockOut) || 0;
        }
        regulation += Math.min(breakTime - minBreakTime, 0);
      }

      const remainingButton = document.createElement('button');
      remainingButton.classList.add('btn', 'ams-btn');
      remainingButton.setAttribute('title', 'Temps de travail restant');
      remainingButton.innerHTML = `

      `;

      const endOfDayButton = document.createElement('button');
      endOfDayButton.classList.add('btn', 'ams-btn');
      endOfDayButton.setAttribute('title', 'Fin de la journée');
      endOfDayButton.innerHTML = `

      `;

      const minToMs   = 60000;
      const worktime  = getWorktime(clockings);
      const remaining = dailyTotal - worktime - regulation;
      const endOfDay  = new Date(Date.now() + remaining * minToMs);

      remainingButton.querySelector('.text').textContent = minutesToString(remaining);
      endOfDayButton.querySelector('.text').textContent = dateToString(endOfDay);

      summary.appendChild(remainingButton);
      summary.appendChild(endOfDayButton);
    }

    function createSpinnerButton() {
      const spinnerButton = document.createElement('button');
      spinnerButton.classList.add('btn', 'ams-btn');
      spinnerButton.setAttribute('title', 'Chargement...');
      spinnerButton.innerHTML = '';
      return spinnerButton;
    }

    function createClockingButton({ clockIn, clockOut }) {
      let clockRange = 0;

      if (clockIn && clockOut) {
        clockRange = clockOut - clockIn;
      } else if (clockIn) {
        const now = new Date()
        clockRange = (now.getHours() * 60) + now.getMinutes() - clockIn;
      }

      const button = document.createElement('button');
      button.classList.add('btn', 'ams-btn-primary');
      button.setAttribute('title', minutesToString(clockRange));
      button.innerHTML = `
        ${clockIn ? minutesToString(clockIn) : '...'}

        ${clockOut ? minutesToString(clockOut) : '...'}
      `;
      return button;
    }

    /**
     * Get the total worktime based on clockings
     */
    function getWorktime (clockings) {
      let worktime = 0

      if (Array.isArray(clockings) && clockings.length > 0) {
        clockings.forEach(({ clockIn, clockOut }) => {
          if (clockIn && clockOut) {
            // For each clock-in/clock-out couple, add the period to the work time
            worktime += (clockOut - clockIn)
          } else if (clockIn) {
            // If the clock-in has no associated clock-out, add the period going from clock-in to current time
            const now = new Date()
            worktime += (now.getHours() * 60) + now.getMinutes() - clockIn
          }
        })
      }

      return worktime
    }

    /**
     * Convert a string (..h.. or -..h..) into a number of minutes
     * @param {String} str
     */
    function stringToMinutes (str) {
      const match = /^(-)?\s*(\d+)\s*[h:]\s*(\d+)$/.exec((str || '').trim())
      if (!match) { return 0 }

      const minutes = (parseInt(match[2]) * 60) + parseInt(match[3])

      return match[1] ? minutes * -1 : minutes
    }

    /**
     * Take a number of minutes and return a formated string (..h.. or -..h..)
     * @param {Integer} nbMinutes
     */
    function minutesToString (nbMinutes) {
      if (!nbMinutes && nbMinutes !== 0) { return 'N/A' }

      let hours = Math.floor(Math.abs(nbMinutes / 60)).toString().padStart(2, '0');
      let minutes = Math.abs(nbMinutes % 60).toString().padStart(2, '0');

      return nbMinutes >= 0 ? `${hours}h${minutes}` : `-${hours}h${minutes}`
    }

    /**
     * Take a date and return a formated string (..h.. or -..h..)
     * @param {Integer} date
     */
    function dateToString(date) {
      if (!date) { return 'N/A'; }

      const hours = date.getHours().toString().padStart(2, '0');
      const minutes = date.getMinutes().toString().padStart(2, '0');

      return `${hours}h${minutes}`;
    }

    function getPunchUrl(date) {
      return `${window.location.origin}/fr/time/punch/${userId}/${date}`;
    }

    /**
     * Get clockings of the given date
     */
    function getClockings(date) {
      return fetch(getPunchUrl(date))
        .then((response) => {
          if (!response.ok) {
            throw new Error(`Request failed with status ${response.status}`);
          }
          return response.text();
        })
        .then((text) => {
          const parser = new DOMParser();
          const clockingPage = parser.parseFromString(text, 'text/html');
          const clockings = [];

          const inputs = clockingPage.querySelectorAll('#punches_collection tr:not(.tr-punch-canceled) input[type=time]');

          for (let i = 0; i < inputs.length; i += 2) {
            clockings.push({
              clockIn: stringToMinutes(inputs[i]?.value),
              clockOut: stringToMinutes(inputs[i + 1]?.value),
            });
          }

          return clockings;
        });
    }

    /**
     * Insert extension specific styles in the document head
     */
    function insertStyles() {
      const style = document.createElement('style');

      style.textContent = `
        .ams-float-right
        {
          float: right;
        }

        .ams-summary > button,
        .ams-ml-4
        {
          margin-left: 4px;
        }

        .ams-btn {
          background-color: transparent;
          color: #444;
          border-color: #AAA;
          min-width: 30px;
        }
        .ams-btn-primary {
          background-color: transparent;
          color: #4583aa;
          border-color: #4583aa;
        }
        .bankHolidayTr {
          background: repeating-linear-gradient(45deg, #ffe7b8, #ffe7b8 10px, #ffffff 10px, #ffffff 20px);
        }
      `;

      document.head.appendChild(style);
    }
  }
})()<|endoftext|>


<commit_before>// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Lang = imports.lang;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const Gettext = imports.gettext.domain('text-scaler');
const _ = Gettext.gettext;

const DEFAULT_VALUE = 1.00;
const MIN_VALUE = 0.50;
const MAX_VALUE = 3.00;

const NUM_DECIMALS = 2;

const TEXT_SCALING_FACTOR_KEY = 'text-scaling-factor';

// Makes sure that the value is in [MIN_VALUE, MAX_VALUE].
function _normalizeValue(value) {
    return Math.max(MIN_VALUE, Math.min(value, MAX_VALUE));
}

// Translates a value in [MIN_VALUE, MAX_VALUE] to [0.00, 1.00].
function _textScalingToSliderValue(textScaling) {
    return (textScaling - MIN_VALUE) / (MAX_VALUE - MIN_VALUE);
}

// Translates a value in [0.00, 1.00] to [MIN_VALUE, MAX_VALUE].
function _sliderValueToTextScaling(sliderValue) {
    return sliderValue * (MAX_VALUE - MIN_VALUE) + MIN_VALUE;
}

// Checks if a given float number matches the default one using NUM_DECIMALS.
function _isDefaultFloatValue(value) {
    return Math.abs(value - DEFAULT_VALUE) < (Math.pow(10, -NUM_DECIMALS) / 2);
}

var TextScalerButton = GObject.registerClass({
    GTypeName: 'TextScalerButton',
}, class A extends GObject.Object {
    constructor() {
        super();

        this.actor = new PanelMenu.Button(0.0, "Text Scaler Button");
        this.actor.setSensitive(true);

        // GSettings to change the text-scaling factor.
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
        this._settings.connect('changed::text-scaling-factor', (settings, key) => this._onSettingsChanged(settings, key));

        // The actual text scaling factor, as a float.
        this._currentValue = this._get_text_scaling_factor();

        // Panel menu icon.
        this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        this._hbox.add_actor(new St.Icon({ style_class: 'system-status-icon',
                                           icon_name: 'preferences-desktop-font' }));
        this._hbox.add_actor(PopupMenu.arrowIcon(St.Side.BOTTOM));
        this.actor.add_actor(this._hbox);

        // Popup Menu.
        this._menu = this.actor.menu;
        Main.panel.menuManager.addMenu(this._menu);

        this._menuItem = new PopupMenu.PopupBaseMenuItem({ activate: true });
        this._menuItem.connect('key-press-event', (actor, event) => this._onMenuItemKeyPressed(actor, event));
        this._menu.addMenuItem(this._menuItem);

        this._entry = new St.Entry();
        this._entry.clutter_text.connect('activate', (entry) => this._onEntryActivated(entry));
        this._entry.clutter_text.connect('key-focus-out', (entry) => this._onEntryKeyFocusOut(entry));
        this._menuItem.add_actor(this._entry);

        // The value currently displayed by the slider, normalized to [0.00, 1.00].
        this._sliderValue = _textScalingToSliderValue(this._currentValue);

        this._slider = new Slider.Slider(this._sliderValue);
        this._slider.connect('notify::value', (slider) => this._onSliderValueChanged(slider));
        this._slider.connect('drag-begin', (slider) => this._onSliderDragBegan(slider));
        this._slider.connect('drag-end', (slider) => this._onSliderDragEnded(slider));
        this._slider.x_expand = true;
        this._menuItem.add_actor(this._slider);

        this._sliderIsDragging = false;

        this._separatorItem = new PopupMenu.PopupSeparatorMenuItem();
        this._menu.addMenuItem(this._separatorItem);

        this._resetValueItem = new PopupMenu.PopupMenuItem(_("Reset to default value"));
        this._resetValueItem.connect('activate', (menuItem, event) => this._onResetValueActivate());
        this._menu.addMenuItem(this._resetValueItem);

        // Make sure we first update the UI with the current state.
        this._updateUI();
    }

    _onSettingsChanged(settings, key) {
        this._updateValue(this._get_text_scaling_factor(), false);
    }

    _onMenuItemKeyPressed(actor, event) {
        return this._slider.onKeyPressEvent(actor, event);
    }

    _onEntryActivated(entry) {
        this._updateValueFromTextEntry(entry);
    }

    _onEntryKeyFocusOut(entry) {
        this._updateValueFromTextEntry(entry);
    }

    _onSliderValueChanged(slider) {
        this._sliderValue = this._slider.value;
        this._updateEntry(_sliderValueToTextScaling(this._sliderValue));

        // We don't want to update the value when the user is explicitly
        // dragging the slider by clicking on the handle and moving it
        // around to avoid the unpredictable (and very confusing) behaviour
        // that would happen due to the mouse pointer being on a different
        // area of the screen right after updating the scaling factor.
        if (!this._sliderIsDragging)
            this._updateValue(_sliderValueToTextScaling(this._sliderValue));
    }

    _onSliderDragBegan(slider) {
        this._sliderIsDragging = true;
    }

    _onSliderDragEnded(slider) {
        // We don't update the scaling factor on 'value-changed'
        // when explicitly dragging, so we need to do it here too.
        this._updateValue(_sliderValueToTextScaling(this._sliderValue));
        this._sliderIsDragging = false;
    }

    _onResetValueActivate(menuItem, event) {
        this._updateValue(DEFAULT_VALUE);
    }

    _updateValueFromTextEntry(entry) {
        let currentText = entry.get_text();
        let value = parseFloat(currentText);

        // Only update the value if it's a valid one, otherwise
        // simply reset the UI to show the current status again.
        if (isFinite(currentText) && !isNaN(currentText) && !isNaN(value)) {
            this._updateValue(value);
        }

        // Force to always update the UI to make sure that whatever
        // value gets actually applied is displayed as it should be.
        this._updateUI();
    }

    // Reads the text scaling factor from GSettings and returns a valid double.
    _get_text_scaling_factor() {
        let gsettings_value = this._settings.get_double(TEXT_SCALING_FACTOR_KEY);
        if (isNaN(gsettings_value))
            return DEFAULT_VALUE;
        return gsettings_value;
    }

    _updateSettings() {
        this._settings.set_double(TEXT_SCALING_FACTOR_KEY, this._currentValue);
    }

    _updateValue(value, updateSettings=false) {
        if (this._currentValue == value)
            return;

        // Need to keep the value between the valid limits.
        this._currentValue = _normalizeValue(value);

        // We don't always want to update the GSettings (e.g. external change).
        if (!updateSettings) {
            this._updateSettings();
        }

        // Always affect the UI to reflect changes.
        this._updateUI();
    }

    _updateUI() {
        this._updateEntry();
        this._updateSlider();
        this._updateResetValueItem();
    }

    _updateEntry(value=null) {
        let valueToDisplay = (value != null) ? value : this._currentValue;

        // We only show NUM_DECIMALS decimals on the text entry widget.
        this._entry.set_text(valueToDisplay.toFixed(NUM_DECIMALS));
    }

    _updateSlider() {
        this._slider.value = _textScalingToSliderValue(this._currentValue);
    }

    _updateResetValueItem() {
        this._resetValueItem.setSensitive(!_isDefaultFloatValue(this._currentValue));
    }
}
);

let _button = null;

function init() {
    Convenience.initTranslations("text-scaler");
}

function enable() {
    _button = new TextScalerButton();
    Main.panel.addToStatusArea('text-scaler-button', _button.actor);
}

function disable() {
    _button.destroy();
    _button = null;
}
<commit_msg>Destroy the button's Clutter actor, not the button itself<commit_after>// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Lang = imports.lang;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const St = imports.gi.St;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const Gettext = imports.gettext.domain('text-scaler');
const _ = Gettext.gettext;

const DEFAULT_VALUE = 1.00;
const MIN_VALUE = 0.50;
const MAX_VALUE = 3.00;

const NUM_DECIMALS = 2;

const TEXT_SCALING_FACTOR_KEY = 'text-scaling-factor';

// Makes sure that the value is in [MIN_VALUE, MAX_VALUE].
function _normalizeValue(value) {
    return Math.max(MIN_VALUE, Math.min(value, MAX_VALUE));
}

// Translates a value in [MIN_VALUE, MAX_VALUE] to [0.00, 1.00].
function _textScalingToSliderValue(textScaling) {
    return (textScaling - MIN_VALUE) / (MAX_VALUE - MIN_VALUE);
}

// Translates a value in [0.00, 1.00] to [MIN_VALUE, MAX_VALUE].
function _sliderValueToTextScaling(sliderValue) {
    return sliderValue * (MAX_VALUE - MIN_VALUE) + MIN_VALUE;
}

// Checks if a given float number matches the default one using NUM_DECIMALS.
function _isDefaultFloatValue(value) {
    return Math.abs(value - DEFAULT_VALUE) < (Math.pow(10, -NUM_DECIMALS) / 2);
}

var TextScalerButton = GObject.registerClass({
    GTypeName: 'TextScalerButton',
}, class A extends GObject.Object {
    constructor() {
        super();

        this.actor = new PanelMenu.Button(0.0, "Text Scaler Button");
        this.actor.setSensitive(true);

        // GSettings to change the text-scaling factor.
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
        this._settings.connect('changed::text-scaling-factor', (settings, key) => this._onSettingsChanged(settings, key));

        // The actual text scaling factor, as a float.
        this._currentValue = this._get_text_scaling_factor();

        // Panel menu icon.
        this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        this._hbox.add_actor(new St.Icon({ style_class: 'system-status-icon',
                                           icon_name: 'preferences-desktop-font' }));
        this._hbox.add_actor(PopupMenu.arrowIcon(St.Side.BOTTOM));
        this.actor.add_actor(this._hbox);

        // Popup Menu.
        this._menu = this.actor.menu;
        Main.panel.menuManager.addMenu(this._menu);

        this._menuItem = new PopupMenu.PopupBaseMenuItem({ activate: true });
        this._menuItem.connect('key-press-event', (actor, event) => this._onMenuItemKeyPressed(actor, event));
        this._menu.addMenuItem(this._menuItem);

        this._entry = new St.Entry();
        this._entry.clutter_text.connect('activate', (entry) => this._onEntryActivated(entry));
        this._entry.clutter_text.connect('key-focus-out', (entry) => this._onEntryKeyFocusOut(entry));
        this._menuItem.add_actor(this._entry);

        // The value currently displayed by the slider, normalized to [0.00, 1.00].
        this._sliderValue = _textScalingToSliderValue(this._currentValue);

        this._slider = new Slider.Slider(this._sliderValue);
        this._slider.connect('notify::value', (slider) => this._onSliderValueChanged(slider));
        this._slider.connect('drag-begin', (slider) => this._onSliderDragBegan(slider));
        this._slider.connect('drag-end', (slider) => this._onSliderDragEnded(slider));
        this._slider.x_expand = true;
        this._menuItem.add_actor(this._slider);

        this._sliderIsDragging = false;

        this._separatorItem = new PopupMenu.PopupSeparatorMenuItem();
        this._menu.addMenuItem(this._separatorItem);

        this._resetValueItem = new PopupMenu.PopupMenuItem(_("Reset to default value"));
        this._resetValueItem.connect('activate', (menuItem, event) => this._onResetValueActivate());
        this._menu.addMenuItem(this._resetValueItem);

        // Make sure we first update the UI with the current state.
        this._updateUI();
    }

    _onSettingsChanged(settings, key) {
        this._updateValue(this._get_text_scaling_factor(), false);
    }

    _onMenuItemKeyPressed(actor, event) {
        return this._slider.onKeyPressEvent(actor, event);
    }

    _onEntryActivated(entry) {
        this._updateValueFromTextEntry(entry);
    }

    _onEntryKeyFocusOut(entry) {
        this._updateValueFromTextEntry(entry);
    }

    _onSliderValueChanged(slider) {
        this._sliderValue = this._slider.value;
        this._updateEntry(_sliderValueToTextScaling(this._sliderValue));

        // We don't want to update the value when the user is explicitly
        // dragging the slider by clicking on the handle and moving it
        // around to avoid the unpredictable (and very confusing) behaviour
        // that would happen due to the mouse pointer being on a different
        // area of the screen right after updating the scaling factor.
        if (!this._sliderIsDragging)
            this._updateValue(_sliderValueToTextScaling(this._sliderValue));
    }

    _onSliderDragBegan(slider) {
        this._sliderIsDragging = true;
    }

    _onSliderDragEnded(slider) {
        // We don't update the scaling factor on 'value-changed'
        // when explicitly dragging, so we need to do it here too.
        this._updateValue(_sliderValueToTextScaling(this._sliderValue));
        this._sliderIsDragging = false;
    }

    _onResetValueActivate(menuItem, event) {
        this._updateValue(DEFAULT_VALUE);
    }

    _updateValueFromTextEntry(entry) {
        let currentText = entry.get_text();
        let value = parseFloat(currentText);

        // Only update the value if it's a valid one, otherwise
        // simply reset the UI to show the current status again.
        if (isFinite(currentText) && !isNaN(currentText) && !isNaN(value)) {
            this._updateValue(value);
        }

        // Force to always update the UI to make sure that whatever
        // value gets actually applied is displayed as it should be.
        this._updateUI();
    }

    // Reads the text scaling factor from GSettings and returns a valid double.
    _get_text_scaling_factor() {
        let gsettings_value = this._settings.get_double(TEXT_SCALING_FACTOR_KEY);
        if (isNaN(gsettings_value))
            return DEFAULT_VALUE;
        return gsettings_value;
    }

    _updateSettings() {
        this._settings.set_double(TEXT_SCALING_FACTOR_KEY, this._currentValue);
    }

    _updateValue(value, updateSettings=false) {
        if (this._currentValue == value)
            return;

        // Need to keep the value between the valid limits.
        this._currentValue = _normalizeValue(value);

        // We don't always want to update the GSettings (e.g. external change).
        if (!updateSettings) {
            this._updateSettings();
        }

        // Always affect the UI to reflect changes.
        this._updateUI();
    }

    _updateUI() {
        this._updateEntry();
        this._updateSlider();
        this._updateResetValueItem();
    }

    _updateEntry(value=null) {
        let valueToDisplay = (value != null) ? value : this._currentValue;

        // We only show NUM_DECIMALS decimals on the text entry widget.
        this._entry.set_text(valueToDisplay.toFixed(NUM_DECIMALS));
    }

    _updateSlider() {
        this._slider.value = _textScalingToSliderValue(this._currentValue);
    }

    _updateResetValueItem() {
        this._resetValueItem.setSensitive(!_isDefaultFloatValue(this._currentValue));
    }
}
);

let _button = null;

function init() {
    Convenience.initTranslations("text-scaler");
}

function enable() {
    _button = new TextScalerButton();
    Main.panel.addToStatusArea('text-scaler-button', _button.actor);
}

function disable() {
    _button.actor.destroy();
    _button = null;
}
<|endoftext|>


<commit_before>/* global describe:true, it:true, before:true, beforeEach:true, afterEach: true, define:true */
define(['spec/support/spec_helper'], function (helpers) {
  "use strict";

  var auth,
      appMock;
  describe('The authentication module', function () {

    describe('initialization of the Auth module', function () {
      var evts;
      beforeEach(helpers.reset('lib/client/auth', function (module) {
        evts = {};
        appMock = { sandbox: {} };
        appMock.core = {
          mediator: {
            on: function (evt, fn) { evts[evt] = fn; }
          }
        };
        auth = module(appMock);
        auth.initialize();
      }));
      it('should not be authenticating', function () {
        auth.isAuthenticating.should.be.a('function');
        auth.isAuthenticating().should.be.false;
      });
      it('should expose a function `authenticating` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('authenticating');
        appMock.sandbox.authenticating.should.equal(auth.isAuthenticating);
      });
      it('should expose a function `login` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('login');
        appMock.sandbox.login.should.equal(auth.login);
      });
      it('should expose a function `logout` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('logout');
        appMock.sandbox.logout.should.equal(auth.logout);
      });
      it('should add a listener on `hull.authComplete` event', function () {
        evts.should.have.keys(['hull.authComplete']);
        evts['hull.authComplete'].should.be.a('function');
      });
    });
  });
});
<commit_msg>Tests the method generating the Authentication URL<commit_after>/* global describe:true, it:true, before:true, beforeEach:true, afterEach: true, define:true */
define(['spec/support/spec_helper'], function (helpers) {
  "use strict";

  describe('The authentication module', function () {
    describe('initialization of the Auth module', function () {
      var auth,
          appMock,
          evts;
      beforeEach(helpers.reset('lib/client/auth', function (module) {
        evts = {};
        appMock = { sandbox: {} };
        appMock.core = {
          mediator: {
            on: function (evt, fn) { evts[evt] = fn; }
          }
        };
        auth = module(appMock);
        auth.initialize();
      }));
      it('should not be authenticating', function () {
        auth.isAuthenticating.should.be.a('function');
        auth.isAuthenticating().should.be.false;
      });
      it('should expose a function `authenticating` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('authenticating');
        appMock.sandbox.authenticating.should.equal(auth.isAuthenticating);
      });
      it('should expose a function `login` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('login');
        appMock.sandbox.login.should.equal(auth.login);
      });
      it('should expose a function `logout` in the sandbox', function () {
        appMock.sandbox.should.contain.keys('logout');
        appMock.sandbox.logout.should.equal(auth.logout);
      });
      it('should add a listener on `hull.authComplete` event', function () {
        evts.should.have.keys(['hull.authComplete']);
        evts['hull.authComplete'].should.be.a('function');
      });
    });

    describe('The authentication URL', function () {
      var authUrlFn,
          authModule,
          config = {appId: 'appId', orgUrl: 'orgUrl'};
      before(helpers.reset('lib/client/auth', function (module) {
        authModule = module({});
        authUrlFn = authModule.authUrl;
        authModule.location = 'app_location'; //Mocks the document.location
      }));
      it('should append the callback_url and auth_referer', function () {
        authUrlFn(config, 'provider').should.eql('orgUrl/auth/provider?app_id=appId&callback_url=app_location&auth_referer=app_location');
      });
      it('should be possible to specify callback_url', function () {
        var config = {appId: 'appId', orgUrl: 'orgUrl', callback_url: 'callback_url'};
        authUrlFn(config, 'provider').should.eql('orgUrl/auth/provider?app_id=appId&callback_url=callback_url&auth_referer=app_location');
      });
      it('should append any custom options', function () {
        var config = {appId: 'appId', orgUrl: 'orgUrl'};
        var opts = {a:'0', b: '1', c:'2'};
        authUrlFn(config, 'provider', opts).should.eql('orgUrl/auth/provider?a=0&b=1&c=2&app_id=appId&callback_url=app_location&auth_referer=app_location');
      });
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  grunt.initConfig({
    pkg: grunt.file.readJSON('bower.json'),
    appConfig: {
      app: 'src',
      name: 'Esef Frontend',
      dist: 'dist'
    },
    banner: '/*!\n' +
            ' * <%= pkg.name %> - v<%= pkg.version %>\n' +
            ' * <%= pkg.homepage %>\n' +
            ' * <%= grunt.template.today("yyyy-mm-dd") %>\n' +
            ' * Author: <%= pkg.author.name %>\n' +
            ' * Copyright (c) <%= grunt.template.today("yyyy") %> Egemsoft' +
            ' * License: <%= pkg.license %>\n' +
            ' */\n',
    concat: {
      js: {
        options: {
          banner: '\'use strict\';\n<%= banner %>\n',
          // remove use strict tags
          process: function(src) {
            return src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1');
          },
          stripBanners: true
        },
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.js': ['<%= appConfig.app %>/{,*/}{,scripts/}{,*/}*.js'],
          '<%= appConfig.dist %>/pagination/pagination.js': ['<%= appConfig.app %>/pagination/{,scripts/}{,*/}*.js'],
          '<%= appConfig.dist %>/storage/storage.js': ['<%= appConfig.app %>/storage/{,scripts/}{,*/}*.js']
        }
      },
      css: {
        options: {
          banner: '<%= banner %>\n'
        },
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.css': ['<%= appConfig.app %>/{,*/}styles/{,*/}*.css']
        }
      }
    },
    connect: {
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= appConfig.app %>'
          ]
        }
      }
    },
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= appConfig.dist %>/*',
            '!<%= appConfig.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= appConfig.app %>/{,*/}scripts/{,*/}*.js'
      ]
    },
    cssmin: {
      dist: {
        files: {
          '<%= appConfig.dist %>/css/esef-frontend.min.css': [
            '.tmp/styles/{,*/}*.css',
            '<%= appConfig.app %>/{,*/}styles/{,*/}*.css'
          ]
        }
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= appConfig.dist %>/',
          src: '{,*/}*.js',
          dest: '.tmp'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.min.js': ['.tmp/bundle/**.js'],
          '<%= appConfig.dist %>/storage/storage.min.js': ['.tmp/storage/**.js'],
          '<%= appConfig.dist %>/pagination/pagination.min.js': ['.tmp/pagination/**.js']
        }
      }
    },
    ngdocs: {
      options: {
        dest: 'docs',
        html5Mode: false,
        title: '<%= appConfig.name %> Documentation'
      },
      all: ['<%= appConfig.app %>/{,*/}{,scripts/}{,*/}*.js']
    }
  });

  grunt.registerTask('test', [
    'jshint',
    'clean:server',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'concat:js',
    // 'concat:css',
    'ngmin',
    // 'cssmin',
    'uglify'
  ]);

  grunt.registerTask('default', [
    'test',
    'build',
    'ngdocs'
  ]);

};<commit_msg>build task updated to generate docs<commit_after>'use strict';

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  grunt.initConfig({
    pkg: grunt.file.readJSON('bower.json'),
    appConfig: {
      app: 'src',
      name: 'Esef Frontend',
      dist: 'dist'
    },
    banner: '/*!\n' +
            ' * <%= pkg.name %> - v<%= pkg.version %>\n' +
            ' * <%= pkg.homepage %>\n' +
            ' * <%= grunt.template.today("yyyy-mm-dd") %>\n' +
            ' * Author: <%= pkg.author.name %>\n' +
            ' * Copyright (c) <%= grunt.template.today("yyyy") %> Egemsoft' +
            ' * License: <%= pkg.license %>\n' +
            ' */\n',
    concat: {
      js: {
        options: {
          banner: '\'use strict\';\n<%= banner %>\n',
          // remove use strict tags
          process: function(src) {
            return src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1');
          },
          stripBanners: true
        },
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.js': ['<%= appConfig.app %>/{,*/}{,scripts/}{,*/}*.js'],
          '<%= appConfig.dist %>/pagination/pagination.js': ['<%= appConfig.app %>/pagination/{,scripts/}{,*/}*.js'],
          '<%= appConfig.dist %>/storage/storage.js': ['<%= appConfig.app %>/storage/{,scripts/}{,*/}*.js']
        }
      },
      css: {
        options: {
          banner: '<%= banner %>\n'
        },
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.css': ['<%= appConfig.app %>/{,*/}styles/{,*/}*.css']
        }
      }
    },
    connect: {
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= appConfig.app %>'
          ]
        }
      }
    },
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= appConfig.dist %>/*',
            '!<%= appConfig.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= appConfig.app %>/{,*/}scripts/{,*/}*.js'
      ]
    },
    cssmin: {
      dist: {
        files: {
          '<%= appConfig.dist %>/css/esef-frontend.min.css': [
            '.tmp/styles/{,*/}*.css',
            '<%= appConfig.app %>/{,*/}styles/{,*/}*.css'
          ]
        }
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        singleRun: true
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= appConfig.dist %>/',
          src: '{,*/}*.js',
          dest: '.tmp'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= appConfig.dist %>/bundle/esef-frontend.min.js': ['.tmp/bundle/**.js'],
          '<%= appConfig.dist %>/storage/storage.min.js': ['.tmp/storage/**.js'],
          '<%= appConfig.dist %>/pagination/pagination.min.js': ['.tmp/pagination/**.js']
        }
      }
    },
    ngdocs: {
      options: {
        dest: 'docs',
        html5Mode: false,
        title: '<%= appConfig.name %> Documentation'
      },
      all: ['<%= appConfig.app %>/{,*/}{,scripts/}{,*/}*.js']
    }
  });

  grunt.registerTask('test', [
    'jshint',
    'clean:server',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'concat:js',
    // 'concat:css',
    'ngmin',
    // 'cssmin',
    'uglify',
    'ngdocs'
  ]);

  grunt.registerTask('default', [
    'test',
    'build'
  ]);

};<|endoftext|>


<commit_before>module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        mongodb : {
            test: {
                host: 'mongodb://localhost:27017/test',
                collections: ['users','contentTypes','nodes','content', 'tokens']
            }
        },
        concurrent: {
            test: {
                tasks: ['mongodb:test', 'shell:stopTestServer', 'shell:startTestServer'],
                options: {
                    logConcurrentOutput: true
                }
            }
        },
        shell : {
            startTestServer: {
                command: "node lib/grasshopper-api test",
                options : {
                    stdout : true,
                    stderr : true
                }
            },
            stopTestServer: {
                command: "tasks/killserver.sh lib/grasshopper-api",
                options : {
                    stdout : true,
                    stderr : true
                }
            },
            startServer: {
                command : "pm2 start lib/grasshopper-api.js -i max -e log/grasshopper.err.log -o log/grasshopper.out.log",
                options: {
                    stout: true,
                    stderr: true
                }
            },
            stopServer : {
                command : "pm2 stop all",
                options: {
                    stout: true,
                    stderr: true
                }
            },
            restartServer : {
                command : "pm2 reload all",
                options: {
                    stout: true,
                    stderr: true
                }
            }
        },
        nodemon: {
            dev: {
                options: {
                    file: 'lib/grasshopper-api.js',
                    ignoredFiles: ['README.md', 'node_modules/**', 'Gruntfile.js','*.log', '*.xml'],
                    legacyWatch: true,
                    env: {
                        PORT: '3000'
                    },
                    cwd: __dirname
                }
            },
            test: {
                options: {
                    file: 'lib/grasshopper-api.js',
                    args: ['test'],
                    ignoredFiles: ['README.md', 'node_modules/**', 'Gruntfile.js','*.log', '*.xml'],
                    legacyWatch: true,
                    env: {
                        PORT: '3000'
                    },
                    cwd: __dirname
                }
            }
        },
        jshint: {
            files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'],
            options: {
                // options here to override JSHint defaults
                globals: {
                    jQuery: true,
                    console: true,
                    module: true,
                    document: true
                }
            }
        },
        watch: {
            files: ['<%= jshint.files %>'],
            tasks: ['jshint']
        }
    });

    grunt.loadTasks('tasks');

    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-nodemon');
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-concurrent');

    grunt.registerTask('dev',['nodemon:dev']);
    grunt.registerTask('test', ['concurrent:test']);

    grunt.registerTask('server:start', ['shell:startServer']);
    grunt.registerTask('server:stop', ['shell:stopServer']);
    grunt.registerTask('server:restart', ['shell:restartServer']);

    grunt.registerTask('default', ['jshint']);

};<commit_msg>Fixed syntax issue with restarting server.<commit_after>module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        mongodb : {
            test: {
                host: 'mongodb://localhost:27017/test',
                collections: ['users','contentTypes','nodes','content', 'tokens']
            }
        },
        concurrent: {
            test: {
                tasks: ['mongodb:test', 'shell:stopTestServer', 'shell:startTestServer'],
                options: {
                    logConcurrentOutput: true
                }
            }
        },
        shell : {
            startTestServer: {
                command: "node lib/grasshopper-api test",
                options : {
                    stdout : true,
                    stderr : true
                }
            },
            stopTestServer: {
                command: "tasks/killserver.sh lib/grasshopper-api",
                options : {
                    stdout : true,
                    stderr : true
                }
            },
            startServer: {
                command : "pm2 start lib/grasshopper-api.js -i max -e log/grasshopper.err.log -o log/grasshopper.out.log",
                options: {
                    stout: true,
                    stderr: true
                }
            },
            stopServer : {
                command : "pm2 stop all",
                options: {
                    stout: true,
                    stderr: true
                }
            },
            restartServer : {
                command : "pm2 restart all",
                options: {
                    stout: true,
                    stderr: true
                }
            }
        },
        nodemon: {
            dev: {
                options: {
                    file: 'lib/grasshopper-api.js',
                    ignoredFiles: ['README.md', 'node_modules/**', 'Gruntfile.js','*.log', '*.xml'],
                    legacyWatch: true,
                    env: {
                        PORT: '3000'
                    },
                    cwd: __dirname
                }
            },
            test: {
                options: {
                    file: 'lib/grasshopper-api.js',
                    args: ['test'],
                    ignoredFiles: ['README.md', 'node_modules/**', 'Gruntfile.js','*.log', '*.xml'],
                    legacyWatch: true,
                    env: {
                        PORT: '3000'
                    },
                    cwd: __dirname
                }
            }
        },
        jshint: {
            files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'],
            options: {
                // options here to override JSHint defaults
                globals: {
                    jQuery: true,
                    console: true,
                    module: true,
                    document: true
                }
            }
        },
        watch: {
            files: ['<%= jshint.files %>'],
            tasks: ['jshint']
        }
    });

    grunt.loadTasks('tasks');

    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-nodemon');
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-concurrent');

    grunt.registerTask('dev',['nodemon:dev']);
    grunt.registerTask('test', ['concurrent:test']);

    grunt.registerTask('server:start', ['shell:startServer']);
    grunt.registerTask('server:stop', ['shell:stopServer']);
    grunt.registerTask('server:restart', ['shell:restartServer']);

    grunt.registerTask('default', ['jshint']);

};<|endoftext|>