<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 5;

(function($) {

    var directives = angular.module('amara.SubtitleEditor.directives.subtitles', []);

    function setCaretPosition(elem, caretPos) {
        /** Move the caret to the specified position.
         * This will work, except for text areas with user inserted line breaks
         */
        if (elem != null) {
            if (elem.createTextRange) {
                var range = elem.createTextRange();
                range.move('character', caretPos);
                range.select();
            }
            else {
                if (elem.selectionStart !== undefined) {
                    elem.focus();
                    elem.setSelectionRange(caretPos, caretPos);
                }
            }
        }
    }
    directives.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            scope.videoId = attrs.videoId;
            scope.languageCode = attrs.languageCode;
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    directives.directive('workingSubtitlesWrapper', function() {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;
            }

            scope.positionInfoTray = function() {
                var li = scope.infoTray.LI
                if(li) {
                    var top = li.offset().top - wrapper.offset().top;
                    infoTray.css('top', top + 'px');
                }
            }

            scope.$watch("infoTray.LI", scope.positionInfoTray);
        };
    });

    directives.directive('subtitleList', function(SubtitleListFinder) {
        return function link(scope, elem, attrs) {
            // set these *before* calling get subtitle since if
            // the subs are bootstrapped it will return right away
            scope.isEditable = attrs.editable === 'true';
            scope.setVideoID(attrs.videoId);
            SubtitleListFinder.register(attrs.subtitleList, elem,
                    elem.controller(), scope);
            if(attrs.languageCode != "") {
                scope.setLanguageCode(attrs.languageCode);
                if(attrs.versionNumber != "") {
                    scope.getSubtitles(attrs.languageCode, attrs.versionNumber);
                } else {
                    scope.initEmptySubtitles();
                }
            }

            // Handle scroll.
            $(elem).parent().scroll(function() {

                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var newScrollTop = $(elem).parent().scrollTop();

                    $('div.subtitles').each(function() {

                        var $set = $(this);

                        if ($set.scrollTop() !== newScrollTop) {
                            $set.scrollTop(newScrollTop);
                        }

                    });
                }

                if(scope.isWorkingSubtitles()) {
                    scope.positionSyncHelpers();
                    scope.positionInfoTray();
                }
            });
            scope.nthChildScope = function(index) {
                var children = elem.children();
                if(0 <= index && index < children.length) {
                    return angular.element(children[index]).scope();
                } else {
                    return null;
                }
            }
        }
    });
    directives.directive('subtitleListItem', function($timeout) {
        return function link(scope, elem, attrs) {
            var elem = $(elem);
            var textarea = $('textarea', elem);

            scope.nextScope = function() {
                var next = elem.next();
                if(next.length > 0) {
                    return next.scope();
                } else {
                    return null;
                }
            }

            scope.prevScope = function() {
                // need to wrap in jquery, since angular's jqLite doesn't
                // support prev()
                var prev = $(elem).prev();
                if(prev.length > 0) {
                    return angular.element(prev).scope();
                } else {
                    return null;
                }
            }

            scope.showTextArea = function(fromClick) {
                if(fromClick) {
                    var caretPos = window.getSelection().anchorOffset;
                } else {
                    var caretPos = scope.editText.length;
                }
                textarea.val(scope.editText).trigger('autosize');
                textarea.show();
                textarea.focus();
                setCaretPosition(textarea.get(0), caretPos);
                scope.$root.$emit('subtitle-edit', scope.subtitle.content());
                // set line-height to 0 because we don't want the whitespace
                // inside the element to add extra space below the textarea
                elem.css('line-height', '0');
                scope.infoTray.LI = elem;
                scope.infoTray.subtitle = scope.subtitle;
            }

            scope.hideTextArea = function() {
                textarea.hide();
                elem.css('line-height', '');
                scope.infoTray.subtitle = scope.infoTray.LI = null;
            }

            textarea.autosize();
            textarea.on('keydown', function(evt) {
                scope.$apply(function() {
                    if (evt.keyCode === 13 && !evt.shiftKey) {
                        // Enter without shift finishes editing
                        scope.finishEditingMode(true);
                        if(scope.lastItem()) {
                            scope.addSubtitleAtEnd();
                            // Have to use a timeout in this case because the
                            // scope for the new subtitle won't be created
                            // until apply() finishes
                            $timeout(function() {
                                scope.nextScope().startEditingMode();
                            });
                        } else {
                            scope.nextScope().startEditingMode();
                        }
                        evt.preventDefault();
                    } else if (evt.keyCode === 27) {
                        // Escape cancels editing
                        scope.finishEditingMode(false);
                        evt.preventDefault();
                    } else if (evt.keyCode == 9) {
                        // Tab navigates to other subs
                        scope.finishEditingMode(true);
                        if(!evt.shiftKey) {
                            var tabTarget = scope.nextScope();
                        } else {
                            var tabTarget = scope.prevScope();
                        }
                        if(tabTarget !== null) {
                            tabTarget.startEditingMode();
                        }
                        evt.preventDefault();

                    }
                });
            });
            textarea.on('keyup', function(evt) {
                scope.$apply(function() {
                    // Update editText and emit the subtitle-edit event
                    scope.editText = textarea.val();
                    var content = scope.subtitleList.contentForMarkdown(
                        scope.editText);
                    scope.$root.$emit('subtitle-edit', content);
                });
            });
            textarea.on('focusout', function(evt) {
                if(scope.isEditing) {
                    scope.$apply(function() {
                        scope.finishEditingMode(true);
                    });
                }
            });
        }
    });

    directives.directive('languageSelector', function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        if(attrs.initialVersionNumber != "") {
                            var versionNumber = attrs.initialVersionNumber;
                        } else {
                            var versionNumber = null;
                        }

                        SubtitleStorage.getLanguages(function(languages){
                            scope.setInitialDisplayLanguage(
                                languages,
                                attrs.initialLanguageCode,
                                versionNumber);
                        });
                    }
                };
            }
        };
    });
})(window.AmarajQuery);
<commit_msg>Fixed bug when trying to focus the next subtitle<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 5;

(function($) {

    var directives = angular.module('amara.SubtitleEditor.directives.subtitles', []);

    function setCaretPosition(elem, caretPos) {
        /** Move the caret to the specified position.
         * This will work, except for text areas with user inserted line breaks
         */
        if (elem != null) {
            if (elem.createTextRange) {
                var range = elem.createTextRange();
                range.move('character', caretPos);
                range.select();
            }
            else {
                if (elem.selectionStart !== undefined) {
                    elem.focus();
                    elem.setSelectionRange(caretPos, caretPos);
                }
            }
        }
    }
    directives.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            scope.videoId = attrs.videoId;
            scope.languageCode = attrs.languageCode;
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    directives.directive('workingSubtitlesWrapper', function() {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;
            }

            scope.positionInfoTray = function() {
                var li = scope.infoTray.LI
                if(li) {
                    var top = li.offset().top - wrapper.offset().top;
                    infoTray.css('top', top + 'px');
                }
            }

            scope.$watch("infoTray.LI", scope.positionInfoTray);
        };
    });

    directives.directive('subtitleList', function(SubtitleListFinder) {
        return function link(scope, elem, attrs) {
            // set these *before* calling get subtitle since if
            // the subs are bootstrapped it will return right away
            scope.isEditable = attrs.editable === 'true';
            scope.setVideoID(attrs.videoId);
            SubtitleListFinder.register(attrs.subtitleList, elem,
                    elem.controller(), scope);
            if(attrs.languageCode != "") {
                scope.setLanguageCode(attrs.languageCode);
                if(attrs.versionNumber != "") {
                    scope.getSubtitles(attrs.languageCode, attrs.versionNumber);
                } else {
                    scope.initEmptySubtitles();
                }
            }

            // Handle scroll.
            $(elem).parent().scroll(function() {

                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var newScrollTop = $(elem).parent().scrollTop();

                    $('div.subtitles').each(function() {

                        var $set = $(this);

                        if ($set.scrollTop() !== newScrollTop) {
                            $set.scrollTop(newScrollTop);
                        }

                    });
                }

                if(scope.isWorkingSubtitles()) {
                    scope.positionSyncHelpers();
                    scope.positionInfoTray();
                }
            });
            scope.nthChildScope = function(index) {
                var children = elem.children();
                if(0 <= index && index < children.length) {
                    return angular.element(children[index]).scope();
                } else {
                    return null;
                }
            }
        }
    });
    directives.directive('subtitleListItem', function($timeout) {
        return function link(scope, elem, attrs) {
            var elem = $(elem);
            var textarea = $('textarea', elem);

            scope.nextScope = function() {
                var next = elem.next();
                if(next.length > 0) {
                    return angular.element(next).scope();
                } else {
                    return null;
                }
            }

            scope.prevScope = function() {
                // need to wrap in jquery, since angular's jqLite doesn't
                // support prev()
                var prev = $(elem).prev();
                if(prev.length > 0) {
                    return angular.element(prev).scope();
                } else {
                    return null;
                }
            }

            scope.showTextArea = function(fromClick) {
                if(fromClick) {
                    var caretPos = window.getSelection().anchorOffset;
                } else {
                    var caretPos = scope.editText.length;
                }
                textarea.val(scope.editText).trigger('autosize');
                textarea.show();
                textarea.focus();
                setCaretPosition(textarea.get(0), caretPos);
                scope.$root.$emit('subtitle-edit', scope.subtitle.content());
                // set line-height to 0 because we don't want the whitespace
                // inside the element to add extra space below the textarea
                elem.css('line-height', '0');
                scope.infoTray.LI = elem;
                scope.infoTray.subtitle = scope.subtitle;
            }

            scope.hideTextArea = function() {
                textarea.hide();
                elem.css('line-height', '');
                scope.infoTray.subtitle = scope.infoTray.LI = null;
            }

            textarea.autosize();
            textarea.on('keydown', function(evt) {
                scope.$apply(function() {
                    if (evt.keyCode === 13 && !evt.shiftKey) {
                        // Enter without shift finishes editing
                        scope.finishEditingMode(true);
                        if(scope.lastItem()) {
                            scope.addSubtitleAtEnd();
                            // Have to use a timeout in this case because the
                            // scope for the new subtitle won't be created
                            // until apply() finishes
                            $timeout(function() {
                                scope.nextScope().startEditingMode();
                            });
                        } else {
                            scope.nextScope().startEditingMode();
                        }
                        evt.preventDefault();
                    } else if (evt.keyCode === 27) {
                        // Escape cancels editing
                        scope.finishEditingMode(false);
                        evt.preventDefault();
                    } else if (evt.keyCode == 9) {
                        // Tab navigates to other subs
                        scope.finishEditingMode(true);
                        if(!evt.shiftKey) {
                            var tabTarget = scope.nextScope();
                        } else {
                            var tabTarget = scope.prevScope();
                        }
                        if(tabTarget !== null) {
                            tabTarget.startEditingMode();
                        }
                        evt.preventDefault();

                    }
                });
            });
            textarea.on('keyup', function(evt) {
                scope.$apply(function() {
                    // Update editText and emit the subtitle-edit event
                    scope.editText = textarea.val();
                    var content = scope.subtitleList.contentForMarkdown(
                        scope.editText);
                    scope.$root.$emit('subtitle-edit', content);
                });
            });
            textarea.on('focusout', function(evt) {
                if(scope.isEditing) {
                    scope.$apply(function() {
                        scope.finishEditingMode(true);
                    });
                }
            });
        }
    });

    directives.directive('languageSelector', function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        if(attrs.initialVersionNumber != "") {
                            var versionNumber = attrs.initialVersionNumber;
                        } else {
                            var versionNumber = null;
                        }

                        SubtitleStorage.getLanguages(function(languages){
                            scope.setInitialDisplayLanguage(
                                languages,
                                attrs.initialLanguageCode,
                                versionNumber);
                        });
                    }
                };
            }
        };
    });
})(window.AmarajQuery);
<|endoftext|>


<commit_before>var gulp = require('gulp');
var chug = require('gulp-chug');
var gulpWebpack = require('gulp-webpack');
var sass = require('gulp-sass');
var rename = require('gulp-rename');
var minifyCss = require('gulp-minify-css');
var file = require('gulp-file');
var preprocess = require('gulp-preprocess');
var gulpif = require('gulp-if');
var prompt = require('gulp-prompt');
var bump = require('gulp-bump');
var git = require('gulp-git');
var assign = require('object-assign');
var webpack = require('webpack');
var del = require('del');
var path = require('path');
var fs = require('fs');
var coveralls = require('gulp-coveralls');
var blanket = require('gulp-blanket-mocha');
var runSequence = require('run-sequence');
var spawn = require('child_process').spawn;
var mkdirp = require('mkdirp');

function getPackageJSON() {
  delete require.cache[require.resolve('./package.json')];
  var packageJSON = require('./package.json');
  delete packageJSON.devDependencies;
  delete packageJSON.config;
  packageJSON.main = 'index.js';
  packageJSON.dependencies = {
    'react': '^0.13.1',
    'react-intl': '^1.2.0',
    'inuit-box-sizing': '~0.2.0',
    'inuit-clearfix': '^0.2.1',
    'inuit-defaults': '~0.2.1',
    'inuit-functions': '~0.2.0',
    'inuit-headings': '~0.3.0',
    'inuit-images': '~0.3.3',
    'inuit-list-bare': '~0.3.0',
    'inuit-lists': '~0.1.0',
    'inuit-mixins': '~0.2.3',
    'inuit-normalize': '~3.0.2',
    'inuit-page': '~0.2.1',
    'inuit-reset': '~0.1.1',
    'inuit-responsive-settings': '~0.1.2',
    'inuit-responsive-tools': '~0.1.1',
    'inuit-shared': '~0.1.5',
    'mkdirp': '^0.5.0',
    'gulp': '^3.8.11',
    'gulp-template': '^3.0.0',
    'gulp-install': '^0.4.0',
    "lodash": "^3.8.0",
    "reflux": "^0.2.7",
    "superagent": "^1.1.0"
  };
  return packageJSON;
}

var opts = {
  dist: path.resolve(__dirname, 'dist'),
  copyAssets: [
    'README.md',
    'src/js/**',
    {
      asset: 'src/scss/**',
      dist: 'dist/scss/'
    },
    {
      asset: 'src/utils/**',
      dist: 'dist/utils/'
    },
    'design/**',
    'src/img/**',
    {
      asset: 'bin/**',
      dist: 'dist/bin/'
    },
    {
      asset: 'templates/**',
      dist: 'dist/templates/'
    },
    {
      asset: 'examples/**',
      dist: 'dist/examples/'
    },
    {
      filename: 'package.json',
      asset: JSON.stringify(getPackageJSON(), null, 2)
    }
  ],
  scssAssets: ['src/scss/**/*.scss'],
  jsAssets: ['src/js/**/*.js'],
  mainJs: 'src/js/index.js',
  mainScss: 'src/scss/grommet-core/index.scss',
  sync: {
    hostname: 'grommet.usa.hp.com',
    username: 'ligo',
    remoteDestination: '/var/www/html/assets/' + getPackageJSON().version
  },
  webpack: {
    output: {
      filename: 'grommet.min.js',
      libraryTarget: 'var',
      library: 'Grommet'
    },
    resolve: {
      modulesDirectories: ['node_modules', 'src/js', 'src/scss']
    },
    externals: {
      'react': 'React'
    }
  },
  distPreprocess: ['dist-css'],
  scsslint: true,
  testPaths: [
    'test/**/*.js'
  ]
};

require('./src/utils/gulp/gulp-tasks')(gulp, opts);

gulp.task('dist-css', function () {
  gulp.src('src/scss/hpinc/*.scss')
    .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
    .pipe(rename('grommet-hpinc.min.css'))
    .pipe(minifyCss())
    .pipe(gulp.dest('dist/'));

  gulp.src('src/scss/hpe/*.scss')
    .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
    .pipe(rename('grommet-hpe.min.css'))
    .pipe(minifyCss())
    .pipe(gulp.dest('dist/'));

  return gulp.src('src/scss/grommet-core/*.scss')
        .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
        .pipe(rename('grommet.min.css'))
        .pipe(minifyCss())
        .pipe(gulp.dest('dist/'));
});

gulp.task('sync-all', function() {

  gulp.src('./examples/demo/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./examples/tour/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./examples/server/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./docs/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));
});

var bowerWebpackConfig = {
  output: {
    filename: 'grommet.js',
    libraryTarget: 'var',
    library: 'Grommet'
  },
  resolve: {
    root: [
      path.resolve(__dirname, 'src/js'),
      path.resolve(__dirname, 'src/scss'),
      path.resolve(__dirname, 'node_modules')
    ],
    extensions: ['', '.js', '.json', '.htm', '.html', '.scss']
  },
  externals: {
    'react': 'React'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'jsx-loader'
      },
      {
        test: /\.svg$/,
        loader: 'file-loader?mimetype=image/svg'
      },
      {
        test: /\.jpg$/,
        loader: 'file-loader?mimetype=image/jpg'
      },
      {
        test: /\.woff$/,
        loader: 'file-loader?mimetype=application/font-woff'
      }
    ]
  },
  plugins: [
   //new webpack.optimize.DedupePlugin()
  ]
};

var bowerMinWebpackConfig = assign({}, bowerWebpackConfig, {
  output: {
    filename: 'grommet.min.js',
    libraryTarget: 'var',
    library: 'Grommet'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })
    //new webpack.optimize.DedupePlugin()
  ]
});

function distCss(path, name, minify) {
  gulp.src(path)
    .pipe(sass({includePaths: [ 'node_modules' ]}))
    .pipe(rename(name))
    .pipe(gulpif(minify, minifyCss()))
    .pipe(gulp.dest('dist-bower/css'));
}

gulp.task('dist-bower:exploded', function() {
  return gulp.src(opts.mainJs)
    .pipe(gulpWebpack(bowerWebpackConfig))
    .pipe(gulp.dest('dist-bower'));
});

gulp.task('dist-bower:minified', function() {
  return gulp.src(opts.mainJs)
    .pipe(gulpWebpack(bowerMinWebpackConfig))
    .pipe(gulp.dest('dist-bower'));
});

gulp.task('dist-bower:preprocess', function(done) {
  del.sync(['dist-bower']);
  runSequence(['dist-bower:exploded', 'dist-bower:minified'], done);
});

gulp.task('dist-bower', ['dist-bower:preprocess'], function() {

  distCss('src/scss/grommet-core/*.scss', 'grommet.css');
  distCss('src/scss/grommet-core/*.scss', 'grommet.min.css', true);
  distCss('src/scss/hpe/*.scss', 'grommet-hpe.css');
  distCss('src/scss/hpe/*.scss', 'grommet-hpe.min.css', true);
  distCss('src/scss/hpinc/*.scss', 'grommet-hpinc.css');
  distCss('src/scss/hpinc/*.scss', 'grommet-hpinc.min.css', true);

  //sample-grommet
  gulp.src('examples/todo-app/index.html')
        .pipe(preprocess({context: { NODE_ENV: 'production'}}))
        .pipe(rename('sample-grommet.html'))
        .pipe(gulp.dest('dist-bower'));

  var bowerJSON = getPackageJSON();
  bowerJSON.dependencies = { 'react': '^0.13.1', 'grommet': 'HewlettPackard/grommet-bower' };
  bowerJSON.main = 'grommet.js';
  delete bowerJSON.devDependencies;
  delete bowerJSON.scripts;
  delete bowerJSON.config;

  return gulp.src('./dist-bower')
          .pipe(file('bower.json', JSON.stringify(bowerJSON, null, 2)))
          .pipe(gulp.dest('dist-bower'));
});

gulp.task('blanket-lcov-reporter', function(done) {
  require('./src/utils/test/test-compiler');
  require('./src/utils/test/mocked-dom')('');

  gulp.src('./test/**/*.js', { read: false }).pipe(blanket({
    instrument: [path.join(__dirname, 'src/js')],
    captureFile: 'test/lcov.info',
    reporter: 'mocha-lcov-reporter'
  }));

  done();
});

gulp.task('coveralls', function() {
  var lcovPath = './test/lcov.info';
  fs.exists(lcovPath, function (exists) {
    if (exists) {
      gulp.src(lcovPath).pipe(coveralls());
    } else {
      console.error('Could not find lcov report file.');
      process.exit(1);
    }
  });
});

gulp.task('release:bump', function(done) {
  gulp.src('./')
    .pipe(prompt.prompt({
        type: 'input',
        name: 'bump',
        message: 'What type of bump would you like to do (patch, minor, major)?',
        validate: function(pass){
          if(pass !== 'patch' && pass !== 'minor' && pass !== 'major'){
              return false;
          }
          return true;
        }
    }, function(res){
        gulp.src('./package.json')
          .pipe(bump({ type: res.bump }))
          .pipe(gulp.dest('./')).on('end', function() {
            opts.copyAssets.push({
              filename: 'package.json',
              asset: JSON.stringify(getPackageJSON(), null, 2)
            });
            done();
          });
    }));
});

gulp.task('release:npm', function(done) {
  process.chdir('dist');
  spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', function() {
    process.chdir(__dirname);
    var version = 'v'+getPackageJSON().version;
    gulp.src('./')
      .pipe(git.add({args: '--all'}))
      .pipe(git.commit(version)).on('end', function() {
        git.push('origin', 'master', function (err) {
          if (err) {
            throw err;
          }

          git.tag(version, version, function (err) {
            if (err) {
              throw err;
            }

            git.push('origin', version, function (err) {
              if (err) {
                throw err;
              }
              process.chdir(__dirname);
              done();
            });
          });
        });
      });
  });
});

gulp.task('release:createTmp', function(done) {
  del.sync(['./tmp']);
  mkdirp('./tmp', function (err) {
    if (err) {
      throw err;
    }
    done();
  });
});

gulp.task('release:bower', ['release:createTmp'], function(done) {
  git.clone('https://github.com/HewlettPackard/grommet-bower.git',
    { cwd: './tmp/' },
    function (err) {
      if (err) {
        throw err;
      }
      gulp.src('./dist-bower/**').pipe(gulp.dest('./tmp/grommet-bower'));

      var version = 'v'+getPackageJSON().version;
      process.chdir('./tmp/grommet-bower');
      gulp.src('./')
        .pipe(git.add({args: '--all'}))
        .pipe(git.commit(version)).on('end', function() {
          git.push('origin', 'master', function (err) {
            if (err) {
              throw err;
            }

            git.tag(version, version, function (err) {
              if (err) {
                throw err;
              }

              git.push('origin', version, function (err) {
                if (err) {
                  throw err;
                }
                process.chdir(__dirname);
                done();
              });
            });
          });
        });
    }
  );
});

gulp.task('release:stable', ['dist', 'release:createTmp'], function(done) {
  if (process.env.CI) {
    git.clone('https://'+ process.env.GH_TOKEN +'@github.com/HewlettPackard/grommet.git',
      { cwd: './tmp/' },
      function (err) {
        if (err) {
          throw err;
        }

        process.chdir('./tmp/grommet');
        git.checkout('stable', function (err) {
          if (err) {
            throw err;
          }

          gulp.src('../../dist/**').pipe(gulp.dest('./'));

          git.status({args: '--porcelain'}, function (err, stdout) {
            if (err) {
              throw err;
            }

            if (stdout && stdout !== '') {
              gulp.src('./')
              .pipe(git.add({args: '--all'}))
              .pipe(git.commit('Stable dev version update.')).on('end', function() {
                git.push('origin', 'stable', function (err) {
                  if (err) {
                    throw err;
                  }

                  process.chdir(__dirname);
                  done();
                });
              });
            } else {
              console.log('No difference since last commit, skipping stable release.');

              process.chdir(__dirname);
              done();
            }
          });
        });
      }
    );
  } else {
    console.warn('Skipping release. Release:stable task should be executed by CI only.');
  }
});

gulp.task('release:clean', function() {
  del.sync(['./tmp']);
});

gulp.task('release:sync', function() {
  gulp.src('./docs/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));
});

gulp.task('release', function(done) {
  runSequence('release:bump', ['dist-bower', 'dist'], 'release:npm', 'release:bower', 'release:clean', 'release:sync', done);
});
<commit_msg>Fixing async issue with release stable.<commit_after>var gulp = require('gulp');
var chug = require('gulp-chug');
var gulpWebpack = require('gulp-webpack');
var sass = require('gulp-sass');
var rename = require('gulp-rename');
var minifyCss = require('gulp-minify-css');
var file = require('gulp-file');
var preprocess = require('gulp-preprocess');
var gulpif = require('gulp-if');
var prompt = require('gulp-prompt');
var bump = require('gulp-bump');
var git = require('gulp-git');
var assign = require('object-assign');
var webpack = require('webpack');
var del = require('del');
var path = require('path');
var fs = require('fs');
var coveralls = require('gulp-coveralls');
var blanket = require('gulp-blanket-mocha');
var runSequence = require('run-sequence');
var spawn = require('child_process').spawn;
var mkdirp = require('mkdirp');

function getPackageJSON() {
  delete require.cache[require.resolve('./package.json')];
  var packageJSON = require('./package.json');
  delete packageJSON.devDependencies;
  delete packageJSON.config;
  packageJSON.main = 'index.js';
  packageJSON.dependencies = {
    'react': '^0.13.1',
    'react-intl': '^1.2.0',
    'inuit-box-sizing': '~0.2.0',
    'inuit-clearfix': '^0.2.1',
    'inuit-defaults': '~0.2.1',
    'inuit-functions': '~0.2.0',
    'inuit-headings': '~0.3.0',
    'inuit-images': '~0.3.3',
    'inuit-list-bare': '~0.3.0',
    'inuit-lists': '~0.1.0',
    'inuit-mixins': '~0.2.3',
    'inuit-normalize': '~3.0.2',
    'inuit-page': '~0.2.1',
    'inuit-reset': '~0.1.1',
    'inuit-responsive-settings': '~0.1.2',
    'inuit-responsive-tools': '~0.1.1',
    'inuit-shared': '~0.1.5',
    'mkdirp': '^0.5.0',
    'gulp': '^3.8.11',
    'gulp-template': '^3.0.0',
    'gulp-install': '^0.4.0',
    "lodash": "^3.8.0",
    "reflux": "^0.2.7",
    "superagent": "^1.1.0"
  };
  return packageJSON;
}

var opts = {
  dist: path.resolve(__dirname, 'dist'),
  copyAssets: [
    'README.md',
    'src/js/**',
    {
      asset: 'src/scss/**',
      dist: 'dist/scss/'
    },
    {
      asset: 'src/utils/**',
      dist: 'dist/utils/'
    },
    'design/**',
    'src/img/**',
    {
      asset: 'bin/**',
      dist: 'dist/bin/'
    },
    {
      asset: 'templates/**',
      dist: 'dist/templates/'
    },
    {
      asset: 'examples/**',
      dist: 'dist/examples/'
    },
    {
      filename: 'package.json',
      asset: JSON.stringify(getPackageJSON(), null, 2)
    }
  ],
  scssAssets: ['src/scss/**/*.scss'],
  jsAssets: ['src/js/**/*.js'],
  mainJs: 'src/js/index.js',
  mainScss: 'src/scss/grommet-core/index.scss',
  sync: {
    hostname: 'grommet.usa.hp.com',
    username: 'ligo',
    remoteDestination: '/var/www/html/assets/' + getPackageJSON().version
  },
  webpack: {
    output: {
      filename: 'grommet.min.js',
      libraryTarget: 'var',
      library: 'Grommet'
    },
    resolve: {
      modulesDirectories: ['node_modules', 'src/js', 'src/scss']
    },
    externals: {
      'react': 'React'
    }
  },
  distPreprocess: ['dist-css'],
  scsslint: true,
  testPaths: [
    'test/**/*.js'
  ]
};

require('./src/utils/gulp/gulp-tasks')(gulp, opts);

gulp.task('dist-css', function () {
  gulp.src('src/scss/hpinc/*.scss')
    .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
    .pipe(rename('grommet-hpinc.min.css'))
    .pipe(minifyCss())
    .pipe(gulp.dest('dist/'));

  gulp.src('src/scss/hpe/*.scss')
    .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
    .pipe(rename('grommet-hpe.min.css'))
    .pipe(minifyCss())
    .pipe(gulp.dest('dist/'));

  return gulp.src('src/scss/grommet-core/*.scss')
        .pipe(sass({includePaths: [path.resolve(__dirname, './node_modules')]}))
        .pipe(rename('grommet.min.css'))
        .pipe(minifyCss())
        .pipe(gulp.dest('dist/'));
});

gulp.task('sync-all', function() {

  gulp.src('./examples/demo/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./examples/tour/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./examples/server/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./docs/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));
});

var bowerWebpackConfig = {
  output: {
    filename: 'grommet.js',
    libraryTarget: 'var',
    library: 'Grommet'
  },
  resolve: {
    root: [
      path.resolve(__dirname, 'src/js'),
      path.resolve(__dirname, 'src/scss'),
      path.resolve(__dirname, 'node_modules')
    ],
    extensions: ['', '.js', '.json', '.htm', '.html', '.scss']
  },
  externals: {
    'react': 'React'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'jsx-loader'
      },
      {
        test: /\.svg$/,
        loader: 'file-loader?mimetype=image/svg'
      },
      {
        test: /\.jpg$/,
        loader: 'file-loader?mimetype=image/jpg'
      },
      {
        test: /\.woff$/,
        loader: 'file-loader?mimetype=application/font-woff'
      }
    ]
  },
  plugins: [
   //new webpack.optimize.DedupePlugin()
  ]
};

var bowerMinWebpackConfig = assign({}, bowerWebpackConfig, {
  output: {
    filename: 'grommet.min.js',
    libraryTarget: 'var',
    library: 'Grommet'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })
    //new webpack.optimize.DedupePlugin()
  ]
});

function distCss(path, name, minify) {
  gulp.src(path)
    .pipe(sass({includePaths: [ 'node_modules' ]}))
    .pipe(rename(name))
    .pipe(gulpif(minify, minifyCss()))
    .pipe(gulp.dest('dist-bower/css'));
}

gulp.task('dist-bower:exploded', function() {
  return gulp.src(opts.mainJs)
    .pipe(gulpWebpack(bowerWebpackConfig))
    .pipe(gulp.dest('dist-bower'));
});

gulp.task('dist-bower:minified', function() {
  return gulp.src(opts.mainJs)
    .pipe(gulpWebpack(bowerMinWebpackConfig))
    .pipe(gulp.dest('dist-bower'));
});

gulp.task('dist-bower:preprocess', function(done) {
  del.sync(['dist-bower']);
  runSequence(['dist-bower:exploded', 'dist-bower:minified'], done);
});

gulp.task('dist-bower', ['dist-bower:preprocess'], function() {

  distCss('src/scss/grommet-core/*.scss', 'grommet.css');
  distCss('src/scss/grommet-core/*.scss', 'grommet.min.css', true);
  distCss('src/scss/hpe/*.scss', 'grommet-hpe.css');
  distCss('src/scss/hpe/*.scss', 'grommet-hpe.min.css', true);
  distCss('src/scss/hpinc/*.scss', 'grommet-hpinc.css');
  distCss('src/scss/hpinc/*.scss', 'grommet-hpinc.min.css', true);

  //sample-grommet
  gulp.src('examples/todo-app/index.html')
        .pipe(preprocess({context: { NODE_ENV: 'production'}}))
        .pipe(rename('sample-grommet.html'))
        .pipe(gulp.dest('dist-bower'));

  var bowerJSON = getPackageJSON();
  bowerJSON.dependencies = { 'react': '^0.13.1', 'grommet': 'HewlettPackard/grommet-bower' };
  bowerJSON.main = 'grommet.js';
  delete bowerJSON.devDependencies;
  delete bowerJSON.scripts;
  delete bowerJSON.config;

  return gulp.src('./dist-bower')
          .pipe(file('bower.json', JSON.stringify(bowerJSON, null, 2)))
          .pipe(gulp.dest('dist-bower'));
});

gulp.task('blanket-lcov-reporter', function(done) {
  require('./src/utils/test/test-compiler');
  require('./src/utils/test/mocked-dom')('');

  gulp.src('./test/**/*.js', { read: false }).pipe(blanket({
    instrument: [path.join(__dirname, 'src/js')],
    captureFile: 'test/lcov.info',
    reporter: 'mocha-lcov-reporter'
  }));

  done();
});

gulp.task('coveralls', function() {
  var lcovPath = './test/lcov.info';
  fs.exists(lcovPath, function (exists) {
    if (exists) {
      gulp.src(lcovPath).pipe(coveralls());
    } else {
      console.error('Could not find lcov report file.');
      process.exit(1);
    }
  });
});

gulp.task('release:bump', function(done) {
  gulp.src('./')
    .pipe(prompt.prompt({
        type: 'input',
        name: 'bump',
        message: 'What type of bump would you like to do (patch, minor, major)?',
        validate: function(pass){
          if(pass !== 'patch' && pass !== 'minor' && pass !== 'major'){
              return false;
          }
          return true;
        }
    }, function(res){
        gulp.src('./package.json')
          .pipe(bump({ type: res.bump }))
          .pipe(gulp.dest('./')).on('end', function() {
            opts.copyAssets.push({
              filename: 'package.json',
              asset: JSON.stringify(getPackageJSON(), null, 2)
            });
            done();
          });
    }));
});

gulp.task('release:npm', function(done) {
  process.chdir('dist');
  spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', function() {
    process.chdir(__dirname);
    var version = 'v'+getPackageJSON().version;
    gulp.src('./')
      .pipe(git.add({args: '--all'}))
      .pipe(git.commit(version)).on('end', function() {
        git.push('origin', 'master', function (err) {
          if (err) {
            throw err;
          }

          git.tag(version, version, function (err) {
            if (err) {
              throw err;
            }

            git.push('origin', version, function (err) {
              if (err) {
                throw err;
              }
              process.chdir(__dirname);
              done();
            });
          });
        });
      });
  });
});

gulp.task('release:createTmp', function(done) {
  del.sync(['./tmp']);
  mkdirp('./tmp', function (err) {
    if (err) {
      throw err;
    }
    done();
  });
});

gulp.task('release:bower', ['release:createTmp'], function(done) {
  git.clone('https://github.com/HewlettPackard/grommet-bower.git',
    { cwd: './tmp/' },
    function (err) {
      if (err) {
        throw err;
      }
      gulp.src('./dist-bower/**').pipe(gulp.dest('./tmp/grommet-bower'));

      var version = 'v'+getPackageJSON().version;
      process.chdir('./tmp/grommet-bower');
      gulp.src('./')
        .pipe(git.add({args: '--all'}))
        .pipe(git.commit(version)).on('end', function() {
          git.push('origin', 'master', function (err) {
            if (err) {
              throw err;
            }

            git.tag(version, version, function (err) {
              if (err) {
                throw err;
              }

              git.push('origin', version, function (err) {
                if (err) {
                  throw err;
                }
                process.chdir(__dirname);
                done();
              });
            });
          });
        });
    }
  );
});

gulp.task('release:stable', ['dist', 'release:createTmp'], function(done) {
  if (process.env.CI) {
    git.clone('https://'+ process.env.GH_TOKEN +'@github.com/HewlettPackard/grommet.git',
      { cwd: './tmp/' },
      function (err) {
        if (err) {
          throw err;
        }

        process.chdir('./tmp/grommet');
        git.checkout('stable', function (err) {
          if (err) {
            throw err;
          }

          gulp.src('../../dist/**').pipe(gulp.dest('./')).on('end', function() {
            git.status({args: '--porcelain'}, function (err, stdout) {
            if (err) {
              throw err;
            }

            if (stdout && stdout !== '') {
              gulp.src('./')
              .pipe(git.add({args: '--all'}))
              .pipe(git.commit('Stable dev version update.')).on('end', function() {
                git.push('origin', 'stable', function (err) {
                  if (err) {
                    throw err;
                  }

                  process.chdir(__dirname);
                  done();
                });
              });
            } else {
              console.log('No difference since last commit, skipping stable release.');

              process.chdir(__dirname);
              done();
            }
          });
          });
        });
      }
    );
  } else {
    console.warn('Skipping release. Release:stable task should be executed by CI only.');
  }
});

gulp.task('release:clean', function() {
  del.sync(['./tmp']);
});

gulp.task('release:sync', function() {
  gulp.src('./docs/gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));

  gulp.src('./gulpfile.js').pipe(chug({
    tasks: ['sync']
  }));
});

gulp.task('release', function(done) {
  runSequence('release:bump', ['dist-bower', 'dist'], 'release:npm', 'release:bower', 'release:clean', 'release:sync', done);
});
<|endoftext|>


<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 15;

(function() {

    var module = angular.module('amara.SubtitleEditor.subtitles.directives', []);

    module.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    module.directive('workingSubtitles', ['VideoPlayer', function(VideoPlayer) {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var currentArrow = undefined;
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;

                if(!scope.timelineShown || !VideoPlayer.isPlaying()) {
                    startHelper.hide();
                    endHelper.hide();
                    return;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
            }

            scope.positionInfoTray = function() {
                var BUFFER = 22;
                var subtitle = scope.currentEdit.storedSubtitle();
                if(subtitle === null) {
                    infoTray.hide();
                    return;
                }
                var li = scope.getSubtitleRepeatItem(subtitle);
                if(li) {
                    currentArrow = li.find(".arrow");
                    var top = li.offset().top - wrapper.offset().top;
                    var bottom = top + li.height();

                    var infoTrayBottom = infoTray.height() + top;
                    var willTrim = wrapper.height() < infoTrayBottom;
                    if (willTrim) {
                        var adjustedTop = top - (infoTrayBottom - wrapper.height());
                        if (adjustedTop + BUFFER < bottom && adjustedTop - BUFFER > top - infoTray.height()) {
                            top = adjustedTop;
                        }
                    }
                    if(top + BUFFER >= 0 && top + BUFFER < wrapper.height()) {
                        infoTray.css('top', top + 'px');
                        infoTray.show();
                        currentArrow.show();
                    } else {
                        infoTray.hide();
                        currentArrow.hide();
                    }
                }
            }

            scope.$watch("currentEdit.draft", function() {
                if (currentArrow) {
                    currentArrow.hide(); // Kinda hate this, i think it would be cleaner to give each line its own controller
                }
                scope.positionInfoTray();
            });

            scope.$watch("timelineShown", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on("video-playback-changes", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on('working-subtitles-scrolled', function() {
                scope.positionSyncHelpers();
                scope.positionInfoTray();
            });
        };
    }]);

    module.directive('subtitleScroller', ["$window", function($window) {
        var window = $($window);
	var scrollingPrevious = [];
	$('div.subtitles').each(function(index) {
	    scrollingPrevious[index] = $(this).scrollTop();
	});
        return function link(scope, elem, attrs) {
            var scroller = $(elem);
            var isWorkingSet = (attrs.subtitleScroller == "working-subtitle-set");
            // Handle scroll.
            scroller.scroll(function() {
                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var delta = 0;
                    var index_scrolled = -1;
                    $('div.subtitles').each(function(index) {
                        var newScrollTop = $(this).scrollTop();
                        if (newScrollTop != scrollingPrevious[index]) {
                            delta = newScrollTop - scrollingPrevious[index];
                            index_scrolled = index;
                        }
                    });
                    if (index_scrolled != -1) {
                        $('div.subtitles').each(function(index) {
                            if (index != index_scrolled) {
                                var newScrollTop = $(this).scrollTop() + delta;
                                $(this).scrollTop(newScrollTop);
                                var updatedScrollTop = $(this).scrollTop();
                                if ((updatedScrollTop != newScrollTop) && (updatedScrollTop != 0)) {
                                    $(this).children().last().height($(this).children().last().height() + newScrollTop - updatedScrollTop);
                                    $(this).scrollTop(newScrollTop);
                                }
                            }
                        });
                    }
                }
		$('div.subtitles').each(function(index) {
		    scrollingPrevious[index] = $(this).scrollTop();
		});

                if(isWorkingSet) {
                    scope.$root.$emit("working-subtitles-scrolled");
                }
            });

            function resizeScroller() {
                if (scope.timelineShown) {
                    var scrollerTop = 398;
                } else {
                    var scrollerTop = 327;
                }
                scroller.height(window.height() - scrollerTop);
            }

            scope.$watch('timelineShown', resizeScroller);
            window.on('resize', resizeScroller);
            resizeScroller();

            if (isWorkingSet) {
                scope.$root.$on('scroll-to-subtitle', function(evt, subtitle) {
                    if(scope.currentEdit.inProgress()) {
                        return;
                    }
                    var target = scope.getSubtitleRepeatItem(subtitle);
                    var prev = target.prev();
                    if(prev.length > 0) {
                        target = prev;
                    }
                    if(target) {
                        scroller.scrollTop(scroller.scrollTop() +
                                target.offset().top - scroller.offset().top);
                    }
                });
            }
        }
    }]);

    module.directive('subtitleRepeat', ["$interpolate", "$filter", "$parse", "DomUtil", "EditorData", function($interpolate, $filter, $parse,
                DomUtil, EditorData) {
        /* Specialized repeat directive to work with subtitleList
         *
         * Because we need to deal potentially thousands of subtitles,
         * ng-repeat is not ideal.  subtitleRepeat is a specialized directive
         * that creates the
 that we need for a subtitle list.  The
         * speedups come from a couple things:
         *
         *   - It hooks up to the change callback of subtitleList to calculate
         *   changes quickly.
         *   - it doesn't create a child scope for each subtitle.
         *
         *  When using subtitle-repeat, set the value of subtitle-repeat to
         *  the subtitleList to bind to.  Optionally, create a read-only
         *  attribute to specify a read-only list.
         */
        return function link($scope, elm, attrs) {
            var subtitleList = $scope.$eval(attrs.subtitleRepeat);
            // Map subtitle ID to DOM node for that subtitle
            var subtitleMap = {};
            // are we a read-only list?
            var readOnly = Boolean(attrs.readOnly);
            var displayTime = $filter('displayTime');
            // Template elements that we use to create list items
            var templateLI = $('
');
            templateLI.append('');
            templateLI.append('');
            if(!readOnly) {
		templateLI.append('!');
                templateLI.append(makeImageButton('remove-subtitle',
                    'images/editor/remove-subtitle.gif'));
                templateLI.append(makeImageButton('insert-subtitle',
                    'images/editor/plus.gif'));
                templateLI.append(
                        '<button class="new-paragraph">¶</button>');
            } else {
                templateLI.append('¶');
            }

            $scope.getSubtitleRepeatItem = function(subtitle) {
                var rv = subtitleMap[subtitle.id];
                if(rv !== undefined) {
                    return rv;
                } else {
                    return null;
                }
            }

            if(!readOnly) {
                $scope.$watch('currentEdit.draft', function(newValue, oldValue) {
                    if(oldValue) {
                        stopEditOn(oldValue);
                    }
                    if(newValue) {
                        startEditOn(newValue);
                    }
                });

                elm.on('click', function(evt) {
                    var action = findSubtitleClick(evt.target);
                    var subtitle = findSubtitleData(evt.target);
                    if(action && $scope.onSubtitleClick) {
                        $scope.$apply(function() {
                            $scope.onSubtitleClick(evt, subtitle, action);
                        });
                    }
                });
            }

            $scope.$watch('timeline.shownSubtitle', function(newSub, oldSub) {
                if(newSub && subtitleMap[newSub.id]) {
                    subtitleMap[newSub.id].addClass('current-subtitle');
                }
                if(oldSub && subtitleMap[oldSub.id]) {
                    subtitleMap[oldSub.id].removeClass('current-subtitle');
                }
            });

            subtitleList.addChangeCallback(onChange);
            reloadSubtitles();

            function makeImageButton(cssClass, imageURLPath) {
                var img = $('');
                img.prop('src', EditorData.staticURL + imageURLPath);
                var button = $('<button />');
                button.prop('class', cssClass);
                button.append(img);
                return button
            }

            function createLIForSubtitle(subtitle) {
                var elt = templateLI.clone();
                renderSubtitle(subtitle, elt);
                subtitleMap[subtitle.id] = elt;
                elt.data('subtitle', subtitle);
                return elt;
            }

            function renderSubtitle(subtitle, elt) {
                var content = subtitle.content();
                var classes = [];

                if($scope.timeline.shownSubtitle === subtitle) {
                    classes.push('current-subtitle');
                }
                if(content == '') {
                    classes.push('empty');
                }
                if(subtitle.startOfParagraph) {
                    classes.push('paragraph-start');
                }
                if($scope.currentEdit.isForSubtitle(subtitle)) {
                    classes.push('edit');
                }
                elt.prop('className', classes.join(' '));
                $('span.subtitle-text', elt).html(content);
                $('span.timing', elt).text(displayTime(subtitle.startTime));
		$('span.warning', elt).toggle($scope.warningsShown && subtitle.hasWarning());
            }

            function findSubtitleData(node) {
                // Find the subtitle that we set with the jquery data()
                // function by starting with node and moving up the DOM tree.
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    var subtitle = $(node).data('subtitle');
                    if(subtitle) {
                        return subtitle;
                    } else {
                        node = node.parentNode;
                    }
                }
                return null;
            }

            function findSubtitleClick(node, dataName) {
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    if(node.tagName == 'BUTTON') {
                        switch(node.className) {
                            case 'insert-subtitle':
                                return 'insert';

                            case 'remove-subtitle':
                                return 'remove';

                            case 'new-paragraph':
                                return 'changeParagraph'
                        }
                    } else if(node.tagName == 'LI') {
                        return 'edit';
                    }
                    node = node.parentNode;
                }
                return null;
            }

            function onChange(change) {
                var subtitle = change.subtitle;
                switch(change.type) {
                    case 'remove':
                        var node = subtitleMap[subtitle.id];
                        node.remove();
                        delete subtitleMap[subtitle.id];
                        break;
                    case 'update':
                        renderSubtitle(subtitle, subtitleMap[subtitle.id]);
                        break;
                    case 'insert':
                        if(change.before !== null) {
                            var node = subtitleMap[change.before.id];
                            node.before(createLIForSubtitle(subtitle));
                        } else {
                            elm.append(createLIForSubtitle(subtitle));
                        }
                        break;
                    case 'reload':
                        reloadSubtitles();
                }
            }

            function startEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
                li.addClass('edit');
                var textarea = $('<textarea class="subtitle-edit" placeholder="Type a subtitle and press Enter"/>');
                textarea.val(draft.markdown);
                li.append(textarea);
                textarea.autosize();
                textarea.focus();
                if(draft.initialCaretPos === undefined) {
                    var caretPos = draft.markdown.length;
                } else {
                    var caretPos = draft.initialCaretPos;
                }
                DomUtil.setSelectionRange(textarea[0], caretPos, caretPos);
                textarea.on('keyup input propertychange', function(evt) {
                    var val = textarea.val();
                    if(val != draft.markdown) {
                        $scope.$apply(function() {
                            draft.markdown = val;
                        });
                    }
                });
                if($scope.onEditKeydown) {
                    textarea.on('keydown', function(evt) {
                        $scope.$apply(function() {
                            $scope.onEditKeydown(evt);
                        });
                    });
                }
            }
            function stopEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
                if(li) {
                    li.removeClass('edit');
                    $('textarea.subtitle-edit', li).remove();
                }
            }

            function reloadSubtitles() {
                elm.empty();
                subtitleMap = {}
                _.each(subtitleList.subtitles, function(subtitle) {
                    elm.append(createLIForSubtitle(subtitle));
                });
            }
        }
    }]);

    module.directive('languageSelector', ["SubtitleStorage", function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        scope.setInitialDisplayLanguage(SubtitleStorage.getLanguages());
                    }
                };
            }
        };
    }]);
})();
<commit_msg>Remove last subtitle if stopped editing and empty<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;
var LOCK_EXPIRATION = 25;
var USER_IDLE_MINUTES = 15;

(function() {

    var module = angular.module('amara.SubtitleEditor.subtitles.directives', []);

    module.directive('subtitleEditor', function() {
        return function link(scope, elm, attrs) {
            // For some reason using ng-keydown at the HTML tag doesn't work.
            // Use jquery instead.
            $(document).keydown(function(evt) {
                scope.$apply(function(scope) {
                    scope.handleAppKeyDown(evt);
                });
            });
        };
    });
    module.directive('workingSubtitles', ['VideoPlayer', function(VideoPlayer) {
        return function link(scope, elem, attrs) {
            var startHelper = $('div.sync-help.begin', elem);
            var endHelper = $('div.sync-help.end', elem);
            var infoTray = $('div.info-tray', elem);
            var subtitleList = $('.subtitles ul', elem);
            var currentArrow = undefined;
            var wrapper = $(elem);

            function getSubtitleTop(index) {
                var li = $('li', subtitleList).eq(index);
                var top = li.offset().top - wrapper.offset().top;
                if(top < 0 || top + startHelper.height() >= wrapper.height()) {
                    return null;
                }
                return top;
            }

            var lastSyncStartIndex = null;
            var lastSyncEndIndex = null;

            scope.positionSyncHelpers = function(startIndex, endIndex) {
                if(startIndex === undefined) {
                    startIndex = lastSyncStartIndex;
                }
                if(endIndex === undefined) {
                    endIndex = lastSyncEndIndex;
                }
                lastSyncStartIndex = startIndex;
                lastSyncEndIndex = endIndex;

                if(!scope.timelineShown || !VideoPlayer.isPlaying()) {
                    startHelper.hide();
                    endHelper.hide();
                    return;
                }
                var startTop = null;
                var endTop = null;
                if(startIndex !== null) {
                    startTop = getSubtitleTop(startIndex);
                }
                if(endIndex !== null) {
                    endTop = getSubtitleTop(endIndex);
                }
                if(startTop !== null) {
                    startHelper.css('top', startTop + 'px');
                    startHelper.show();
                } else {
                    startHelper.hide();
                }
                if(endTop !== null) {
                    endHelper.css('top', endTop + 'px');
                    endHelper.show();
                } else {
                    endHelper.hide();
                }
            }

            scope.positionInfoTray = function() {
                var BUFFER = 22;
                var subtitle = scope.currentEdit.storedSubtitle();
                if(subtitle === null) {
                    infoTray.hide();
                    return;
                }
                var li = scope.getSubtitleRepeatItem(subtitle);
                if(li) {
                    currentArrow = li.find(".arrow");
                    var top = li.offset().top - wrapper.offset().top;
                    var bottom = top + li.height();

                    var infoTrayBottom = infoTray.height() + top;
                    var willTrim = wrapper.height() < infoTrayBottom;
                    if (willTrim) {
                        var adjustedTop = top - (infoTrayBottom - wrapper.height());
                        if (adjustedTop + BUFFER < bottom && adjustedTop - BUFFER > top - infoTray.height()) {
                            top = adjustedTop;
                        }
                    }
                    if(top + BUFFER >= 0 && top + BUFFER < wrapper.height()) {
                        infoTray.css('top', top + 'px');
                        infoTray.show();
                        currentArrow.show();
                    } else {
                        infoTray.hide();
                        currentArrow.hide();
                    }
                }
            }

            scope.$watch("currentEdit.draft", function() {
                if (currentArrow) {
                    currentArrow.hide(); // Kinda hate this, i think it would be cleaner to give each line its own controller
                }
                scope.positionInfoTray();
            });

            scope.$watch("timelineShown", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on("video-playback-changes", function() {
                scope.positionSyncHelpers();
            });

            scope.$root.$on('working-subtitles-scrolled', function() {
                scope.positionSyncHelpers();
                scope.positionInfoTray();
            });
        };
    }]);

    module.directive('subtitleScroller', ["$window", function($window) {
        var window = $($window);
	var scrollingPrevious = [];
	$('div.subtitles').each(function(index) {
	    scrollingPrevious[index] = $(this).scrollTop();
	});
        return function link(scope, elem, attrs) {
            var scroller = $(elem);
            var isWorkingSet = (attrs.subtitleScroller == "working-subtitle-set");
            // Handle scroll.
            scroller.scroll(function() {
                // If scroll sync is locked.
                if (scope.scrollingSynced) {
                    var delta = 0;
                    var index_scrolled = -1;
                    $('div.subtitles').each(function(index) {
                        var newScrollTop = $(this).scrollTop();
                        if (newScrollTop != scrollingPrevious[index]) {
                            delta = newScrollTop - scrollingPrevious[index];
                            index_scrolled = index;
                        }
                    });
                    if (index_scrolled != -1) {
                        $('div.subtitles').each(function(index) {
                            if (index != index_scrolled) {
                                var newScrollTop = $(this).scrollTop() + delta;
                                $(this).scrollTop(newScrollTop);
                                var updatedScrollTop = $(this).scrollTop();
                                if ((updatedScrollTop != newScrollTop) && (updatedScrollTop != 0)) {
                                    $(this).children().last().height($(this).children().last().height() + newScrollTop - updatedScrollTop);
                                    $(this).scrollTop(newScrollTop);
                                }
                            }
                        });
                    }
                }
		$('div.subtitles').each(function(index) {
		    scrollingPrevious[index] = $(this).scrollTop();
		});

                if(isWorkingSet) {
                    scope.$root.$emit("working-subtitles-scrolled");
                }
            });

            function resizeScroller() {
                if (scope.timelineShown) {
                    var scrollerTop = 398;
                } else {
                    var scrollerTop = 327;
                }
                scroller.height(window.height() - scrollerTop);
            }

            scope.$watch('timelineShown', resizeScroller);
            window.on('resize', resizeScroller);
            resizeScroller();

            if (isWorkingSet) {
                scope.$root.$on('scroll-to-subtitle', function(evt, subtitle) {
                    if(scope.currentEdit.inProgress()) {
                        return;
                    }
                    var target = scope.getSubtitleRepeatItem(subtitle);
                    var prev = target.prev();
                    if(prev.length > 0) {
                        target = prev;
                    }
                    if(target) {
                        scroller.scrollTop(scroller.scrollTop() +
                                target.offset().top - scroller.offset().top);
                    }
                });
            }
        }
    }]);

    module.directive('subtitleRepeat', ["$interpolate", "$filter", "$parse", "DomUtil", "EditorData", function($interpolate, $filter, $parse,
                DomUtil, EditorData) {
        /* Specialized repeat directive to work with subtitleList
         *
         * Because we need to deal potentially thousands of subtitles,
         * ng-repeat is not ideal.  subtitleRepeat is a specialized directive
         * that creates the
 that we need for a subtitle list.  The
         * speedups come from a couple things:
         *
         *   - It hooks up to the change callback of subtitleList to calculate
         *   changes quickly.
         *   - it doesn't create a child scope for each subtitle.
         *
         *  When using subtitle-repeat, set the value of subtitle-repeat to
         *  the subtitleList to bind to.  Optionally, create a read-only
         *  attribute to specify a read-only list.
         */
        return function link($scope, elm, attrs) {
            var subtitleList = $scope.$eval(attrs.subtitleRepeat);
            // Map subtitle ID to DOM node for that subtitle
            var subtitleMap = {};
            // are we a read-only list?
            var readOnly = Boolean(attrs.readOnly);
            var displayTime = $filter('displayTime');
            // Template elements that we use to create list items
            var templateLI = $('
');
            templateLI.append('');
            templateLI.append('');
            if(!readOnly) {
		templateLI.append('!');
                templateLI.append(makeImageButton('remove-subtitle',
                    'images/editor/remove-subtitle.gif'));
                templateLI.append(makeImageButton('insert-subtitle',
                    'images/editor/plus.gif'));
                templateLI.append(
                        '<button class="new-paragraph">¶</button>');
            } else {
                templateLI.append('¶');
            }

            $scope.getSubtitleRepeatItem = function(subtitle) {
                var rv = subtitleMap[subtitle.id];
                if(rv !== undefined) {
                    return rv;
                } else {
                    return null;
                }
            }

            if(!readOnly) {
                $scope.$watch('currentEdit.draft', function(newValue, oldValue) {
                    if(oldValue) {
                        stopEditOn(oldValue);
                    }
                    if(newValue) {
                        startEditOn(newValue);
                    }
                });
                elm.on('click', function(evt) {
                    var action = findSubtitleClick(evt.target);
                    var subtitle = findSubtitleData(evt.target);
                    if(action && $scope.onSubtitleClick) {
                        $scope.$apply(function() {
                            $scope.onSubtitleClick(evt, subtitle, action);
                        });
                    }
                });
            }

            $scope.$watch('timeline.shownSubtitle', function(newSub, oldSub) {
                if(newSub && subtitleMap[newSub.id]) {
                    subtitleMap[newSub.id].addClass('current-subtitle');
                }
                if(oldSub && subtitleMap[oldSub.id]) {
                    subtitleMap[oldSub.id].removeClass('current-subtitle');
                }
            });

            subtitleList.addChangeCallback(onChange);
            reloadSubtitles();

            function makeImageButton(cssClass, imageURLPath) {
                var img = $('');
                img.prop('src', EditorData.staticURL + imageURLPath);
                var button = $('<button />');
                button.prop('class', cssClass);
                button.append(img);
                return button
            }

            function createLIForSubtitle(subtitle) {
                var elt = templateLI.clone();
                renderSubtitle(subtitle, elt);
                subtitleMap[subtitle.id] = elt;
                elt.data('subtitle', subtitle);
                return elt;
            }

            function renderSubtitle(subtitle, elt) {
                var content = subtitle.content();
                var classes = [];

                if($scope.timeline.shownSubtitle === subtitle) {
                    classes.push('current-subtitle');
                }
                if(content == '') {
                    classes.push('empty');
                }
                if(subtitle.startOfParagraph) {
                    classes.push('paragraph-start');
                }
                if($scope.currentEdit.isForSubtitle(subtitle)) {
                    classes.push('edit');
                }
                elt.prop('className', classes.join(' '));
                $('span.subtitle-text', elt).html(content);
                $('span.timing', elt).text(displayTime(subtitle.startTime));
		$('span.warning', elt).toggle($scope.warningsShown && subtitle.hasWarning());
            }

            function findSubtitleData(node) {
                // Find the subtitle that we set with the jquery data()
                // function by starting with node and moving up the DOM tree.
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    var subtitle = $(node).data('subtitle');
                    if(subtitle) {
                        return subtitle;
                    } else {
                        node = node.parentNode;
                    }
                }
                return null;
            }

            function findSubtitleClick(node, dataName) {
                var toplevelNode = elm[0];
                while(node && node != toplevelNode) {
                    if(node.tagName == 'BUTTON') {
                        switch(node.className) {
                            case 'insert-subtitle':
                                return 'insert';

                            case 'remove-subtitle':
                                return 'remove';

                            case 'new-paragraph':
                                return 'changeParagraph'
                        }
                    } else if(node.tagName == 'LI') {
                        return 'edit';
                    }
                    node = node.parentNode;
                }
                return null;
            }

            function onChange(change) {
                var subtitle = change.subtitle;
                switch(change.type) {
                    case 'remove':
                        var node = subtitleMap[subtitle.id];
                        node.remove();
                        delete subtitleMap[subtitle.id];
                        break;
                    case 'update':
                        renderSubtitle(subtitle, subtitleMap[subtitle.id]);
                        break;
                    case 'insert':
                        if(change.before !== null) {
                            var node = subtitleMap[change.before.id];
                            node.before(createLIForSubtitle(subtitle));
                        } else {
                            elm.append(createLIForSubtitle(subtitle));
                        }
                        break;
                    case 'reload':
                        reloadSubtitles();
                }
            }

            function startEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
		if (li) {
                    li.addClass('edit');
                    var textarea = $('<textarea class="subtitle-edit" placeholder="Type a subtitle and press Enter"/>');
                    textarea.val(draft.markdown);
                    li.append(textarea);
                    textarea.autosize();
                    textarea.focus();
                    if(draft.initialCaretPos === undefined) {
                        var caretPos = draft.markdown.length;
                    } else {
                        var caretPos = draft.initialCaretPos;
                    }
                    DomUtil.setSelectionRange(textarea[0], caretPos, caretPos);
                    textarea.on('keyup input propertychange', function(evt) {
                        var val = textarea.val();
                        if(val != draft.markdown) {
                            $scope.$apply(function() {
                                draft.markdown = val;
                            });
                        }
                    });
                    if($scope.onEditKeydown) {
                        textarea.on('keydown', function(evt) {
                            $scope.$apply(function() {
                                $scope.onEditKeydown(evt);
                            });
                        });
                    }
                }
            }
            function stopEditOn(draft) {
                var li = subtitleMap[draft.storedSubtitle.id];
                if(li) {
                    li.removeClass('edit');
                    $('textarea.subtitle-edit', li).remove();
                    if ((draft.storedSubtitle.content().length == 0) && (subtitleMap[draft.storedSubtitle.id].next().html() == undefined)) {
                        subtitleList.removeSubtitle(draft.storedSubtitle);
                    }
                }
            }

            function reloadSubtitles() {
                elm.empty();
                subtitleMap = {}
                _.each(subtitleList.subtitles, function(subtitle) {
                    elm.append(createLIForSubtitle(subtitle));
                });
            }
        }
    }]);

    module.directive('languageSelector', ["SubtitleStorage", function(SubtitleStorage) {
        return {
            compile: function compile(elm, attrs, transclude) {
                return {
                    post: function post(scope, elm, attrs) {
                        /* For some reason, if we use ng-select angular
                         * creates an extra option because it thinks
                         * scope.versionNumber is an invalid value for the
                         * options, even though it isn't.  I think it has to
                         * do with the fact that we manually create options
                         * with ng-repeat.  In any case, we handle the
                         * select ourself.
                         */
                        $('select.version-select', elm).change(function(evt) {
                            scope.versionNumber = this.value;
                            scope.$digest();
                        });
                        scope.setInitialDisplayLanguage(SubtitleStorage.getLanguages());
                    }
                };
            }
        };
    }]);
})();
<|endoftext|>


<commit_before>'use strict';

const child = require('child_process');
const del = require('del');
const gulp = require('gulp');
const loadPlugins = require('gulp-load-plugins');
const os = require('os');
const path = require('path');
const pkg = require('./package.json');

/**
 * The task settings.
 * @type {object}
 */
const config = {
  output: `${pkg.name}-${pkg.version}.zip`,
  sources: ['akismet.js', '*.json', '*.md', '*.txt', 'bin/*.js', 'lib/*.js']
};

/**
 * The task plugins.
 * @type {object}
 */
const plugins = loadPlugins({
  pattern: ['gulp-*', '@*/gulp-*', 'vinyl-*'],
  replaceString: /^(gulp|vinyl)-/
});

/**
 * Runs the default tasks.
 */
gulp.task('default', ['build']);

/**
 * Builds the sources.
 */
gulp.task('build', () => gulp.src('src/**/*.js')
  .pipe(plugins.babel())
  .pipe(gulp.dest('lib'))
);

/**
 * Checks the package dependencies.
 */
gulp.task('check', () => {
  const {david} = plugins.cedx.david;
  return gulp.src('package.json').pipe(david()).on('error', function(err) {
    console.error(err);
    this.emit('end');
  });
});

/**
 * Deletes all generated files and reset any saved state.
 */
gulp.task('clean', () =>
  del(['var/**/*', `${os.homedir()}/.forever/akismet.log`])
);

/**
 * Sends the results of the code coverage.
 */
gulp.task('coverage', ['test'], () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'codacy-coverage.cmd' : 'codacy-coverage');
  return _exec(`${command} < var/lcov.info`);
});

/**
 * Creates a distribution file for this program.
 */
gulp.task('dist', ['default'], () => gulp.src(config.sources, {base: '.'})
  .pipe(plugins.zip(config.output))
  .pipe(gulp.dest('var'))
);

/**
 * Builds the documentation.
 */
gulp.task('doc', () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'esdoc.cmd' : 'esdoc');
  return del('doc/api').then(() => _exec(`${command} -c doc/esdoc.json`));
});

/**
 * Fixes the coding standards issues.
 */
gulp.task('fix', () => gulp.src(['*.js', 'src/**/*.js', 'test/**/*.js'], {base: '.'})
  .pipe(plugins.eslint({fix: true}))
  .pipe(gulp.dest('.'))
);

/**
 * Performs static analysis of source code.
 */
gulp.task('lint', () => gulp.src(['*.js', 'src/**/*.js', 'test/**/*.js'])
  .pipe(plugins.eslint())
  .pipe(plugins.eslint.format())
  .pipe(plugins.eslint.failAfterError())
);

/**
 * Starts the Akismet server.
 */
gulp.task('serve', () => {
  if ('_server' in global) global._server.kill();
  global._server = child.fork('bin/cli.js');
  return Promise.resolve();
});

/**
 * Runs the unit tests.
 */
gulp.task('test', ['test:instrument'], () => gulp.src(['test/**/*.js'], {read: false})
  .pipe(plugins.mocha())
  .pipe(plugins.istanbul.writeReports({dir: 'var', reporters: ['lcovonly']}))
);

gulp.task('test:env', () =>
  'AKISMET_API_KEY' in process.env ? Promise.resolve() : Promise.reject(new Error('AKISMET_API_KEY environment variable not set.'))
);

gulp.task('test:instrument', ['test:setup'], () => gulp.src(['src/**/*.js'])
  .pipe(plugins.istanbul({instrumenter: require('isparta').Instrumenter}))
  .pipe(plugins.istanbul.hookRequire())
);

gulp.task('test:setup', ['test:env'], () => new Promise(resolve => {
  process.env.BABEL_DISABLE_CACHE = process.platform == 'win32' ? '1' : '0';
  require('babel-register');
  resolve();
}));

/**
 * Runs a command and prints its output.
 * @param {string} command The command to run, with space-separated arguments.
 * @param {object} [options] The settings to customize how the process is spawned.
 * @return {Promise<string>} The command output when it is finally terminated.
 */
function _exec(command, options = {}) {
  return new Promise((resolve, reject) => child.exec(command, options, (err, stdout) => {
    if (err) reject(err);
    else resolve(stdout.trim());
  }));
}
<commit_msg>Removed obsolete code<commit_after>'use strict';

const child = require('child_process');
const del = require('del');
const gulp = require('gulp');
const loadPlugins = require('gulp-load-plugins');
const os = require('os');
const path = require('path');
const pkg = require('./package.json');

/**
 * The task settings.
 * @type {object}
 */
const config = {
  output: `${pkg.name}-${pkg.version}.zip`,
  sources: ['*.json', '*.md', '*.txt', 'lib/**/*.js']
};

/**
 * The task plugins.
 * @type {object}
 */
const plugins = loadPlugins({
  pattern: ['gulp-*', '@*/gulp-*', 'vinyl-*'],
  replaceString: /^(gulp|vinyl)-/
});

/**
 * Runs the default tasks.
 */
gulp.task('default', ['build']);

/**
 * Builds the sources.
 */
gulp.task('build', () => gulp.src('src/**/*.js')
  .pipe(plugins.babel())
  .pipe(gulp.dest('lib'))
);

/**
 * Checks the package dependencies.
 */
gulp.task('check', () => {
  const {david} = plugins.cedx.david;
  return gulp.src('package.json').pipe(david()).on('error', function(err) {
    console.error(err);
    this.emit('end');
  });
});

/**
 * Deletes all generated files and reset any saved state.
 */
gulp.task('clean', () =>
  del('var/**/*')
);

/**
 * Sends the results of the code coverage.
 */
gulp.task('coverage', ['test'], () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'codacy-coverage.cmd' : 'codacy-coverage');
  return _exec(`${command} < var/lcov.info`);
});

/**
 * Creates a distribution file for this program.
 */
gulp.task('dist', ['default'], () => gulp.src(config.sources, {base: '.'})
  .pipe(plugins.zip(config.output))
  .pipe(gulp.dest('var'))
);

/**
 * Builds the documentation.
 */
gulp.task('doc', () => {
  let command = path.join('node_modules/.bin', process.platform == 'win32' ? 'esdoc.cmd' : 'esdoc');
  return del('doc/api').then(() => _exec(`${command} -c doc/esdoc.json`));
});

/**
 * Fixes the coding standards issues.
 */
gulp.task('fix', () => gulp.src(['*.js', 'src/**/*.js', 'test/**/*.js'], {base: '.'})
  .pipe(plugins.eslint({fix: true}))
  .pipe(gulp.dest('.'))
);

/**
 * Performs static analysis of source code.
 */
gulp.task('lint', () => gulp.src(['*.js', 'src/**/*.js', 'test/**/*.js'])
  .pipe(plugins.eslint())
  .pipe(plugins.eslint.format())
  .pipe(plugins.eslint.failAfterError())
);

/**
 * Runs the unit tests.
 */
gulp.task('test', ['test:instrument'], () => gulp.src(['test/**/*.js'], {read: false})
  .pipe(plugins.mocha())
  .pipe(plugins.istanbul.writeReports({dir: 'var', reporters: ['lcovonly']}))
);

gulp.task('test:env', () =>
  'AKISMET_API_KEY' in process.env ? Promise.resolve() : Promise.reject(new Error('AKISMET_API_KEY environment variable not set.'))
);

gulp.task('test:instrument', ['test:setup'], () => gulp.src(['src/**/*.js'])
  .pipe(plugins.istanbul({instrumenter: require('isparta').Instrumenter}))
  .pipe(plugins.istanbul.hookRequire())
);

gulp.task('test:setup', ['test:env'], () => new Promise(resolve => {
  process.env.BABEL_DISABLE_CACHE = process.platform == 'win32' ? '1' : '0';
  require('babel-register');
  resolve();
}));

/**
 * Runs a command and prints its output.
 * @param {string} command The command to run, with space-separated arguments.
 * @param {object} [options] The settings to customize how the process is spawned.
 * @return {Promise<string>} The command output when it is finally terminated.
 */
function _exec(command, options = {}) {
  return new Promise((resolve, reject) => child.exec(command, options, (err, stdout) => {
    if (err) reject(err);
    else resolve(stdout.trim());
  }));
}
<|endoftext|>