<commit_before>/*
 * Vertical Tabs
 *
 * Main entry point of this add-on.
 */
var VerticalTabs = {

    init: function() {
        window.removeEventListener("DOMContentLoaded", this, false);

        // Move the bottom stuff (statusbar, findbar) in with the
        // tabbrowser.  That way it will share the same (horizontal)
        // space as the brower.  In other words, the bottom stuff no
        // longer extends across the whole bottom of the window.
        var contentbox = document.getElementById("appcontent");
        var bottom = document.getElementById("browser-bottombox");
        contentbox.appendChild(bottom);

        // Move the tabs next to the app content, make them vertical,
        // and restore their width from previous session
        var leftbox = document.getElementById("verticaltabs-box");
        var tabs = document.getElementById("tabbrowser-tabs");
        leftbox.insertBefore(tabs, leftbox.firstChild);
        tabs.orient = "vertical";
        tabs.mTabstrip.orient = "vertical";
        tabs.tabbox.orient = "horizontal"; // probably not necessary
        tabs.setAttribute("width", Services.prefs.getIntPref('extensions.verticaltabs.width'));

        // Hook up event handler for splitter so that the width of the
        // tab bar is persisted.
        var splitter = document.getElementById("verticaltabs-splitter");
        splitter.addEventListener('mouseup', this, false);

        VTTabbrowserTabs.init();
        VTMultiSelect.init();
        VTGroups.init();

        // Fix up each individual tab for vertical layout, including
        // ones that are opened later on.
        tabs.addEventListener('TabOpen', this, true);
        for (let i=0; i < tabs.childNodes.length; i++) {
            this.initTab(tabs.childNodes[i]);
        }
    },

    initTab: function(aTab) {
        aTab.setAttribute('align', 'stretch');
        aTab.removeAttribute('maxwidth');
        aTab.removeAttribute('minwidth');
        aTab.removeAttribute('width');
        aTab.removeAttribute('flex');
        aTab.maxWidth = 65000;
        aTab.minWidth = 0;
    },

	onTabbarResized: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        setTimeout(function() {
            Services.prefs.setIntPref('extensions.verticaltabs.width',
                                      tabs.boxObject.width);
        }, 10);
	},

    /*** Event handlers ***/

    handleEvent: function(aEvent) {
        switch (aEvent.type) {
        case 'DOMContentLoaded':
            this.init();
            return;
        case 'TabOpen':
            this.onTabOpen(aEvent);
            return;
        case 'mouseup':
            this.onMouseUp(aEvent);
            return;
        }
    },

    onTabOpen: function(aEvent) {
        this.initTab(aEvent.originalTarget);
    },

    onMouseUp: function(aEvent) {
        if (aEvent.target.getAttribute("id") == "verticaltabs-splitter") {
            this.onTabbarResized();
        }
    }

};
window.addEventListener("DOMContentLoaded", VerticalTabs, false);

/*
 * Persistently store tab attributes in the session store service.
 *
 * Heavily inspired by Tree Style Tab's TreeStyleTabUtils.
 */
var VTTabDataStore = {

    getTabValue: function(aTab, aKey) {
        var value = null;
        try {
            value = this.sessionStore.getTabValue(aTab, aKey);
        } catch(ex) {
            // Ignore
        }
        return value;
    },

    setTabValue: function(aTab, aKey, aValue) {
        if (!aValue) {
            this.deleteTabValue(aTab, aKey);
        }

        aTab.setAttribute(aKey, aValue);
        try {
            this.checkCachedSessionDataExpiration(aTab);
            this.sessionStore.setTabValue(aTab, aKey, aValue);
        } catch(ex) {
            // Ignore
        }
    },

    deleteTabValue: function(aTab, aKey) {
        aTab.removeAttribute(aKey);
        try {
            this.checkCachedSessionDataExpiration(aTab);
            this.sessionStore.setTabValue(aTab, aKey, '');
            this.sessionStore.deleteTabValue(aTab, aKey);
        } catch(ex) {
            // Ignore
        }
    },

    // workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm
    checkCachedSessionDataExpiration: function(aTab) {
        var data = aTab.linkedBrowser.__SS_data;
        if (data &&
            data._tabStillLoading &&
            aTab.getAttribute('busy') != 'true')
            data._tabStillLoading = false;
    }
};
XPCOMUtils.defineLazyServiceGetter(VTTabDataStore, "sessionStore",
                                   "@mozilla.org/browser/sessionstore;1",
                                   "nsISessionStore");
<commit_msg>When multiple tabs are selected, close them all.<commit_after>/*
 * Vertical Tabs
 *
 * Main entry point of this add-on.
 */
var VerticalTabs = {

    init: function() {
        window.removeEventListener("DOMContentLoaded", this, false);

        // Move the bottom stuff (statusbar, findbar) in with the
        // tabbrowser.  That way it will share the same (horizontal)
        // space as the brower.  In other words, the bottom stuff no
        // longer extends across the whole bottom of the window.
        var contentbox = document.getElementById("appcontent");
        var bottom = document.getElementById("browser-bottombox");
        contentbox.appendChild(bottom);

        // Move the tabs next to the app content, make them vertical,
        // and restore their width from previous session
        var leftbox = document.getElementById("verticaltabs-box");
        var tabs = document.getElementById("tabbrowser-tabs");
        leftbox.insertBefore(tabs, leftbox.firstChild);
        tabs.orient = "vertical";
        tabs.mTabstrip.orient = "vertical";
        tabs.tabbox.orient = "horizontal"; // probably not necessary
        tabs.setAttribute("width", Services.prefs.getIntPref('extensions.verticaltabs.width'));

        // Hook up event handler for splitter so that the width of the
        // tab bar is persisted.
        var splitter = document.getElementById("verticaltabs-splitter");
        splitter.addEventListener('mouseup', this, false);

        VTTabbrowserTabs.init();
        VTMultiSelect.init();
        VTGroups.init();

        // Fix up each individual tab for vertical layout, including
        // ones that are opened later on.
        tabs.addEventListener('TabOpen', this, true);
        for (let i=0; i < tabs.childNodes.length; i++) {
            this.initTab(tabs.childNodes[i]);
        }

        // Hook up event handler for closing multiple tabs
        tabs.addEventListener('TabClose', this, false);
    },

    initTab: function(aTab) {
        aTab.setAttribute('align', 'stretch');
        aTab.removeAttribute('maxwidth');
        aTab.removeAttribute('minwidth');
        aTab.removeAttribute('width');
        aTab.removeAttribute('flex');
        aTab.maxWidth = 65000;
        aTab.minWidth = 0;
    },

	onTabbarResized: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        setTimeout(function() {
            Services.prefs.setIntPref('extensions.verticaltabs.width',
                                      tabs.boxObject.width);
        }, 10);
	},

    /*** Event handlers ***/

    handleEvent: function(aEvent) {
        switch (aEvent.type) {
        case 'DOMContentLoaded':
            this.init();
            return;
        case 'TabOpen':
            this.onTabOpen(aEvent);
            return;
        case 'TabClose':
            this.onTabClose(aEvent);
            return;
        case 'mouseup':
            this.onMouseUp(aEvent);
            return;
        }
    },

    onTabOpen: function(aEvent) {
        this.initTab(aEvent.target);
    },

    onTabClose: function(aEvent) {
        if (aEvent.target.getAttribute("multiselect-closing") == "true") {
            return;
        }
        if (!aEvent.target.selected) {
            return;
        }

        // If multiple tabs are selected (by means of the
        // VTMultiSelect feature), close them as well.
        var tabs = document.getElementById("tabbrowser-tabs");
        var toclose = VTMultiSelect.getMultiSelection();
        VTMultiSelect.clearMultiSelect();

        var tab;
        for (var i=0; i < toclose.length; i++) {
            tab = toclose[i];
            if (tab.selected) {
                continue;
            }
            tab.setAttribute("multiselect-closing", "true");
            tabs.tabbrowser.removeTab(tab);
        }
    },

    onMouseUp: function(aEvent) {
        if (aEvent.target.getAttribute("id") == "verticaltabs-splitter") {
            this.onTabbarResized();
        }
    }

};
window.addEventListener("DOMContentLoaded", VerticalTabs, false);

/*
 * Persistently store tab attributes in the session store service.
 *
 * Heavily inspired by Tree Style Tab's TreeStyleTabUtils.
 */
var VTTabDataStore = {

    getTabValue: function(aTab, aKey) {
        var value = null;
        try {
            value = this.sessionStore.getTabValue(aTab, aKey);
        } catch(ex) {
            // Ignore
        }
        return value;
    },

    setTabValue: function(aTab, aKey, aValue) {
        if (!aValue) {
            this.deleteTabValue(aTab, aKey);
        }

        aTab.setAttribute(aKey, aValue);
        try {
            this.checkCachedSessionDataExpiration(aTab);
            this.sessionStore.setTabValue(aTab, aKey, aValue);
        } catch(ex) {
            // Ignore
        }
    },

    deleteTabValue: function(aTab, aKey) {
        aTab.removeAttribute(aKey);
        try {
            this.checkCachedSessionDataExpiration(aTab);
            this.sessionStore.setTabValue(aTab, aKey, '');
            this.sessionStore.deleteTabValue(aTab, aKey);
        } catch(ex) {
            // Ignore
        }
    },

    // workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm
    checkCachedSessionDataExpiration: function(aTab) {
        var data = aTab.linkedBrowser.__SS_data;
        if (data &&
            data._tabStillLoading &&
            aTab.getAttribute('busy') != 'true')
            data._tabStillLoading = false;
    }
};
XPCOMUtils.defineLazyServiceGetter(VTTabDataStore, "sessionStore",
                                   "@mozilla.org/browser/sessionstore;1",
                                   "nsISessionStore");
<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var vinylPaths = require('vinyl-paths');
var globby = require('globby');
var mergeStream = require('merge-stream');
var path = require('path');
var runSequence = require('run-sequence');
var browserify = require('browserify');
var watchify = require('watchify');
var scssSyntax = require('postcss-scss');
var stylelint = require('stylelint');
var postcssReporter = require('postcss-reporter');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
var bs = require('browser-sync').create();
var del = require('del');

var browsers = [
  'last 2 versions',
  'Explorer >= 8',
  'Firefox ESR',
  'Android >= 4',
  'iOS >= 7'
];
var entries = [];

// Lint stylesheets
gulp.task('stylelint', function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.postcss([
      stylelint(),
      postcssReporter({clearMessages: true})
    ], {syntax: scssSyntax}));
});

// Lint JavaScript
gulp.task('eslint', function () {
  return gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.eslint.failAfterError());
});

// Build stylesheets for local development
gulp.task('styles:dev', ['sprites', 'iconfonts'], function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.sourcemaps.init({loadMaps: true}))
    .pipe($.sass({
      outputStyle: 'expanded',
      includePaths: ['.tmp/styles']
    })
    .on('error', $.sass.logError))
    .pipe($.postcss([
      autoprefixer({browsers: browsers})
    ]))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('.tmp/styles'));
});

// Build stylesheets for production
gulp.task('styles', ['sprites', 'iconfonts'], function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.sass({
      outputStyle: 'expanded',
      includePaths: ['.tmp/styles']
    })
    .on('error', $.sass.logError))
    .pipe($.postcss([
      autoprefixer({browsers: browsers}),
      cssnano({
        safe: true,
        autoprefixer: false
      })
    ]))
    .pipe(gulp.dest('dist/styles'));
});

// Generate CSS sprites from PNG files
gulp.task('sprites', function () {
  return gulp.src('app/images/_sprites/*.png')
    .pipe($.newer('.tmp/images/sprites.png'))
    .pipe($.spritesmith({
      imgName: 'images/sprites.png',
      cssName: 'styles/sprites.css',
      padding: 2,
      cssOpts: {
        cssSelector: function (item) {
          return '.sprite-' + item.name;
        }
      }
    }))
    .pipe(gulp.dest('.tmp'));
});

// Generate icon fonts from SVG files
gulp.task('iconfonts', function () {
  return gulp.src('app/fonts/_glyphs/*.svg')
    .pipe($.newer('.tmp/fonts/glyphs.woff2'))
    .pipe($.iconfontCss({
      fontName: 'glyphs',
      targetPath: '../styles/glyphs.css',   // Relative path from gulp.dest()
      fontPath: '../fonts/',                // Base url(...) in CSS code
      cssClass: 'glyph'
    }))
    .pipe($.iconfont({
      fontName: 'glyphs',
      prependUnicode: true,
      formats: ['eot', 'woff2', 'woff', 'ttf', 'svg']
    }))
    .pipe(gulp.dest('.tmp/fonts'));
});

// Build and watch scripts for local development
gulp.task('scripts:dev', function () {
  var stream = mergeStream();
  globby.sync('app/scripts/*.js').forEach(function (file) {
    if (entries.indexOf(file) !== -1) {
      return;
    }
    var bundler = browserify({
      entries: file,
      cache: {},
      packageCache: {},
      plugin: [watchify],
      debug: true
    });
    bundler
      .on('log', $.util.log)
      .on('update', bundle);
    stream.add(bundle());
    entries.push(file);
    function bundle() {
      return bundler.bundle()
        .on('error', function (error) {
          $.util.log($.util.colors.red('Browserify error:') + '\n' + error.message);
          this.emit('end');
        })
        .pipe(source(path.relative('app/scripts', file)))
        .pipe(gulp.dest('.tmp/scripts'));
    }
  });
  return stream.isEmpty() ? null : stream;
});

// Build scripts for production
gulp.task('scripts', function () {
  var stream = mergeStream();
  globby.sync('app/scripts/*.js').forEach(function (file) {
    var bundleStream = browserify(file).bundle()
      .on('error', function (error) {
        $.util.log($.util.colors.red('Browserify error:') + '\n' + error.message);
        this.emit('end');
      })
      .pipe(source(path.relative('app/scripts', file)))
      .pipe(buffer())
      .pipe($.uglify())
      .pipe(gulp.dest('dist/scripts'));
    stream.add(bundleStream);
  });
  return stream.isEmpty() ? null : stream;
});

// Minify HTML
gulp.task('html', function () {
  return gulp.src('app/**/*.html')
    .pipe($.htmlmin({
      collapseBooleanAttributes: true,
      collapseWhitespace: true,
      conservativeCollapse: true,
      minifyCSS: true,
      minifyJS: true,
      preventAttributesEscaping: true,
      removeAttributeQuotes: true,
      removeComments: true,
      removeCommentsFromCDATA: true,
      removeEmptyAttributes: true,
      removeOptionalTags: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeTagWhitespace: true,
      useShortDoctype: true
    }))
    .pipe(gulp.dest('dist'));
});

// Optimize images
gulp.task('images', ['sprites'], function () {
  return gulp.src([
    'app/images/**',
    '.tmp/images/**',
    '!app/images/_*/**'
  ])
    .pipe($.ignore.include({isFile: true}))
    .pipe($.imagemin({
      progressive: true,
      interlaced: true,
      svgoPlugins: [{cleanupIDs: false}]
    }))
    .pipe(gulp.dest('dist/images'));
});

// Copy fonts
gulp.task('fonts', ['iconfonts'], function () {
  return gulp.src([
    'app/fonts/**',
    '.tmp/fonts/**',
    '!app/fonts/_*/**'
  ])
    .pipe($.ignore.include({isFile: true}))
    .pipe(gulp.dest('dist/fonts'));
});

// Copy all extra files like favicon, .htaccess
gulp.task('extras', function () {
  return gulp.src([
    'app/**',
    '!app/{styles,scripts,images,fonts}/**',
    '!**/{*.html,.DS_Store}'
  ], {dot: true})
    .pipe($.ignore.include({isFile: true}))
    .pipe(gulp.dest('dist'));
});

// Static asset revisioning: "main.css" -> "main-e90e18eef7.css"
gulp.task('filerev', function () {
  return gulp.src('dist/**/*.{css,js,png,jpg,gif,eot,svg,ttf,woff,woff2}')
    .pipe(vinylPaths(del))
    .pipe($.rev())
    .pipe(gulp.dest('dist'))
    .pipe($.rev.manifest())
    .pipe(gulp.dest('.tmp'));
});

gulp.task('rev', ['filerev'], function () {
  var manifest = gulp.src('.tmp/rev-manifest.json');
  return gulp.src('dist/**/*.{html,css}')
    .pipe($.revReplace({manifest: manifest}))
    .pipe(gulp.dest('dist'));
});

// Clean output directories
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
gulp.task('clean:serve', del.bind(null, '.tmp'));
gulp.task('clean:build', del.bind(null, ['.tmp/{images,fonts}', 'dist']));

// Start browsersync development server
gulp.task('serve', ['pre:serve'], function () {
  bs.init({
    notify: false,
    server: {
      baseDir: ['.tmp', 'app'],
      routes: {
        '/node_modules': 'node_modules'
      }
    },
    files: ['app/**/*.html', '.tmp/**']
  });
  gulp.watch('app/scripts/*.js', function (event) {
    if (event.type === 'added' || event.type === 'renamed') {
      runSequence('scripts:dev');
    }
  });
  gulp.watch('app/scripts/**/*.js', ['eslint']);
  gulp.watch('app/styles/**/*.scss', ['stylelint', 'styles:dev']);
  gulp.watch('app/images/_sprites/*.png', ['styles:dev']);
  gulp.watch('app/fonts/_glyphs/*.svg', ['styles:dev']);
});

gulp.task('pre:serve', function (callback) {
  runSequence('clean:serve', ['styles:dev', 'scripts:dev'], callback);
});

// Start local server from the "dist" directory
gulp.task('serve:dist', function () {
  bs.init({
    notify: false,
    server: 'dist'
  });
});

// Run linters
gulp.task('lint', ['eslint', 'stylelint']);

// Build production files
gulp.task('build', function (callback) {
  runSequence('clean:build', ['html', 'styles', 'scripts', 'images', 'fonts', 'extras'], 'rev', callback);
});

// Push production files to "gh-pages" branch
gulp.task('deploy', ['default'], function () {
  return gulp.src('dist/**/*')
    .pipe($.ghPages());
});

// Default task: lint and build files
gulp.task('default', function (callback) {
  runSequence('lint', 'build', callback);
});
<commit_msg>Arrange gulpfile<commit_after>'use strict';

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var vinylPaths = require('vinyl-paths');
var globby = require('globby');
var mergeStream = require('merge-stream');
var path = require('path');
var runSequence = require('run-sequence');
var browserify = require('browserify');
var watchify = require('watchify');
var scssSyntax = require('postcss-scss');
var stylelint = require('stylelint');
var postcssReporter = require('postcss-reporter');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
var bs = require('browser-sync').create();
var del = require('del');

var browsers = [
  'last 2 versions',
  'Explorer >= 8',
  'Firefox ESR',
  'Android >= 4',
  'iOS >= 7'
];
var entries = [];

// Lint stylesheets
gulp.task('stylelint', function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.postcss([
      stylelint(),
      postcssReporter({clearMessages: true})
    ], {syntax: scssSyntax}));
});

// Lint JavaScript
gulp.task('eslint', function () {
  return gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.eslint.failAfterError());
});

// Run linters
gulp.task('lint', ['eslint', 'stylelint']);

// Build stylesheets for local development
gulp.task('styles:dev', ['sprites', 'iconfonts'], function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.sourcemaps.init({loadMaps: true}))
    .pipe($.sass({
      outputStyle: 'expanded',
      includePaths: ['.tmp/styles']
    })
    .on('error', $.sass.logError))
    .pipe($.postcss([
      autoprefixer({browsers: browsers})
    ]))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('.tmp/styles'));
});

// Build stylesheets for production
gulp.task('styles', ['sprites', 'iconfonts'], function () {
  return gulp.src('app/styles/**/*.scss')
    .pipe($.sass({
      outputStyle: 'expanded',
      includePaths: ['.tmp/styles']
    })
    .on('error', $.sass.logError))
    .pipe($.postcss([
      autoprefixer({browsers: browsers}),
      cssnano({
        safe: true,
        autoprefixer: false
      })
    ]))
    .pipe(gulp.dest('dist/styles'));
});

// Generate CSS sprites from PNG files
gulp.task('sprites', function () {
  return gulp.src('app/images/_sprites/*.png')
    .pipe($.newer('.tmp/images/sprites.png'))
    .pipe($.spritesmith({
      imgName: 'images/sprites.png',
      cssName: 'styles/sprites.css',
      padding: 2,
      cssOpts: {
        cssSelector: function (item) {
          return '.sprite-' + item.name;
        }
      }
    }))
    .pipe(gulp.dest('.tmp'));
});

// Generate icon fonts from SVG files
gulp.task('iconfonts', function () {
  return gulp.src('app/fonts/_glyphs/*.svg')
    .pipe($.newer('.tmp/fonts/glyphs.woff2'))
    .pipe($.iconfontCss({
      fontName: 'glyphs',
      targetPath: '../styles/glyphs.css',   // Relative path from gulp.dest()
      fontPath: '../fonts/',                // Base url(...) in CSS code
      cssClass: 'glyph'
    }))
    .pipe($.iconfont({
      fontName: 'glyphs',
      prependUnicode: true,
      formats: ['eot', 'woff2', 'woff', 'ttf', 'svg']
    }))
    .pipe(gulp.dest('.tmp/fonts'));
});

// Build and watch scripts for local development
gulp.task('scripts:dev', function () {
  var stream = mergeStream();
  globby.sync('app/scripts/*.js').forEach(function (file) {
    if (entries.indexOf(file) !== -1) {
      return;
    }
    var bundler = browserify({
      entries: file,
      cache: {},
      packageCache: {},
      plugin: [watchify],
      debug: true
    });
    bundler
      .on('log', $.util.log)
      .on('update', bundle);
    stream.add(bundle());
    entries.push(file);
    function bundle() {
      return bundler.bundle()
        .on('error', function (error) {
          $.util.log($.util.colors.red('Browserify error:') + '\n' + error.message);
          this.emit('end');
        })
        .pipe(source(path.relative('app/scripts', file)))
        .pipe(gulp.dest('.tmp/scripts'));
    }
  });
  return stream.isEmpty() ? null : stream;
});

// Build scripts for production
gulp.task('scripts', function () {
  var stream = mergeStream();
  globby.sync('app/scripts/*.js').forEach(function (file) {
    var bundleStream = browserify(file).bundle()
      .on('error', function (error) {
        $.util.log($.util.colors.red('Browserify error:') + '\n' + error.message);
        this.emit('end');
      })
      .pipe(source(path.relative('app/scripts', file)))
      .pipe(buffer())
      .pipe($.uglify())
      .pipe(gulp.dest('dist/scripts'));
    stream.add(bundleStream);
  });
  return stream.isEmpty() ? null : stream;
});

// Minify HTML
gulp.task('html', function () {
  return gulp.src('app/**/*.html')
    .pipe($.htmlmin({
      collapseBooleanAttributes: true,
      collapseWhitespace: true,
      conservativeCollapse: true,
      minifyCSS: true,
      minifyJS: true,
      preventAttributesEscaping: true,
      removeAttributeQuotes: true,
      removeComments: true,
      removeCommentsFromCDATA: true,
      removeEmptyAttributes: true,
      removeOptionalTags: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeTagWhitespace: true,
      useShortDoctype: true
    }))
    .pipe(gulp.dest('dist'));
});

// Optimize images
gulp.task('images', ['sprites'], function () {
  return gulp.src([
    'app/images/**',
    '.tmp/images/**',
    '!app/images/_*/**'
  ])
    .pipe($.ignore.include({isFile: true}))
    .pipe($.imagemin({
      progressive: true,
      interlaced: true,
      svgoPlugins: [{cleanupIDs: false}]
    }))
    .pipe(gulp.dest('dist/images'));
});

// Copy fonts
gulp.task('fonts', ['iconfonts'], function () {
  return gulp.src([
    'app/fonts/**',
    '.tmp/fonts/**',
    '!app/fonts/_*/**'
  ])
    .pipe($.ignore.include({isFile: true}))
    .pipe(gulp.dest('dist/fonts'));
});

// Copy all extra files like favicon, .htaccess
gulp.task('extras', function () {
  return gulp.src([
    'app/**',
    '!app/{styles,scripts,images,fonts}/**',
    '!**/{*.html,.DS_Store}'
  ], {dot: true})
    .pipe($.ignore.include({isFile: true}))
    .pipe(gulp.dest('dist'));
});

// Static asset revisioning: "main.css" -> "main-e90e18eef7.css"
gulp.task('filerev', function () {
  return gulp.src('dist/**/*.{css,js,png,jpg,gif,eot,svg,ttf,woff,woff2}')
    .pipe(vinylPaths(del))
    .pipe($.rev())
    .pipe(gulp.dest('dist'))
    .pipe($.rev.manifest())
    .pipe(gulp.dest('.tmp'));
});

gulp.task('rev', ['filerev'], function () {
  var manifest = gulp.src('.tmp/rev-manifest.json');
  return gulp.src('dist/**/*.{html,css}')
    .pipe($.revReplace({manifest: manifest}))
    .pipe(gulp.dest('dist'));
});

// Clean output directories
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
gulp.task('clean:serve', del.bind(null, '.tmp'));
gulp.task('clean:build', del.bind(null, ['.tmp/{images,fonts}', 'dist']));

// Start browsersync development server
gulp.task('serve', ['pre:serve'], function () {
  bs.init({
    notify: false,
    server: {
      baseDir: ['.tmp', 'app'],
      routes: {
        '/node_modules': 'node_modules'
      }
    },
    files: ['app/**/*.html', '.tmp/**']
  });
  gulp.watch('app/scripts/*.js', function (event) {
    if (event.type === 'added' || event.type === 'renamed') {
      runSequence('scripts:dev');
    }
  });
  gulp.watch('app/scripts/**/*.js', ['eslint']);
  gulp.watch('app/styles/**/*.scss', ['stylelint', 'styles:dev']);
  gulp.watch('app/images/_sprites/*.png', ['styles:dev']);
  gulp.watch('app/fonts/_glyphs/*.svg', ['styles:dev']);
});

gulp.task('pre:serve', function (callback) {
  runSequence('clean:serve', ['styles:dev', 'scripts:dev'], callback);
});

// Start local server from the "dist" directory
gulp.task('serve:dist', function () {
  bs.init({
    notify: false,
    server: 'dist'
  });
});

// Build production files
gulp.task('build', function (callback) {
  runSequence('clean:build', ['html', 'styles', 'scripts', 'images', 'fonts', 'extras'], 'rev', callback);
});

// Push production files to "gh-pages" branch
gulp.task('deploy', ['default'], function () {
  return gulp.src('dist/**/*')
    .pipe($.ghPages());
});

// Default task: lint and build files
gulp.task('default', function (callback) {
  runSequence('lint', 'build', callback);
});
<|endoftext|>


<commit_before>define([
  'client/client_bootstrap',
  'client/controllers/dashboard'
],
function (bootstrap, DashboardController) {
  describe('client bootstrap', function () {

    var config;
    beforeEach(function () {
      spyOn(DashboardController.prototype, 'render');
      config = {
        'page-type': 'dashboard'
      };
    });

    it('instantiates a controller from config data', function () {
      var controller = bootstrap(config);

      expect(controller instanceof DashboardController).toBe(true);
      expect(controller.render).toHaveBeenCalled();
    });

    it('executes page preprocessors', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      bootstrap(config);
      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });
  });
});
<commit_msg>Fix tests for client bootstrap<commit_after>define([
  'client/client_bootstrap',
  'client/controllers/dashboard'
],
function (bootstrap, DashboardController) {
  describe('client bootstrap', function () {

    var config;
    beforeEach(function () {
      spyOn(DashboardController.prototype, 'render');
      config = {
        'page-type': 'dashboard'
      };
      $('body').removeClass('ready loaded');
    });

    it('instantiates a controller from config data', function () {
      var controller = bootstrap(config);

      expect(controller instanceof DashboardController).toBe(true);
      expect(controller.render).toHaveBeenCalled();
    });

    it('executes page preprocessors on controller ready', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      var controller = bootstrap(config);

      expect(bootstrap.preprocessors[0]).not.toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).not.toHaveBeenCalled();
      controller.trigger('ready');
      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });

    it('executes page preprocessors immediately if no controller defined', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      bootstrap({
        'page-type': 'no-contoller'
      });

      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });

    it('adds a ready class to the body on controller ready', function () {
      var controller = bootstrap(config);

      expect($('body').hasClass('ready')).toBe(false);
      controller.trigger('ready');
      expect($('body').hasClass('ready')).toBe(true);
    });

    it('adds ready class to body immediately if no controller defined', function () {
      bootstrap({
        'page-type': 'no-contoller'
      });

      expect($('body').hasClass('ready')).toBe(true);
    });
  });
});
<|endoftext|>


<commit_before>/*    Copyright 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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/>.
 */
'use strict';

const log = require("../net2/logger.js")(__filename);
const rclient = require('../util/redis_manager.js').getRedisClient();
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Block = require('./Block.js');
const DNSTool = require('../net2/DNSTool.js')
const dnsTool = new DNSTool()
const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();
const _ = require('lodash');
const fc = require('../net2/config.js');
const featureName = 'smart_block';
let instance = null;
const expiring = 24 * 60 * 60 * 3;  // three days

class BlockManager {
    constructor() {
        if (instance == null) {
            instance = this;
            const refreshInterval = 15 * 60; // 15 mins
            sem.once('IPTABLES_READY', async () => {
                if (fc.isFeatureOn(featureName)) {
                    this.scheduleId = setInterval(() => {
                        this.scheduleRefreshBlockLevel();
                    }, refreshInterval * 1000)
                }
                fc.onFeature(featureName, async (feature, status) => {
                    if (feature !== featureName) {
                        return
                    }
                    await this.reenforcePolicies();
                    if (status) {
                        this.scheduleId = setInterval(() => {
                            this.scheduleRefreshBlockLevel();
                        }, refreshInterval * 1000)
                    } else {
                        clearInterval(this.scheduleId);
                    }
                })
            })
        }
        return instance
    }
    async reenforcePolicies() {
        const PolicyManager2 = require('../alarm/PolicyManager2.js');
        const pm2 = new PolicyManager2();
        log.info('smart block enabled, re-enforce policies');
        const policies = await pm2.loadActivePoliciesAsync();
        policies.map((policy) => {
            pm2.tryPolicyEnforcement(policy, 'reenforce', policy);
        })
    }
    ipBlockInfoKey(ip) {
        return `ip:block:info:${ip}`
    }
    domainBlockInfoKey(domain) {
        return `domain:block:info:${domain}`
    }
    categoryDomainBlockInfoKey(domain) {
        return `category:block:info:${domain}`
    }
    getCategoryIpMapping(category) {
        return `rdns:category:${category}`
    }
    async getPureCategoryIps(category, categoryIps, originDomain) {
        if (!fc.isFeatureOn(featureName)) {
            return categoryIps;
        }
        const pureCategoryIps = [], mixupCategoryIps = [], mixupIpInfos = [];
        try {
            for (const categoryIp of categoryIps) {
                let pure = true;
                let mixupDomain;
                let mixupCategory;
                const domains = await dnsTool.getAllDns(categoryIp);
                for (const domain of domains) {
                    mixupDomain = domain;
                    const ips = await dnsTool.getIPsByDomain(domain);
                    for (const ip of ips) {
                        const intel = (await intelTool.getIntel(ip)) || {};
                        mixupCategory = intel.category;
                        pure = pure && intel.category == category;
                        if (!pure) break;
                    }
                    if (!pure) break;
                }
                if (pure) {
                    pureCategoryIps.push(categoryIp)
                } else {
                    mixupCategoryIps.push(categoryIp);
                    mixupIpInfos.push({
                        ip: categoryIp,
                        mixupDomain: mixupDomain,
                        mixupCategory: mixupCategory
                    })
                }
            }
            const categoryIpMappingKey = this.getCategoryIpMapping(category);
            mixupCategoryIps.length > 0 && await rclient.sremAsync(categoryIpMappingKey, mixupCategoryIps);
            pureCategoryIps.length > 0 && await rclient.saddAsync(categoryIpMappingKey, pureCategoryIps);
            const categoryDomainBlockInfoKey = this.categoryDomainBlockInfoKey(originDomain);
            await rclient.setAsync(categoryDomainBlockInfoKey, JSON.stringify({
                pureCategoryIps: pureCategoryIps,
                mixupIpInfos: mixupIpInfos
            }))
            rclient.expireat(categoryDomainBlockInfoKey, parseInt((+new Date) / 1000) + expiring);
        } catch (e) {
            log.info("get pure category ips failed", e)
        }
        return pureCategoryIps;
    }
    async applyNewDomain(ip, domain) {
        await this.updateIpBlockInfo(ip, domain, 'newDomain')
    }
    async scheduleRefreshBlockLevel() {
        const ipBlockKeys = await rclient.keysAsync("ip:block:info:*");
        log.info('schedule refresh block level for these ips:', ipBlockKeys)
        ipBlockKeys.map(async (key) => {
            let ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            const { targetDomains, ip, blockLevel, blockSet } = ipBlockInfo;
            const allDomains = await dnsTool.getAllDns(ip);
            const sharedDomains = _.differenceWith(allDomains, targetDomains, (a, b) => {
                return this.domainCovered(b, a);
            });
            if (sharedDomains.length == 0 && blockLevel == 'domain') {
                Block.block(ip, blockSet)
            }
            if (sharedDomains.length > 0 && blockLevel == 'ip') {
                Block.unblock(ip, blockSet);
            }
            ipBlockInfo.ts = new Date() / 1000;
            ipBlockInfo.sharedDomains = sharedDomains;
            ipBlockInfo.allDomains = allDomains;
            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
        })
    }
    async updateIpBlockInfo(ip, domain, action, blockSet = 'block_domain_set') {
        let ipBlockInfo = {
            blockSet: blockSet,
            ip: ip,
            targetDomains: [],
            sharedDomains: [],
            allDomains: [],
            ts: new Date() / 1000
        }
        let exist;
        if (!fc.isFeatureOn(featureName)) {
            ipBlockInfo.blockLevel = 'ip';
        } else {
            const key = this.ipBlockInfoKey(ip);
            exist = (await rclient.existsAsync(key) == 1);
            if (exist) {
                ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            }
            switch (action) {
                case 'block': {
                    // if the ip shared with other domain, should not apply ip level block
                    // if a.com and b.com share ip and one of them block, it should be domain level
                    // if both block, should update to ip level
                    !ipBlockInfo.targetDomains.includes(domain) && ipBlockInfo.targetDomains.push(domain);
                    const allDomains = await dnsTool.getAllDns(ip);
                    const sharedDomains = _.differenceWith(allDomains, ipBlockInfo.targetDomains, (a, b) => {
                        return this.domainCovered(b, a);
                    });
                    sharedDomains.length > 0 && log.info(`${ipBlockInfo.targetDomains.join(',')} ip ${ip} shared with domains ${sharedDomains.join(',')}`)
                    if (sharedDomains.length == 0) {
                        ipBlockInfo.blockLevel = 'ip';
                    } else {
                        ipBlockInfo.blockLevel = 'domain';
                    }
                    ipBlockInfo.sharedDomains = sharedDomains;
                    ipBlockInfo.allDomains = allDomains;
                    ipBlockInfo.ts = new Date() / 1000;
                    await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    break;
                }
                case 'unblock': {
                    !ipBlockInfo.sharedDomains.includes(domain) && ipBlockInfo.sharedDomains.push(domain);
                    ipBlockInfo.targetDomains = _.filter(ipBlockInfo.targetDomains, (a) => {
                        return a != domain;
                    })
                    if (ipBlockInfo.targetDomains.length == 0) {
                        await rclient.delAsync(key);
                    } else {
                        ipBlockInfo.ts = new Date() / 1000;
                        ipBlockInfo.blockLevel = 'domain';
                        await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    }
                    break;
                }
                case 'newDomain': {
                    if (exist) {
                        // it is old ip and new domain
                        const { blockSet, targetDomains } = ipBlockInfo;
                        const alreayExistInTargetDomains = _.find(targetDomains, (targetDomain) => {
                            return this.domainCovered(targetDomain, domain);
                        })
                        if (!alreayExistInTargetDomains) {
                            if (ipBlockInfo.blockLevel == 'ip') {
                                log.info('ip block level change when new doamin comming', ip, domain)
                                ipBlockInfo.blockLevel = 'domain';
                                Block.unblock(ip, blockSet);
                            }
                            ipBlockInfo.sharedDomains.push(domain);
                            ipBlockInfo.allDomains = await dnsTool.getAllDns(ip);
                            ipBlockInfo.ts = new Date() / 1000;
                            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                        }
                    }
                    break;
                }
            }
        }
        if (action == 'block' || action == 'unblock' || (action == 'newDomain' && exist)) {
            await this.updateDomainBlockInfo(domain, ipBlockInfo);
        }
        return ipBlockInfo;
    }
    async updateDomainBlockInfo(domain, ipBlockInfo) {
        const key = this.domainBlockInfoKey(domain);
        let domainBlockInfo = await rclient.getAsync(key);
        try {
            domainBlockInfo = JSON.parse(domainBlockInfo) || {};
        } catch (err) {
            domainBlockInfo = {};
        }
        domainBlockInfo[ipBlockInfo.ip] = ipBlockInfo;
        await rclient.setAsync(key, JSON.stringify(domainBlockInfo));
        rclient.expireat(key, parseInt((+new Date) / 1000) + expiring);
    }
    domainCovered(blockDomain, otherDomain) {
        // a.b.com covred x.a.b.com
        if (!otherDomain) return true;
        if (blockDomain.startsWith('*.')) blockDomain = blockDomain.substring(2)
        if (otherDomain.startsWith('*.')) otherDomain = otherDomain.substring(2)
        const h1Sections = blockDomain.split('.').reverse();
        const h2Sections = otherDomain.split('.').reverse();
        for (let i = 0; i < h1Sections.length; i++) {
            if (h1Sections[i] !== h2Sections[i])
                return false;
        }
        return true;
    }
}

module.exports = BlockManager
<commit_msg>update<commit_after>/*    Copyright 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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/>.
 */
'use strict';

const log = require("../net2/logger.js")(__filename);
const rclient = require('../util/redis_manager.js').getRedisClient();
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Block = require('./Block.js');
const DNSTool = require('../net2/DNSTool.js')
const dnsTool = new DNSTool()
const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();
const _ = require('lodash');
const fc = require('../net2/config.js');
const featureName = 'smart_block';
let instance = null;
const expiring = 24 * 60 * 60 * 3;  // three days

class BlockManager {
    constructor() {
        if (instance == null) {
            instance = this;
            const refreshInterval = 15 * 60; // 15 mins
            sem.once('IPTABLES_READY', async () => {
                if (fc.isFeatureOn(featureName)) {
                    this.scheduleId = setInterval(() => {
                        this.scheduleRefreshBlockLevel();
                    }, refreshInterval * 1000)
                }
                fc.onFeature(featureName, async (feature, status) => {
                    if (feature !== featureName) {
                        return
                    }
                    await this.reenforcePolicies();
                    if (status) {
                        this.scheduleId = setInterval(() => {
                            this.scheduleRefreshBlockLevel();
                        }, refreshInterval * 1000)
                    } else {
                        clearInterval(this.scheduleId);
                    }
                })
            })
        }
        return instance
    }
    async reenforcePolicies() {
        const PolicyManager2 = require('../alarm/PolicyManager2.js');
        const pm2 = new PolicyManager2();
        log.info('smart block enabled, re-enforce policies');
        const policies = await pm2.loadActivePoliciesAsync();
        policies.map((policy) => {
            pm2.tryPolicyEnforcement(policy, 'reenforce', policy);
        })
    }
    ipBlockInfoKey(ip) {
        return `ip:block:info:${ip}`
    }
    domainBlockInfoKey(domain) {
        return `domain:block:info:${domain}`
    }
    categoryDomainBlockInfoKey(domain) {
        return `category:block:info:${domain}`
    }
    getCategoryIpMapping(category) {
        return `rdns:category:${category}`
    }
    async getPureCategoryIps(category, categoryIps, originDomain) {
        if (!fc.isFeatureOn(featureName)) {
            return categoryIps;
        }
        const pureCategoryIps = [], mixupCategoryIps = [], mixupIpInfos = [];
        try {
            for (const categoryIp of categoryIps) {
                let pure = true;
                let mixupDomain;
                let mixupCategory;
                const domains = await dnsTool.getAllDns(categoryIp);
                for (const domain of domains) {
                    mixupDomain = domain;
                    const ips = await dnsTool.getIPsByDomain(domain);
                    for (const ip of ips) {
                        const intel = (await intelTool.getIntel(ip)) || {};
                        mixupCategory = intel.category;
                        pure = pure && intel.category == category;
                        if (!pure) break;
                    }
                    if (!pure) break;
                }
                if (pure) {
                    pureCategoryIps.push(categoryIp)
                } else {
                    mixupCategoryIps.push(categoryIp);
                    mixupIpInfos.push({
                        ip: categoryIp,
                        mixupDomain: mixupDomain,
                        mixupCategory: mixupCategory
                    })
                }
            }
            const categoryIpMappingKey = this.getCategoryIpMapping(category);
            mixupCategoryIps.length > 0 && await rclient.sremAsync(categoryIpMappingKey, mixupCategoryIps);
            pureCategoryIps.length > 0 && await rclient.saddAsync(categoryIpMappingKey, pureCategoryIps);
            const categoryDomainBlockInfoKey = this.categoryDomainBlockInfoKey(originDomain);
            await rclient.setAsync(categoryDomainBlockInfoKey, JSON.stringify({
                pureCategoryIps: pureCategoryIps,
                mixupIpInfos: mixupIpInfos
            }))
            rclient.expireat(categoryDomainBlockInfoKey, parseInt((+new Date) / 1000) + expiring);
        } catch (e) {
            log.info("get pure category ips failed", e)
        }
        return pureCategoryIps;
    }
    async applyNewDomain(ip, domain) {
        await this.updateIpBlockInfo(ip, domain, 'newDomain')
    }
    async scheduleRefreshBlockLevel() {
        const ipBlockKeys = await rclient.keysAsync("ip:block:info:*");
        log.info('schedule refresh block level for these ips:', ipBlockKeys)
        ipBlockKeys.map(async (key) => {
            try {
                let ipBlockInfo = JSON.parse(await rclient.getAsync(key));
                if (!ipBlockInfo) continue;
                const { targetDomains, ip, blockLevel, blockSet } = ipBlockInfo;
                const allDomains = await dnsTool.getAllDns(ip);
                const sharedDomains = _.differenceWith(allDomains, targetDomains, (a, b) => {
                    return this.domainCovered(b, a);
                });
                if (sharedDomains.length == 0 && blockLevel == 'domain') {
                    Block.block(ip, blockSet)
                }
                if (sharedDomains.length > 0 && blockLevel == 'ip') {
                    Block.unblock(ip, blockSet);
                }
                ipBlockInfo.ts = new Date() / 1000;
                ipBlockInfo.sharedDomains = sharedDomains;
                ipBlockInfo.allDomains = allDomains;
                await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
            } catch (err) {
                log.warn('parse error', err);
            }
        })
    }
    async updateIpBlockInfo(ip, domain, action, blockSet = 'block_domain_set') {
        let ipBlockInfo = {
            blockSet: blockSet,
            ip: ip,
            targetDomains: [],
            sharedDomains: [],
            allDomains: [],
            ts: new Date() / 1000
        }
        let exist;
        if (!fc.isFeatureOn(featureName)) {
            ipBlockInfo.blockLevel = 'ip';
        } else {
            const key = this.ipBlockInfoKey(ip);
            exist = (await rclient.existsAsync(key) == 1);
            if (exist) {
                ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            }
            switch (action) {
                case 'block': {
                    // if the ip shared with other domain, should not apply ip level block
                    // if a.com and b.com share ip and one of them block, it should be domain level
                    // if both block, should update to ip level
                    !ipBlockInfo.targetDomains.includes(domain) && ipBlockInfo.targetDomains.push(domain);
                    const allDomains = await dnsTool.getAllDns(ip);
                    const sharedDomains = _.differenceWith(allDomains, ipBlockInfo.targetDomains, (a, b) => {
                        return this.domainCovered(b, a);
                    });
                    sharedDomains.length > 0 && log.info(`${ipBlockInfo.targetDomains.join(',')} ip ${ip} shared with domains ${sharedDomains.join(',')}`)
                    if (sharedDomains.length == 0) {
                        ipBlockInfo.blockLevel = 'ip';
                    } else {
                        ipBlockInfo.blockLevel = 'domain';
                    }
                    ipBlockInfo.sharedDomains = sharedDomains;
                    ipBlockInfo.allDomains = allDomains;
                    ipBlockInfo.ts = new Date() / 1000;
                    await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    break;
                }
                case 'unblock': {
                    !ipBlockInfo.sharedDomains.includes(domain) && ipBlockInfo.sharedDomains.push(domain);
                    ipBlockInfo.targetDomains = _.filter(ipBlockInfo.targetDomains, (a) => {
                        return a != domain;
                    })
                    if (ipBlockInfo.targetDomains.length == 0) {
                        await rclient.delAsync(key);
                    } else {
                        ipBlockInfo.ts = new Date() / 1000;
                        ipBlockInfo.blockLevel = 'domain';
                        await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    }
                    break;
                }
                case 'newDomain': {
                    if (exist) {
                        // it is old ip and new domain
                        const { blockSet, targetDomains } = ipBlockInfo;
                        const alreayExistInTargetDomains = _.find(targetDomains, (targetDomain) => {
                            return this.domainCovered(targetDomain, domain);
                        })
                        if (!alreayExistInTargetDomains) {
                            if (ipBlockInfo.blockLevel == 'ip') {
                                log.info('ip block level change when new doamin comming', ip, domain)
                                ipBlockInfo.blockLevel = 'domain';
                                Block.unblock(ip, blockSet);
                            }
                            ipBlockInfo.sharedDomains.push(domain);
                            ipBlockInfo.allDomains = await dnsTool.getAllDns(ip);
                            ipBlockInfo.ts = new Date() / 1000;
                            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                        }
                    }
                    break;
                }
            }
        }
        if (action == 'block' || action == 'unblock' || (action == 'newDomain' && exist)) {
            await this.updateDomainBlockInfo(domain, ipBlockInfo);
        }
        return ipBlockInfo;
    }
    async updateDomainBlockInfo(domain, ipBlockInfo) {
        const key = this.domainBlockInfoKey(domain);
        let domainBlockInfo = await rclient.getAsync(key);
        try {
            domainBlockInfo = JSON.parse(domainBlockInfo) || {};
        } catch (err) {
            domainBlockInfo = {};
        }
        domainBlockInfo[ipBlockInfo.ip] = ipBlockInfo;
        await rclient.setAsync(key, JSON.stringify(domainBlockInfo));
        rclient.expireat(key, parseInt((+new Date) / 1000) + expiring);
    }
    domainCovered(blockDomain, otherDomain) {
        // a.b.com covred x.a.b.com
        if (!otherDomain) return true;
        if (blockDomain.startsWith('*.')) blockDomain = blockDomain.substring(2)
        if (otherDomain.startsWith('*.')) otherDomain = otherDomain.substring(2)
        const h1Sections = blockDomain.split('.').reverse();
        const h2Sections = otherDomain.split('.').reverse();
        for (let i = 0; i < h1Sections.length; i++) {
            if (h1Sections[i] !== h2Sections[i])
                return false;
        }
        return true;
    }
}

module.exports = BlockManager
<|endoftext|>


<commit_before>/* globals describe, it, expect, expectObservable, hot */
var Rx = require('../../dist/cjs/Rx');
var Observable = Rx.Observable;
var Notification = Rx.Notification;

describe('Observable.prototype.dematerialize()', function () {
  it('should dematerialize a happy stream', function () {
    var values = {
      a: Notification.createNext('w'),
      b: Notification.createNext('x'),
      c: Notification.createNext('y'),
      d: Notification.createComplete()
    };

    var e1 =   hot('--a--b--c--d--|', values);
    var expected = '--w--x--y--|';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize a sad stream', function () {
    var values = {
      a: Notification.createNext('w'),
      b: Notification.createNext('x'),
      c: Notification.createNext('y'),
      d: Notification.createError('error')
    };

    var e1 =   hot('--a--b--c--d--|', values);
    var expected = '--w--x--y--#';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize stream does not completes', function () {
    var e1 =   hot('------');
    var expected = '-';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize stream never completes', function () {
    var e1 = Observable.never();
    var expected = '-';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize stream does not emit', function () {
    var e1 =   hot('----|');
    var expected = '----|)';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize empty stream', function () {
    var e1 = Observable.empty();
    var expected = '|';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize stream throws', function () {
    var error = 'error';
    var e1 =   hot('(x|)', {x: Notification.createError(error)});
    var expected = '#';

    expectObservable(e1.dematerialize()).toBe(expected, null, error);
  });

  it('should dematerialize and completes when stream compltes with complete notification', function () {
    var e1 =   hot('----(a|)', { a: Notification.createComplete() });
    var expected = '----|';

    expectObservable(e1.dematerialize()).toBe(expected);
  });

  it('should dematerialize and completes when stream emits complete notification', function () {
    var e1 =   hot('----a--|', { a: Notification.createComplete() });
    var expected = '----|';

    expectObservable(e1.dematerialize()).toBe(expected);
  });
});<commit_msg>test(dematerialize): add subscription assertions for dematerialize tests<commit_after>/* globals describe, it, expect, expectObservable, expectSubscriptions, hot, cold */
var Rx = require('../../dist/cjs/Rx');
var Observable = Rx.Observable;
var Notification = Rx.Notification;

describe('Observable.prototype.dematerialize()', function () {
  it('should dematerialize a happy stream', function () {
    var values = {
      a: Notification.createNext('w'),
      b: Notification.createNext('x'),
      c: Notification.createNext('y'),
      d: Notification.createComplete()
    };

    var e1 =   hot('--a--b--c--d--|', values);
    var e1subs =   '^          !';
    var expected = '--w--x--y--|';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize a sad stream', function () {
    var values = {
      a: Notification.createNext('w'),
      b: Notification.createNext('x'),
      c: Notification.createNext('y'),
      d: Notification.createError('error')
    };

    var e1 =   hot('--a--b--c--d--|', values);
    var e1subs =   '^          !';
    var expected = '--w--x--y--#';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize stream does not completes', function () {
    var e1 =   hot('------');
    var e1subs =   '^';
    var expected = '-';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize stream never completes', function () {
    var e1 =  cold('-');
    var e1subs =   '^';
    var expected = '-';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize stream does not emit', function () {
    var e1 =   hot('----|');
    var e1subs =   '^   !';
    var expected = '----|';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize empty stream', function () {
    var e1 =  cold('|');
    var e1subs =   '(^!)';
    var expected = '|';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize stream throws', function () {
    var error = 'error';
    var e1 =   hot('(x|)', {x: Notification.createError(error)});
    var e1subs =   '(^!)';
    var expected = '#';

    expectObservable(e1.dematerialize()).toBe(expected, null, error);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize and completes when stream compltes with complete notification', function () {
    var e1 =   hot('----(a|)', { a: Notification.createComplete() });
    var e1subs =   '^   !';
    var expected = '----|';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should dematerialize and completes when stream emits complete notification', function () {
    var e1 =   hot('----a--|', { a: Notification.createComplete() });
    var e1subs =   '^   !';
    var expected = '----|';

    expectObservable(e1.dematerialize()).toBe(expected);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });
});<|endoftext|>


<commit_before>import { secureFetch } from '../../common/util/util'
import { setErrorMessage } from '../../manager/actions/status'

export function requestingTripsForCalendar (feedId, pattern, calendarId) {
  return {
    type: 'REQUESTING_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId
  }
}

export function receiveTripsForCalendar (feedId, pattern, calendarId, trips) {
  return {
    type: 'RECEIVE_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function fetchTripsForCalendar (feedId, pattern, calendarId) {
  return function (dispatch, getState) {
    dispatch(requestingTripsForCalendar(feedId, pattern, calendarId))
    const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${pattern.id}&calendarId=${calendarId}`
    return secureFetch(url, getState())
      .then(res => res.json())
      .then(trips => {
        dispatch(receiveTripsForCalendar(feedId, pattern, calendarId, trips))
      })
  }
}

export function savingTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'SAVING_TRIPS',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

// export function receiveTrips (feedId, trips) {
//   return {
//     type: 'RECEIVE_TRIPS',
//     feedId,
//     trips
//   }
// }

export function saveTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    let errorIndexes = []
    dispatch(savingTrips(feedId, pattern, calendarId, trips))
    return Promise.all(trips.filter(t => t).map((trip, index) => {
      const tripExists = trip.id !== 'new' && trip.id !== null
      const method = tripExists ? 'put' : 'post'
      const url = tripExists
        ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
        : `/api/manager/secure/trip?feedId=${feedId}`
      trip.id = tripExists ? trip.id : null
      return secureFetch(url, getState(), method, trip)
        .then(res => {
          if (res.status >= 300) {
            errorCount++
            errorIndexes.push(index)
            return null
          } else {
            return res.json()
          }
        })
    }))
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while saving trips.  Could not save ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      return errorIndexes
    })
    // return result
  }
}

// TODO: action is under construction...
export function saveMultipleTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    let errorIndexes = []
    dispatch(savingTrips(feedId, pattern, calendarId, trips))
    const newTrips = []
    const existingTrips = []
    trips.forEach(t => {
      const tripExists = t.id !== 'new' && t.id !== null
      if (tripExists) {
        existingTrips.push(t)
      } else {
        newTrips.push(t)
      }
    })
    return secureFetch(url, getState(), method, trip)
      .then(res => {
        if (res.status >= 300) {
          errorCount++
          errorIndexes.push(index)
          return null
        } else {
          return res.json()
        }
      })
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while saving trips.  Could not save ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      return errorIndexes
    })
    // return result
  }
}

// export function saveTrip (feedId, trip) {
//   // return function (dispatch, getState) {
//     // const url = `/api/manager/secure/trip/?feedId=${feedId}&patternId=${pattern.id}&calendarId=${calendarId}`
//     const url = trip.id !== 'new'
//       ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
//       : `/api/manager/secure/trip?feedId=${feedId}`
//     return secureFetch(url, getState())
//       .then(res => res.json())
//       .then(trips => {
//         return trip
//       })
//   // }
// }

export function deletingTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'DELETING_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function deletedTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'DELETED_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function deleteTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    dispatch(deletingTrips(feedId, pattern, calendarId, trips))
    return Promise.all(trips.map(trip => {
      const url = `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
      return secureFetch(url, getState(), 'delete', trip)
        .then(res => res.json())
        .catch(error => {
          console.log(error)
          errorCount++
        })
    }))
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while deleting trips.  Could not delete ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      // dispatch(deletedTrips(feedId, pattern, calendarId, trips))
      // return trips
    })
    // return result
  }
}

export function updateCellValue (value, rowIndex, key) {
  return {
    type: 'UPDATE_TIMETABLE_CELL_VALUE',
    value,
    rowIndex,
    key
  }
}

export function toggleRowSelection (rowIndex) {
  return {
    type: 'TOGGLE_SINGLE_TIMETABLE_ROW_SELECTION',
    rowIndex
  }
}
export function toggleAllRows (select) {
  return {
    type: 'TOGGLE_ALL_TIMETABLE_ROW_SELECTION',
    select
  }
}
export function toggleDepartureTimes () {
  return {
    type: 'TOGGLE_DEPARTURE_TIMES'
  }
}
export function setOffset (seconds) {
  return {
    type: 'SET_TIMETABLE_OFFSET',
    seconds
  }
}
export function addNewTrip (trip) {
  return {
    type: 'ADD_NEW_TRIP',
    trip
  }
}
// export function fetchTripsForCalendar (feedId, patternId, calendarId) {
//   return function (dispatch, getState) {
//     dispatch(requestingTripsForCalendar(feedId, patternId, calendarId))
//     const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${patternId}&calendarId=${calendarId}`
//     return secureFetch(url, getState())
//       .then(res => res.json())
//       .then(trips => {
//         dispatch(receiveTripsForCalendar(feedId, patternId, calendarId, trips))
//       })
//   }
// }
<commit_msg>fix lint issue<commit_after>import { secureFetch } from '../../common/util/util'
import { setErrorMessage } from '../../manager/actions/status'

export function requestingTripsForCalendar (feedId, pattern, calendarId) {
  return {
    type: 'REQUESTING_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId
  }
}

export function receiveTripsForCalendar (feedId, pattern, calendarId, trips) {
  return {
    type: 'RECEIVE_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function fetchTripsForCalendar (feedId, pattern, calendarId) {
  return function (dispatch, getState) {
    dispatch(requestingTripsForCalendar(feedId, pattern, calendarId))
    const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${pattern.id}&calendarId=${calendarId}`
    return secureFetch(url, getState())
      .then(res => res.json())
      .then(trips => {
        dispatch(receiveTripsForCalendar(feedId, pattern, calendarId, trips))
      })
  }
}

export function savingTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'SAVING_TRIPS',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

// export function receiveTrips (feedId, trips) {
//   return {
//     type: 'RECEIVE_TRIPS',
//     feedId,
//     trips
//   }
// }

export function saveTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    let errorIndexes = []
    dispatch(savingTrips(feedId, pattern, calendarId, trips))
    return Promise.all(trips.filter(t => t).map((trip, index) => {
      const tripExists = trip.id !== 'new' && trip.id !== null
      const method = tripExists ? 'put' : 'post'
      const url = tripExists
        ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
        : `/api/manager/secure/trip?feedId=${feedId}`
      trip.id = tripExists ? trip.id : null
      return secureFetch(url, getState(), method, trip)
        .then(res => {
          if (res.status >= 300) {
            errorCount++
            errorIndexes.push(index)
            return null
          } else {
            return res.json()
          }
        })
    }))
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while saving trips.  Could not save ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      return errorIndexes
    })
    // return result
  }
}

// TODO: action is under construction...
export function saveMultipleTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    let errorIndexes = []
    dispatch(savingTrips(feedId, pattern, calendarId, trips))
    const newTrips = []
    const existingTrips = []
    trips.forEach(t => {
      const tripExists = t.id !== 'new' && t.id !== null
      if (tripExists) {
        existingTrips.push(t)
      } else {
        newTrips.push(t)
      }
    })
    const createUrl = `/api/manager/secure/trip?feedId=${feedId}`
    return secureFetch(createUrl, getState(), 'post')
      .then(res => {
        if (res.status >= 300) {
          errorCount++
          // errorIndexes.push(index)
          return null
        } else {
          return res.json()
        }
      })
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while saving trips.  Could not save ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      return errorIndexes
    })
    // return result
  }
}

// export function saveTrip (feedId, trip) {
//   // return function (dispatch, getState) {
//     // const url = `/api/manager/secure/trip/?feedId=${feedId}&patternId=${pattern.id}&calendarId=${calendarId}`
//     const url = trip.id !== 'new'
//       ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
//       : `/api/manager/secure/trip?feedId=${feedId}`
//     return secureFetch(url, getState())
//       .then(res => res.json())
//       .then(trips => {
//         return trip
//       })
//   // }
// }

export function deletingTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'DELETING_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function deletedTrips (feedId, pattern, calendarId, trips) {
  return {
    type: 'DELETED_TRIPS_FOR_CALENDAR',
    feedId,
    pattern,
    calendarId,
    trips
  }
}

export function deleteTripsForCalendar (feedId, pattern, calendarId, trips) {
  return function (dispatch, getState) {
    let errorCount = 0
    dispatch(deletingTrips(feedId, pattern, calendarId, trips))
    return Promise.all(trips.map(trip => {
      const url = `/api/manager/secure/trip/${trip.id}?feedId=${feedId}`
      return secureFetch(url, getState(), 'delete', trip)
        .then(res => res.json())
        .catch(error => {
          console.log(error)
          errorCount++
        })
    }))
    .then(trips => {
      // console.log(trips)
      if (errorCount) {
        dispatch(setErrorMessage(`Unknown error encountered while deleting trips.  Could not delete ${errorCount} trips`))
      }
      dispatch(fetchTripsForCalendar(feedId, pattern, calendarId))
      // dispatch(deletedTrips(feedId, pattern, calendarId, trips))
      // return trips
    })
    // return result
  }
}

export function updateCellValue (value, rowIndex, key) {
  return {
    type: 'UPDATE_TIMETABLE_CELL_VALUE',
    value,
    rowIndex,
    key
  }
}

export function toggleRowSelection (rowIndex) {
  return {
    type: 'TOGGLE_SINGLE_TIMETABLE_ROW_SELECTION',
    rowIndex
  }
}
export function toggleAllRows (select) {
  return {
    type: 'TOGGLE_ALL_TIMETABLE_ROW_SELECTION',
    select
  }
}
export function toggleDepartureTimes () {
  return {
    type: 'TOGGLE_DEPARTURE_TIMES'
  }
}
export function setOffset (seconds) {
  return {
    type: 'SET_TIMETABLE_OFFSET',
    seconds
  }
}
export function addNewTrip (trip) {
  return {
    type: 'ADD_NEW_TRIP',
    trip
  }
}
// export function fetchTripsForCalendar (feedId, patternId, calendarId) {
//   return function (dispatch, getState) {
//     dispatch(requestingTripsForCalendar(feedId, patternId, calendarId))
//     const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${patternId}&calendarId=${calendarId}`
//     return secureFetch(url, getState())
//       .then(res => res.json())
//       .then(trips => {
//         dispatch(receiveTripsForCalendar(feedId, patternId, calendarId, trips))
//       })
//   }
// }
<|endoftext|>


<commit_before>
'use strict';

// We assume this JS is sourced at the end of any HTML, avoiding the
// need for a $(document).ready(…) call. But it really needs the
// document fully loaded to operated properly.

// TODO: put this in our own namespace, not in the window…

common_init();

start_checking_for_needed_updates();

function update_needed() {
    console.log('update needed…')
}

// Support for AJAX loaded modal window.
// Focuses on first input textbox after it loads the window.
$('[data-toggle="modal"]').click(function(e) {

    e.preventDefault();

    var url = $(this).attr('href');

    if (url.indexOf('#') == 0) {
        $(url).modal('open');

    } else {

        $.get(url, function(data) {
            $(data).modal().on("shown", function () {

                //$('input:visible:enabled:first').focus();
                //$first_input.select();

                var $first_input = $('input:visible:enabled').first();

                $first_input.focus(function() {
                    // OMG. Thanks http://stackoverflow.com/a/10576409/654755
                    this.selectionStart = this.selectionEnd = this.value.length;
                });

                $first_input.focus();

            }).on('hidden', function () {
                $(this).remove();
            });
        });
    }
});
<commit_msg>Make an anonymous JS func now named for re-usability.<commit_after>
'use strict';

// We assume this JS is sourced at the end of any HTML, avoiding the
// need for a $(document).ready(…) call. But it really needs the
// document fully loaded to operated properly.

// TODO: put this in our own namespace, not in the window…

common_init();

start_checking_for_needed_updates();

function update_needed() {
    console.log('update needed…')
}

function handle_modal(e) {

    e.preventDefault();

    var url = $(this).attr('href');

    if (url.indexOf('#') == 0) {
        $(url).modal('open');

    } else {

        $.get(url, function(data) {

            $(data).modal().on("shown", function () {

                //$('input:visible:enabled:first').focus();
                //$first_input.select();

                var $first_input = $('input:visible:enabled').first();

                $first_input.focus(function() {
                    // OMG. Thanks http://stackoverflow.com/a/10576409/654755
                    this.selectionStart = this.selectionEnd = this.value.length;
                });

                $first_input.focus();

            }).on('hidden', function () {
                $(this).remove();
            });
        });
    }
}

// Support for AJAX loaded modal window.
// Focuses on first input textbox after it loads the window.
$('[data-toggle="modal"]').click(handle_modal);
<|endoftext|>


<commit_before>'use strict';

var $ = require('gulp');
var $$ = require('./config.plugins');
var paths = require('./config.paths');

var argv = $$.yargs.argv,
    isEnvProd = argv.env === 'prod';

$.task('build:styles:less', function() {
    return $.src([
        paths.styles.src.shared.file,
        paths.styles.src.pages.album.file
    ])
        .pipe($$.plumber())
        .pipe($$.less({
            plugins: [
                new $$.lessPluginAutoprefix({
                    browsers: ['last 2 versions']
                }),
                new $$.lessPluginCsscomb()
            ]
        }))
        .pipe($.dest(paths.styles.dist.dir));
});

$.task('build:styles:bless', function() {
    return $.src(paths.styles.dist.shared.file)
        .pipe($$.plumber())
        .pipe($$.sakugawa({
            maxSelectors: 4000,
            mediaQueries: 'separate',
            suffix: '-'
        }))
        .pipe($.dest(paths.styles.dist.shared.blessed.dir));
});

$.task('build:styles:minify', function() {
    return $.src(paths.styles.dist.files, {
        base: paths.styles.dist.dir
    })
        .pipe($$.plumber())
        .pipe($$.if(isEnvProd, $$.sourcemaps.init()))
        .pipe($$.if(isEnvProd, $$.minifyCss({
            compatibility: 'ie8',
            keepSpecialComments: 0
        })))
        .pipe($$.if(isEnvProd, $$.sourcemaps.write('./', {
            sourceMappingURL: function(file) {
                return paths.styles.dist.url + '/' + file.relative + '.map';
            }
        })))
        .pipe($.dest(paths.styles.dist.dir));
});

$.task('build:styles', function(done) {
    return $$.runSequence('build:styles:less', 'build:styles:bless', 'build:styles:minify', done);
});
<commit_msg>[GULP] Changed the browser support by the LESS "autoprefixer" to "last 3 versions".<commit_after>'use strict';

var $ = require('gulp');
var $$ = require('./config.plugins');
var paths = require('./config.paths');

var argv = $$.yargs.argv,
    isEnvProd = argv.env === 'prod';

$.task('build:styles:less', function() {
    return $.src([
        paths.styles.src.shared.file,
        paths.styles.src.pages.album.file
    ])
        .pipe($$.plumber())
        .pipe($$.less({
            plugins: [
                new $$.lessPluginAutoprefix({
                    browsers: ['last 3 versions']
                }),
                new $$.lessPluginCsscomb()
            ]
        }))
        .pipe($.dest(paths.styles.dist.dir));
});

$.task('build:styles:bless', function() {
    return $.src(paths.styles.dist.shared.file)
        .pipe($$.plumber())
        .pipe($$.sakugawa({
            maxSelectors: 4000,
            mediaQueries: 'separate',
            suffix: '-'
        }))
        .pipe($.dest(paths.styles.dist.shared.blessed.dir));
});

$.task('build:styles:minify', function() {
    return $.src(paths.styles.dist.files, {
        base: paths.styles.dist.dir
    })
        .pipe($$.plumber())
        .pipe($$.if(isEnvProd, $$.sourcemaps.init()))
        .pipe($$.if(isEnvProd, $$.minifyCss({
            compatibility: 'ie8',
            keepSpecialComments: 0
        })))
        .pipe($$.if(isEnvProd, $$.sourcemaps.write('./', {
            sourceMappingURL: function(file) {
                return paths.styles.dist.url + '/' + file.relative + '.map';
            }
        })))
        .pipe($.dest(paths.styles.dist.dir));
});

$.task('build:styles', function(done) {
    return $$.runSequence('build:styles:less', 'build:styles:bless', 'build:styles:minify', done);
});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

function buildGroupTree(response) {
  var data = [];
  for (var uri in response) {
    var group = createGroupNode(uri, response[uri]);
    if (group.text)
      data.push(group);
  }
  return data;
}

function invokeGroupTree() {
  $('.group-hierarchy').jstree({
    'plugins' : ['sort'],
    'core' : {
      'data' :
        function(node, cb) {
          var nodeId;
          var json_url = (rest_base_url + vocab + '/groups');
          if (node.id !== '#') {
            nodeId = node.id;
            json_url = (rest_base_url + vocab + '/groupMembers');
          }
          var jsondata = $.ajax({
            data: $.param({'uri' : nodeId, 'lang' : content_lang}),
            url: json_url,
            success: function (response) {
              if (response.groupHierarchy) { // the default hierarchy query that fires when a page loads.
                cb(buildGroupTree(response.groupHierarchy));
              } else {
                var children = [];
                for (var memberUri in response.members) {
                  var child = {'id' : memberUri, 'text' : response.members[memberUri].label,'parent' : nodeId, children : false, a_attr : { "href" : memberUri, 'state' : { 'opened' : false }}};
                  if (response.members[memberUri].hasMembers)
                    child.children = true;
                  children.push(child);
                }
                cb(children);
              }
            }
          });
      },
      'animation' : 0,
      'themes' : { 'icons': false },
    }
  });
}

function createGroupNode(uri, groupObject) {
  var node = {'id' : uri, 'parent' : '#', children : [], a_attr : { "href" : uri }};
  node.text = groupObject.label;
  if (groupObject.members)
    node.children = true;
  node.state = { 'opened' : false };
  //if (groupObject.members) {
    //for (var memberUri in groupObject.members) {
      //var child = { 'id' : memberUri, 'text' : groupObject.members[memberUri], 'parent' : uri };
      //if (child.text)
        //node.children.push(child);
    //}
  //}
  return node;
}

<commit_msg>opening the nodes when clicking on a preflabel<commit_after>/*
 * Copyright (c) 2012 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

function buildGroupTree(response) {
  var data = [];
  for (var uri in response) {
    var group = createGroupNode(uri, response[uri]);
    if (group.text)
      data.push(group);
  }
  return data;
}

function invokeGroupTree() {
  var $treeObject = $('.group-hierarchy');
  $treeObject.on('activate_node.jstree', function(event, node) {
    $treeObject.jstree('open_node', node.node);
  });

  $('.group-hierarchy').jstree({
    'plugins' : ['sort'],
    'core' : {
      'data' :
        function(node, cb) {
          var nodeId;
          var json_url = (rest_base_url + vocab + '/groups');
          if (node.id !== '#') {
            nodeId = node.id;
            json_url = (rest_base_url + vocab + '/groupMembers');
          }
          var jsondata = $.ajax({
            data: $.param({'uri' : nodeId, 'lang' : content_lang}),
            url: json_url,
            success: function (response) {
              if (response.groupHierarchy) { // the default hierarchy query that fires when a page loads.
                cb(buildGroupTree(response.groupHierarchy));
              } else {
                var children = [];
                for (var memberUri in response.members) {
                  var child = {'id' : memberUri, 'text' : response.members[memberUri].label,'parent' : nodeId, children : false, a_attr : { "href" : memberUri, 'state' : { 'opened' : false }}};
                  if (response.members[memberUri].hasMembers)
                    child.children = true;
                  children.push(child);
                }
                cb(children);
              }
            }
          });
      },
      'animation' : 0,
      'themes' : { 'icons': false },
    }
  });
}

function createGroupNode(uri, groupObject) {
  var node = {'id' : uri, 'parent' : '#', children : [], a_attr : { "href" : uri }};
  node.text = groupObject.label;
  if (groupObject.members)
    node.children = true;
  node.state = { 'opened' : false };
  //if (groupObject.members) {
    //for (var memberUri in groupObject.members) {
      //var child = { 'id' : memberUri, 'text' : groupObject.members[memberUri], 'parent' : uri };
      //if (child.text)
        //node.children.push(child);
    //}
  //}
  return node;
}

<|endoftext|>


<commit_before>// --------------------------------------------------
// Stuff for the login screen
// --------------------------------------------------
define(function (require) {

	// Dependencies
	var $ = require('jquery')
		, Backbone = require('backbone')
		, _ = require('underscore')
		, $win = $(window)
		, LoginAnimation = require('../animation/decoy-anim')
	;

	// Create view
	var View = {};

	// Constructor
	View.initialize = function() {
		_.bindAll(this);

		// Cache
		this.$branding = $('.branding');
		this.$form = $('.form');
		this.$container = $('.max-width');

		// Listen for resizes and adjust the size of the branding container
		this.resize();
		$win.on('orientationchange resize load', _.debounce(this.resize, 100));

		// Animate in in the subtitle
		this.$('h1').addClass('show');
		_.delay(_.bind(function() {
			this.$('h4').addClass('show');
		}, this), 1000);

		//Aadd decoy login animation on screens greater than screen-sm
		if( $win.width() >= 768 )
			this.loginAnim = new LoginAnimation({
				el: '#main',
				color: rgb2hex($('body').css('background-color')),
				size: 25,
				spawnRate: 1,
				cellRate: 1,
				cellBrightness: 1,
				colorRange: 0.5,
				flashSpeedIn: 0.04,
				flashSpeedOut: 0.02
			});
	};

	// Fix the size of the branding piece
	View.resize = function() {
		this.$branding.css('top', (this.$form.outerHeight() - this.$branding.height())/2 );
	};

	// Utility function for converting rgb() values to hexes
	// http://stackoverflow.com/a/3627747
	function rgb2hex(rgb) {
		if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb;
		rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
		function hex(x) { return ("0" + parseInt(x).toString(16)).slice(-2); }
		return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
	}

	// Return it
	return Backbone.View.extend(View);

});
<commit_msg>Make the rgb2hex work with rgba() values<commit_after>// --------------------------------------------------
// Stuff for the login screen
// --------------------------------------------------
define(function (require) {

	// Dependencies
	var $ = require('jquery')
		, Backbone = require('backbone')
		, _ = require('underscore')
		, $win = $(window)
		, LoginAnimation = require('../animation/decoy-anim')
	;

	// Create view
	var View = {};

	// Constructor
	View.initialize = function() {
		_.bindAll(this);

		// Cache
		this.$branding = $('.branding');
		this.$form = $('.form');
		this.$container = $('.max-width');

		// Listen for resizes and adjust the size of the branding container
		this.resize();
		$win.on('orientationchange resize load', _.debounce(this.resize, 100));

		// Animate in in the subtitle
		this.$('h1').addClass('show');
		_.delay(_.bind(function() {
			this.$('h4').addClass('show');
		}, this), 1000);

		//Aadd decoy login animation on screens greater than screen-sm
		if( $win.width() >= 768 )
			this.loginAnim = new LoginAnimation({
				el: '#main',
				color: rgb2hex($('body').css('background-color')),
				size: 25,
				spawnRate: 1,
				cellRate: 1,
				cellBrightness: 1,
				colorRange: 0.5,
				flashSpeedIn: 0.04,
				flashSpeedOut: 0.02
			});
	};

	// Fix the size of the branding piece
	View.resize = function() {
		this.$branding.css('top', (this.$form.outerHeight() - this.$branding.height())/2 );
	};

	// Utility function for converting rgb() values to hexes
	// http://stackoverflow.com/a/3627747
	function rgb2hex(rgb) {
		if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb;
		rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/);
		function hex(x) { return ("0" + parseInt(x).toString(16)).slice(-2); }
		return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
	}

	// Return it
	return Backbone.View.extend(View);

});
<|endoftext|>


<commit_before>var socket;
var username;

function createNewSocketConnection() {
	// close open connection
	if (socket) {
		closeSocketConnection();
	}

	// get new username
	var usernameInput = document.getElementById("username");
	username = usernameInput.value;
	usernameInput.value = "";

	// if not empty create the connection
	if (username) {

		// set username header
		var usernameHeader = document.getElementById("usernameHeader");
		usernameHeader.innerHTML = username;

		socket = new WebSocket('ws://lovat.herokuapp.com/chat' + username);
		socket.onopen = function(event) {
			console.log(username + ' connected 🎉');
		}

		socket.onerror = function(event) {
			log('Error: ' + JSON.stringify(event));
		}

		socket.onmessage = function(event) {
			var message = JSON.parse(event.data);
			log(message.from + ": " + message.content);
		}

		socket.onclose = function(event) {
			log(username + ' closed connection 😱');
		}
	}
}

function closeSocketConnection() {
	if (socket) {
		var usernameHeader = document.getElementById("usernameHeader");
		usernameHeader.innerHTML = "";
		socket.close();
		socket = null;
	}
}

function sendMessageOverSocket() {
	var content = document.getElementById("msg");
	var message = content.value;
	content.value = "";
	// only send if username and message are not empty
	if (socket && username && message) {
		var json = JSON.stringify({
			from : username,
			to : "",
			content : message
		});
		socket.send(json);
		message = "";
	}
}

function log(text) {
	var li = document.createElement('li');
	li.className = "List-item";
	li.innerHTML = text;
	var list = document.getElementById("log");
	if (list !== null) {
		list.prepend(li);
	}
}

window.addEventListener("beforeunload", function() {
	socket.close();
});
<commit_msg>Updated websocket connection to secure using wss<commit_after>var socket;
var username;

function createNewSocketConnection() {
	// close open connection
	if (socket) {
		closeSocketConnection();
	}

	// get new username
	var usernameInput = document.getElementById("username");
	username = usernameInput.value;
	usernameInput.value = "";

	// if not empty create the connection
	if (username) {

		// set username header
		var usernameHeader = document.getElementById("usernameHeader");
		usernameHeader.innerHTML = username;

		socket = new WebSocket('wss://lovat.herokuapp.com/chat/' + username);
		socket.onopen = function(event) {
			console.log(username + ' connected 🎉');
		}

		socket.onerror = function(event) {
			log('Error: ' + JSON.stringify(event));
		}

		socket.onmessage = function(event) {
			var message = JSON.parse(event.data);
			log(message.from + ": " + message.content);
		}

		socket.onclose = function(event) {
			log(username + ' closed connection 😱');
		}
	}
}

function closeSocketConnection() {
	if (socket) {
		var usernameHeader = document.getElementById("usernameHeader");
		usernameHeader.innerHTML = "";
		socket.close();
		socket = null;
	}
}

function sendMessageOverSocket() {
	var content = document.getElementById("msg");
	var message = content.value;
	content.value = "";
	// only send if username and message are not empty
	if (socket && username && message) {
		var json = JSON.stringify({
			from : username,
			to : "",
			content : message
		});
		socket.send(json);
		message = "";
	}
}

function log(text) {
	var li = document.createElement('li');
	li.className = "List-item";
	li.innerHTML = text;
	var list = document.getElementById("log");
	if (list !== null) {
		list.prepend(li);
	}
}

window.addEventListener("beforeunload", function() {
	socket.close();
});
<|endoftext|>


<commit_before>
// Initialise OCRJS namespace
if (OCRJS === undefined) {
    var OCRJS = {};
}

OCRJS.OcrBase = Base.extend({
    constructor: function(options) {
        this._log = [];
        this.options = {
            log: false,
        }
        $.extend(this.options, options);
        this._listeners = {};
    },

    _logger: function(arguments) {
        if (!this.options.log)
            return;
        console.log(arguments);
    },

    registerListener: function(key) {
        if (this._listeners[key] != undefined)
            throw "Listener already registered: '" + key + "'";
        this._listeners[key] = [];
        return this;
    },

    addListener: function(key, func) {
        var namespace = null;
        if (key.match(/^(.+)\.([^\.]+)$/))
            key = RegExp.$1, namespace = RegExp.$2;
        if (namespace) {
            func.__namespace = namespace;
        }
        if (this._listeners[key] == undefined)
            throw "Unknown callback: '" + key + "'";
        this._listeners[key].push(func);
        return this;
    },

    addListeners: function(obj) {
        if (typeof obj != "object")
            throw "addListeners except an object parameter, got " + (typeof obj)
                + " instead";
        var self = this;
        $.each(obj, function(k, v) { self.addListener(k, v) });
        return this;
    },

    callListeners: function() {
        var args = Array.prototype.slice.call(arguments);
        var key = args.shift();
        if (this._listeners[key] == undefined)
            throw "Unknown callback: '" + key + "'";
        $.each(this._listeners[key], function(i, func) {
            func.apply(
                func.callee, args.concat(Array.prototype.slice.call(arguments)));
        });
    },

    removeListeners: function(key, func) {
        var namespace = null;
        if (key.match(/^(.*)\.([^\.]+)$/))
            key = RegExp.$1, namespace = RegExp.$2;
        if (key != "" && this._listeners[key] === undefined) {
            console.error("Unknown callback:", key, this._listeners);
        }
        if (func) {
            var i = this._listeners[key].indexOf(func);
            if (i != -1)
                this._listeners.splice(i, 1);
            else
                console.error("Attempted to remove unknown listener callback");
        } else {
            var self = this;
            $.each(self._listeners, function(k, funcs) {
                if (k == "" || k == key) {
                    $.each(funcs, function(i, f) {
                        if (f.__namespace && f.__namespace == namespace) {
                            funcs.splice(i, 1);
                        }
                    });
                }
            });
        }
    },
});

// Base widget for OCR items.  Just provides
// logging window facilities
OCRJS.OcrBaseWidget = OCRJS.OcrBase.extend({
    constructor: function(parent, options) {
        this.base(options);
        this.parent = parent;
        this.options = {
            log: false,
        }
        $.extend(this.options, options);
    },

    getState: function() {
    },

    setState: function() {
    },
});
<commit_msg>Change the context of event handlers to the object to which they're attached.  This might break lots of stuff.<commit_after>
// Initialise OCRJS namespace
if (OCRJS === undefined) {
    var OCRJS = {};
}

OCRJS.OcrBase = Base.extend({
    constructor: function(options) {
        this._log = [];
        this.options = {
            log: false,
        }
        $.extend(this.options, options);
        this._listeners = {};
    },

    _logger: function(arguments) {
        if (!this.options.log)
            return;
        console.log(arguments);
    },

    registerListener: function(key) {
        if (this._listeners[key] != undefined)
            throw "Listener already registered: '" + key + "'";
        this._listeners[key] = [];
        return this;
    },

    addListener: function(key, func) {
        var namespace = null;
        if (key.match(/^(.+)\.([^\.]+)$/))
            key = RegExp.$1, namespace = RegExp.$2;
        if (namespace) {
            func.__namespace = namespace;
        }
        if (this._listeners[key] == undefined)
            throw "Unknown callback: '" + key + "'";
        this._listeners[key].push(func);
        return this;
    },

    addListeners: function(obj) {
        if (typeof obj != "object")
            throw "addListeners except an object parameter, got " + (typeof obj)
                + " instead";
        var self = this;
        $.each(obj, function(k, v) { self.addListener(k, v) });
        return this;
    },

    callListeners: function() {
        var self = this;
        var args = Array.prototype.slice.call(arguments);
        var key = args.shift();
        if (this._listeners[key] == undefined)
            throw "Unknown callback: '" + key + "'";
        $.each(this._listeners[key], function(i, func) {
            func.apply(
                self, args.concat(Array.prototype.slice.call(arguments)));
        });
    },

    removeListeners: function(key, func) {
        var namespace = null;
        if (key.match(/^(.*)\.([^\.]+)$/))
            key = RegExp.$1, namespace = RegExp.$2;
        if (key != "" && this._listeners[key] === undefined) {
            console.error("Unknown callback:", key, this._listeners);
        }
        if (func) {
            var i = this._listeners[key].indexOf(func);
            if (i != -1)
                this._listeners.splice(i, 1);
            else
                console.error("Attempted to remove unknown listener callback");
        } else {
            var self = this;
            $.each(self._listeners, function(k, funcs) {
                if (k == "" || k == key) {
                    $.each(funcs, function(i, f) {
                        if (f.__namespace && f.__namespace == namespace) {
                            funcs.splice(i, 1);
                        }
                    });
                }
            });
        }
    },
});

// Base widget for OCR items.  Just provides
// logging window facilities
OCRJS.OcrBaseWidget = OCRJS.OcrBase.extend({
    constructor: function(parent, options) {
        this.base(options);
        this.parent = parent;
        this.options = {
            log: false,
        }
        $.extend(this.options, options);
    },

    getState: function() {
    },

    setState: function() {
    },
});
<|endoftext|>