<commit_before>/*jslint node: true */
'use strict';

var pkg = require('./package.json');

//Using exclusion patterns slows down Grunt significantly
//instead of creating a set of patterns like '**/*.js' and '!**/node_modules/**'
//this method is used to create a set of inclusive patterns for all subdirectories
//skipping node_modules, bower_components, dist, and any .dirs
//This enables users to create any directory structure they desire.
var createFolderGlobs = function(fileTypePatterns) {
  fileTypePatterns = Array.isArray(fileTypePatterns) ? fileTypePatterns : [fileTypePatterns];
  var ignore = ['node_modules','bower_components','dist','temp'];
  var fs = require('fs');
  return fs.readdirSync(process.cwd())
          .map(function(file){
            if (ignore.indexOf(file) !== -1 ||
                file.indexOf('.') === 0 ||
                !fs.lstatSync(file).isDirectory()) {
              return null;
            } else {
              return fileTypePatterns.map(function(pattern) {
                return file + '/**/' + pattern;
              });
            }
          })
          .filter(function(patterns){
            return patterns;
          })
          .concat(fileTypePatterns);
};

module.exports = function (grunt) {

  // load all grunt tasks
  require('load-grunt-tasks')(grunt);

  // Project configuration.
  grunt.initConfig({
    connect: {
      main: {
        options: {
          port: 9001
        }
      }
    },
    watch: {
      main: {
        options: {
            livereload: true,
            livereloadOnError: false,
            spawn: false
        },
        files: [createFolderGlobs(['*.js','*.less','*.html']),'!_SpecRunner.html','!.grunt'],
        tasks: [] //all the tasks are run dynamically during the watch event handler
      }
    },
    jshint: {
      main: {
        options: {
            jshintrc: '.jshintrc'
        },
        src: createFolderGlobs('*.js')
      }
    },
    clean: {
      before:{
        src:['dist','temp']
      },
      after: {
        src:['temp']
      }
    },
    less: {
      production: {
        options: {
        },
        files: [{
          expand: true,
          src: ['bower_components/avCommon/themes/**/app.less'],
          dest: 'temp/',
          ext: '.css',
        }]
      }
    },
    autoprefixer: {
      options: {
        browsers: ['ie >= 8', 'ff > 4', 'last 8 versions']
      },
      main: {
        src: 'temp/bower_components/avCommon/themes/**/app.css'
      }
    },
    ngtemplates: {
      main: {
        options: {
            module: pkg.name,
            htmlmin:'<%= htmlmin.main.options %>'
        },
        src: [createFolderGlobs('*.html'),'!index.html','!_SpecRunner.html'],
        dest: 'temp/templates.js'
      }
    },
    copy: {
      main: {
        files: [
          {src: ['img/**'], dest: 'dist/'},
          {src: ['img/**'], dest: 'dist/'},
          {src: ['temp_data/**'], dest: 'dist/'},
          {
            expand: true,
            cwd:'bower_components/avCommon/themes',
            src: ['**/*.png'],
            dest: 'dist/themes/',
            ext: '.png',
            extDot: 'first'
          },
          {
            expand: true,
            cwd: 'bower_components/bootstrap/fonts/',
            src: ['**'],
            dest: 'dist/themes/fonts/'
          },
          {
            expand: true,
            cwd: 'bower_components/bootstrap/fonts/',
            src: ['**'],
            dest: 'dist/themes/fonts/'
          },
          {
            expand: true,
            cwd: 'bower_components/font-awesome/fonts/',
            src: ['**'],
            dest: 'dist/fonts/'
          }
          //{src: ['bower_components/angular-ui-utils/ui-utils-ieshiv.min.js'], dest: 'dist/'},
          //{src: ['bower_components/select2/*.png','bower_components/select2/*.gif'], dest:'dist/css/',flatten:true,expand:true},
          //{src: ['bower_components/angular-mocks/angular-mocks.js'], dest: 'dist/'}
        ]
      }
    },
    dom_munger:{
      read: {
        options: {
          read:[
            {selector:'script[class="libnocompat"]',attribute:'src',writeto:'libnocompatjs'},
            {selector:'script[class="libcompat"]',attribute:'src',writeto:'libcompatjs'},
            {selector:'script[class="lib"]',attribute:'src',writeto:'libjs'},
            {selector:'script[class="app"]',attribute:'src',writeto:'appjs'},
            {selector:'link[rel="stylesheet"][data-concat!="false"]',attribute:'href',writeto:'appcss'}
          ]
        },
        src: 'index.html'
      },
      update: {
        options: {
          remove: ['script[data-remove!="false"]','link[data-remove!="false"]'],
          append: [
            {selector:'body',html:'<!--[if lte IE 8]><script src="/booth/libcompat-v3.0.1.min.js"></script><![endif]--><!--[if gte IE 9]><script src="/booth/libnocompat-v3.0.1.min.js"></script><![endif]--><!--[if !IE]><!--><script src="/booth/libnocompat-v3.0.1.min.js"></script><!--<![endif]-->'},
            {selector:'body',html:'<!--All the source code of this program under copyright. Take a look at the license details at https://github.com/agoravoting/agora-core-view/blob/master/README.md -->'},
            {selector:'body',html:'<script src="/booth/lib-v3.0.1.min.js"></script>'},
            {selector:'body',html:'<script src="/booth/avConfig-v3.0.1.js"></script>'},
            {selector:'body',html:'<script src="/booth/avThemes-v3.0.1.js"></script>'},
            {selector:'body',html:'<script src="/booth/app-v3.0.1.min.js"></script>'},
            {selector:'body',html:'<script src="/booth/avPlugins-v3.0.1.js"></script>'},
            {selector:'head',html:'<link rel="stylesheet" id="theme" data-base="/booth/" href="/booth/themes/default/app.min.css">'}
          ]
        },
        src:'index.html',
        dest: 'dist/index.html'
      }
    },
    cssmin: {
      main: {
        files: [{
            expand: true,
            cwd:'temp/bower_components/avCommon/themes',
            src: ['**/app.css'],
            dest: 'dist/themes/',
            ext: '.min.css',
            extDot: 'first'
        }]
      },
    },
    concat: {
      main: {
        files: {
          'temp/libcompat.js': [
            'bower_components/avCommon/dist/libcompat-v3.0.1.js',
            '<%= dom_munger.data.libcompatjs %>'
          ],
          'temp/libnocompat.js': [
            'bower_components/avCommon/dist/libnocompat-v3.0.1.js',
            '<%= dom_munger.data.libnocompatjs %>'
          ],
          'temp/lib.js': ['<%= dom_munger.data.libjs %>'],
          'temp/app.js': ['<%= dom_munger.data.appjs %>','<%= ngtemplates.main.dest %>'],
          'dist/avConfig-v3.0.1.js': ['avConfig.js'],
          'dist/avThemes-v3.0.1.js': ['bower_components/avCommon/dist/avThemes-v3.0.1.js'],
          'dist/avPlugins-v3.0.1.js': ['plugins/**/*.js']
        }
      }
    },
    "merge-json": {
      main: {
        files: {
            "dist/locales/en.json": ["locales/en.json", "plugins/**/locales/en.json"],
            "dist/locales/es.json": ["locales/es.json", "plugins/**/locales/es.json"],
            "dist/locales/gl.json": ["locales/gl.json", "plugins/**/locales/gl.json"],
            "dist/locales/ca.json": ["locales/ca.json", "plugins/**/locales/ca.json"]
        }
      }
    },
    ngAnnotate: {
      main: {
        files: {
        'temp/app.js':['temp/app.js'],
        'temp/lib.js': ['temp/lib.js'],
        'temp/libnocompat.js': ['temp/libnocompat.js'],
        'temp/libcompat.js': ['temp/libcompat.js']
        }
      }
    },
    uglify: {
      main: {
        options:{
          mangle: false,
          compress: {},
          beautify: true
        },
        files: {
          'dist/app-v3.0.1.min.js': 'temp/app.js',
          'dist/lib-v3.0.1.min.js': 'temp/lib.js',
          'dist/libnocompat-v3.0.1.min.js': 'temp/libnocompat.js',
          'dist/libcompat-v3.0.1.min.js': 'temp/libcompat.js',
          'dist/avWidgets.min.js': 'avWidgets.js',

          "dist/locales/moment/es.js": "bower_components/moment/lang/es.js",
          "dist/locales/moment/gl.js": "bower_components/moment/lang/gl.js",
          "dist/locales/moment/ca.js": "bower_components/moment/lang/ca.js"
        }
      }
    },
    htmlmin: {
      main: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          removeAttributeQuotes: false,
          removeComments: false,
          removeEmptyAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true
        },
        files: {
          'dist/index.html': 'dist/index.html'
        }
      }
    },
    imagemin: {
      main:{
        files: [{
          expand: true, cwd:'dist/',
          src:['**/{*.png,*.jpg}'],
          dest: 'dist/'
        }]
      }
    },
    karma: {
      options: {
        frameworks: ['jasmine'],
        files: [  //this files data is also updated in the watch handler, if updated change there too
          '<%= dom_munger.data.libnocompatjs %>',
          '<%= dom_munger.data.libjs %>',
          'avConfig.js',
          'avThemes.js',
          'avWidgets.js',
          '<%= dom_munger.data.appjs %>',
          '<%= ngtemplates.main.dest %>',
          'bower_components/angular-mocks/angular-mocks.js',
          createFolderGlobs('*-spec.js')
        ],
        logLevel:'ERROR',
        reporters:['mocha'],
        autoWatch: false, //watching is handled by grunt-contrib-watch
        singleRun: true
      },
      all_tests: {
        browsers: ['PhantomJS','Chrome','Firefox']
      },
      during_watch: {
        browsers: ['PhantomJS']
      },
    },
    protractor: {
      options: {
        configFile: "node_modules/protractor/referenceConf.js", // Default config file
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false, // If true, protractor will not use colors in its output.
        args: {
        // Arguments passed to the command
      }
    },
    //your_target: {   // Grunt requires at least one target to run so you can simply put 'all: {}' here too.
      all: {
      options: {
        configFile: "e2e.conf.js", // Target-specific config file
        args: {} // Target-specific arguments
      }
    },
  },

  });

  grunt.registerTask('build',['jshint','clean:before','less','autoprefixer','dom_munger','ngtemplates','cssmin','concat','merge-json','ngAnnotate','uglify','copy','htmlmin','imagemin','clean:after']);
  grunt.registerTask('serve', ['dom_munger:read','jshint','connect', 'watch']);
  grunt.registerTask('test',['dom_munger:read','karma:all_tests']);

  grunt.event.on('watch', function(action, filepath) {
    //https://github.com/gruntjs/grunt-contrib-watch/issues/156

    var tasksToRun = [];

    if ((filepath.lastIndexOf('.json') !== -1 && filepath.lastIndexOf('.json') === filepath.length - 5) ||
        (filepath.lastIndexOf('.js') !== -1 && filepath.lastIndexOf('.js') === filepath.length - 3)) {

      //lint the changed js file
      grunt.config('jshint.main.src', filepath);
      tasksToRun.push('jshint');

      //find the appropriate unit t est for the changed file
      var spec = filepath;
      if (filepath.lastIndexOf('-spec.js') === -1 || filepath.lastIndexOf('-spec.js') !== filepath.length - 8) {
        spec = filepath.substring(0,filepath.length - 3) + '-spec.js';
      }

      //if the spec exists then lets run it
      if (grunt.file.exists(spec)) {
        var files = [].concat(grunt.config('dom_munger.data.libnocompatjs'));
        files.concat(grunt.config('dom_munger.data.libjs'));
        files.push('bower_components/angular-mocks/angular-mocks.js');
        files.push('avConfig.js');
        files.push('avThemes.js');
        files.push('avWidgets.js');
        files.concat(grunt.config('dom_munger.data.appjs'));
        files.concat(grunt.config('ngtemplates.main.dest'));
        files.push(spec);
        grunt.config('karma.options.files', files);
        tasksToRun.push('karma:during_watch');
      }
    }

    //if index.html changed, we need to reread the <script> tags so our next run of karma
    //will have the correct environment
    if (filepath === 'index.html') {
      tasksToRun.push('dom_munger:read');
    }

    grunt.config('watch.main.tasks',tasksToRun);

  });

  grunt.loadNpmTasks('grunt-protractor-runner');
};
<commit_msg>Checking avConfig.js existence and version<commit_after>/*jslint node: true */
'use strict';

var pkg = require('./package.json');
var AV_CONFIG_VERSION = '3.1.0';

//Using exclusion patterns slows down Grunt significantly
//instead of creating a set of patterns like '**/*.js' and '!**/node_modules/**'
//this method is used to create a set of inclusive patterns for all subdirectories
//skipping node_modules, bower_components, dist, and any .dirs
//This enables users to create any directory structure they desire.
var createFolderGlobs = function(fileTypePatterns) {
  fileTypePatterns = Array.isArray(fileTypePatterns) ? fileTypePatterns : [fileTypePatterns];
  var ignore = ['node_modules','bower_components','dist','temp'];
  var fs = require('fs');
  return fs.readdirSync(process.cwd())
          .map(function(file){
            if (ignore.indexOf(file) !== -1 ||
                file.indexOf('.') === 0 ||
                !fs.lstatSync(file).isDirectory()) {
              return null;
            } else {
              return fileTypePatterns.map(function(pattern) {
                return file + '/**/' + pattern;
              });
            }
          })
          .filter(function(patterns){
            return patterns;
          })
          .concat(fileTypePatterns);
};

module.exports = function (grunt) {

  // load all grunt tasks
  require('load-grunt-tasks')(grunt);

  // custom grunt task to check avConfig.js
  grunt.registerTask('check_config', function() {
    var fs = require('fs');
    var done = this.async();
    grunt.log.ok('Checking avConfig.js...');
    var conf = fs.readFile('avConfig.js', function(err, data) {
        if (err) {
            grunt.log.error('No avConfig.js file found');
            done(false);
        } else {
            var match = data.toString().match(/AV_CONFIG_VERSION = [\'\"]([\w\.]*)[\'\"];/);
            if (!match) {
                grunt.log.error('Invalid avConfig.js version');
            } else {
                var v = match[1];
                if (v === AV_CONFIG_VERSION) {
                    return done();
                } else {
                    grunt.log.error('Invalid avConfig.js version: ' + v);
                }
            }
            done(false);
        }
    });
  });

  // Project configuration.
  grunt.initConfig({
    connect: {
      main: {
        options: {
          port: 9001
        }
      }
    },
    watch: {
      main: {
        options: {
            livereload: true,
            livereloadOnError: false,
            spawn: false
        },
        files: [createFolderGlobs(['*.js','*.less','*.html']),'!_SpecRunner.html','!.grunt'],
        tasks: [] //all the tasks are run dynamically during the watch event handler
      }
    },
    jshint: {
      main: {
        options: {
            jshintrc: '.jshintrc'
        },
        src: createFolderGlobs('*.js')
      }
    },
    clean: {
      before:{
        src:['dist','temp']
      },
      after: {
        src:['temp']
      }
    },
    less: {
      production: {
        options: {
        },
        files: [{
          expand: true,
          src: ['bower_components/avCommon/themes/**/app.less'],
          dest: 'temp/',
          ext: '.css',
        }]
      }
    },
    autoprefixer: {
      options: {
        browsers: ['ie >= 8', 'ff > 4', 'last 8 versions']
      },
      main: {
        src: 'temp/bower_components/avCommon/themes/**/app.css'
      }
    },
    ngtemplates: {
      main: {
        options: {
            module: pkg.name,
            htmlmin:'<%= htmlmin.main.options %>'
        },
        src: [createFolderGlobs('*.html'),'!index.html','!_SpecRunner.html'],
        dest: 'temp/templates.js'
      }
    },
    copy: {
      main: {
        files: [
          {src: ['img/**'], dest: 'dist/'},
          {src: ['img/**'], dest: 'dist/'},
          {src: ['temp_data/**'], dest: 'dist/'},
          {
            expand: true,
            cwd:'bower_components/avCommon/themes',
            src: ['**/*.png'],
            dest: 'dist/themes/',
            ext: '.png',
            extDot: 'first'
          },
          {
            expand: true,
            cwd: 'bower_components/bootstrap/fonts/',
            src: ['**'],
            dest: 'dist/themes/fonts/'
          },
          {
            expand: true,
            cwd: 'bower_components/bootstrap/fonts/',
            src: ['**'],
            dest: 'dist/themes/fonts/'
          },
          {
            expand: true,
            cwd: 'bower_components/font-awesome/fonts/',
            src: ['**'],
            dest: 'dist/fonts/'
          }
          //{src: ['bower_components/angular-ui-utils/ui-utils-ieshiv.min.js'], dest: 'dist/'},
          //{src: ['bower_components/select2/*.png','bower_components/select2/*.gif'], dest:'dist/css/',flatten:true,expand:true},
          //{src: ['bower_components/angular-mocks/angular-mocks.js'], dest: 'dist/'}
        ]
      }
    },
    dom_munger:{
      read: {
        options: {
          read:[
            {selector:'script[class="libnocompat"]',attribute:'src',writeto:'libnocompatjs'},
            {selector:'script[class="libcompat"]',attribute:'src',writeto:'libcompatjs'},
            {selector:'script[class="lib"]',attribute:'src',writeto:'libjs'},
            {selector:'script[class="app"]',attribute:'src',writeto:'appjs'},
            {selector:'link[rel="stylesheet"][data-concat!="false"]',attribute:'href',writeto:'appcss'}
          ]
        },
        src: 'index.html'
      },
      update: {
        options: {
          remove: ['script[data-remove!="false"]','link[data-remove!="false"]'],
          append: [
            {selector:'body',html:'<!--[if lte IE 8]><script src="/booth/libcompat-v3.0.1.min.js"></script><![endif]--><!--[if gte IE 9]><script src="/booth/libnocompat-v3.0.1.min.js"></script><![endif]--><!--[if !IE]><!--><script src="/booth/libnocompat-v3.0.1.min.js"></script><!--<![endif]-->'},
            {selector:'body',html:'<!--All the source code of this program under copyright. Take a look at the license details at https://github.com/agoravoting/agora-core-view/blob/master/README.md -->'},
            {selector:'body',html:'<script src="/booth/lib-v3.0.1.min.js"></script>'},
            {selector:'body',html:'<script src="/booth/avConfig-v3.0.1.js"></script>'},
            {selector:'body',html:'<script src="/booth/avThemes-v3.0.1.js"></script>'},
            {selector:'body',html:'<script src="/booth/app-v3.0.1.min.js"></script>'},
            {selector:'body',html:'<script src="/booth/avPlugins-v3.0.1.js"></script>'},
            {selector:'head',html:'<link rel="stylesheet" id="theme" data-base="/booth/" href="/booth/themes/default/app.min.css">'}
          ]
        },
        src:'index.html',
        dest: 'dist/index.html'
      }
    },
    cssmin: {
      main: {
        files: [{
            expand: true,
            cwd:'temp/bower_components/avCommon/themes',
            src: ['**/app.css'],
            dest: 'dist/themes/',
            ext: '.min.css',
            extDot: 'first'
        }]
      },
    },
    concat: {
      main: {
        files: {
          'temp/libcompat.js': [
            'bower_components/avCommon/dist/libcompat-v3.0.1.js',
            '<%= dom_munger.data.libcompatjs %>'
          ],
          'temp/libnocompat.js': [
            'bower_components/avCommon/dist/libnocompat-v3.0.1.js',
            '<%= dom_munger.data.libnocompatjs %>'
          ],
          'temp/lib.js': ['<%= dom_munger.data.libjs %>'],
          'temp/app.js': ['<%= dom_munger.data.appjs %>','<%= ngtemplates.main.dest %>'],
          'dist/avConfig-v3.0.1.js': ['avConfig.js'],
          'dist/avThemes-v3.0.1.js': ['bower_components/avCommon/dist/avThemes-v3.0.1.js'],
          'dist/avPlugins-v3.0.1.js': ['plugins/**/*.js']
        }
      }
    },
    "merge-json": {
      main: {
        files: {
            "dist/locales/en.json": ["locales/en.json", "plugins/**/locales/en.json"],
            "dist/locales/es.json": ["locales/es.json", "plugins/**/locales/es.json"],
            "dist/locales/gl.json": ["locales/gl.json", "plugins/**/locales/gl.json"],
            "dist/locales/ca.json": ["locales/ca.json", "plugins/**/locales/ca.json"]
        }
      }
    },
    ngAnnotate: {
      main: {
        files: {
        'temp/app.js':['temp/app.js'],
        'temp/lib.js': ['temp/lib.js'],
        'temp/libnocompat.js': ['temp/libnocompat.js'],
        'temp/libcompat.js': ['temp/libcompat.js']
        }
      }
    },
    uglify: {
      main: {
        options:{
          mangle: false,
          compress: {},
          beautify: true
        },
        files: {
          'dist/app-v3.0.1.min.js': 'temp/app.js',
          'dist/lib-v3.0.1.min.js': 'temp/lib.js',
          'dist/libnocompat-v3.0.1.min.js': 'temp/libnocompat.js',
          'dist/libcompat-v3.0.1.min.js': 'temp/libcompat.js',
          'dist/avWidgets.min.js': 'avWidgets.js',

          "dist/locales/moment/es.js": "bower_components/moment/lang/es.js",
          "dist/locales/moment/gl.js": "bower_components/moment/lang/gl.js",
          "dist/locales/moment/ca.js": "bower_components/moment/lang/ca.js"
        }
      }
    },
    htmlmin: {
      main: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          removeAttributeQuotes: false,
          removeComments: false,
          removeEmptyAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true
        },
        files: {
          'dist/index.html': 'dist/index.html'
        }
      }
    },
    imagemin: {
      main:{
        files: [{
          expand: true, cwd:'dist/',
          src:['**/{*.png,*.jpg}'],
          dest: 'dist/'
        }]
      }
    },
    karma: {
      options: {
        frameworks: ['jasmine'],
        files: [  //this files data is also updated in the watch handler, if updated change there too
          '<%= dom_munger.data.libnocompatjs %>',
          '<%= dom_munger.data.libjs %>',
          'avConfig.js',
          'avThemes.js',
          'avWidgets.js',
          '<%= dom_munger.data.appjs %>',
          '<%= ngtemplates.main.dest %>',
          'bower_components/angular-mocks/angular-mocks.js',
          createFolderGlobs('*-spec.js')
        ],
        logLevel:'ERROR',
        reporters:['mocha'],
        autoWatch: false, //watching is handled by grunt-contrib-watch
        singleRun: true
      },
      all_tests: {
        browsers: ['PhantomJS','Chrome','Firefox']
      },
      during_watch: {
        browsers: ['PhantomJS']
      },
    },
    protractor: {
      options: {
        configFile: "node_modules/protractor/referenceConf.js", // Default config file
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false, // If true, protractor will not use colors in its output.
        args: {
        // Arguments passed to the command
      }
    },
    //your_target: {   // Grunt requires at least one target to run so you can simply put 'all: {}' here too.
      all: {
      options: {
        configFile: "e2e.conf.js", // Target-specific config file
        args: {} // Target-specific arguments
      }
    },
  },

  });

  grunt.registerTask('build',['check_config', 'jshint','clean:before','less','autoprefixer','dom_munger','ngtemplates','cssmin','concat','merge-json','ngAnnotate','uglify','copy','htmlmin','imagemin','clean:after']);
  grunt.registerTask('serve', ['dom_munger:read','jshint','connect', 'watch']);
  grunt.registerTask('test',['dom_munger:read','karma:all_tests']);

  grunt.event.on('watch', function(action, filepath) {
    //https://github.com/gruntjs/grunt-contrib-watch/issues/156

    var tasksToRun = [];

    if ((filepath.lastIndexOf('.json') !== -1 && filepath.lastIndexOf('.json') === filepath.length - 5) ||
        (filepath.lastIndexOf('.js') !== -1 && filepath.lastIndexOf('.js') === filepath.length - 3)) {

      //lint the changed js file
      grunt.config('jshint.main.src', filepath);
      tasksToRun.push('jshint');

      //find the appropriate unit t est for the changed file
      var spec = filepath;
      if (filepath.lastIndexOf('-spec.js') === -1 || filepath.lastIndexOf('-spec.js') !== filepath.length - 8) {
        spec = filepath.substring(0,filepath.length - 3) + '-spec.js';
      }

      //if the spec exists then lets run it
      if (grunt.file.exists(spec)) {
        var files = [].concat(grunt.config('dom_munger.data.libnocompatjs'));
        files.concat(grunt.config('dom_munger.data.libjs'));
        files.push('bower_components/angular-mocks/angular-mocks.js');
        files.push('avConfig.js');
        files.push('avThemes.js');
        files.push('avWidgets.js');
        files.concat(grunt.config('dom_munger.data.appjs'));
        files.concat(grunt.config('ngtemplates.main.dest'));
        files.push(spec);
        grunt.config('karma.options.files', files);
        tasksToRun.push('karma:during_watch');
      }
    }

    //if index.html changed, we need to reread the <script> tags so our next run of karma
    //will have the correct environment
    if (filepath === 'index.html') {
      tasksToRun.push('dom_munger:read');
    }

    grunt.config('watch.main.tasks',tasksToRun);

  });

  grunt.loadNpmTasks('grunt-protractor-runner');
};
<|endoftext|>


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

    var exec = require('child_process').exec;

    /*
    WHAT NEEDS TO HAPPEN FOR A RELEASE

    MANUAL: Merge everything into master and switch to master branch
    BASH: npm test - Test proposed release
    *MANUAL: Lint proposed release

    BASH: grunt - loads of stuff

    MANUAL: Create branch for the next release
    */

    // Project configuration.
    grunt.initConfig({
        builddir: 'js-build',
        pkg: grunt.file.readJSON('package.json'),
        meta: {
            banner: '/**\n' + ' * <%= pkg.description %>\n' +
                ' * @version v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
                ' * @link <%= pkg.homepage %>\n' +
                ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + ' */'
        },
        concat: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: ['app/js/<%= pkg.name %>.js', 'app/js/controllers/*.js', 'app/js/directives/*.js', 'app/js/filters/*.js', 'app/js/services/*.js', 'app/js/lib/*.js'],
                dest: '<%= builddir %>/<%= pkg.name %>.js'
            }
        },
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: '<%= builddir %>/<%= pkg.name %>.js',
                dest: '<%= builddir %>/<%= pkg.name %>.min.js'
            }
        },
        lint: {
            files: ['grunt.js', 'common/**/*.js', 'modules/**/*.js']
        },
        copy: {
            // Copy the required files into the npm distribution folder
//                    {expand: true, src: ['path/*'], dest: 'dest/', filter: 'isFile'}, // includes files in path
//                    {expand: true, src: ['path/**'], dest: 'dest/'}, // includes files in path and its subdirs
//                    {expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'}, // makes all src relative to cwd
//                    {expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'} // flattens results to a single level
            minimal: {
                files: [
                    {src: 'bower.json', dest: 'minimal/bower.json'},
                    {src: 'app/css/forms-angular.css', dest:'minimal/app/css/forms-angular.css'},
                    {src: 'js-build/forms-angular.min.js', dest:'minimal/app/lib/forms-angular.min.js'},
                    {src: 'app/partials/404.html', dest: 'minimal/app/partials/404.html'},
                    {src: 'app/partials/base-edit.html', dest: 'minimal/app/partials/base-edit.html'},
                    {src: 'app/partials/base-list.html', dest: 'minimal/app/partials/base-list.html'},
                    {src: 'package.json', dest:'minimal/package.json'},
                    {src: 'server/lib/data_form.js', dest: 'minimal/server/lib/data_form.js'}
                ]
            },
            bower: {
                files: [
                    {src: 'bower.json', dest: 'js-build/bower.json'},
                    {src: 'app/css/forms-angular.css', dest:'js-build/forms-angular.css'}
                ]
            },
            npm: {
                files: [
                    {src: 'package.json', dest:'npm-build/package.json'},
                    {src: 'LICENSE.txt', dest:'npm-build/LICENSE.txt'},
                    {src: 'server/lib/data_form.js',dest:'npm-build/lib/data_form.js'}
                ]
            }
        },
        bump: {
            options: {
                files: ['package.json','bower.json','./minimal/package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                commitFiles: ['-a'], // '-a' for all files
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
            }
        }
    });

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('modify_json', 'Modify the package.json and bower.json files.', function() {
        var filename = 'minimal/bower.json';
        var content = grunt.file.readJSON(filename);
        content.name = "fng-minimal";
        grunt.file.write(filename, JSON.stringify(content,null,2));

        var filename = 'minimal/package.json';
        var content = grunt.file.readJSON(filename);
        content.name = "fng-minimal";
        content.description = "A minimal deployment of forms-angular";
        var start = content.scripts.start;
        content.scripts = {start: start};
        delete content.devDependencies;
        grunt.file.write(filename, JSON.stringify(content,null,2));

        var filename = 'npm-build/package.json';
        var content = grunt.file.readJSON(filename);
        content.name = "forms-angular";
        content.main = "lib/data_form.js";
        delete content.scripts;
        delete content.dependencies.express;
        delete content.dependencies.bower;
        delete content.dependencies.mongoose;
        delete content.devDependencies;
        grunt.file.write(filename, JSON.stringify(content,null,2));

        grunt.log.ok('Modified 3 files');
    });

    grunt.registerTask('commit-tag-push', 'Do a git commit, tag and push', function(folder) {
        var startDir = process.cwd();
        var opts = grunt.config.get(['bump']).options;
        process.chdir(folder)
        var globalVersion = grunt.config.get().pkg.version;
        var commitMessage = opts.commitMessage.replace('%VERSION%', globalVersion);

        var done = this.async();
        var queue = [];
        var next = function () {
            if (!queue.length) {
                process.chdir(startDir)
                return done();
            }
            queue.shift()();
        };
        var runIt = function (behavior) {
            queue.push(behavior);
        };

        runIt(function () {
            exec('git commit ' + opts.commitFiles.join(' ') + ' -m "' + commitMessage + '"', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not create the commit:\n  ' + stderr);
                }
                grunt.log.ok('Committed as "' + commitMessage + '"');
                next();
            });
        });

        var tagName = opts.tagName.replace('%VERSION%', globalVersion);
        var tagMessage = opts.tagMessage.replace('%VERSION%', globalVersion);

        runIt(function () {
            exec('git tag -a ' + tagName + ' -m "' + tagMessage + '"', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not create the tag:\n  ' + stderr);
                }
                grunt.log.ok('Tagged as "' + tagName + '"');
                next();
            });
        });

        runIt(function () {
            exec('git push ' + opts.pushTo + ' && git push ' + opts.pushTo + ' --tags', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not push to ' + opts.pushTo + ':\n  ' + stderr);
                }
                grunt.log.ok('Pushed to ' + opts.pushTo);
                next();
            });
        });

        next();
    });

    grunt.registerTask('npm-publish', 'Publish a package', function (folder) {
        var done = this.async();
        exec('npm publish ' + folder, function (err, stdout, stderr) {
            grunt.log.ok(stdout);
            if (err) {
                grunt.fatal('Can publish to npm:\n  ' + stderr);
            }
            grunt.log.ok('Package published');
            done();
        });
    });

    var bumpLevel = grunt.option('bumpLevel') || 'patch';

    grunt.registerTask('default', ['concat', 'uglify']);

    grunt.registerTask('release', ['bump-only:' + bumpLevel, 'concat', 'uglify', 'copy', 'modify_json', 'bump-commit', 'commit-tag-push:js-build', 'npm-publish:npm-build']);
};
<commit_msg>Generate the correct files when running grunt<commit_after>module.exports = function(grunt) {

    var exec = require('child_process').exec;

    /*
    WHAT NEEDS TO HAPPEN FOR A RELEASE

    MANUAL: Merge everything into master and switch to master branch
    BASH: npm test - Test proposed release
    *MANUAL: Lint proposed release

    BASH: grunt - loads of stuff

    MANUAL: Create branch for the next release
    */

    // Project configuration.
    grunt.initConfig({
        builddir: 'js-build',
        pkg: grunt.file.readJSON('package.json'),
        meta: {
            banner: '/**\n' + ' * <%= pkg.description %>\n' +
                ' * @version v<%= pkg.version %> - ' +
                '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
                ' * @link <%= pkg.homepage %>\n' +
                ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + ' */'
        },
        concat: {
            options: {
                banner: '/*! forms-angular <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: ['app/js/forms-angular.js', 'app/js/controllers/*.js', 'app/js/directives/*.js', 'app/js/filters/*.js', 'app/js/services/*.js', 'app/js/lib/*.js'],
                dest: '<%= builddir %>/forms-angular.js'
            }
        },
        uglify: {
            options: {
                banner: '/*! forms-angular <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: '<%= builddir %>/forms-angular.js',
                dest: '<%= builddir %>/forms-angular.min.js'
            }
        },
        lint: {
            files: ['grunt.js', 'common/**/*.js', 'modules/**/*.js']
        },
        copy: {
            // Copy the required files into the npm distribution folder
//                    {expand: true, src: ['path/*'], dest: 'dest/', filter: 'isFile'}, // includes files in path
//                    {expand: true, src: ['path/**'], dest: 'dest/'}, // includes files in path and its subdirs
//                    {expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'}, // makes all src relative to cwd
//                    {expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'} // flattens results to a single level
            minimal: {
                files: [
                    {src: 'bower.json', dest: 'minimal/bower.json'},
                    {src: 'app/css/forms-angular.css', dest:'minimal/app/css/forms-angular.css'},
                    {src: 'js-build/forms-angular.min.js', dest:'minimal/app/lib/forms-angular.min.js'},
                    {src: 'app/partials/404.html', dest: 'minimal/app/partials/404.html'},
                    {src: 'app/partials/base-edit.html', dest: 'minimal/app/partials/base-edit.html'},
                    {src: 'app/partials/base-list.html', dest: 'minimal/app/partials/base-list.html'},
                    {src: 'package.json', dest:'minimal/package.json'},
                    {src: 'server/lib/data_form.js', dest: 'minimal/server/lib/data_form.js'}
                ]
            },
            bower: {
                files: [
                    {src: 'bower.json', dest: 'js-build/bower.json'},
                    {src: 'app/css/forms-angular.css', dest:'js-build/forms-angular.css'}
                ]
            },
            npm: {
                files: [
                    {src: 'package.json', dest:'npm-build/package.json'},
                    {src: 'LICENSE.txt', dest:'npm-build/LICENSE.txt'},
                    {src: 'server/lib/data_form.js',dest:'npm-build/lib/data_form.js'}
                ]
            }
        },
        bump: {
            options: {
                files: ['package.json','bower.json','./minimal/package.json'],
                updateConfigs: ['pkg'],
                commit: true,
                commitMessage: 'Release v%VERSION%',
                commitFiles: ['-a'], // '-a' for all files
                createTag: true,
                tagName: 'v%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
            }
        }
    });

    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('modify_json', 'Modify the package.json and bower.json files.', function() {
        var filename = 'minimal/bower.json';
        var content = grunt.file.readJSON(filename);
        content.name = "fng-minimal";
        grunt.file.write(filename, JSON.stringify(content,null,2));

        var filename = 'minimal/package.json';
        var content = grunt.file.readJSON(filename);
        content.name = "fng-minimal";
        content.description = "A minimal deployment of forms-angular";
        var start = content.scripts.start;
        content.scripts = {start: start};
        delete content.devDependencies;
        grunt.file.write(filename, JSON.stringify(content,null,2));

        var filename = 'npm-build/package.json';
        var content = grunt.file.readJSON(filename);
        content.name = "forms-angular";
        content.main = "lib/data_form.js";
        delete content.scripts;
        delete content.dependencies.express;
        delete content.dependencies.bower;
        delete content.dependencies.mongoose;
        delete content.devDependencies;
        grunt.file.write(filename, JSON.stringify(content,null,2));

        grunt.log.ok('Modified 3 files');
    });

    grunt.registerTask('commit-tag-push', 'Do a git commit, tag and push', function(folder) {
        var startDir = process.cwd();
        var opts = grunt.config.get(['bump']).options;
        process.chdir(folder)
        var globalVersion = grunt.config.get().pkg.version;
        var commitMessage = opts.commitMessage.replace('%VERSION%', globalVersion);

        var done = this.async();
        var queue = [];
        var next = function () {
            if (!queue.length) {
                process.chdir(startDir)
                return done();
            }
            queue.shift()();
        };
        var runIt = function (behavior) {
            queue.push(behavior);
        };

        runIt(function () {
            exec('git commit ' + opts.commitFiles.join(' ') + ' -m "' + commitMessage + '"', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not create the commit:\n  ' + stderr);
                }
                grunt.log.ok('Committed as "' + commitMessage + '"');
                next();
            });
        });

        var tagName = opts.tagName.replace('%VERSION%', globalVersion);
        var tagMessage = opts.tagMessage.replace('%VERSION%', globalVersion);

        runIt(function () {
            exec('git tag -a ' + tagName + ' -m "' + tagMessage + '"', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not create the tag:\n  ' + stderr);
                }
                grunt.log.ok('Tagged as "' + tagName + '"');
                next();
            });
        });

        runIt(function () {
            exec('git push ' + opts.pushTo + ' && git push ' + opts.pushTo + ' --tags', function (err, stdout, stderr) {
                if (err) {
                    grunt.fatal('Can not push to ' + opts.pushTo + ':\n  ' + stderr);
                }
                grunt.log.ok('Pushed to ' + opts.pushTo);
                next();
            });
        });

        next();
    });

    grunt.registerTask('npm-publish', 'Publish a package', function (folder) {
        var done = this.async();
        exec('npm publish ' + folder, function (err, stdout, stderr) {
            grunt.log.ok(stdout);
            if (err) {
                grunt.fatal('Can publish to npm:\n  ' + stderr);
            }
            grunt.log.ok('Package published');
            done();
        });
    });

    var bumpLevel = grunt.option('bumpLevel') || 'patch';

    grunt.registerTask('default', ['concat', 'uglify']);

    grunt.registerTask('release', ['bump-only:' + bumpLevel, 'concat', 'uglify', 'copy', 'modify_json', 'bump-commit', 'commit-tag-push:js-build', 'npm-publish:npm-build']);
};
<|endoftext|>


<commit_before>(function () {
    'use strict';
    // TODO l10n ?

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.seo.xml');

    website.EditorBar.include({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'click a[data-action=promote-current-page]': 'promotePage',
        }),
        promotePage: function () {
            (new website.seo.Configurator()).appendTo($(document.body));
        },
    });

    website.seo = {};

    website.seo.PageParser = openerp.Class.extend({
        url: function () {
            var url = window.location.href;
            var hashIndex = url.indexOf('#');
            return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
        },
        title: function () {
            return $(document.title).text();
        },
        headers: function (tag) {
            return $('#wrap '+tag).map(function () {
                return $(this).text();
            });
        },
        images: function () {
            return $('#wrap img').map(function () {
                var $img = $(this);
                return  {
                    src: $img.attr('src'),
                    alt: $img.attr('alt'),
                };
            });
        },
        company: function () {
            return $('meta[name="openerp.company"]').attr('value');
        },
    });

    website.seo.Tip = openerp.Widget.extend({
        template: 'website.seo_tip',
        events: {
            'closed.bs.alert': 'destroy',
        },
        init: function (parent, options) {
            this.message = options.message;
            // cf. http://getbootstrap.com/components/#alerts
            // success, info, warning or danger
            this.type = options.type || 'info';
            this._super(parent);
        },
    });

    website.seo.Keyword = openerp.Widget.extend({
        template: 'website.seo_keyword',
        events: {
            'click a[data-action=remove-keyword]': 'destroy',
        },
        maxWordsPerKeyword: 4, // TODO Check
        types: {
            // cf. http://getbootstrap.com/components/#labels
            // default, primary, success, info, warning, danger
            perfect: 'success',
            advised: 'primary',
            used: 'warning',
            custom: 'default',
        },
        init: function (parent, options) {
            this.keyword = options.keyword;
            this.type = this.types[options.type] || 'default';
            this._super(parent);
        },
        destroy: function () {
            this.trigger('removed');
            this._super();
        },
    });

    website.seo.KeywordList = openerp.Widget.extend({
        template: 'website.seo_list',
        maxKeywords: 10,
        keywords: function () {
            var result = [];
            this.$('.js_seo_keyword').each(function () {
                result.push($(this).data('keyword'));
            });
            return result;
        },
        isKeywordListFull: function () {
            return this.keywords().length >= this.maxKeywords;
        },
        isExistingKeyword: function (word) {
            return _.contains(this.keywords(), word);
        },
        add: function (candidate, suggested) {
            var self = this;
            // TODO Refine
            var word = candidate ? candidate.replace(/[,;.:<>]+/g, " ").replace(/ +/g, " ").trim() : "";
            if (word && !self.isKeywordListFull() && !self.isExistingKeyword(word)) {
                var type = suggested ? 'advised' : 'custom';
                var keyword = new website.seo.Keyword(self, {
                    keyword: word,
                    type: type,
                });
                keyword.on('removed', null, function () {
                   self.trigger('list-not-full');
                });
                keyword.appendTo(self.$el);
            }
            if (self.isKeywordListFull()) {
                self.trigger('list-full');
            }
        },
    });

    website.seo.Suggestion = openerp.Widget.extend({
        template: 'website.seo_suggestion',
        events: {
            'click .js_seo_suggestion': 'select'
        },
        types: {
            // cf. http://getbootstrap.com/components/#labels
            // default, primary, success, info, warning, danger
            primary: 'primary',
            secondary: 'info',

        },
        init: function (parent, options) {
            this.keyword = options.keyword;
            this.type = this.types[options.type || 'primary'];
            this._super(parent);
        },
        select: function () {
            this.trigger('selected');
        },
    });

    website.seo.SuggestionList = openerp.Widget.extend({
        template: 'website.seo_list',
        init: function (parent, companyName) {
            this.companyName = companyName;
            this._super(parent);
        },
        start: function () {
            var self = this;
            self.$el.append("Loading...");
            // cf. https://github.com/ddm/seo
            // TODO Try with /recommend/
            $.getJSON("http://seo.eu01.aws.af.cm/suggest/"+encodeURIComponent(self.companyName), function (list) {
                self.$el.empty();
                // TODO Improve algorithm + Ajust based on custom user keywords
                var nameRegex = new RegExp(self.companyName, "gi");
                var cleanList = _.map(list, function removeCompanyName (word) {
                    return word.replace(nameRegex, "").trim();
                });
                // TODO Order properly ?
                cleanList.push(self.companyName);
                _.each(_.uniq(cleanList), function (keyword) {
                    if (keyword) {
                        var suggestion = new website.seo.Suggestion(self, {
                            keyword: keyword
                        });
                        suggestion.on('selected', self, function () {
                           self.trigger('selected', keyword);
                        });
                        suggestion.appendTo(self.$el);
                    }
                });
            });
        },
        suggestions: function () {
            return this.$('.js_seo_suggestion').map(function () {
                return $(this).data('keyword');
            });
        },
    });

    website.seo.Image = openerp.Widget.extend({
        template: 'website.seo_image',
        init: function (parent, options) {
            this.src = options.src;
            this.alt = options.alt;
            this._super(parent);
        },
    });

    website.seo.ImageList = openerp.Widget.extend({
        start: function () {
            var self = this;
            new website.seo.PageParser().images().each(function (index, image) {
                new website.seo.Image(self, image).appendTo(self.$el);
            });
        },
        images: function () {
            var result = [];
            this.$('input').each(function () {
               var $input = $(this);
               result.push({
                   src: $input.attr('src'),
                   alt: $input.val(),
               });
            });
            return result;
        },
        add: function (image) {
            new website.seo.Image(this, image).appendTo(this.$el);
        },
    });

    website.seo.Configurator = openerp.Widget.extend({
        template: 'website.seo_configuration',
        events: {
            'keypress input[name=seo_page_keywords]': 'confirmKeyword',
            'click button[data-action=add]': 'addKeyword',
            'click button[data-action=update]': 'update',
            'hidden.bs.modal': 'destroy'
        },
        maxTitleSize: 65,
        maxDescriptionSize: 155,
        start: function () {
            var self = this;
            var $modal = this.$el;
            var pageParser = new website.seo.PageParser();
            $modal.find('.js_seo_page_url').text(pageParser.url());
            $modal.find('input[name=seo_page_title]').val(pageParser.title());
            this.suggestImprovements(pageParser);
            this.imageList = new website.seo.ImageList(this);
            this.imageList.appendTo($modal.find('.js_seo_image_list'));
            this.keywordList = new website.seo.KeywordList(this);
            this.keywordList.on('list-full', null, function () {
                $modal.find('input[name=seo_page_keywords]')
                    .attr('readonly', "readonly")
                    .attr('placeholder', "Remove a keyword first");
                $modal.find('button[data-action=add]')
                    .prop('disabled', true).addClass('disabled');
            });
            this.keywordList.on('list-not-full', null, function () {
                $modal.find('input[name=seo_page_keywords]')
                    .removeAttr('readonly').attr('placeholder', "");
                $modal.find('button[data-action=add]')
                    .prop('disabled', false).removeClass('disabled');
            });
            this.keywordList.appendTo($modal.find('.js_seo_keywords_list'));
            var companyName = pageParser.company().toLowerCase();
            this.suggestionList = new website.seo.SuggestionList(this, companyName);
            this.suggestionList.on('selected', self, function (word) {
               self.acceptSuggestion(word);
            });
            this.suggestionList.appendTo($modal.find('.js_seo_suggestions_list'));
            $modal.modal();
        },
        suggestImprovements: function (parser) {
            var tips = [];
            var self = this;
            function displayTip(message, type) {
                new website.seo.Tip(this, {
                   message: message,
                   type: type
                }).appendTo(self.$('.js_seo_tips'));
            }
            var pageParser = parser || new website.seo.PageParser();
            if (!pageParser.headers('h1').length === 0) {
                tips.push({
                    type: 'warning',
                    message: "You don't have an <h1> tag on your page.",
                });
            }
            if (pageParser.headers('h1').length > 1) {
                tips.push({
                    type: 'warning',
                    message: "You have more than one <h1> tag on your page.",
                });
            }
            if (tips.length > 0) {
                _.each(tips, function (tip) {
                    displayTip(tip.message, tip.type);
                });
            } else {
                displayTip("Your page makup is appropriate for search engines.", 'success');
            }
        },
        confirmKeyword: function (e) {
            if (e.keyCode == 13) {
                this.addKeyword();
            }
        },
        addKeyword: function (word) {
            var $input = this.$('input[name=seo_page_keywords]');
            var keyword = _.isString(word) ? word : $input.val();
            this.keywordList.add(keyword, false);
            $input.val("");
        },
        acceptSuggestion: function (suggestion) {
            this.keywordList.add(suggestion, true);
        },
        update: function () {
            var data = {
                title: this.$('input[name=seo_page_title]').val(),
                description: this.$('input[name=seo_page_title]').val(),
                keywords: this.keywordList.keywords(),
                images: this.imageList.images(),
            };
            console.log(data);
            // TODO Persist changes
        },
    });
})();
<commit_msg>[IMP] SEO: improve suggestions based on custom keywords<commit_after>(function () {
    'use strict';
    // TODO l10n ?

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.seo.xml');

    website.EditorBar.include({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'click a[data-action=promote-current-page]': 'promotePage',
        }),
        promotePage: function () {
            (new website.seo.Configurator()).appendTo($(document.body));
        },
    });

    website.seo = {};

    website.seo.PageParser = openerp.Class.extend({
        url: function () {
            var url = window.location.href;
            var hashIndex = url.indexOf('#');
            return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
        },
        title: function () {
            return $(document.title).text();
        },
        headers: function (tag) {
            return $('#wrap '+tag).map(function () {
                return $(this).text();
            });
        },
        images: function () {
            return $('#wrap img').map(function () {
                var $img = $(this);
                return  {
                    src: $img.attr('src'),
                    alt: $img.attr('alt'),
                };
            });
        },
        company: function () {
            return $('meta[name="openerp.company"]').attr('value');
        },
    });

    website.seo.Tip = openerp.Widget.extend({
        template: 'website.seo_tip',
        events: {
            'closed.bs.alert': 'destroy',
        },
        init: function (parent, options) {
            this.message = options.message;
            // cf. http://getbootstrap.com/components/#alerts
            // success, info, warning or danger
            this.type = options.type || 'info';
            this._super(parent);
        },
    });

    website.seo.Keyword = openerp.Widget.extend({
        template: 'website.seo_keyword',
        events: {
            'click a[data-action=remove-keyword]': 'destroy',
        },
        maxWordsPerKeyword: 4, // TODO Check
        types: {
            // cf. http://getbootstrap.com/components/#labels
            // default, primary, success, info, warning, danger
            perfect: 'success',
            advised: 'primary',
            used: 'warning',
            custom: 'default',
        },
        init: function (parent, options) {
            this.keyword = options.keyword;
            this.type = this.types[options.type] || 'default';
            this._super(parent);
        },
        destroy: function () {
            this.trigger('removed');
            this._super();
        },
    });

    website.seo.KeywordList = openerp.Widget.extend({
        template: 'website.seo_list',
        maxKeywords: 10,
        keywords: function () {
            var result = [];
            this.$('.js_seo_keyword').each(function () {
                result.push($(this).data('keyword'));
            });
            return result;
        },
        isKeywordListFull: function () {
            return this.keywords().length >= this.maxKeywords;
        },
        isExistingKeyword: function (word) {
            return _.contains(this.keywords(), word);
        },
        add: function (candidate, suggested) {
            var self = this;
            // TODO Refine
            var word = candidate ? candidate.replace(/[,;.:<>]+/g, " ").replace(/ +/g, " ").trim() : "";
            if (word && !self.isKeywordListFull() && !self.isExistingKeyword(word)) {
                var type = suggested ? 'advised' : 'custom';
                var keyword = new website.seo.Keyword(self, {
                    keyword: word,
                    type: type,
                });
                keyword.on('removed', null, function () {
                   self.trigger('list-not-full');
                   self.trigger('removed', word);
                });
                keyword.appendTo(self.$el);
            }
            if (self.isKeywordListFull()) {
                self.trigger('list-full');
            }
        },
    });

    website.seo.Suggestion = openerp.Widget.extend({
        template: 'website.seo_suggestion',
        events: {
            'click .js_seo_suggestion': 'select'
        },
        types: {
            // cf. http://getbootstrap.com/components/#labels
            // default, primary, success, info, warning, danger
            primary: 'primary',
            secondary: 'info',

        },
        init: function (parent, options) {
            this.keyword = options.keyword;
            this.type = this.types[options.type || 'primary'];
            this._super(parent);
        },
        select: function () {
            this.trigger('selected');
        },
    });

    website.seo.SuggestionList = openerp.Widget.extend({
        template: 'website.seo_list',
        init: function (parent, companyName) {
            this.companyName = companyName;
            this.rootWords = [ companyName ];
            this._super(parent);
        },
        start: function () {
            this.refresh();
        },
        refresh: function () {
            var self = this;
            self.$el.empty();
            self.$el.append("Loading...");
            var requests = _.map(this.rootWords, function (word) {
                // cf. https://github.com/ddm/seo
                // TODO Try with /recommend/
                return $.getJSON("http://seo.eu01.aws.af.cm/suggest/"+encodeURIComponent(word));
            });
            function addSuggestions (list) {
                // TODO Improve algorithm + Ajust based on custom user keywords
                var nameRegex = new RegExp(self.companyName, "gi");
                var cleanList = _.map(list, function removeCompanyName (word) {
                    return word.replace(nameRegex, "").trim();
                });
                // TODO Order properly ?
                cleanList.push(self.companyName);
                _.each(_.uniq(cleanList), function (keyword) {
                    if (keyword) {
                        var suggestion = new website.seo.Suggestion(self, {
                            keyword: keyword
                        });
                        suggestion.on('selected', self, function () {
                           self.trigger('selected', keyword);
                        });
                        suggestion.appendTo(self.$el);
                    }
                });
            }
            $.when.apply($, requests).then(function (result) {
                self.$el.empty();
                if (result && _.isArray(result[0])) {
                    var suggestionLists = _.map(Array.prototype.slice.call(arguments), function (l) {
                        return l[0];
                    });
                    _.each(suggestionLists, addSuggestions);
                } else if (result && _.isString(result[0])) {
                    addSuggestions(result);
                }
            });
        },
        suggestions: function () {
            return this.$('.js_seo_suggestion').map(function () {
                return $(this).data('keyword');
            });
        },
        addRootWord: function (word) {
            this.rootWords.push(word);
            this.refresh();
        },
        removeRootWord: function (word) {
            this.rootWords = _.reject(this.rootWords, function (rootWord) {
                return rootWord === word;
            });
            if (this.rootWords.length === 0) {
                this.rootWords.push(companyName);
            }
            this.refresh();
        },
    });

    website.seo.Image = openerp.Widget.extend({
        template: 'website.seo_image',
        init: function (parent, options) {
            this.src = options.src;
            this.alt = options.alt;
            this._super(parent);
        },
    });

    website.seo.ImageList = openerp.Widget.extend({
        start: function () {
            var self = this;
            new website.seo.PageParser().images().each(function (index, image) {
                new website.seo.Image(self, image).appendTo(self.$el);
            });
        },
        images: function () {
            var result = [];
            this.$('input').each(function () {
               var $input = $(this);
               result.push({
                   src: $input.attr('src'),
                   alt: $input.val(),
               });
            });
            return result;
        },
        add: function (image) {
            new website.seo.Image(this, image).appendTo(this.$el);
        },
    });

    website.seo.Configurator = openerp.Widget.extend({
        template: 'website.seo_configuration',
        events: {
            'keypress input[name=seo_page_keywords]': 'confirmKeyword',
            'click button[data-action=add]': 'addKeyword',
            'click button[data-action=update]': 'update',
            'hidden.bs.modal': 'destroy'
        },
        maxTitleSize: 65,
        maxDescriptionSize: 155,
        start: function () {
            var self = this;
            var $modal = this.$el;
            var pageParser = new website.seo.PageParser();
            $modal.find('.js_seo_page_url').text(pageParser.url());
            $modal.find('input[name=seo_page_title]').val(pageParser.title());
            this.suggestImprovements(pageParser);
            this.imageList = new website.seo.ImageList(this);
            this.imageList.appendTo($modal.find('.js_seo_image_list'));
            this.keywordList = new website.seo.KeywordList(this);
            this.keywordList.on('list-full', self, function () {
                $modal.find('input[name=seo_page_keywords]')
                    .attr('readonly', "readonly")
                    .attr('placeholder', "Remove a keyword first");
                $modal.find('button[data-action=add]')
                    .prop('disabled', true).addClass('disabled');
            });
            this.keywordList.on('list-not-full', self, function () {
                $modal.find('input[name=seo_page_keywords]')
                    .removeAttr('readonly').attr('placeholder', "");
                $modal.find('button[data-action=add]')
                    .prop('disabled', false).removeClass('disabled');
            });
            this.keywordList.appendTo($modal.find('.js_seo_keywords_list'));
            var companyName = pageParser.company().toLowerCase();
            this.suggestionList = new website.seo.SuggestionList(this, companyName);
            this.suggestionList.on('selected', self, function (word) {
               self.acceptSuggestion(word);
            });
            this.keywordList.on('removed', self, function (word) {
               this.suggestionList.removeRootWord(word);
            });
            this.suggestionList.appendTo($modal.find('.js_seo_suggestions_list'));
            $modal.modal();
        },
        suggestImprovements: function (parser) {
            var tips = [];
            var self = this;
            function displayTip(message, type) {
                new website.seo.Tip(this, {
                   message: message,
                   type: type
                }).appendTo(self.$('.js_seo_tips'));
            }
            var pageParser = parser || new website.seo.PageParser();
            if (!pageParser.headers('h1').length === 0) {
                tips.push({
                    type: 'warning',
                    message: "You don't have an <h1> tag on your page.",
                });
            }
            if (pageParser.headers('h1').length > 1) {
                tips.push({
                    type: 'warning',
                    message: "You have more than one <h1> tag on your page.",
                });
            }
            if (tips.length > 0) {
                _.each(tips, function (tip) {
                    displayTip(tip.message, tip.type);
                });
            } else {
                displayTip("Your page makup is appropriate for search engines.", 'success');
            }
        },
        confirmKeyword: function (e) {
            if (e.keyCode == 13) {
                this.addKeyword();
            }
        },
        addKeyword: function (word) {
            var $input = this.$('input[name=seo_page_keywords]');
            var keyword = _.isString(word) ? word : $input.val();
            this.keywordList.add(keyword, false);
            this.suggestionList.addRootWord(keyword);
            $input.val("");
        },
        acceptSuggestion: function (suggestion) {
            this.keywordList.add(suggestion, true);
        },
        update: function () {
            var data = {
                title: this.$('input[name=seo_page_title]').val(),
                description: this.$('input[name=seo_page_title]').val(),
                keywords: this.keywordList.keywords(),
                images: this.imageList.images(),
            };
            console.log(data);
            // TODO Persist changes
        },
    });
})();
<|endoftext|>