<commit_before>"use strict";

// Modules
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

/**
 * Env
 * Get npm lifecycle event to identify the environment
 */
var ENV = process.env.npm_lifecycle_event;
var isTest = ENV === "test" || ENV === "test-watch";
var isProd = ENV === "build";

isProd = true;

module.exports = (function makeWebpackConfig() {
  /**
   * Config
   * Reference: http://webpack.github.io/docs/configuration.html
   * This is the object where all configuration gets set
   */
  var config = {};

  /**
   * Entry
   * Reference: http://webpack.github.io/docs/configuration.html#entry
   * Should be an empty object if it's generating a test build
   * Karma will set this when it's a test build
   */
  config.entry = {
    app: "./src/js/app.js",
  };

  /**
   * Output
   * Reference: http://webpack.github.io/docs/configuration.html#output
   * Should be an empty object if it's generating a test build
   * Karma will handle setting it up for you when it's a test build
   */
  config.output = isTest
    ? {}
    : {
        // Absolute output directory
        path: __dirname + "/dist",

        // Output path from the view of the page
        // Uses webpack-dev-server in development
        publicPath: "/",

        // Filename for entry points
        // Only adds hash in build mode
        filename: isProd ? "[name].[hash].js" : "[name].bundle.js",

        // Filename for non-entry points
        // Only adds hash in build mode
        chunkFilename: isProd ? "[name].[hash].js" : "[name].bundle.js",
      };

  /**
   * Devtool
   * Reference: http://webpack.github.io/docs/configuration.html#devtool
   * Type of sourcemap to use per build type
   */
  if (isTest) {
    config.devtool = "inline-source-map";
  } else if (isProd) {
    config.devtool = "source-map";
  } else {
    config.devtool = "eval-source-map";
  }

  /**
   * Loaders
   * Reference: http://webpack.github.io/docs/configuration.html#module-loaders
   * List: http://webpack.github.io/docs/list-of-loaders.html
   * This handles most of the magic responsible for converting modules
   */

  // Initialize module
  config.module = {
    rules: [
      {
        // JS LOADER
        // Reference: https://github.com/babel/babel-loader
        // Transpile .js files using babel-loader
        // Compiles ES6 and ES7 into ES5 code
        test: /\.js$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
      {
        // CSS LOADER
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        // ASSET LOADER
        // Reference: https://github.com/webpack/file-loader
        // Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output
        // Rename the file using the asset hash
        // Pass along the updated reference to your code
        // You can add here any file extension you want to get copied to your output
        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
        loader: "file-loader",
      },
      {
        // HTML LOADER
        // Reference: https://github.com/webpack/raw-loader
        // Allow loading html through js
        test: /\.html$/,
        loader: "raw-loader",
      },
    ],
  };

  /**
   * PostCSS
   * Reference: https://github.com/postcss/autoprefixer-core
   * Add vendor prefixes to your css
   */
  // NOTE: This is now handled in the `postcss.config.js`
  //       webpack2 has some issues, making the config file necessary

  /**
   * Plugins
   * Reference: http://webpack.github.io/docs/configuration.html#plugins
   * List: http://webpack.github.io/docs/list-of-plugins.html
   */
  config.plugins = [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin(),
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery",
      sigma: "sigma",
    }),
  ];

  config.optimization = {
    runtimeChunk: {
      name: function (entrypoint) {
        return `runtimechunk~${entrypoint.name}`;
      },
    },
    minimize: false,
  };

  // Skip rendering index.html in test mode
  if (!isTest) {
    // Reference: https://github.com/ampedandwired/html-webpack-plugin
    // Render index.html
    config.plugins.push(
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
      }),

      // Reference: https://github.com/webpack/extract-text-webpack-plugin
      // Extract css files
      // Disabled when in test mode or not in build mode
      new MiniCssExtractPlugin({
        allChunks: true,
      }),
    );
  }

  // Add build specific plugins
  if (isProd) {
    config.plugins.push(
      // Reference: http://webpack.github.io/docs/list-of-plugins.html#noerrorsplugin
      // Only emit files when there are no errors
      new webpack.NoEmitOnErrorsPlugin(),

      // Copy assets from the public folder
      // Reference: https://github.com/kevlened/copy-webpack-plugin
      new CopyWebpackPlugin({
        patterns: [{ from: __dirname + "/src/public", to: "." }],
      }),
    );
    config.optimization = {
      minimize: true,
    };
  }

  /**
   * Dev server configuration
   * Reference: http://webpack.github.io/docs/configuration.html#devserver
   * Reference: http://webpack.github.io/docs/webpack-dev-server.html
   */
  config.devServer = {
    contentBase: "./src/public",
    stats: "minimal",
    host: "0.0.0.0",
    compress: true,
    disableHostCheck: true,
  };

  return config;
})();
<commit_msg>[client]: Remove 'isProd=true' in webpack<commit_after>"use strict";

// Modules
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

/**
 * Env
 * Get npm lifecycle event to identify the environment
 */
var ENV = process.env.npm_lifecycle_event;
var isTest = ENV === "test" || ENV === "test-watch";
var isProd = ENV === "build";

module.exports = (function makeWebpackConfig() {
  /**
   * Config
   * Reference: http://webpack.github.io/docs/configuration.html
   * This is the object where all configuration gets set
   */
  var config = {};

  /**
   * Entry
   * Reference: http://webpack.github.io/docs/configuration.html#entry
   * Should be an empty object if it's generating a test build
   * Karma will set this when it's a test build
   */
  config.entry = {
    app: "./src/js/app.js",
  };

  /**
   * Output
   * Reference: http://webpack.github.io/docs/configuration.html#output
   * Should be an empty object if it's generating a test build
   * Karma will handle setting it up for you when it's a test build
   */
  config.output = isTest
    ? {}
    : {
        // Absolute output directory
        path: __dirname + "/dist",

        // Output path from the view of the page
        // Uses webpack-dev-server in development
        publicPath: "/",

        // Filename for entry points
        // Only adds hash in build mode
        filename: isProd ? "[name].[hash].js" : "[name].bundle.js",

        // Filename for non-entry points
        // Only adds hash in build mode
        chunkFilename: isProd ? "[name].[hash].js" : "[name].bundle.js",
      };

  /**
   * Devtool
   * Reference: http://webpack.github.io/docs/configuration.html#devtool
   * Type of sourcemap to use per build type
   */
  if (isTest) {
    config.devtool = "inline-source-map";
  } else if (isProd) {
    config.devtool = "source-map";
  } else {
    config.devtool = "eval-source-map";
  }

  /**
   * Loaders
   * Reference: http://webpack.github.io/docs/configuration.html#module-loaders
   * List: http://webpack.github.io/docs/list-of-loaders.html
   * This handles most of the magic responsible for converting modules
   */

  // Initialize module
  config.module = {
    rules: [
      {
        // JS LOADER
        // Reference: https://github.com/babel/babel-loader
        // Transpile .js files using babel-loader
        // Compiles ES6 and ES7 into ES5 code
        test: /\.js$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
      {
        // CSS LOADER
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        // ASSET LOADER
        // Reference: https://github.com/webpack/file-loader
        // Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output
        // Rename the file using the asset hash
        // Pass along the updated reference to your code
        // You can add here any file extension you want to get copied to your output
        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
        loader: "file-loader",
      },
      {
        // HTML LOADER
        // Reference: https://github.com/webpack/raw-loader
        // Allow loading html through js
        test: /\.html$/,
        loader: "raw-loader",
      },
    ],
  };

  /**
   * PostCSS
   * Reference: https://github.com/postcss/autoprefixer-core
   * Add vendor prefixes to your css
   */
  // NOTE: This is now handled in the `postcss.config.js`
  //       webpack2 has some issues, making the config file necessary

  /**
   * Plugins
   * Reference: http://webpack.github.io/docs/configuration.html#plugins
   * List: http://webpack.github.io/docs/list-of-plugins.html
   */
  config.plugins = [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin(),
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery",
      sigma: "sigma",
    }),
  ];

  config.optimization = {
    runtimeChunk: {
      name: function (entrypoint) {
        return `runtimechunk~${entrypoint.name}`;
      },
    },
    minimize: false,
  };

  // Skip rendering index.html in test mode
  if (!isTest) {
    // Reference: https://github.com/ampedandwired/html-webpack-plugin
    // Render index.html
    config.plugins.push(
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        inject: "body",
      }),

      // Reference: https://github.com/webpack/extract-text-webpack-plugin
      // Extract css files
      // Disabled when in test mode or not in build mode
      new MiniCssExtractPlugin({
        allChunks: true,
      }),
    );
  }

  // Add build specific plugins
  if (isProd) {
    config.plugins.push(
      // Reference: http://webpack.github.io/docs/list-of-plugins.html#noerrorsplugin
      // Only emit files when there are no errors
      new webpack.NoEmitOnErrorsPlugin(),

      // Copy assets from the public folder
      // Reference: https://github.com/kevlened/copy-webpack-plugin
      new CopyWebpackPlugin({
        patterns: [{ from: __dirname + "/src/public", to: "." }],
      }),
    );
    config.optimization = {
      minimize: true,
    };
  }

  /**
   * Dev server configuration
   * Reference: http://webpack.github.io/docs/configuration.html#devserver
   * Reference: http://webpack.github.io/docs/webpack-dev-server.html
   */
  config.devServer = {
    contentBase: "./src/public",
    stats: "minimal",
    host: "0.0.0.0",
    compress: true,
    disableHostCheck: true,
  };

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


<commit_before>//- JavaScript source code

//- custom.js ~~
//                                                      ~~ (c) SRW, 09 Dec 2013
//                                                  ~~ last updated 22 Dec 2014

(function () {
    'use strict';

 // Pragmas

    /*jshint maxparams: 2, quotmark: single, strict: true */

    /*jslint indent: 4, maxlen: 80, node: true */

    /*properties
        cf_connecting_ip, 'cf-connecting-ip', cf_connecting_ipv6,
        'cf-connecting-ipv6', cf_ipcountry, 'cf-ipcountry', cf_pseudo_ipv4,
        'cf-pseudo-ipv4', cf_ray, 'cf-ray', cf_visitor, 'cf-visitor',
        connection, content_length, 'content-length', dnt, env, from,
        hasOwnProperty, headers, heroku_request_id, host, ip, log, method,
        origin, parse, referer, remoteAddress, replace, split, timestamp, url,
        via, warning, x_att_deviceid, 'x-att-deviceid', x_forwarded_for,
        'x-forwarded-for', x_forwarded_port, 'x-forwarded-port',
        x_forwarded_proto, 'x-forwarded-proto', 'x-request-id',
        x_request_start, 'x-request-start', x_wap_profile, 'x-wap-profile'
    */

 // Module definitions

    exports.log = function (request) {
     // This is a custom logging function that executes once for every request
     // if logging is enabled. This function is optional, however, because
     // QMachine provides a default logging function. I have used a convention
     // in which hyphens are converted into underscores in order to prevent
     // inconveniences with certain tools that attempt to auto-detect schemas
     // from JSON. Also, I have commented out various headers that I don't
     // personally find useful. Ironically, the "Do Not Track" header (DNT) is
     // logged alongside the tracking data because I don't understand the legal
     // stuff yet, but I will delete all personal data from entries that opted
     // out once I figure out which data are considered "personal".
        var headers, y;
        headers = request.headers;
        y = {
            host: headers.host,
            method: request.method,
            timestamp: new Date(),
            url: request.url
        };
        if (headers.hasOwnProperty('cf-connecting-ip')) {
         // See http://goo.gl/81sHn8.
            y.cf_connecting_ip = headers['cf-connecting-ip'];
        }
        if (headers.hasOwnProperty('cf-connecting-ipv6')) {
         // See http://goo.gl/rjQIhh.
            y.cf_connecting_ipv6 = headers['cf-connecting-ipv6'];
        }
        if (headers.hasOwnProperty('cf-ipcountry')) {
         // See http://goo.gl/QpDIBe.
            y.cf_ipcountry = headers['cf-ipcountry'];
        }
        if (headers.hasOwnProperty('cf-pseudo-ipv4')) {
         // See http://goo.gl/rjQIhh.
            y.cf_pseudo_ipv4 = headers['cf-pseudo-ipv4'];
        }
     /*
        if (headers.hasOwnProperty('cf-ray')) {
         // See http://goo.gl/wMfrD8.
            y.cf_ray = headers['cf-ray'];
        }
        if (headers.hasOwnProperty('cf-visitor')) {
         // See http://goo.gl/N7W6wJ.
            y.cf_visitor = JSON.parse(headers['cf-visitor']);
        }
     */
        if (headers.hasOwnProperty('content-length')) {
            y.content_length = parseInt(headers['content-length'], 10);
        }
        if (headers.hasOwnProperty('dnt')) {
         // See http://goo.gl/Rrxu4L.
            y.dnt = parseInt(headers.dnt, 10);
        }
        if (headers.hasOwnProperty('from')) {
         // See http://goo.gl/RNi5So.
            y.from = headers.from;
        }
        if (headers.hasOwnProperty('origin')) {
         // See http://goo.gl/BZldtx.
            y.origin = headers.origin;
        }
        if (headers.hasOwnProperty('referer')) {
         // See http://goo.gl/BCW8Vf.
            y.referer = headers.referer;
        }
        if (headers.hasOwnProperty('via')) {
         // See http://goo.gl/RNi5So.
            y.via = headers.via;
        }
        if (headers.hasOwnProperty('warning')) {
         // See http://goo.gl/RNi5So.
            y.warning = headers.warning;
        }
        if (headers.hasOwnProperty('x-att-deviceid')) {
         // See http://goo.gl/IRuyx.
            y.x_att_deviceid = headers['x-att-deviceid'];
        }
        if (headers.hasOwnProperty('x-forwarded-for')) {
         // See http://goo.gl/ZtqLv1.
            y.ip = headers['x-forwarded-for'].split(',')[0];
        } else {
            y.ip = request.connection.remoteAddress;
        }
     /*
        if (headers.hasOwnProperty('x-forwarded-port')) {
         // See http://goo.gl/B8Ks7h.
            y.x_forwarded_port = headers['x-forwarded-port'];
        }
        if (headers.hasOwnProperty('x-forwarded-proto')) {
         // See http://goo.gl/B8Ks7h.
            y.x_forwarded_proto = headers['x-forwarded-proto'];
        }
     */
        if (headers.hasOwnProperty('x-request-id')) {
         // See http://goo.gl/3xqgw8.
            y.heroku_request_id = headers['x-request-id'];
        }
     /*
        if (headers.hasOwnProperty('x-request-start')) {
         // See http://goo.gl/B8Ks7h.
            y.x_request_start = parseInt(headers['x-request-start'], 10);
        }
     */
        if (headers.hasOwnProperty('x-wap-profile')) {
         // See http://goo.gl/vDg0CV.
            y.x_wap_profile = headers['x-wap-profile'];
        }
        return y;
    };

    exports.parse = function (x) {
     // This function needs documentation.
        return JSON.parse(x, function (key, val) {
         // This function needs documentation.
            /*jslint unparam: true */
            if (typeof val === 'string') {
                return val.replace(/[$][{]([A-Z0-9_]+)[}]/g, function ($0, $1) {
                 // This function needs documentation.
                    return process.env[$1];
                });
            }
            return val;
        });
    };

 // That's all, folks!

    return;

}());

//- vim:set syntax=javascript:
<commit_msg>Cleaned up the custom logging example for the Node.js app<commit_after>//- JavaScript source code

//- custom.js ~~
//                                                      ~~ (c) SRW, 09 Dec 2013
//                                                  ~~ last updated 28 Dec 2014

(function () {
    'use strict';

 // Pragmas

    /*jshint maxparams: 2, quotmark: single, strict: true */

    /*jslint indent: 4, maxlen: 80, node: true */

    /*properties
        connection, content_length, 'content-length', dnt, env, hasOwnProperty,
        headers, host, ip, log, method, parse, remoteAddress, replace, split,
        timestamp, url, 'x-forwarded-for'
    */

 // Module definitions

    exports.log = function (request) {
     // This is a custom logging function that executes once for every request
     // if logging is enabled. This function is optional, however, because
     // QMachine provides a default logging function. I have used a convention
     // in which hyphens are converted into underscores in order to prevent
     // inconveniences with certain tools that attempt to auto-detect schemas
     // from JSON. Ironically, the "Do Not Track" header (DNT) is logged
     // alongside the tracking data because I don't understand the legal stuff
     // yet, but I will delete all personal data from entries that opted out
     // once I figure out which data are considered "personal".
        var headers, y;
        headers = request.headers;
        y = {
            host: headers.host,
            method: request.method,
            timestamp: new Date(),
            url: request.url
        };
        if (headers.hasOwnProperty('content-length')) {
            y.content_length = parseInt(headers['content-length'], 10);
        }
        if (headers.hasOwnProperty('dnt')) {
         // See http://goo.gl/Rrxu4L.
            y.dnt = parseInt(headers.dnt, 10);
        }
        if (headers.hasOwnProperty('x-forwarded-for')) {
         // See http://goo.gl/ZtqLv1.
            y.ip = headers['x-forwarded-for'].split(',')[0];
        } else {
            y.ip = request.connection.remoteAddress;
        }
        return y;
    };

    exports.parse = function (x) {
     // This function needs documentation.
        return JSON.parse(x, function (key, val) {
         // This function needs documentation.
            /*jslint unparam: true */
            if (typeof val === 'string') {
                return val.replace(/[$][{]([A-Z0-9_]+)[}]/g, function ($0, $1) {
                 // This function needs documentation.
                    return process.env[$1];
                });
            }
            return val;
        });
    };

 // That's all, folks!

    return;

}());

//- vim:set syntax=javascript:
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require("webpack");
const childProcess = require('child_process')
// As of v3.0.3 GitRevisionPlugin does not work with MiniCssExtractPlugin
// const GitRevisionPlugin = require('git-revision-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// These steps remove the need for a separate plugin to set the githash
const gitHashLength = 8
const gitHash = childProcess.execSync('git rev-parse --short HEAD').toString().substring(0,gitHashLength);

module.exports = {
  entry: {
      main: './src/index.js',
  },
  output: {
    filename: '[name]-bundle.js',
    path: path.resolve(__dirname, 'dist', gitHash),
    publicPath: path.join('/dist', gitHash, '/'),
  },

  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  resolve: {
    alias: {
      marionette: 'backbone.marionette/lib/backbone.marionette.min',

      'jquery.touchswipe': 'jquery-touchswipe',
      'jquery.ui': 'vendor/jquery/jquery-ui.min', // Revert to old version for testing...
      // 'jquery.ui': 'jquery-ui', // Only required for timepicker-addon 1.5.5 apparently!
      'jquery-ui.timepicker': 'jquery-ui-timepicker-addon', // Need to update timepicker css to avoid showing microseconds/milliseconds
      // Jquery-ui-combox is not the official combobox extension from npm
      // Instead it is a collection of extensions: (https://github.com/bseth99/jquery-ui-extensions)
      'jquery-ui.combobox': 'vendor/jquery/jquery-ui.combobox',

      // Jquery.flot provided by NPM package (exact name match)
      // Jquery.flot.resize also from NPM but slightly older version 1.0.0 2012 instead of 2013 (vendor lib)
      'jquery.flot.resize': 'jquery-flot-resize',
      'jquery.flot.pie': 'flot-pie',
      'jquery.flot.time': 'vendor/flot/jquery.flot.time.min',
      'jquery.flot.selection': 'vendor/flot/jquery.flot.selection',
      'jquery.flot.stack': 'vendor/flot/jquery.flot.stack',

       // Jquery flot tooltip is provided ny NPM with exact name match,
       // so not aliased here, was: 'vendor/flot/jquery.flot.tooltip',
      'jquery.flot.tickrotor': 'vendor/flot/jquery.flot.tickrotor',
      'jquery.flot.axislabels': 'flot-axislabels',

      // We can't currently use the magnific-popup from npm e.g.:
      // 'jquery.mp': 'magnific-popup',
      // The vendor library has been modified to append proposal to the request
      // Hence the vendor magnific-popup has a dependency on our utils/xhrimage
      'jquery.mp': 'vendor/jquery/jquery.magnific-popup',

      // jeditable provided by NPM.
      'jquery.editable': 'jquery-jeditable/dist/jquery.jeditable.min',
      'jquery.editable.datepicker': 'jquery-jeditable/dist/jquery.jeditable.datepicker.min',

      // Jquery.color plugin also NPM package
      'jquery.color': 'jquery-color',

      // Caman npm depends on fibers, canvas, fs which we don't want...
      // So use direct downloaded dependency
      caman: 'vendor/caman.min',

      // heatmap in npm has dependency on canvas/node-gyp... so use old one for now
      heatmap: 'vendor/hmap',

      // gunzip is actually the zlib library
      // https://npm.taobao.org/package/zlibjs
      gzip: 'zlibjs/bin/gunzip.min',

      markdown: 'markdown/lib/markdown',

      highmaps: 'highcharts/highmaps',
      'highmaps-world': '@highcharts/map-collection/custom/world',

      // Vue packages from npm (vee-validate requires promise polyfill - also npm)
      vue: 'vue/dist/vue.min',
      veevalidate: 'vee-validate/dist/vee-validate.min',
    },
    modules: [
      path.resolve(__dirname, 'src/js'),
      path.resolve(__dirname, 'src/css'),
      path.resolve(__dirname, 'node_modules'),
    ]
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: [
          {
            loader: 'underscore-template-loader',
            options: {
              engine: 'underscore',
            }
          }
        ],
        exclude: [
          path.resolve(__dirname, 'src/js/templates/vue')
        ]
      },
      {
        test: /\.xml$/,
        use: [
          {
            loader: 'raw-loader',
          }
        ]
      },
      // Font loader - url should be relative to entry main.scss file
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: '../../assets/fonts', // output path is relative to main module outputPath
            publicPath: '/assets/fonts'
          }
        }
      },
      // SVG could be images or fonts so use more explicit test here...
      {
        test: /font-awesome\/.+\.(svg)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: '../../assets/fonts',
            publicPath: '/assets/fonts'
          }
        }
      },
      {
        test: /\.vue$/,
        use: [
          {
            loader: 'vue-loader',
          }
        ]
      },
      {
        test: /vue\/.+\.html$/,
        use: ['html-loader']
      },
      // We need to help Caman load properly
      // Caman adds to the window object within a browser
      // The import loader ensures it it recognised as browser env not NodeJS
      {
        test: /caman\.min\.js$/,
        use: "imports-loader?exports=>undefined,require=>false,this=>window"
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          // Extract the CSS into separate files if in production
          MiniCssExtractPlugin.loader,
          "css-loader", // translates CSS into CommonJS
          "sass-loader" // compiles Sass to CSS, using Node Sass by default
        ]
      },
      {
        test: /\.(png|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 4096, // Anything less than this limit is inlined
              name: '[path][name].[ext]',
              outputPath: '../../assets',
              publicPath: '/assets',
              context: 'src',
            }
          }
        ]
      }
    ]
  },

  plugins: [
    new webpack.ProvidePlugin({
       $: "jquery",
        jQuery: "jquery",
        _: "underscore",
        "window.jQuery": "jquery",
        Highcharts: "highmaps"
    }),
    // This generates a short (8 char) git hash used for build paths
    // new GitRevisionPlugin({
    //   commithashCommand: 'rev-parse --short HEAD'
    // }),
    // This builds an index.php file from the src template
    new HtmlWebpackPlugin({
      title: 'SynchWeb Webpack',
      filename: path.resolve(__dirname, 'index.php'),
      template: 'src/index.php',
    }),
    // Copy static assets to the assets folder
    // Anything matching in the from path is copied so images/file.png => assets/images/file.png
    new CopyPlugin([
      // { context: path.resolve(__dirname, 'src/css/stylesheets'),
      //   from: 'images/**',
      //   to: path.resolve(__dirname, 'assets') },
      { context: path.resolve(__dirname, 'src'),
        from: 'images/**',
        to: path.resolve(__dirname, 'assets') }
    ]),
    // Ignore all locale files of moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ]
}
<commit_msg>Update webpack to cleaner githash method<commit_after>const path = require('path');
const webpack = require("webpack");
const childProcess = require('child_process')
// As of v3.0.3 GitRevisionPlugin does not work with MiniCssExtractPlugin
// const GitRevisionPlugin = require('git-revision-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// These steps remove the need for a separate plugin to set the githash
const gitHash = childProcess.execSync('git rev-parse --short HEAD').toString().trim();

module.exports = {
  entry: {
      main: './src/index.js',
  },
  output: {
    filename: '[name]-bundle.js',
    path: path.resolve(__dirname, 'dist', gitHash),
    publicPath: path.join('/dist', gitHash, '/'),
  },

  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
  resolve: {
    alias: {
      marionette: 'backbone.marionette/lib/backbone.marionette.min',

      'jquery.touchswipe': 'jquery-touchswipe',
      'jquery.ui': 'vendor/jquery/jquery-ui.min', // Revert to old version for testing...
      // 'jquery.ui': 'jquery-ui', // Only required for timepicker-addon 1.5.5 apparently!
      'jquery-ui.timepicker': 'jquery-ui-timepicker-addon', // Need to update timepicker css to avoid showing microseconds/milliseconds
      // Jquery-ui-combox is not the official combobox extension from npm
      // Instead it is a collection of extensions: (https://github.com/bseth99/jquery-ui-extensions)
      'jquery-ui.combobox': 'vendor/jquery/jquery-ui.combobox',

      // Jquery.flot provided by NPM package (exact name match)
      // Jquery.flot.resize also from NPM but slightly older version 1.0.0 2012 instead of 2013 (vendor lib)
      'jquery.flot.resize': 'jquery-flot-resize',
      'jquery.flot.pie': 'flot-pie',
      'jquery.flot.time': 'vendor/flot/jquery.flot.time.min',
      'jquery.flot.selection': 'vendor/flot/jquery.flot.selection',
      'jquery.flot.stack': 'vendor/flot/jquery.flot.stack',

       // Jquery flot tooltip is provided ny NPM with exact name match,
       // so not aliased here, was: 'vendor/flot/jquery.flot.tooltip',
      'jquery.flot.tickrotor': 'vendor/flot/jquery.flot.tickrotor',
      'jquery.flot.axislabels': 'flot-axislabels',

      // We can't currently use the magnific-popup from npm e.g.:
      // 'jquery.mp': 'magnific-popup',
      // The vendor library has been modified to append proposal to the request
      // Hence the vendor magnific-popup has a dependency on our utils/xhrimage
      'jquery.mp': 'vendor/jquery/jquery.magnific-popup',

      // jeditable provided by NPM.
      'jquery.editable': 'jquery-jeditable/dist/jquery.jeditable.min',
      'jquery.editable.datepicker': 'jquery-jeditable/dist/jquery.jeditable.datepicker.min',

      // Jquery.color plugin also NPM package
      'jquery.color': 'jquery-color',

      // Caman npm depends on fibers, canvas, fs which we don't want...
      // So use direct downloaded dependency
      caman: 'vendor/caman.min',

      // heatmap in npm has dependency on canvas/node-gyp... so use old one for now
      heatmap: 'vendor/hmap',

      // gunzip is actually the zlib library
      // https://npm.taobao.org/package/zlibjs
      gzip: 'zlibjs/bin/gunzip.min',

      markdown: 'markdown/lib/markdown',

      highmaps: 'highcharts/highmaps',
      'highmaps-world': '@highcharts/map-collection/custom/world',

      // Vue packages from npm (vee-validate requires promise polyfill - also npm)
      vue: 'vue/dist/vue.min',
      veevalidate: 'vee-validate/dist/vee-validate.min',
    },
    modules: [
      path.resolve(__dirname, 'src/js'),
      path.resolve(__dirname, 'src/css'),
      path.resolve(__dirname, 'node_modules'),
    ]
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: [
          {
            loader: 'underscore-template-loader',
            options: {
              engine: 'underscore',
            }
          }
        ],
        exclude: [
          path.resolve(__dirname, 'src/js/templates/vue')
        ]
      },
      {
        test: /\.xml$/,
        use: [
          {
            loader: 'raw-loader',
          }
        ]
      },
      // Font loader - url should be relative to entry main.scss file
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: '../../assets/fonts', // output path is relative to main module outputPath
            publicPath: '/assets/fonts'
          }
        }
      },
      // SVG could be images or fonts so use more explicit test here...
      {
        test: /font-awesome\/.+\.(svg)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: '../../assets/fonts',
            publicPath: '/assets/fonts'
          }
        }
      },
      {
        test: /\.vue$/,
        use: [
          {
            loader: 'vue-loader',
          }
        ]
      },
      {
        test: /vue\/.+\.html$/,
        use: ['html-loader']
      },
      // We need to help Caman load properly
      // Caman adds to the window object within a browser
      // The import loader ensures it it recognised as browser env not NodeJS
      {
        test: /caman\.min\.js$/,
        use: "imports-loader?exports=>undefined,require=>false,this=>window"
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          // Extract the CSS into separate files if in production
          MiniCssExtractPlugin.loader,
          "css-loader", // translates CSS into CommonJS
          "sass-loader" // compiles Sass to CSS, using Node Sass by default
        ]
      },
      {
        test: /\.(png|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 4096, // Anything less than this limit is inlined
              name: '[path][name].[ext]',
              outputPath: '../../assets',
              publicPath: '/assets',
              context: 'src',
            }
          }
        ]
      }
    ]
  },

  plugins: [
    new webpack.ProvidePlugin({
       $: "jquery",
        jQuery: "jquery",
        _: "underscore",
        "window.jQuery": "jquery",
        Highcharts: "highmaps"
    }),
    // This generates a short (8 char) git hash used for build paths
    // new GitRevisionPlugin({
    //   commithashCommand: 'rev-parse --short HEAD'
    // }),
    // This builds an index.php file from the src template
    new HtmlWebpackPlugin({
      title: 'SynchWeb Webpack',
      filename: path.resolve(__dirname, 'index.php'),
      template: 'src/index.php',
    }),
    // Copy static assets to the assets folder
    // Anything matching in the from path is copied so images/file.png => assets/images/file.png
    new CopyPlugin([
      // { context: path.resolve(__dirname, 'src/css/stylesheets'),
      //   from: 'images/**',
      //   to: path.resolve(__dirname, 'assets') },
      { context: path.resolve(__dirname, 'src'),
        from: 'images/**',
        to: path.resolve(__dirname, 'assets') }
    ]),
    // Ignore all locale files of moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ]
}
<|endoftext|>


<commit_before>const path = require('path');

module.exports = {
  entry: path.join(__dirname, 'src', 'index.tsx'),

  output: {
    path: path.join(__dirname, "dist"),
    filename: "js/bundle.js",
    libraryTarget: "var",
    library: "EntryPoint"
  },

  // Enable sourcemaps for debugging webpack's output.
  resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],

    modules: [
      path.join(__dirname, 'node_modules'),
      path.join(__dirname, './src')
    ]
  },

  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.js$/, loader: 'babel-loader' },
      {
        test: /\.tsx?$/,
        loader: "awesome-typescript-loader",
        options: {
          configFileName: path.join(__dirname, 'tsconfig.json')
        }
      },
      {
        test: /\.(css|less)$/,
        loader: 'style-loader!css-loader!less-loader?sourceMap'
      },
      { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" },
      { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" },

      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  },

  externals: {
    "react": "React",
    "react-dom": "ReactDOM",
    "jquery": "jQuery"
  }
};

if(process.env.NODE_ENV === "development"){
  //load styles from from git submodule
  module.exports.devtool = "source-map";
}
<commit_msg>capitalize index in webpack config<commit_after>const path = require('path');

module.exports = {
  entry: path.join(__dirname, 'src', 'Index.tsx'),

  output: {
    path: path.join(__dirname, "dist"),
    filename: "js/bundle.js",
    libraryTarget: "var",
    library: "EntryPoint"
  },

  // Enable sourcemaps for debugging webpack's output.
  resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],

    modules: [
      path.join(__dirname, 'node_modules'),
      path.join(__dirname, './src')
    ]
  },

  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.js$/, loader: 'babel-loader' },
      {
        test: /\.tsx?$/,
        loader: "awesome-typescript-loader",
        options: {
          configFileName: path.join(__dirname, 'tsconfig.json')
        }
      },
      {
        test: /\.(css|less)$/,
        loader: 'style-loader!css-loader!less-loader?sourceMap'
      },
      { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" },
      { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" },

      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  },

  externals: {
    "react": "React",
    "react-dom": "ReactDOM",
    "jquery": "jQuery"
  }
};

if(process.env.NODE_ENV === "development"){
  //load styles from from git submodule
  module.exports.devtool = "source-map";
}
<|endoftext|>


<commit_before>const path = require('path');

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
// publicPath = '/build' because index.html requires a ./build/bundle.js
const publicPath = '/build';

module.exports = {
  // context: __dirname + '/src',
  entry: './src/index.jsx',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js',
    publicPath,
  },
  devServer: {
    // contentBase = '~/public/' so that index.html becomes the default html at GET '/'
    contentBase: path.join(__dirname, 'public'),
    inline: true,
    port: 8080,
    historyApiFallback: true,
    // TODO: BLOG: NOTE that if you redirect every route, it will include the React index
    // route, so you won't be served React at all! Instead, proxy every browsers
    // path that is not '/'. But that could be problematic as well because
    // you cannot predict what other routes you are gonna have to manually make
    // into exceptions. Therefore it might be best to group all non-front-end
    // paths by prefixing all backend request by '/api'.
    proxy: {
      /**
       * @todo blog '/api': 'http://localhost:3000' will direct '/api/posts' to
       * 'localhost:3000/api/hosts. To avoid this, use 'pathRewrite'
       * @todo you must add a "/" at the end of the proxy target. Otherwise
       * webpack somehow proxies everything. But where did /posts come from
       * after I deleted everything still?
       */
      '/api/**': {
        target: 'http://[::1]:3000/',
        pathRewrite: { '^/api': '' },
      },
    },
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react'],
        },
      },
      {
        test: /\.css$/,
        loaders: [
          require.resolve('style-loader'),
          require.resolve('css-loader'),
        ],
      },
    ],
  },
  watch: true,
};
<commit_msg>modified webpack.config.js to use updated App.jsx and enable debugging<commit_after>const path = require('path');
const webpack = require('webpack');

// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
// publicPath = '/build' because index.html requires a ./build/bundle.js
const publicPath = '/build';

module.exports = {
  // context: __dirname + '/src',
  entry: './src/App.jsx',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js',
    publicPath,
  },
  devtool: '#eval-source-map',
  devServer: {
    // contentBase = '~/public/' so that index.html becomes the default html at GET '/'
    contentBase: path.join(__dirname, 'public'),
    inline: true,
    port: 8080,
    historyApiFallback: true,
    // TODO: BLOG: NOTE that if you redirect every route, it will include the React index
    // route, so you won't be served React at all! Instead, proxy every browsers
    // path that is not '/'. But that could be problematic as well because
    // you cannot predict what other routes you are gonna have to manually make
    // into exceptions. Therefore it might be best to group all non-front-end
    // paths by prefixing all backend request by '/api'.
    proxy: {
      /**
       * @todo blog '/api': 'http://localhost:3000' will direct '/api/posts' to
       * 'localhost:3000/api/hosts. To avoid this, use 'pathRewrite'
       * @todo you must add a "/" at the end of the proxy target. Otherwise
       * webpack somehow proxies everything. But where did /posts come from
       * after I deleted everything still?
       */
      '/api/**': {
        target: 'http://[::1]:3000/',
        pathRewrite: { '^/api': '' },
      },
    },
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      debug: true,
    }),
  ],
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react'],
        },
      },
      {
        test: /\.css$/,
        loaders: [
          require.resolve('style-loader'),
          require.resolve('css-loader'),
        ],
      },
    ],
  },
  watch: true,
};
<|endoftext|>


<commit_before>
  basis.require('basis.event');
  basis.require('basis.dom');
  basis.require('basis.dom.event');
  basis.require('basis.cssom');
  basis.require('basis.layout');
  basis.require('basis.l10n');
  basis.require('basis.ui');

 /**
  * @see ./demo/defile/popup.html
  * @namespace basis.ui.popup
  */

  var namespace = this.path;

  //
  // import names
  //

  var Class = basis.Class;
  var DOM = basis.dom;
  var Event = basis.dom.event;
  var cssom = basis.cssom;
  var layout = basis.layout;

  var getter = basis.getter;
  var arrayFrom = basis.array.from;
  var createEvent = basis.event.create;

  var UINode = basis.ui.Node;

  //
  // definitions
  //

  var templates = basis.template.define(namespace, {
    Popup: resource('templates/popup/Popup.tmpl'),
    Balloon: resource('templates/popup/Balloon.tmpl'),
    popupManager: resource('templates/popup/popupManager.tmpl')
  });

  //
  // main part
  //

  var LEFT = 'LEFT';
  var RIGHT = 'RIGHT';
  var TOP = 'TOP';
  var BOTTOM = 'BOTTOM';
  var CENTER = 'CENTER';

  var ORIENTATION = {
    VERTICAL: 'V',
    HORIZONTAL: 'H'
  };

  var ROTATE_MATRIX = String('LTRTRBLBLCCTRCCBCCCCCCCC');

  var LETTER_TO_SIDE = {
    L: LEFT,
    R: RIGHT,
    T: TOP,
    B: BOTTOM,
    C: CENTER
  };

  var FLIP = {
    LEFT: RIGHT,
    RIGHT: LEFT,
    TOP: BOTTOM,
    BOTTOM: TOP,
    CENTER: CENTER
  };

  var DEFAULT_DIR = [RIGHT, BOTTOM, RIGHT, TOP].join(' ');
  // all possible correct dir values
  var DIR_MAP = (function(){
    var h = [LEFT, CENTER, RIGHT];
    var v = [TOP, CENTER, BOTTOM];
    var result = {};
    var perm = {};

    for (var i = 0; i < 3; i++)
      for (var j = 0; j < 3; j++)
      {
        perm[h[i] + ' ' + v[j]] = h[i] + ' ' + v[j];
        perm[v[j] + ' ' + h[i]] = h[i] + ' ' + v[j];
      }

    for (var p1 in perm)
      for (var p2 in perm)
        result[p1 + ' ' + p2] = perm[p1] + ' ' + perm[p2];

    return result;
  })();

  function normalizeDir(value, valueOnFailure){
    return DIR_MAP[typeof value == 'string' && value.toUpperCase()] || valueOnFailure;
  }

 /**
  * @class
  */
  var Popup = Class(UINode, {
    className: namespace + '.Popup',

    template: templates.Popup,
    binding: {
      visible: {
        events: 'show hide',
        getter: function(node){
          return node.visible ? 'visible' : 'hidden';
        }
      },
      orientation: {
        events: 'layoutChanged',
        getter: function(node){
          return (node.orientation + '-' + node.dir.qw().slice(2, 4).join('-')).toLowerCase();
        }
      }
    },

    action: {
      hide: function(){
        this.hide();
      }
    },

    emit_beforeShow: createEvent('beforeShow'),
    emit_show: createEvent('show'),
    emit_hide: createEvent('hide'),
    emit_realign: createEvent('realign'),
    emit_layoutChanged: createEvent('layoutChanged', 'oldOrientation', 'oldDir'),

    visible: false,
    autorotate: false,

    dir: '',
    defaultDir: DEFAULT_DIR,
    orientation: ORIENTATION.VERTICAL,

    hideOnAnyClick: true,
    hideOnKey: false,
    hideOnScroll: true,
    ignoreClickFor: null,

    init: function(){
      UINode.prototype.init.call(this);

      this.ignoreClickFor = arrayFrom(this.ignoreClickFor);

      if (this.dir)
      {
        this.dir = normalizeDir(this.dir, DEFAULT_DIR);
        this.defaultDir = this.dir;
      }
      else
      {
        this.defaultDir = normalizeDir(this.defaultDir, DEFAULT_DIR);
        this.dir = this.defaultDir;
      }

      this.setLayout(this.defaultDir, this.orientation);
    },
    templateSync: function(noRecreate){
      UINode.prototype.templateSync.call(this, noRecreate);

      if (this.element)
        this.realign();
    },
    setLayout: function(dir, orientation, noRealign){
      var oldDir = this.dir;
      var oldOrientation = this.orientation;

      this.dir = normalizeDir(dir, this.dir);

      if (typeof orientation == 'string')
        this.orientation = orientation;

      if (oldDir != this.dir || oldOrientation != this.orientation)
      {
        this.emit_layoutChanged(oldOrientation, oldDir);
        if (!noRealign)
          this.realign();
      }
    },
    flip: function(orientation){
      var dir = this.dir.qw();
      var v = orientation == ORIENTATION.VERTICAL;

      dir[0 + v] = FLIP[dir[0 + v]];
      dir[2 + v] = FLIP[dir[2 + v]];

      this.setLayout(dir.join(' '));
    },
    rotate: function(offset){
      var dir = this.dir.qw();
      var result = [];

      offset = ((offset % 4) + 4) % 4;

      if (!offset)
        return dir;

      // point
      var a = dir[0].charAt(0);
      var b = dir[1].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;
      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      // direction
      var a = dir[2].charAt(0);
      var b = dir[3].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      offset = a != 'C' && b != 'C' && (dir[0] == dir[2]) != (dir[1] == dir[3]) ? -offset + 4 : offset;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;
      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      return result;
    },
    isFitToViewport: function(dir){
      if (this.visible && this.relElement)
      {
        var box = new layout.Box(this.relElement, false, this.element.offsetParent);
        var viewport = new layout.Viewport(this.element.offsetParent);
        var width = this.element.offsetWidth;
        var height = this.element.offsetHeight;

        dir = normalizeDir(dir, this.dir).qw();

        var pointX = dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()];
        var pointY = dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()];

        if (
            (dir[2] != LEFT) * (pointX < (width >> (dir[2] == CENTER)))
            ||
            (dir[2] != RIGHT) * ((viewport.width - pointX) < (width >> (dir[2] == CENTER)))
            ||
            (dir[3] != TOP) * (pointY < (height >> (dir[3] == CENTER)))
            ||
            (dir[3] != BOTTOM) * ((viewport.height - pointY) < (height >> (dir[3] == CENTER)))
           )
          return false;

        return {
          x: pointX,
          y: pointY
        };
      }
    },
    setZIndex: function(zIndex){
      this.zIndex = isNaN(zIndex) ? 'auto' : zIndex;
      cssom.setStyle(this.element, {
        'z-index': zIndex
      });
    },
    realign: function(){
      this.setZIndex(this.zIndex);

      if (this.visible && this.relElement)
      {
        var dir = this.dir.qw();
        var point;
        var rotateOffset = 0;
        var curDir = dir;
        var dirH = dir[2];
        var dirV = dir[3];
        var maxRotate = typeof this.autorotate == 'number' || !this.autorotate.length ? 3 : this.autorotate.length;

        while (this.autorotate && rotateOffset <= maxRotate)
        {
          if (point = this.isFitToViewport(curDir.join(' ')))
          {
            dirH = curDir[2];
            dirV = curDir[3];
            this.setLayout(curDir.join(' '), null, true);
            break;
          }

          if (rotateOffset == maxRotate)
            break;

          if (Array.isArray(this.autorotate))
          {
            var rotate = this.autorotate[rotateOffset++];

            if (typeof rotate == 'string')
              curDir = normalizeDir(rotate, curDir.join(' ')).split(' ');
            else
              curDir = this.rotate(rotate);
          }
          else
            curDir = this.rotate(++rotateOffset * this.autorotate);
        }

        if (!point)
        {
          var box = new layout.Box(this.relElement, false, this.element.offsetParent);

          point = {
            x: dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()],
            y: dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()]
          };
        }

        var offsetParentBox = new layout.Box(this.element.offsetParent);
        var style = {
          left: 'auto',
          right: 'auto',
          top: 'auto',
          bottom: 'auto'
        };

        switch (dirH)
        {
          case LEFT:
            style.left = point.x + 'px';
            break;
          case CENTER:
            style.left = Math.round(point.x - this.element.offsetWidth/2) + 'px';
            break;
          case RIGHT:
            style.right = (offsetParentBox.width - point.x) + 'px';
            break;
        }

        switch (dirV)
        {
          case TOP:
            style.top = point.y + 'px';
            break;
          case CENTER:
            style.top = Math.round(point.y - this.element.offsetHeight/2) + 'px';
            break;
          case BOTTOM:
            style.bottom = (offsetParentBox.height - point.y) + 'px';
            break;
        }

        cssom.setStyle(this.element, style);

        this.emit_realign();
      }
    },
    show: function(relElement, dir, orientation){
      // assign new offset element
      this.relElement = DOM.get(relElement) || this.relElement;

      // set up direction and orientation
      this.setLayout(normalizeDir(dir, this.defaultDir), orientation);

      // if not visible yet, make popup visible
      if (!this.visible)
      {
        // error on relElement no assigned
        if (!this.relElement)
        {
          ;;;basis.dev.warn('Popup#show(): relElement missed');
          return;
        }

        // make element invisible & insert element into DOM
        cssom.visibility(this.element, false);

        popupManager.appendChild(this);

        // dispatch `beforeShow` event, there we can fill popup with content
        this.emit_beforeShow();

        // set visible flag
        this.visible = true;

        // realign position and make it visible
        this.realign();

        cssom.visibility(this.element, true);

        // dispatch `show` event, there we can set focus for elements etc.
        this.emit_show();
      }
      else
        this.realign();
    },
    hide: function(){
      if (this.visible)
      {
        // set visible flag
        this.visible = false;

        if (this.parentNode)
          popupManager.removeChild(this);

        // dispatch event
        this.emit_hide();
      }
    },
    hideAll: function(){
      popupManager.clear();
    },
    destroy: function(){
      this.hide();

      UINode.prototype.destroy.call(this);
    }
  });

 /**
  * @class
  */
  var Balloon = Class(Popup, {
    className: namespace + '.Balloon',

    template: templates.Balloon
  });

  //
  //  Popup manager
  //

  // NOTE: popupManager adds global event handlers dynamically because click event
  // which makes popup visible can also hide it (as click outside of popup).

  var popupManager = new UINode({
    template: templates.popupManager,

    selection: true,

    emit_childNodesModified: function(delta){
      if (delta.deleted)
        for (var i = delta.deleted.length - 1, item; item = delta.deleted[i]; i--)
          item.hide();

      if (delta.inserted && !delta.deleted && this.childNodes.length == delta.inserted.length)
      {
        Event.addGlobalHandler('click', this.hideByClick, this);
        Event.addGlobalHandler('keydown', this.hideByKey, this);
        Event.addGlobalHandler('scroll', this.hideByScroll, this);
        Event.addHandler(window, 'resize', this.realignAll, this);
      }

      if (this.lastChild)
        this.lastChild.select();
      else
      {
        Event.removeGlobalHandler('click', this.hideByClick, this);
        Event.removeGlobalHandler('keydown', this.hideByKey, this);
        Event.removeGlobalHandler('scroll', this.hideByScroll, this);
        Event.removeHandler(window, 'resize', this.realignAll, this);
      }

      UINode.prototype.emit_childNodesModified.call(this, delta);
    },

    insertBefore: function(newChild, refChild){
      if (UINode.prototype.insertBefore.call(this,newChild, refChild))
        newChild.setZIndex(basis.ui.window ? basis.ui.window.getWindowTopZIndex() : 2001);
    },
    removeChild: function(popup){
      if (popup)
      {
        if (popup.hideOnAnyClick && popup.nextSibling)
          this.removeChild(popup.nextSibling);

        UINode.prototype.removeChild.call(this, popup);
      }
    },
    realignAll: function(){
      for (var popup = this.firstChild; popup; popup = popup.nextSibling)
        popup.realign();
    },
    clear: function(){
      if (this.firstChild)
        this.removeChild(this.firstChild);
    },
    hideByClick: function(event){
      if (!this.firstChild)
        return;

      var sender = Event.sender(event);
      var ancestorAxis = DOM.axis(sender, DOM.AXIS_ANCESTOR_OR_SELF);

      for (var popup = this.lastChild; popup; popup = popup.previousSibling)
      {
        if (ancestorAxis.has(popup.element) || ancestorAxis.some(Array.prototype.has, popup.ignoreClickFor))
        {
          while (popup = popup.nextSibling)
          {
            if (popup.hideOnAnyClick)
            {
              this.removeChild(popup);
              break;
            }
          }

          return;
        }
      }

      // remove first hideOnAnyClick:true popup
      this.removeChild(this.getChild(true, 'hideOnAnyClick'));
    },
    hideByKey: function(event){
      var key = Event.key(event);
      var popup = this.lastChild;
      if (popup && popup.hideOnKey)
      {
        var result = false;

        if (typeof popup.hideOnKey == 'function')
          result = popup.hideOnKey(key);
        else
          if (Array.isArray(popup.hideOnKey))
            result = popup.hideOnKey.has(key);

        if (result)
          popup.hide();
      }
    },
    hideByScroll: function(event){
      var sender = Event.sender(event);

      if (DOM.parentOf(sender, this.element))
        return;

      var popup = this.lastChild;
      while (popup)
      {
        var next = popup.previousSibling;
        if (popup.hideOnScroll && popup.relElement && popup.offsetParent !== sender && DOM.parentOf(sender, popup.relElement))
          popup.hide();
        popup = next;
      }
    }
  });

  basis.ready(function(){
    DOM.insert(document.body, popupManager.element, DOM.INSERT_BEGIN);
    popupManager.realignAll();
  });

  //
  // export names
  //

  module.exports = {
    // const
    ORIENTATION: ORIENTATION,
    DIR: {
      LEFT: LEFT,
      RIGHT: RIGHT,
      TOP: TOP,
      BOTTOM: BOTTOM,
      CENTER: CENTER
    },

    // classes
    Popup: Popup,
    Balloon: Balloon
  };
<commit_msg>reduce Event.* calls<commit_after>
  basis.require('basis.event');
  basis.require('basis.dom');
  basis.require('basis.dom.event');
  basis.require('basis.cssom');
  basis.require('basis.layout');
  basis.require('basis.l10n');
  basis.require('basis.ui');

 /**
  * @see ./demo/defile/popup.html
  * @namespace basis.ui.popup
  */

  var namespace = this.path;

  //
  // import names
  //

  var Class = basis.Class;
  var DOM = basis.dom;
  var Event = basis.dom.event;
  var cssom = basis.cssom;
  var layout = basis.layout;

  var getter = basis.getter;
  var arrayFrom = basis.array.from;
  var createEvent = basis.event.create;

  var UINode = basis.ui.Node;

  //
  // definitions
  //

  var templates = basis.template.define(namespace, {
    Popup: resource('templates/popup/Popup.tmpl'),
    Balloon: resource('templates/popup/Balloon.tmpl'),
    popupManager: resource('templates/popup/popupManager.tmpl')
  });

  //
  // main part
  //

  var LEFT = 'LEFT';
  var RIGHT = 'RIGHT';
  var TOP = 'TOP';
  var BOTTOM = 'BOTTOM';
  var CENTER = 'CENTER';

  var ORIENTATION = {
    VERTICAL: 'V',
    HORIZONTAL: 'H'
  };

  var ROTATE_MATRIX = String('LTRTRBLBLCCTRCCBCCCCCCCC');

  var LETTER_TO_SIDE = {
    L: LEFT,
    R: RIGHT,
    T: TOP,
    B: BOTTOM,
    C: CENTER
  };

  var FLIP = {
    LEFT: RIGHT,
    RIGHT: LEFT,
    TOP: BOTTOM,
    BOTTOM: TOP,
    CENTER: CENTER
  };

  var DEFAULT_DIR = [RIGHT, BOTTOM, RIGHT, TOP].join(' ');
  // all possible correct dir values
  var DIR_MAP = (function(){
    var h = [LEFT, CENTER, RIGHT];
    var v = [TOP, CENTER, BOTTOM];
    var result = {};
    var perm = {};

    for (var i = 0; i < 3; i++)
      for (var j = 0; j < 3; j++)
      {
        perm[h[i] + ' ' + v[j]] = h[i] + ' ' + v[j];
        perm[v[j] + ' ' + h[i]] = h[i] + ' ' + v[j];
      }

    for (var p1 in perm)
      for (var p2 in perm)
        result[p1 + ' ' + p2] = perm[p1] + ' ' + perm[p2];

    return result;
  })();

  function normalizeDir(value, valueOnFailure){
    return DIR_MAP[typeof value == 'string' && value.toUpperCase()] || valueOnFailure;
  }

 /**
  * @class
  */
  var Popup = Class(UINode, {
    className: namespace + '.Popup',

    template: templates.Popup,
    binding: {
      visible: {
        events: 'show hide',
        getter: function(node){
          return node.visible ? 'visible' : 'hidden';
        }
      },
      orientation: {
        events: 'layoutChanged',
        getter: function(node){
          return (node.orientation + '-' + node.dir.qw().slice(2, 4).join('-')).toLowerCase();
        }
      }
    },

    action: {
      hide: function(){
        this.hide();
      }
    },

    emit_beforeShow: createEvent('beforeShow'),
    emit_show: createEvent('show'),
    emit_hide: createEvent('hide'),
    emit_realign: createEvent('realign'),
    emit_layoutChanged: createEvent('layoutChanged', 'oldOrientation', 'oldDir'),

    visible: false,
    autorotate: false,

    dir: '',
    defaultDir: DEFAULT_DIR,
    orientation: ORIENTATION.VERTICAL,

    hideOnAnyClick: true,
    hideOnKey: false,
    hideOnScroll: true,
    ignoreClickFor: null,

    init: function(){
      UINode.prototype.init.call(this);

      this.ignoreClickFor = arrayFrom(this.ignoreClickFor);

      if (this.dir)
      {
        this.dir = normalizeDir(this.dir, DEFAULT_DIR);
        this.defaultDir = this.dir;
      }
      else
      {
        this.defaultDir = normalizeDir(this.defaultDir, DEFAULT_DIR);
        this.dir = this.defaultDir;
      }

      this.setLayout(this.defaultDir, this.orientation);
    },
    templateSync: function(noRecreate){
      UINode.prototype.templateSync.call(this, noRecreate);

      if (this.element)
        this.realign();
    },
    setLayout: function(dir, orientation, noRealign){
      var oldDir = this.dir;
      var oldOrientation = this.orientation;

      this.dir = normalizeDir(dir, this.dir);

      if (typeof orientation == 'string')
        this.orientation = orientation;

      if (oldDir != this.dir || oldOrientation != this.orientation)
      {
        this.emit_layoutChanged(oldOrientation, oldDir);
        if (!noRealign)
          this.realign();
      }
    },
    flip: function(orientation){
      var dir = this.dir.qw();
      var v = orientation == ORIENTATION.VERTICAL;

      dir[0 + v] = FLIP[dir[0 + v]];
      dir[2 + v] = FLIP[dir[2 + v]];

      this.setLayout(dir.join(' '));
    },
    rotate: function(offset){
      var dir = this.dir.qw();
      var result = [];

      offset = ((offset % 4) + 4) % 4;

      if (!offset)
        return dir;

      // point
      var a = dir[0].charAt(0);
      var b = dir[1].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;
      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      // direction
      var a = dir[2].charAt(0);
      var b = dir[3].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      offset = a != 'C' && b != 'C' && (dir[0] == dir[2]) != (dir[1] == dir[3]) ? -offset + 4 : offset;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;
      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      return result;
    },
    isFitToViewport: function(dir){
      if (this.visible && this.relElement)
      {
        var box = new layout.Box(this.relElement, false, this.element.offsetParent);
        var viewport = new layout.Viewport(this.element.offsetParent);
        var width = this.element.offsetWidth;
        var height = this.element.offsetHeight;

        dir = normalizeDir(dir, this.dir).qw();

        var pointX = dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()];
        var pointY = dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()];

        if (
            (dir[2] != LEFT) * (pointX < (width >> (dir[2] == CENTER)))
            ||
            (dir[2] != RIGHT) * ((viewport.width - pointX) < (width >> (dir[2] == CENTER)))
            ||
            (dir[3] != TOP) * (pointY < (height >> (dir[3] == CENTER)))
            ||
            (dir[3] != BOTTOM) * ((viewport.height - pointY) < (height >> (dir[3] == CENTER)))
           )
          return false;

        return {
          x: pointX,
          y: pointY
        };
      }
    },
    setZIndex: function(zIndex){
      this.zIndex = isNaN(zIndex) ? 'auto' : zIndex;
      cssom.setStyle(this.element, {
        'z-index': zIndex
      });
    },
    realign: function(){
      this.setZIndex(this.zIndex);

      if (this.visible && this.relElement)
      {
        var dir = this.dir.qw();
        var point;
        var rotateOffset = 0;
        var curDir = dir;
        var dirH = dir[2];
        var dirV = dir[3];
        var maxRotate = typeof this.autorotate == 'number' || !this.autorotate.length ? 3 : this.autorotate.length;

        while (this.autorotate && rotateOffset <= maxRotate)
        {
          if (point = this.isFitToViewport(curDir.join(' ')))
          {
            dirH = curDir[2];
            dirV = curDir[3];
            this.setLayout(curDir.join(' '), null, true);
            break;
          }

          if (rotateOffset == maxRotate)
            break;

          if (Array.isArray(this.autorotate))
          {
            var rotate = this.autorotate[rotateOffset++];

            if (typeof rotate == 'string')
              curDir = normalizeDir(rotate, curDir.join(' ')).split(' ');
            else
              curDir = this.rotate(rotate);
          }
          else
            curDir = this.rotate(++rotateOffset * this.autorotate);
        }

        if (!point)
        {
          var box = new layout.Box(this.relElement, false, this.element.offsetParent);

          point = {
            x: dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()],
            y: dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()]
          };
        }

        var offsetParentBox = new layout.Box(this.element.offsetParent);
        var style = {
          left: 'auto',
          right: 'auto',
          top: 'auto',
          bottom: 'auto'
        };

        switch (dirH)
        {
          case LEFT:
            style.left = point.x + 'px';
            break;
          case CENTER:
            style.left = Math.round(point.x - this.element.offsetWidth/2) + 'px';
            break;
          case RIGHT:
            style.right = (offsetParentBox.width - point.x) + 'px';
            break;
        }

        switch (dirV)
        {
          case TOP:
            style.top = point.y + 'px';
            break;
          case CENTER:
            style.top = Math.round(point.y - this.element.offsetHeight/2) + 'px';
            break;
          case BOTTOM:
            style.bottom = (offsetParentBox.height - point.y) + 'px';
            break;
        }

        cssom.setStyle(this.element, style);

        this.emit_realign();
      }
    },
    show: function(relElement, dir, orientation){
      // assign new offset element
      this.relElement = DOM.get(relElement) || this.relElement;

      // set up direction and orientation
      this.setLayout(normalizeDir(dir, this.defaultDir), orientation);

      // if not visible yet, make popup visible
      if (!this.visible)
      {
        // error on relElement no assigned
        if (!this.relElement)
        {
          ;;;basis.dev.warn('Popup#show(): relElement missed');
          return;
        }

        // make element invisible & insert element into DOM
        cssom.visibility(this.element, false);

        popupManager.appendChild(this);

        // dispatch `beforeShow` event, there we can fill popup with content
        this.emit_beforeShow();

        // set visible flag
        this.visible = true;

        // realign position and make it visible
        this.realign();

        cssom.visibility(this.element, true);

        // dispatch `show` event, there we can set focus for elements etc.
        this.emit_show();
      }
      else
        this.realign();
    },
    hide: function(){
      if (this.visible)
      {
        // set visible flag
        this.visible = false;

        if (this.parentNode)
          popupManager.removeChild(this);

        // dispatch event
        this.emit_hide();
      }
    },
    hideAll: function(){
      popupManager.clear();
    },
    destroy: function(){
      this.hide();

      UINode.prototype.destroy.call(this);
    }
  });

 /**
  * @class
  */
  var Balloon = Class(Popup, {
    className: namespace + '.Balloon',

    template: templates.Balloon
  });

  //
  //  Popup manager
  //

  // NOTE: popupManager adds global event handlers dynamically because click event
  // which makes popup visible can also hide it (as click outside of popup).

  var popupManager = new UINode({
    template: templates.popupManager,

    selection: true,

    emit_childNodesModified: function(delta){
      if (delta.deleted)
        for (var i = delta.deleted.length - 1, item; item = delta.deleted[i]; i--)
          item.hide();

      if (delta.inserted && !delta.deleted && this.childNodes.length == delta.inserted.length)
      {
        Event.addGlobalHandler('click', this.hideByClick, this);
        Event.addGlobalHandler('keydown', this.hideByKey, this);
        Event.addGlobalHandler('scroll', this.hideByScroll, this);
        Event.addHandler(window, 'resize', this.realignAll, this);
      }

      if (this.lastChild)
        this.lastChild.select();
      else
      {
        Event.removeGlobalHandler('click', this.hideByClick, this);
        Event.removeGlobalHandler('keydown', this.hideByKey, this);
        Event.removeGlobalHandler('scroll', this.hideByScroll, this);
        Event.removeHandler(window, 'resize', this.realignAll, this);
      }

      UINode.prototype.emit_childNodesModified.call(this, delta);
    },

    insertBefore: function(newChild, refChild){
      if (UINode.prototype.insertBefore.call(this,newChild, refChild))
        newChild.setZIndex(basis.ui.window ? basis.ui.window.getWindowTopZIndex() : 2001);
    },
    removeChild: function(popup){
      if (popup)
      {
        if (popup.hideOnAnyClick && popup.nextSibling)
          this.removeChild(popup.nextSibling);

        UINode.prototype.removeChild.call(this, popup);
      }
    },
    realignAll: function(){
      for (var popup = this.firstChild; popup; popup = popup.nextSibling)
        popup.realign();
    },
    clear: function(){
      if (this.firstChild)
        this.removeChild(this.firstChild);
    },
    hideByClick: function(event){
      if (!this.firstChild)
        return;

      var ancestorAxis = DOM.axis(event.sender, DOM.AXIS_ANCESTOR_OR_SELF);

      for (var popup = this.lastChild; popup; popup = popup.previousSibling)
      {
        if (ancestorAxis.has(popup.element) || ancestorAxis.some(Array.prototype.has, popup.ignoreClickFor))
        {
          while (popup = popup.nextSibling)
          {
            if (popup.hideOnAnyClick)
            {
              this.removeChild(popup);
              break;
            }
          }

          return;
        }
      }

      // remove first hideOnAnyClick:true popup
      this.removeChild(this.getChild(true, 'hideOnAnyClick'));
    },
    hideByKey: function(event){
      var popup = this.lastChild;
      if (popup && popup.hideOnKey)
      {
        var result = false;

        if (typeof popup.hideOnKey == 'function')
          result = popup.hideOnKey(event.key);
        else
          if (Array.isArray(popup.hideOnKey))
            result = popup.hideOnKey.has(event.key);

        if (result)
          popup.hide();
      }
    },
    hideByScroll: function(event){
      var sender = event.sender;

      if (DOM.parentOf(sender, this.element))
        return;

      var popup = this.lastChild;
      while (popup)
      {
        var next = popup.previousSibling;
        if (popup.hideOnScroll && popup.relElement && popup.offsetParent !== sender && DOM.parentOf(sender, popup.relElement))
          popup.hide();
        popup = next;
      }
    }
  });

  basis.ready(function(){
    DOM.insert(document.body, popupManager.element, DOM.INSERT_BEGIN);
    popupManager.realignAll();
  });

  //
  // export names
  //

  module.exports = {
    // const
    ORIENTATION: ORIENTATION,
    DIR: {
      LEFT: LEFT,
      RIGHT: RIGHT,
      TOP: TOP,
      BOTTOM: BOTTOM,
      CENTER: CENTER
    },

    // classes
    Popup: Popup,
    Balloon: Balloon
  };
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
// Media Data
//
// Based on Flock (https://github.com/wwidd/flock).
// Until the server stores data in a NoSQL way, the client
// has to build the NoSQL cache in this class. This makes page initialization
// on high media and tag count a bit slow (2-3s on netbooks, tablets).
// Once SQLite is ditched on the server, the JSON can come unaltered and
// there would be no need to use Flock for building the cache.
////////////////////////////////////////////////////////////////////////////////
/*global jOrder, flock, escape */
var app = app || {};

app.data = function (data, jOrder, flock, services) {
	// initializing cache
	data.cache = data.cache || flock();

	// checks whether an object has any properties
	function isEmpty(obj) {
		var key;
		for (key in obj) {
			if (obj.hasOwnProperty(key)) {
				return false;
			}
		}
		return true;
	}

	// extracts filename from path
	function splitPath(path) {
		var bits = path.split('/');
		return {
			file: bits.pop()
		};
	}

	// preprocesses video metadata
	function preprocess(json) {
		var i, row, fileInfo;
		for (i = 0; i < json.length; i++) {
			row = json[i];
			fileInfo = splitPath(row.path);
			row.file = fileInfo.file;
			row.lfile = fileInfo.file.toLowerCase();
		}
		return json;
	}

	data.media = function () {
		var

		// FIFO stack search results
		stack = [{
			term: '',
			data: {}
		}],

		self = {
			stack: function () {
				return stack;
			},

			// initializes data object: calls service, populates cache
			init: function (handler) {
				handler = handler || function () {};

				// calling service
				services.media.get('', function (json) {
					json = preprocess(json.data);

					var cache = data.cache,
							i, row,
							j, tag,
							tags,
							table;

					// setting up datastore roots
					cache.set('tag', {});
					cache.set('media', {});
					cache.set('keyword', {});
					cache.set('kind', {});
					cache.set('name', {});
					cache.set('rating', {});
					cache.set('field', {});
					cache.set('search', {});

					// loading media data into cache
					for (i = 0; i < json.length; i++) {
						row = json[i];		// quick reference
						tags = row.tags;	// backup of flat tags
						row.tags = {};		// tags field will be rewritten

						// storing media node in cache
						cache.set(['media', row.mediaid], row);

						// setting properties
						self.setRating(row);
						for (j = 0; j < tags.length; j++) {
							self.addTag(row, tags[j]);
						}
					}

					// setting up jOrder table
					// required for paging
					table = jOrder(preprocess(json))
						.index('pager', ['lfile'], {ordered: true, grouped: true, type: jOrder.string});

					// initializing stack
					stack = [{
						term: '',
						data: {
							cache: cache,
							table: table
						}
					}];

					handler();
				});
				return self;
			},

			// filters current state further
			// - expression: filter expression, comma-separated
			// returns a flag indicating if the result set changed
			filter: function (expression) {
				if (!expression.length) {
					stack.splice(0, stack.length - 1);
					return true;
				}

				var terms = (',' + expression).split(','), term,
						tstack = jOrder.shallow(stack).reverse(),
						tags, matches, hits,
						i,
						result = false;

				// stack can't be longer than terms
				if (stack.length > terms.length) {
					stack.splice(0, stack.length - terms.length);
					result = true;
				}

				// skipping already processed search terms
				skipper:
				for (i = 1; i < terms.length; i++) {
					if (i >= stack.length) {
						// stack is shorter than terms, continuing below
						result = true;
						break skipper;
					} else if (terms[i] !== tstack[i].term) {
						// cutting stack at first non-matching term
						stack.splice(0, stack.length - i);
						result = true;
						break skipper;
					}
				}

				for (; i < terms.length; i++) {
					// taking next term
					term = terms[i];

					// tags matching the entered string
					tags = data.cache.multiget(['search'].concat(term.toLowerCase().split('')).concat(['', 'tag', 'tag']));

					// ids of media entries having one or more of the matched tags
					matches = data.cache.multiget(['tag', tags, 'media', '*'], {mode: flock.keys});

					// intersection of tag matches and previous hits
					hits = stack[0].data.cache.multiget(['media', matches], {mode: flock.both});

					// adding search hits to stack
					stack.unshift({
						term: term,
						data: {
							cache: flock({
								media: hits
							}),
							table: jOrder(jOrder.values(hits))
								.index('pager', ['lfile'], {ordered: true, grouped: true, type: jOrder.string})
						}
					});
				}

				return result;
			},

			// retrieves a reference to the data associated with a media entry
			getRow: function (mediaid) {
				return data.cache.get(['media', mediaid]) || {};
			},

			// adds a tag to media entry
			addTag: function (row, tag) {
				var cache = data.cache,
						mediaid = row.mediaid,
						ref, tmp,
						path_media_tag = ['media', mediaid, 'tags', tag],
						path_tag = ['tag', tag];

				if (cache.get(path_media_tag) !== tag) {
					// creating new tag
					ref = cache.get(path_tag);
					if (typeof ref === 'undefined') {
						tmp = tag.split(':');
						ref = {
							tag: tag,
							name: tmp[0],
							kind: tmp[1],
							media: {},
							count: 0
						};
						cache.set(path_tag, ref);
						cache.set(['name', tmp[0], tmp[1]], ref);
						cache.set(['kind', tmp[1], tmp[0]], ref);
						cache.set(['search'].concat(tag.toLowerCase().split('').concat(['tag'])), ref);
					}

					// setting references
					cache.set(path_media_tag, ref);
					ref.media[mediaid] = row;
					ref.count = ref.count + 1;

					// tag was added
					return true;
				} else {
					// tag was not added
					return false;
				}
			},

			// removes tag from media entry
			removeTag: function (mediaid, tag) {
				var cache = data.cache,
						tmp;

				// removing tag from medium if present
				if (typeof cache.get(['media', mediaid, 'tags', tag]) !== 'undefined') {
					// removing direct references
					cache.unset(['media', mediaid, 'tags', tag]);
					cache.unset(['tag', tag, 'media', mediaid]);
					cache.set(['tag', tag, 'count'], cache.get(['tag', tag, 'count']) - 1);

					// removing tag altogether
					if (isEmpty(cache.get(['tag', tag, 'media']))) {
						tmp = tag.split(':');
						cache.unset(['tag', tag]);
						cache.unset(['name', tmp[0], tmp[1]]);
						cache.unset(['kind', tmp[1], tmp[0]]);
						cache.unset(['search'].concat(tag.toLowerCase().split('')).concat(['tag']));

						// removing name altogether
						if (isEmpty(cache.get(['name', tmp[0]]))) {
							cache.unset(['name', tmp[0]]);
						}
						// removing kind altogether
						if (isEmpty(cache.get(['kind', tmp[1]]))) {
							cache.unset(['kind', tmp[1]]);
						}
					}

					// tag was removed
					return true;
				} else {
					// tag was not removed
					return false;
				}
			},

			// sets rating on media entry
			setRating: function (row, rating) {
				var cache = data.cache,
						mediaid = row.mediaid,
						rate_path = ['media', mediaid, 'rating'],
						current = cache.get(rate_path);

				// new rating value
				rating = rating || row.rating;

				if (rating !== current.rating) {
					// on media node
					cache.set(rate_path, rating);

					// rating lookup
					cache.unset(['rating', current.rating, mediaid]);
					cache.set(['rating', rating, mediaid], row);

					// rating was changed
					return true;
				} else {
					// rating was not changed
					return false;
				}
			},

			// retrieves one page from the table
			getPage: function (page, items) {
				var table = stack[0].data.table;
				return table ?
					table.orderby(['lfile'], jOrder.asc, {offset: page * items, limit: items, renumber: true}) :
					[];
			},

			// returns first row of page
			getFirst: function (page, items) {
				var table = stack[0].data.table;
				return table ?
					table.orderby(['lfile'], jOrder.asc, {offset: page * items, limit: 1, renumber: true}) :
					[{}];
			},

			// returns total number of pages in dataset
			getPages: function (items) {
				var table = stack[0].data.table;
				return table ?
					Math.ceil(table.count() / items) :
					0;
			},

			// updates hashes for media entries
			// - diffs: object containing fields to update, indexed by media id
			//	 format: {mediaid: media record}}
			// should be called 'merge'
			update: function (diffs) {
				var cache = data.cache,
						mediaid, diff,
						key;

				for (mediaid in diffs) {
					if (diffs.hasOwnProperty(mediaid) &&
							typeof cache.get(['media', mediaid]) !== 'undefined') {
						// it is possible that the mediaid being polled is not loaded ATM
						diff = diffs[mediaid];
						for (key in diff) {
							if (diff.hasOwnProperty(key)) {
								// WARNING: diff[key] can be object or array,
								// requiring cross-references to be created
								cache.set(['media', mediaid, key], diff[key]);
							}
						}
					}
				}

				return self;
			}
		};

		return self;
	}();

	return data;
}(app.data || {},
	jOrder,
	flock,
	app.services);

<commit_msg>performance opt. for first search term<commit_after>////////////////////////////////////////////////////////////////////////////////
// Media Data
//
// Based on Flock (https://github.com/wwidd/flock).
// Until the server stores data in a NoSQL way, the client
// has to build the NoSQL cache in this class. This makes page initialization
// on high media and tag count a bit slow (2-3s on netbooks, tablets).
// Once SQLite is ditched on the server, the JSON can come unaltered and
// there would be no need to use Flock for building the cache.
////////////////////////////////////////////////////////////////////////////////
/*global jOrder, flock, escape */
var app = app || {};

app.data = function (data, jOrder, flock, services) {
	// initializing cache
	data.cache = data.cache || flock();

	// checks whether an object has any properties
	function isEmpty(obj) {
		var key;
		for (key in obj) {
			if (obj.hasOwnProperty(key)) {
				return false;
			}
		}
		return true;
	}

	// extracts filename from path
	function splitPath(path) {
		var bits = path.split('/');
		return {
			file: bits.pop()
		};
	}

	// preprocesses video metadata
	function preprocess(json) {
		var i, row, fileInfo;
		for (i = 0; i < json.length; i++) {
			row = json[i];
			fileInfo = splitPath(row.path);
			row.file = fileInfo.file;
			row.lfile = fileInfo.file.toLowerCase();
		}
		return json;
	}

	data.media = function () {
		var

		// FIFO stack search results
		stack = [{
			term: '',
			data: {}
		}],

		self = {
			stack: function () {
				return stack;
			},

			// initializes data object: calls service, populates cache
			init: function (handler) {
				handler = handler || function () {};

				// calling service
				services.media.get('', function (json) {
					json = preprocess(json.data);

					var cache = data.cache,
							i, row,
							j, tag,
							tags,
							table;

					// setting up datastore roots
					cache.set('tag', {});
					cache.set('media', {});
					cache.set('keyword', {});
					cache.set('kind', {});
					cache.set('name', {});
					cache.set('rating', {});
					cache.set('field', {});
					cache.set('search', {});

					// loading media data into cache
					for (i = 0; i < json.length; i++) {
						row = json[i];		// quick reference
						tags = row.tags;	// backup of flat tags
						row.tags = {};		// tags field will be rewritten

						// storing media node in cache
						cache.set(['media', row.mediaid], row);

						// setting properties
						self.setRating(row);
						for (j = 0; j < tags.length; j++) {
							self.addTag(row, tags[j]);
						}
					}

					// setting up jOrder table
					// required for paging
					table = jOrder(preprocess(json))
						.index('pager', ['lfile'], {ordered: true, grouped: true, type: jOrder.string});

					// initializing stack
					stack = [{
						term: '',
						data: {
							cache: cache,
							table: table
						}
					}];

					handler();
				});
				return self;
			},

			// filters current state further
			// - expression: filter expression, comma-separated
			// returns a flag indicating if the result set changed
			filter: function (expression) {
				if (!expression.length) {
					stack.splice(0, stack.length - 1);
					return true;
				}

				var terms = (',' + expression).split(','), term,
						tstack = jOrder.shallow(stack).reverse(),
						tags, matches, hits,
						i,
						result = false;

				// stack can't be longer than terms
				if (stack.length > terms.length) {
					stack.splice(0, stack.length - terms.length);
					result = true;
				}

				// skipping already processed search terms
				skipper:
				for (i = 1; i < terms.length; i++) {
					if (i >= stack.length) {
						// stack is shorter than terms, continuing below
						result = true;
						break skipper;
					} else if (terms[i] !== tstack[i].term) {
						// cutting stack at first non-matching term
						stack.splice(0, stack.length - i);
						result = true;
						break skipper;
					}
				}

				// processing terms that differ from what's already on the stack
				for (; i < terms.length; i++) {
					// taking next term
					term = terms[i];

					// acquiring tags matching the entered string
					tags = data.cache.multiget(['search'].concat(term.toLowerCase().split('')).concat(['', 'tag', 'tag']));

					// acquiring search hits
					if (stack.length === 1) {
						// performing direct query on first search term
						hits = data.cache.multiget(['tag', tags, 'media', '*'], {mode: flock.both});
					} else {
						// performing two-stage query on subsequent terms
						// first, taking _all_ media ids where tags match, then
						// matching them against previous search results
						matches = data.cache.multiget(['tag', tags, 'media', '*'], {mode: flock.keys});
						hits = stack[0].data.cache.multiget(['media', matches], {mode: flock.both});
					}

					// adding search hits to stack
					stack.unshift({
						term: term,
						data: {
							cache: flock({
								media: hits
							}),
							table: jOrder(jOrder.values(hits))
								.index('pager', ['lfile'], {ordered: true, grouped: true, type: jOrder.string})
						}
					});
				}

				return result;
			},

			// retrieves a reference to the data associated with a media entry
			getRow: function (mediaid) {
				return data.cache.get(['media', mediaid]) || {};
			},

			// adds a tag to media entry
			addTag: function (row, tag) {
				var cache = data.cache,
						mediaid = row.mediaid,
						ref, tmp,
						path_media_tag = ['media', mediaid, 'tags', tag],
						path_tag = ['tag', tag];

				if (cache.get(path_media_tag) !== tag) {
					// creating new tag
					ref = cache.get(path_tag);
					if (typeof ref === 'undefined') {
						tmp = tag.split(':');
						ref = {
							tag: tag,
							name: tmp[0],
							kind: tmp[1],
							media: {},
							count: 0
						};
						cache.set(path_tag, ref);
						cache.set(['name', tmp[0], tmp[1]], ref);
						cache.set(['kind', tmp[1], tmp[0]], ref);
						cache.set(['search'].concat(tag.toLowerCase().split('').concat(['tag'])), ref);
					}

					// setting references
					cache.set(path_media_tag, ref);
					ref.media[mediaid] = row;
					ref.count = ref.count + 1;

					// tag was added
					return true;
				} else {
					// tag was not added
					return false;
				}
			},

			// removes tag from media entry
			removeTag: function (mediaid, tag) {
				var cache = data.cache,
						tmp;

				// removing tag from medium if present
				if (typeof cache.get(['media', mediaid, 'tags', tag]) !== 'undefined') {
					// removing direct references
					cache.unset(['media', mediaid, 'tags', tag]);
					cache.unset(['tag', tag, 'media', mediaid]);
					cache.set(['tag', tag, 'count'], cache.get(['tag', tag, 'count']) - 1);

					// removing tag altogether
					if (isEmpty(cache.get(['tag', tag, 'media']))) {
						tmp = tag.split(':');
						cache.unset(['tag', tag]);
						cache.unset(['name', tmp[0], tmp[1]]);
						cache.unset(['kind', tmp[1], tmp[0]]);
						cache.unset(['search'].concat(tag.toLowerCase().split('')).concat(['tag']));

						// removing name altogether
						if (isEmpty(cache.get(['name', tmp[0]]))) {
							cache.unset(['name', tmp[0]]);
						}
						// removing kind altogether
						if (isEmpty(cache.get(['kind', tmp[1]]))) {
							cache.unset(['kind', tmp[1]]);
						}
					}

					// tag was removed
					return true;
				} else {
					// tag was not removed
					return false;
				}
			},

			// sets rating on media entry
			setRating: function (row, rating) {
				var cache = data.cache,
						mediaid = row.mediaid,
						rate_path = ['media', mediaid, 'rating'],
						current = cache.get(rate_path);

				// new rating value
				rating = rating || row.rating;

				if (rating !== current.rating) {
					// on media node
					cache.set(rate_path, rating);

					// rating lookup
					cache.unset(['rating', current.rating, mediaid]);
					cache.set(['rating', rating, mediaid], row);

					// rating was changed
					return true;
				} else {
					// rating was not changed
					return false;
				}
			},

			// retrieves one page from the table
			getPage: function (page, items) {
				var table = stack[0].data.table;
				return table ?
					table.orderby(['lfile'], jOrder.asc, {offset: page * items, limit: items, renumber: true}) :
					[];
			},

			// returns first row of page
			getFirst: function (page, items) {
				var table = stack[0].data.table;
				return table ?
					table.orderby(['lfile'], jOrder.asc, {offset: page * items, limit: 1, renumber: true}) :
					[{}];
			},

			// returns total number of pages in dataset
			getPages: function (items) {
				var table = stack[0].data.table;
				return table ?
					Math.ceil(table.count() / items) :
					0;
			},

			// updates hashes for media entries
			// - diffs: object containing fields to update, indexed by media id
			//	 format: {mediaid: media record}}
			// should be called 'merge'
			update: function (diffs) {
				var cache = data.cache,
						mediaid, diff,
						key;

				for (mediaid in diffs) {
					if (diffs.hasOwnProperty(mediaid) &&
							typeof cache.get(['media', mediaid]) !== 'undefined') {
						// it is possible that the mediaid being polled is not loaded ATM
						diff = diffs[mediaid];
						for (key in diff) {
							if (diff.hasOwnProperty(key)) {
								// WARNING: diff[key] can be object or array,
								// requiring cross-references to be created
								cache.set(['media', mediaid, key], diff[key]);
							}
						}
					}
				}

				return self;
			}
		};

		return self;
	}();

	return data;
}(app.data || {},
	jOrder,
	flock,
	app.services);

<|endoftext|>


<commit_before>/*globals require, module */

'use strict';

var eventify, events;

eventify = require('./eventify');
events = require('./events');

module.exports = streamify;

/**
 * Public function `streamify`.
 *
 * Asynchronously serialises a data structure into a stream of JSON
 * data. Sanely handles promises, dates, maps and other iterables.
 *
 * @param data:        The data to transform
 *
 * @option promises:   'resolve' or 'ignore', default is 'resolve'.
 *
 * @option dates:      'toJSON' or 'ignore', default is 'toJSON'.
 *
 * @option maps:       'object', or 'ignore', default is 'object'.
 *
 * @option iterables:  'array', or 'ignore', default is 'array'.
 *
 * @option debug:      Log debug messages to the console.
 **/

function streamify (data, options) {
    var stream, emitter, json, ended;

    // TODO: options.buffers, options.replacer, options.space

    stream = new JsonStream(push);
    emitter = eventify(data, options);
    json = '';

    emitter.on(events.array, array);
    emitter.on(events.object, object);
    emitter.on(events.property, property);
    emitter.on(events.string, string);
    emitter.on(events.number, value);
    emitter.on(events.literal, value);
    emitter.on(events.endArray, endArray);
    emitter.on(events.endObject, endObject);
    emitter.on(events.end, end);

    return stream;

    function push () {
        if (ended) {
            return stream.push(null);
        }

        stream.push(json, 'utf8');
        json = '';
    }

    // TODO: Work out how to do commas sanely

    function array () {
        json += '[';
    }

    function object () {
        json += '{';
    }

    function property (name) {
        json += '"' + name + '":';
    }

    function string (s) {
        value('"' + s + '"');
    }

    function value (v) {
        json += v;
    }

    function endArray () {
        json += ']';
    }

    function endObject () {
        json += '}';
    }

    function end () {
        ended = true;
    }
}

// HACK: What follows is only here temporarily, until #5 is resolved.
var util, Readable, check;

util = require('util');
Readable = require('stream').Readable;
check = require('check-types');

util.inherits(JsonStream, Readable);

function JsonStream (read) {
    // https://nodejs.org/api/stream.html#stream_class_stream_readable_1

    if (check.not.instance(this, JsonStream)) {
        return new JsonStream(read);
    }

    check.assert.function(read, 'Invalid read implementation');

    this._read = function () {
        // TODO: Check it is okay to ignore size argument

        read();
    };

    return Readable.call(this, { encoding: 'utf8' });
}

<commit_msg>Comma insertion.<commit_after>/*globals require, module */

'use strict';

var eventify, events;

eventify = require('./eventify');
events = require('./events');

module.exports = streamify;

/**
 * Public function `streamify`.
 *
 * Asynchronously serialises a data structure into a stream of JSON
 * data. Sanely handles promises, dates, maps and other iterables.
 *
 * @param data:        The data to transform
 *
 * @option promises:   'resolve' or 'ignore', default is 'resolve'.
 *
 * @option dates:      'toJSON' or 'ignore', default is 'toJSON'.
 *
 * @option maps:       'object', or 'ignore', default is 'object'.
 *
 * @option iterables:  'array', or 'ignore', default is 'array'.
 *
 * @option debug:      Log debug messages to the console.
 **/

function streamify (data, options) {
    var stream, emitter, json, isBegun, isEnded, isProperty;

    // TODO: options.buffers, options.replacer, options.space

    stream = new JsonStream(push);
    emitter = eventify(data, options);
    json = '';

    emitter.on(events.array, array);
    emitter.on(events.object, object);
    emitter.on(events.property, property);
    emitter.on(events.string, string);
    emitter.on(events.number, value);
    emitter.on(events.literal, value);
    emitter.on(events.endArray, endArray);
    emitter.on(events.endObject, endObject);
    emitter.on(events.end, end);

    return stream;

    function push () {
        if (isEnded) {
            return stream.push(null);
        }

        stream.push(json, 'utf8');
        json = '';
    }

    function array () {
        begin();
        json += '[';
    }

    function begin () {
        if (isBegun) {
            if (isProperty) {
                isProperty = false;
            } else {
                json += ',';
            }
        } else {
            isBegun = true;
        }
    }

    function object () {
        begin();
        json += '{';
    }

    function property (name) {
        begin();
        json += '"' + name + '":';
        isProperty = true;
    }

    function string (s) {
        value('"' + s + '"');
    }

    function value (v) {
        begin();
        json += v;
    }

    function endArray () {
        json += ']';
    }

    function endObject () {
        json += '}';
    }

    function end () {
        isEnded = true;
    }
}

// HACK: What follows is only here temporarily, until #5 is resolved.
var util, Readable, check;

util = require('util');
Readable = require('stream').Readable;
check = require('check-types');

util.inherits(JsonStream, Readable);

function JsonStream (read) {
    // https://nodejs.org/api/stream.html#stream_class_stream_readable_1

    if (check.not.instance(this, JsonStream)) {
        return new JsonStream(read);
    }

    check.assert.function(read, 'Invalid read implementation');

    this._read = function () {
        // TODO: Check it is okay to ignore size argument

        read();
    };

    return Readable.call(this, { encoding: 'utf8' });
}

<|endoftext|>