<commit_before>'use strict';
import Webpack from 'webpack';
import Merge from 'webpack-merge';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HTMLWebpackPlugin from 'html-webpack-plugin';
import DotEnv from 'dotenv-webpack';
import Path from 'path';
import PostCSS from './postcss.config';

// const appEnv = (process.env.NODE_ENV || 'dev');
const isProdEnv = (process.env.NODE_ENV === 'production');
  console.log(`Node Environment:\t${process.env.NODE_ENV}`);

const VENDOR_LIBS = [
  'body-parser',
  'bson',
  'cloudinary',
  'cloudinary-react',
  'cloudinary_js',
  // 'dotenv-webpack',
  'jquery',
  'lodash',
  'react',
  'react-dom',
  'react-motion',
  'react-redux',
  'react-router',
  'react-router-redux',
  'redux',
  'redux-thunk',
  // 'request',
  'uuid'
  // 'webpack-merge'
];

// 'react-hot-loader/patch', 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server'

// patch: 'react-hot-loader/patch',
// client: 'webpack-dev-server/client?http://localhost:3000',
// hmr: 'webpack/hot/only-dev-server',

// bundle: Path.resolve(__dirname, 'src/App'),
// context: Path.resolve(__dirname, 'src'),

const BASE_CONFIG = {
  entry: {
    patch: 'react-hot-loader/patch',
    hmr: 'webpack/hot/only-dev-server',
    bundle: Path.resolve(__dirname, 'src/App'),
    vendor: VENDOR_LIBS
  },
  output: {
    path: Path.join(__dirname, 'dist'),
    filename: '[name].[hash].js',
    // publicPath: '/' // Server-Relative
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/i,
        include: Path.resolve(__dirname, 'src'),
        exclude: /(node_modules|bower_components)/,
        use: 'babel'
      }, {
        test: /\.css$/i,
        loader: ExtractTextPlugin.extract({
          fallback: 'style',
          use: 'css'
        })
      }, {
        test: /\.(scss|sass)$/i,
        loaders: ExtractTextPlugin.extract({
          fallback: 'style',
          use: ['css', 'postcss', 'sass?outputStyle=expanded']
        })
      }, {
        test: /\.json$/i,
        use: 'json'
      }, {
        test: /\.(bmp|gif|jpe?g|png|svg|ttif)$/i,
        use: [
          // {
          //   loader: 'url',
          //   options: { limit: 40000 }
          // },
          'file?name=/images/[name].[ext]',
          'image-webpack'
        ]
      }, {
        test: /\.(pdf|doc[mstx]?|ppt[mstx]?|od[fpst])$/i,
        use: 'file?name=/docs/[name].[ext]'
      }
    ]
  },
  plugins: [
    // Configure and read in local environment variables:
    new DotEnv({
      path: './.env'
    }),
    new Webpack.optimize.CommonsChunkPlugin({
      names: ['vendor', 'manifest'],
      minChunks: Infinity
    }),
    new Webpack.DefinePlugin({
      // 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')

      // 'process.env': {
      //   NODE_ENV: JSON.stringify(process.env.NODE_ENV)
      // }
    }),
    new Webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
      options: {
        postcss: { PostCSS }
      }
    }),
    new ExtractTextPlugin('styles.css'),
    new HTMLWebpackPlugin({
      template: 'assets/index.html'
    })
  ],
  node: {
    console: false,
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  },
  cache: true,
  watch: true,
  devtool: `${isProdEnv ? 'cheap-eval' : 'inline'}-source-map`,
  resolve: {
    extensions: ['.js', '.jsx']
  },
  resolveLoader: {
    moduleExtensions: ['-loader']
  }
};

const DEV_SERVER = {
  plugins: [
    new Webpack.HotModuleReplacementPlugin(),
    new Webpack.NamedModulesPlugin()
  ],
  devServer: {
    contentBase: Path.resolve(__dirname, 'dist'),
    historyApiFallback: true,
    host: 'localhost',
    hot: true,
    inline: true,
    noInfo: false,
    port: 3000,
    publicPath: '/'
  },
  stats: {
    assets: true,
    children: false,
    chunks: false,
    colors: {
      bold: '\u001b[1m',
      cyan: '\u001b[1m\u001b[36m',
      green: '\u001b[1m\u001b[32m',
      magenta: '\u001b[1m\u001b[35m',
      red: '\u001b[1m\u001b[31m',
      yellow: '\u001b[1m\u001b[33m'
    },
    errorDetails: true,
    hash: false,
    modules: false,
    publicPath: false,
    reasons: true,
    timings: true,
    version: false,
    warnings: true
  }
};

const AGGREGATE_CONFIG = isProdEnv
  ? BASE_CONFIG
  : Merge(BASE_CONFIG, DEV_SERVER);

export default AGGREGATE_CONFIG;

//     new Webpack.optimize.DedupePlugin(),
//     new Webpack.optimize.UglifyJsPlugin({
//       compress: { warnings: false },
//       output: { comments: false },
//       sourceMap: false
//     })
<commit_msg>fix (Webpack): Change devtool to address Chrome's sourceMap parsing warning<commit_after>'use strict';
import Webpack from 'webpack';
import Merge from 'webpack-merge';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HTMLWebpackPlugin from 'html-webpack-plugin';
import DotEnv from 'dotenv-webpack';
import Path from 'path';
import PostCSS from './postcss.config';

// const appEnv = (process.env.NODE_ENV || 'dev');
const isProdEnv = (process.env.NODE_ENV === 'production');
  console.log(`Node Environment:\t${process.env.NODE_ENV}`);

const VENDOR_LIBS = [
  'body-parser',
  'bson',
  'cloudinary',
  'cloudinary-react',
  'cloudinary_js',
  // 'dotenv-webpack',
  'jquery',
  'lodash',
  'react',
  'react-dom',
  'react-motion',
  'react-redux',
  'react-router',
  'react-router-redux',
  'redux',
  'redux-thunk',
  // 'request',
  'uuid'
  // 'webpack-merge'
];

// 'react-hot-loader/patch', 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server'

// patch: 'react-hot-loader/patch',
// client: 'webpack-dev-server/client?http://localhost:3000',
// hmr: 'webpack/hot/only-dev-server',

// bundle: Path.resolve(__dirname, 'src/App'),
// context: Path.resolve(__dirname, 'src'),

const BASE_CONFIG = {
  entry: {
    patch: 'react-hot-loader/patch',
    hmr: 'webpack/hot/only-dev-server',
    bundle: Path.resolve(__dirname, 'src/App'),
    vendor: VENDOR_LIBS
  },
  output: {
    path: Path.join(__dirname, 'dist'),
    filename: '[name].[hash].js',
    // publicPath: '/' // Server-Relative
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/i,
        include: Path.resolve(__dirname, 'src'),
        exclude: /(node_modules|bower_components)/,
        use: 'babel'
      }, {
        test: /\.css$/i,
        loader: ExtractTextPlugin.extract({
          fallback: 'style',
          use: 'css'
        })
      }, {
        test: /\.(scss|sass)$/i,
        loaders: ExtractTextPlugin.extract({
          fallback: 'style',
          use: ['css', 'postcss', 'sass?outputStyle=expanded']
        })
      }, {
        test: /\.json$/i,
        use: 'json'
      }, {
        test: /\.(bmp|gif|jpe?g|png|svg|ttif)$/i,
        use: [
          // {
          //   loader: 'url',
          //   options: { limit: 40000 }
          // },
          'file?name=/images/[name].[ext]',
          'image-webpack'
        ]
      }, {
        test: /\.(pdf|doc[mstx]?|ppt[mstx]?|od[fpst])$/i,
        use: 'file?name=/docs/[name].[ext]'
      }
    ]
  },
  plugins: [
    // Configure and read in local environment variables:
    new DotEnv({
      path: './.env'
    }),
    new Webpack.optimize.CommonsChunkPlugin({
      names: ['vendor', 'manifest'],
      minChunks: Infinity
    }),
    new Webpack.DefinePlugin({
      // 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')

      // 'process.env': {
      //   NODE_ENV: JSON.stringify(process.env.NODE_ENV)
      // }
    }),
    new Webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
      options: {
        postcss: { PostCSS }
      }
    }),
    new ExtractTextPlugin('styles.css'),
    new HTMLWebpackPlugin({
      template: 'assets/index.html'
    })
  ],
  node: {
    console: false,
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  },
  cache: true,
  watch: true,
  devtool: `cheap-${isProdEnv ? '' : 'module'}-source-map`,
  resolve: {
    extensions: ['.js', '.jsx']
  },
  resolveLoader: {
    moduleExtensions: ['-loader']
  }
};

const DEV_SERVER = {
  plugins: [
    new Webpack.HotModuleReplacementPlugin(),
    new Webpack.NamedModulesPlugin()
  ],
  devServer: {
    contentBase: Path.resolve(__dirname, 'dist'),
    historyApiFallback: true,
    host: 'localhost',
    hot: true,
    inline: true,
    noInfo: false,
    port: 3000,
    publicPath: '/'
  },
  stats: {
    assets: true,
    children: false,
    chunks: false,
    colors: {
      bold: '\u001b[1m',
      cyan: '\u001b[1m\u001b[36m',
      green: '\u001b[1m\u001b[32m',
      magenta: '\u001b[1m\u001b[35m',
      red: '\u001b[1m\u001b[31m',
      yellow: '\u001b[1m\u001b[33m'
    },
    errorDetails: true,
    hash: false,
    modules: false,
    publicPath: false,
    reasons: true,
    timings: true,
    version: false,
    warnings: true
  }
};

const AGGREGATE_CONFIG = isProdEnv
  ? BASE_CONFIG
  : Merge(BASE_CONFIG, DEV_SERVER);

export default AGGREGATE_CONFIG;

//     new Webpack.optimize.DedupePlugin(),
//     new Webpack.optimize.UglifyJsPlugin({
//       compress: { warnings: false },
//       output: { comments: false },
//       sourceMap: false
//     })
<|endoftext|>


<commit_before>import ancestors from 'ancestors'
import contains from 'dom-contains'
import indexof from 'indexof'

const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
const E_WHERE = 'Argument 2 of seek must be a number or a Text Node.'

const SHOW_TEXT = 4
const TEXT_NODE = 3

const DOCUMENT_POSITION_PRECEDING = 2
const DOCUMENT_POSITION_FOLLOWING = 4

export default function seek(iter, where) {
  if (iter.whatToShow !== SHOW_TEXT) {
    throw new Error(E_SHOW);
  }

  let count = 0;
  let node = iter.referenceNode;
  let predicates = null;

  if (isNumber(where)) {
    predicates = {
      forward: () => count < where,
      backward: () => count > where
    };
  } else if (isText(where)) {
    debugger
    let forward = before(node, where) ? () => false : () => node !== where
    let backward = () => node != where || !iter.pointerBeforeReferenceNode
    predicates = {forward, backward}
  } else {
    throw new Error(E_WHERE);
  }

  while (predicates.forward() && (node = iter.nextNode()) !== null) {
    count += node.nodeValue.length;
  }

  while (predicates.backward() && (node = iter.previousNode()) !== null) {
    count -= node.nodeValue.length;
  }

  return count;
}

function isNumber(n) {
  return !isNaN(parseInt(n)) && isFinite(n);
}

function isText(node) {
  return node.nodeType === TEXT_NODE;
}

function before(ref, node) {
  if (ref === node) return false

  let common = null
  let left = [ref].concat(ancestors(ref)).reverse()
  let right = [node].concat(ancestors(node)).reverse()

  while (left[0] === right[0]) {
    common = left.shift()
    right.shift()
  }

  left = left[0]
  right = right[0]

  if (left == null) return false
  if (right == null) return true

  let l = indexof(common.childNodes, left)
  let r = indexof(common.childNodes, right)
  return l > r
}
<commit_msg>Remove debugging statement<commit_after>import ancestors from 'ancestors'
import contains from 'dom-contains'
import indexof from 'indexof'

const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
const E_WHERE = 'Argument 2 of seek must be a number or a Text Node.'

const SHOW_TEXT = 4
const TEXT_NODE = 3

const DOCUMENT_POSITION_PRECEDING = 2
const DOCUMENT_POSITION_FOLLOWING = 4

export default function seek(iter, where) {
  if (iter.whatToShow !== SHOW_TEXT) {
    throw new Error(E_SHOW);
  }

  let count = 0;
  let node = iter.referenceNode;
  let predicates = null;

  if (isNumber(where)) {
    predicates = {
      forward: () => count < where,
      backward: () => count > where
    };
  } else if (isText(where)) {
    let forward = before(node, where) ? () => false : () => node !== where
    let backward = () => node != where || !iter.pointerBeforeReferenceNode
    predicates = {forward, backward}
  } else {
    throw new Error(E_WHERE);
  }

  while (predicates.forward() && (node = iter.nextNode()) !== null) {
    count += node.nodeValue.length;
  }

  while (predicates.backward() && (node = iter.previousNode()) !== null) {
    count -= node.nodeValue.length;
  }

  return count;
}

function isNumber(n) {
  return !isNaN(parseInt(n)) && isFinite(n);
}

function isText(node) {
  return node.nodeType === TEXT_NODE;
}

function before(ref, node) {
  if (ref === node) return false

  let common = null
  let left = [ref].concat(ancestors(ref)).reverse()
  let right = [node].concat(ancestors(node)).reverse()

  while (left[0] === right[0]) {
    common = left.shift()
    right.shift()
  }

  left = left[0]
  right = right[0]

  if (left == null) return false
  if (right == null) return true

  let l = indexof(common.childNodes, left)
  let r = indexof(common.childNodes, right)
  return l > r
}
<|endoftext|>


<commit_before>import path from 'path'
import webpack from 'webpack'
import DashboardPlugin from 'webpack-dashboard/plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import HtmlWebpackHarddiskPlugin from 'html-webpack-harddisk-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import SpritesmithPlugin from 'webpack-spritesmith'
import PrepackWebpackPlugin from 'prepack-webpack-plugin'
import {exec} from 'child_process'

const [isDev, isProd] = [
  process.env.NODE_ENV === 'development',
  process.env.NODE_ENV === 'production'
]

exec('rm build/*')

const extractLESS = new ExtractTextPlugin(isDev ? '[name].css' : '[name]_[chunkhash:5].css')

const plugins = [
  new webpack.BannerPlugin([
    '硕鼠宝',
    'https://shuoshubao.github.io/'
  ].join('\n')),
  new webpack.DefinePlugin({
    ENV: JSON.stringify(isDev ? 'dev' : 'prod')
  }),
  new webpack.EnvironmentPlugin(['NODE_ENV']),
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    React: 'react',
    ReactDom: 'react-dom',
    PropTypes: 'prop-types',
    classnames: 'classnames'
  }),
  extractLESS,
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  new HtmlWebpackPlugin({
    alwaysWriteToDisk: true,
    filename: '../index.html',
    template: 'template/index.ejs',
    title: 'WEB前端开发🐿',
    chunks: ['manifest', 'vendor', 'app'],
    minify: {
      useShortDoctype: true,
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true,
      minifyCSS: true,
      removeScriptTypeAttributes: true,
      removeStyleTypeAttributes: true,
      sortAttributes: true,
      sortClassName: true,
      keepClosingSlash: false
    },
    ENV: isDev ? 'dev' : 'prod'
  }),
  new HtmlWebpackHarddiskPlugin(),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module => module.context && module.context.includes('node_modules')
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest'
  }),
  new SpritesmithPlugin({
    src: {
      cwd: path.resolve(__dirname, 'spriteImgSrc'),
      glob: '*.png'
    },
    target: {
      image: path.resolve(__dirname, 'spriteImgTarget/sprite.png'),
      css: path.resolve(__dirname, 'spriteImgTarget/sprite.less')
    },
    apiOptions: {
      cssImageRef: 'sprite.png'
    }
  }),
  // new DashboardPlugin()
]

if(isDev) {
  plugins.push(new webpack.HotModuleReplacementPlugin())
}
if(isProd) {
  plugins.push(...[
    new PrepackWebpackPlugin(),
    new webpack.optimize.UglifyJsPlugin()
  ])
}

const webpackConfig = {
  entry: {
    vendor: ['react', 'react-dom', 'prop-types', 'classnames'],
    app: './asset/app'
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    // publicPath: isProd ? 'https://orn2bxyo7.bkt.clouddn.com/' : '/build/',
    // publicPath: isProd ? 'https://shuoshubao.github.io/build/' : '/build/',
    publicPath: '/build/',
    jsonpFunction: 'webpackJsonp',
    filename: isDev ? '[name].js' : '[name]_[chunkhash:5].js'
  },
  module: {
    rules: [
      {
        test: /\.png|gif|jpg/,
        use: [
          {
            loader: 'url-loader',
            query: {
              limit: 1024,
              name: isDev ? '[name].[ext]' : '[name]_[chunkhash:5].[ext]'
            }
          }
        ]
      },
      {
        test: /\.t[e]?xt$/,
        use: ['raw-loader']
      },
      {
        test: /\.vtpl$/,
        use: ['raw-loader']
      },
      {
        test: /\.ejs/,
        loader: 'ejs-loader'
      },
      {
        test: /\.tpl/,
        loader: 'ejs-loader'
      },
      {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: extractLESS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                modules: true,
                localIdentName: '[local]_[hash:5]',
                minimize: isProd
              }
            },
            {
              loader: 'less-loader'
            }
          ]
        })
      },
      {
        test: /\.styl$/,
        loader: ['style-loader', 'css-loader', 'autoprefixer-loader', 'stylus-loader']
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          plugins: [
            'transform-object-assign',
            'transform-decorators-legacy'
          ],
          presets: ['es2015', 'stage-2', 'react']
        }
      }
    ]
  },
  plugins,
  resolve: {
    modules: ['node_modules', 'spriteImgTarget'],
    extensions: ['.js', '.jsx', '.json'],
    mainFields: ['browser', 'main'],
    alias: {
      style: path.join(__dirname, 'style'),
      component: path.join(__dirname, 'component')
    }
  },
  externals: {

  }
}

if(isDev) {
  webpackConfig.devtool = 'source-map'
  webpackConfig.devServer = {
    inline: true,
    hot: true,
    port: 9090,
    publicPath: '/build/',
    filename: '[name]_[hash].js',
    historyApiFallback: true,
    proxy: {

    }
  }
}

module.exports = webpackConfig
<commit_msg>webpack.ProvidePlugin moment<commit_after>import path from 'path'
import webpack from 'webpack'
import DashboardPlugin from 'webpack-dashboard/plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import HtmlWebpackHarddiskPlugin from 'html-webpack-harddisk-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import SpritesmithPlugin from 'webpack-spritesmith'
import PrepackWebpackPlugin from 'prepack-webpack-plugin'
import {exec} from 'child_process'

const [isDev, isProd] = [
  process.env.NODE_ENV === 'development',
  process.env.NODE_ENV === 'production'
]

exec('rm build/*')

const extractLESS = new ExtractTextPlugin(isDev ? '[name].css' : '[name]_[chunkhash:5].css')

const plugins = [
  new webpack.BannerPlugin([
    '硕鼠宝',
    'https://shuoshubao.github.io/'
  ].join('\n')),
  new webpack.DefinePlugin({
    ENV: JSON.stringify(isDev ? 'dev' : 'prod')
  }),
  new webpack.EnvironmentPlugin(['NODE_ENV']),
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    React: 'react',
    ReactDom: 'react-dom',
    PropTypes: 'prop-types',
    classnames: 'classnames',
    moment: 'moment'
  }),
  extractLESS,
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  new HtmlWebpackPlugin({
    alwaysWriteToDisk: true,
    filename: '../index.html',
    template: 'template/index.ejs',
    title: 'WEB前端开发🐿',
    chunks: ['manifest', 'vendor', 'app'],
    minify: {
      useShortDoctype: true,
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true,
      minifyCSS: true,
      removeScriptTypeAttributes: true,
      removeStyleTypeAttributes: true,
      sortAttributes: true,
      sortClassName: true,
      keepClosingSlash: false
    },
    ENV: isDev ? 'dev' : 'prod'
  }),
  new HtmlWebpackHarddiskPlugin(),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module => module.context && module.context.includes('node_modules')
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest'
  }),
  new SpritesmithPlugin({
    src: {
      cwd: path.resolve(__dirname, 'spriteImgSrc'),
      glob: '*.png'
    },
    target: {
      image: path.resolve(__dirname, 'spriteImgTarget/sprite.png'),
      css: path.resolve(__dirname, 'spriteImgTarget/sprite.less')
    },
    apiOptions: {
      cssImageRef: 'sprite.png'
    }
  }),
  // new DashboardPlugin()
]

if(isDev) {
  plugins.push(new webpack.HotModuleReplacementPlugin())
}
if(isProd) {
  plugins.push(...[
    new PrepackWebpackPlugin(),
    new webpack.optimize.UglifyJsPlugin()
  ])
}

const webpackConfig = {
  entry: {
    vendor: ['react', 'react-dom', 'prop-types', 'classnames'],
    app: './asset/app'
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    // publicPath: isProd ? 'https://orn2bxyo7.bkt.clouddn.com/' : '/build/',
    // publicPath: isProd ? 'https://shuoshubao.github.io/build/' : '/build/',
    publicPath: '/build/',
    jsonpFunction: 'webpackJsonp',
    filename: isDev ? '[name].js' : '[name]_[chunkhash:5].js'
  },
  module: {
    rules: [
      {
        test: /\.png|gif|jpg/,
        use: [
          {
            loader: 'url-loader',
            query: {
              limit: 1024,
              name: isDev ? '[name].[ext]' : '[name]_[chunkhash:5].[ext]'
            }
          }
        ]
      },
      {
        test: /\.t[e]?xt$/,
        use: ['raw-loader']
      },
      {
        test: /\.vtpl$/,
        use: ['raw-loader']
      },
      {
        test: /\.ejs/,
        loader: 'ejs-loader'
      },
      {
        test: /\.tpl/,
        loader: 'ejs-loader'
      },
      {test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file?hash=sha512&digest=hex&name=font/[name].[hash:8].[ext]'},
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: extractLESS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                modules: true,
                localIdentName: '[local]_[hash:5]',
                minimize: isProd
              }
            },
            {
              loader: 'less-loader'
            }
          ]
        })
      },
      {
        test: /\.styl$/,
        loader: ['style-loader', 'css-loader', 'autoprefixer-loader', 'stylus-loader']
      },
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          plugins: [
            'transform-object-assign',
            'transform-decorators-legacy'
          ],
          presets: ['es2015', 'stage-2', 'react']
        }
      }
    ]
  },
  plugins,
  resolve: {
    modules: ['node_modules', 'spriteImgTarget'],
    extensions: ['.js', '.jsx', '.json'],
    mainFields: ['browser', 'main'],
    alias: {
      style: path.join(__dirname, 'style'),
      component: path.join(__dirname, 'component')
    }
  },
  externals: {

  }
}

if(isDev) {
  webpackConfig.devtool = 'source-map'
  webpackConfig.devServer = {
    inline: true,
    hot: true,
    port: 9090,
    publicPath: '/build/',
    filename: '[name]_[hash].js',
    historyApiFallback: true,
    proxy: {

    }
  }
}

module.exports = webpackConfig
<|endoftext|>


<commit_before>const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpackConfig = require('./webpack.config');
const merge = require('webpack-merge');
const path = require('path');

module.exports = merge(webpackConfig, {

    mode: 'production',

    devtool: 'cheap-module-source-map',

    output: {
        filename: '[name].js',
        library: 'marcflennert-dev-lib',
        libraryTarget: 'umd',
        path: path.resolve(__dirname, 'dist'),
        pathinfo: true,
        publicPath: '/'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            hash: true,
            template: path.join(__dirname, 'views/pages/about.ejs'),
            title: 'About Marc Flennert',
            filename: 'about.html'
        }),
        new HtmlWebpackPlugin({
            hash: true,
            template: path.join(__dirname, 'views/pages/reel.ejs'),
            title: 'Professional Work - By Marc Flennert'
        }),
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ]

});
<commit_msg>Update outpout library name.<commit_after>const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpackConfig = require('./webpack.config');
const merge = require('webpack-merge');
const path = require('path');

module.exports = merge(webpackConfig, {

    mode: 'production',

    devtool: 'cheap-module-source-map',

    output: {
        filename: '[name].js',
        library: 'marcflennert-lib',
        libraryTarget: 'umd',
        path: path.resolve(__dirname, 'dist'),
        pathinfo: true,
        publicPath: '/'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            hash: true,
            template: path.join(__dirname, 'views/pages/about.ejs'),
            title: 'About Marc Flennert',
            filename: 'about.html'
        }),
        new HtmlWebpackPlugin({
            hash: true,
            template: path.join(__dirname, 'views/pages/work.ejs'),
            title: 'Professional Work - By Marc Flennert'
        }),
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ]

});
<|endoftext|>


<commit_before>import { mergeObjects, deepClone, getWatcher } from './util'
import devtoolMiddleware from './middlewares/devtool'
import override from './override'

let Vue
let uid = 0

class Store {

  /**
   * @param {Object} options
   *        - {Object} state
   *        - {Object} actions
   *        - {Object} mutations
   *        - {Array} middlewares
   *        - {Boolean} strict
   */

  constructor ({
    state = {},
    mutations = {},
    modules = {},
    middlewares = [],
    strict = false
  } = {}) {
    this._getterCacheId = 'vuex_store_' + uid++
    this._dispatching = false
    this._rootMutations = this._mutations = mutations
    this._modules = modules
    // bind dispatch to self
    const dispatch = this.dispatch
    this.dispatch = (...args) => {
      dispatch.apply(this, args)
    }
    // use a Vue instance to store the state tree
    // suppress warnings just in case the user has added
    // some funky global mixins
    const silent = Vue.config.silent
    Vue.config.silent = true
    this._vm = new Vue({
      data: state
    })
    Vue.config.silent = silent
    this._setupModuleState(state, modules)
    this._setupModuleMutations(modules)
    this._setupMiddlewares(middlewares, state)
    // add extra warnings in strict mode
    if (strict) {
      this._setupMutationCheck()
    }
  }

  /**
   * Getter for the entire state tree.
   * Read only.
   *
   * @return {Object}
   */

  get state () {
    return this._vm._data
  }

  set state (v) {
    throw new Error('[vuex] Vuex root state is read only.')
  }

  /**
   * Dispatch an action.
   *
   * @param {String} type
   */

  dispatch (type, ...payload) {
    const mutation = this._mutations[type]
    const prevSnapshot = this._prevSnapshot
    const state = this.state
    let snapshot, clonedPayload
    if (mutation) {
      this._dispatching = true
      // apply the mutation
      if (Array.isArray(mutation)) {
        mutation.forEach(m => m(state, ...payload))
      } else {
        mutation(state, ...payload)
      }
      this._dispatching = false
      // invoke middlewares
      if (this._needSnapshots) {
        snapshot = this._prevSnapshot = deepClone(state)
        clonedPayload = deepClone(payload)
      }
      this._middlewares.forEach(m => {
        if (m.onMutation) {
          if (m.snapshot) {
            m.onMutation({ type, payload: clonedPayload }, snapshot, prevSnapshot, this)
          } else {
            m.onMutation({ type, payload }, state, this)
          }
        }
      })
    } else {
      console.warn(`[vuex] Unknown mutation: ${type}`)
    }
  }

  /**
   * Watch state changes on the store.
   * Same API as Vue's $watch, except when watching a function,
   * the function gets the state as the first argument.
   *
   * @param {String|Function} expOrFn
   * @param {Function} cb
   * @param {Object} [options]
   */

  watch (expOrFn, cb, options) {
    return this._vm.$watch(() => {
      return typeof expOrFn === 'function'
        ? expOrFn(this.state)
        : this._vm.$get(expOrFn)
    }, cb, options)
  }

  /**
   * Hot update actions and mutations.
   *
   * @param {Object} options
   *        - {Object} [mutations]
   *        - {Object} [modules]
   */

  hotUpdate ({ mutations, modules } = {}) {
    this._rootMutations = this._mutations = mutations || this._rootMutations
    this._setupModuleMutations(modules || this._modules)
  }

  /**
   * Attach sub state tree of each module to the root tree.
   *
   * @param {Object} state
   * @param {Object} modules
   */

  _setupModuleState (state, modules) {
    const { setPath } = Vue.parsers.path
    Object.keys(modules).forEach(key => {
      setPath(state, key, modules[key].state || {})
    })
  }

  /**
   * Bind mutations for each module to its sub tree and
   * merge them all into one final mutations map.
   *
   * @param {Object} modules
   */

  _setupModuleMutations (modules) {
    this._modules = modules
    const { getPath } = Vue.parsers.path
    const allMutations = [this._rootMutations]
    Object.keys(modules).forEach(key => {
      const module = modules[key]
      if (!module || !module.mutations) return
      // bind mutations to sub state tree
      const mutations = {}
      Object.keys(module.mutations).forEach(name => {
        const original = module.mutations[name]
        mutations[name] = (state, ...args) => {
          original(getPath(state, key), ...args)
        }
      })
      allMutations.push(mutations)
    })
    this._mutations = mergeObjects(allMutations)
  }

  /**
   * Setup mutation check: if the vuex instance's state is mutated
   * outside of a mutation handler, we throw en error. This effectively
   * enforces all mutations to the state to be trackable and hot-reloadble.
   * However, this comes at a run time cost since we are doing a deep
   * watch on the entire state tree, so it is only enalbed with the
   * strict option is set to true.
   */

  _setupMutationCheck () {
    const Watcher = getWatcher(this._vm)
    /* eslint-disable no-new */
    new Watcher(this._vm, '$data', () => {
      if (!this._dispatching) {
        throw new Error(
          '[vuex] Do not mutate vuex store state outside mutation handlers.'
        )
      }
    }, { deep: true, sync: true })
    /* eslint-enable no-new */
  }

  /**
   * Setup the middlewares. The devtools middleware is always
   * included, since it does nothing if no devtool is detected.
   *
   * A middleware can demand the state it receives to be
   * "snapshots", i.e. deep clones of the actual state tree.
   *
   * @param {Array} middlewares
   * @param {Object} state
   */

  _setupMiddlewares (middlewares, state) {
    this._middlewares = [devtoolMiddleware].concat(middlewares)
    this._needSnapshots = middlewares.some(m => m.snapshot)
    if (this._needSnapshots) {
      console.log(
        '[vuex] One or more of your middlewares are taking state snapshots ' +
        'for each mutation. Make sure to use them only during development.'
      )
    }
    const initialSnapshot = this._prevSnapshot = this._needSnapshots
      ? deepClone(state)
      : null
    // call init hooks
    this._middlewares.forEach(m => {
      if (m.onInit) {
        m.onInit(m.snapshot ? initialSnapshot : state, this)
      }
    })
  }
}

function install (_Vue) {
  Vue = _Vue
  override(Vue)
}

function createLogger () {
  console.warn(
    '[vuex] Vuex.createLogger has been deprecated.' +
    'Use `import createLogger from \'vuex/logger\' instead.'
  )
}

export default {
  Store,
  install,
  createLogger
}
<commit_msg>handle not installed case<commit_after>import { mergeObjects, deepClone, getWatcher } from './util'
import devtoolMiddleware from './middlewares/devtool'
import override from './override'

let Vue
let uid = 0

class Store {

  /**
   * @param {Object} options
   *        - {Object} state
   *        - {Object} actions
   *        - {Object} mutations
   *        - {Array} middlewares
   *        - {Boolean} strict
   */

  constructor ({
    state = {},
    mutations = {},
    modules = {},
    middlewares = [],
    strict = false
  } = {}) {
    this._getterCacheId = 'vuex_store_' + uid++
    this._dispatching = false
    this._rootMutations = this._mutations = mutations
    this._modules = modules
    // bind dispatch to self
    const dispatch = this.dispatch
    this.dispatch = (...args) => {
      dispatch.apply(this, args)
    }
    // use a Vue instance to store the state tree
    // suppress warnings just in case the user has added
    // some funky global mixins
    if (!Vue) {
      throw new Error(
        '[vuex] must call Vue.use(Vuex) before creating a store instance.'
      )
    }
    const silent = Vue.config.silent
    Vue.config.silent = true
    this._vm = new Vue({
      data: state
    })
    Vue.config.silent = silent
    this._setupModuleState(state, modules)
    this._setupModuleMutations(modules)
    this._setupMiddlewares(middlewares, state)
    // add extra warnings in strict mode
    if (strict) {
      this._setupMutationCheck()
    }
  }

  /**
   * Getter for the entire state tree.
   * Read only.
   *
   * @return {Object}
   */

  get state () {
    return this._vm._data
  }

  set state (v) {
    throw new Error('[vuex] Vuex root state is read only.')
  }

  /**
   * Dispatch an action.
   *
   * @param {String} type
   */

  dispatch (type, ...payload) {
    const mutation = this._mutations[type]
    const prevSnapshot = this._prevSnapshot
    const state = this.state
    let snapshot, clonedPayload
    if (mutation) {
      this._dispatching = true
      // apply the mutation
      if (Array.isArray(mutation)) {
        mutation.forEach(m => m(state, ...payload))
      } else {
        mutation(state, ...payload)
      }
      this._dispatching = false
      // invoke middlewares
      if (this._needSnapshots) {
        snapshot = this._prevSnapshot = deepClone(state)
        clonedPayload = deepClone(payload)
      }
      this._middlewares.forEach(m => {
        if (m.onMutation) {
          if (m.snapshot) {
            m.onMutation({ type, payload: clonedPayload }, snapshot, prevSnapshot, this)
          } else {
            m.onMutation({ type, payload }, state, this)
          }
        }
      })
    } else {
      console.warn(`[vuex] Unknown mutation: ${type}`)
    }
  }

  /**
   * Watch state changes on the store.
   * Same API as Vue's $watch, except when watching a function,
   * the function gets the state as the first argument.
   *
   * @param {String|Function} expOrFn
   * @param {Function} cb
   * @param {Object} [options]
   */

  watch (expOrFn, cb, options) {
    return this._vm.$watch(() => {
      return typeof expOrFn === 'function'
        ? expOrFn(this.state)
        : this._vm.$get(expOrFn)
    }, cb, options)
  }

  /**
   * Hot update actions and mutations.
   *
   * @param {Object} options
   *        - {Object} [mutations]
   *        - {Object} [modules]
   */

  hotUpdate ({ mutations, modules } = {}) {
    this._rootMutations = this._mutations = mutations || this._rootMutations
    this._setupModuleMutations(modules || this._modules)
  }

  /**
   * Attach sub state tree of each module to the root tree.
   *
   * @param {Object} state
   * @param {Object} modules
   */

  _setupModuleState (state, modules) {
    const { setPath } = Vue.parsers.path
    Object.keys(modules).forEach(key => {
      setPath(state, key, modules[key].state || {})
    })
  }

  /**
   * Bind mutations for each module to its sub tree and
   * merge them all into one final mutations map.
   *
   * @param {Object} modules
   */

  _setupModuleMutations (modules) {
    this._modules = modules
    const { getPath } = Vue.parsers.path
    const allMutations = [this._rootMutations]
    Object.keys(modules).forEach(key => {
      const module = modules[key]
      if (!module || !module.mutations) return
      // bind mutations to sub state tree
      const mutations = {}
      Object.keys(module.mutations).forEach(name => {
        const original = module.mutations[name]
        mutations[name] = (state, ...args) => {
          original(getPath(state, key), ...args)
        }
      })
      allMutations.push(mutations)
    })
    this._mutations = mergeObjects(allMutations)
  }

  /**
   * Setup mutation check: if the vuex instance's state is mutated
   * outside of a mutation handler, we throw en error. This effectively
   * enforces all mutations to the state to be trackable and hot-reloadble.
   * However, this comes at a run time cost since we are doing a deep
   * watch on the entire state tree, so it is only enalbed with the
   * strict option is set to true.
   */

  _setupMutationCheck () {
    const Watcher = getWatcher(this._vm)
    /* eslint-disable no-new */
    new Watcher(this._vm, '$data', () => {
      if (!this._dispatching) {
        throw new Error(
          '[vuex] Do not mutate vuex store state outside mutation handlers.'
        )
      }
    }, { deep: true, sync: true })
    /* eslint-enable no-new */
  }

  /**
   * Setup the middlewares. The devtools middleware is always
   * included, since it does nothing if no devtool is detected.
   *
   * A middleware can demand the state it receives to be
   * "snapshots", i.e. deep clones of the actual state tree.
   *
   * @param {Array} middlewares
   * @param {Object} state
   */

  _setupMiddlewares (middlewares, state) {
    this._middlewares = [devtoolMiddleware].concat(middlewares)
    this._needSnapshots = middlewares.some(m => m.snapshot)
    if (this._needSnapshots) {
      console.log(
        '[vuex] One or more of your middlewares are taking state snapshots ' +
        'for each mutation. Make sure to use them only during development.'
      )
    }
    const initialSnapshot = this._prevSnapshot = this._needSnapshots
      ? deepClone(state)
      : null
    // call init hooks
    this._middlewares.forEach(m => {
      if (m.onInit) {
        m.onInit(m.snapshot ? initialSnapshot : state, this)
      }
    })
  }
}

function install (_Vue) {
  Vue = _Vue
  override(Vue)
}

function createLogger () {
  console.warn(
    '[vuex] Vuex.createLogger has been deprecated.' +
    'Use `import createLogger from \'vuex/logger\' instead.'
  )
}

export default {
  Store,
  install,
  createLogger
}
<|endoftext|>


<commit_before>import ArraySchema from 'normalizr/lib/IterableSchema';
import EntitySchema from 'normalizr/lib/EntitySchema';
import UnionSchema from 'normalizr/lib/UnionSchema';
import merge from "lodash/merge";

export function denormalize(entity, entities, entitySchema) {
  const denormalized = {};
  if (entitySchema instanceof UnionSchema) {
    return denormalize(
      Object.assign({}, entity, { [entity.schema]: entity.id }),
      entities,
      entitySchema.getItemSchema()
    )[entity.schema];
  }
  Object.keys(entitySchema)
    .filter(attribute => attribute.substring(0, 1) !== "_")
    .filter(attribute => !!entity[attribute])
    .forEach(attribute => {

      const itemId = entity[attribute];

      if (entitySchema[attribute] instanceof ArraySchema) {
        const itemSchema = entitySchema[attribute].getItemSchema();
        const itemKey = itemSchema.getKey();
        denormalized[attribute] = itemId.map(id => {
          const item = entities[itemKey][id];
          return denormalize(item, entities, itemSchema);
        });
      }

      if (entitySchema[attribute] instanceof EntitySchema) {
        const itemSchema = entitySchema[attribute];
        const itemKey = itemSchema.getKey();
        const item = entities[itemKey][itemId];
        denormalized[attribute] = denormalize(item, entities, itemSchema);
      }

    });

  return merge({}, entity, denormalized);
}
<commit_msg>Decouple things a bit<commit_after>import ArraySchema from 'normalizr/lib/IterableSchema';
import EntitySchema from 'normalizr/lib/EntitySchema';
import UnionSchema from 'normalizr/lib/UnionSchema';
import merge from "lodash/merge";

function denormalizeArray(items, entities, schema) {
  const itemSchema = schema.getItemSchema();
  const itemKey = itemSchema.getKey();
  return items.map(id => {
    const item = entities[itemKey][id];
    return denormalize(item, entities, itemSchema);
  });
}

function denormalizeUnion(entity, entities, schema) {
  const itemSchema = schema.getItemSchema();
  return denormalize(
    Object.assign({}, entity, { [entity.schema]: entity.id }),
    entities,
    itemSchema
  )[entity.schema];
}

export function denormalize(entity, entities, entitySchema, bag = {}) {
  const denormalized = {};
  if (entitySchema instanceof UnionSchema) {
    return denormalizeUnion(entity, entities, entitySchema);
  }
  Object.keys(entitySchema)
    .filter(attribute => attribute.substring(0, 1) !== "_")
    .filter(attribute => !!entity[attribute])
    .forEach(attribute => {

      const itemId = entity[attribute];

      if (entitySchema[attribute] instanceof ArraySchema) {
        denormalized[attribute] = denormalizeArray(itemId, entities, entitySchema[attribute]);
      } else if (entitySchema[attribute] instanceof EntitySchema) {
        const itemSchema = entitySchema[attribute];
        const itemKey = itemSchema.getKey();
        const item = entities[itemKey][itemId];
        denormalized[attribute] = denormalize(item, entities, itemSchema);
      }

    });

  return merge({}, entity, denormalized);
}
<|endoftext|>


<commit_before>// UUID
// https://gist.github.com/LeverOne/1308368
/* jshint ignore:start */
function uuid(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b}
/* jshint ignore:end */

module.exports = {
  // Empties properties
  __empty: function(doc) {
    this.forEach(doc, function(value, key) {
      delete doc[key];
    });
  },

  // Copies properties from an object to another
  __update: function(dest, src) {
    this.forEach(src, function(value, key) {
      dest[key] = value;
    });
  },

  // Removes an item from an array
  __remove: function(array, item) {
    var index = this.indexOf(array, item);
    if (index != -1) array.splice(index, 1);
  },

  __id: function() {
    var id = this.id || 'id';
    return id;
  },

  getById: function(collection, id) {
    var self = this;
    return this.find(collection, function(doc) {
      return doc[self.__id()] === id;
    });
  },

  createId: function(collection, doc) {
    return uuid();
  },

  upsert: function(collection, doc) {
    if (doc[this.__id()]) {
      // id is set
      var d = this.getById(collection, doc[this.__id()]);
      if (d) {
        // replace properties of existing object
        this.__empty(d);
        this.assign(d, doc);
      } else {
        // push new object
        collection.push(doc);
      }
    } else {
      // create id and push new object
      doc[this.__id()] = this.createId(collection, doc);
      collection.push(doc);
    }

    return doc;
  },

  updateById: function(collection, id, attrs) {
    var doc = this.getById(collection, id);

    if (doc) this.assign(doc, attrs, {id: id});

    return doc;
  },

  updateWhere: function(collection, predicate, attrs) {
    var self = this;
    var docs = this.filter(collection, predicate);

    docs.forEach(function(doc) {
      self.assign(doc, attrs, {id: doc.id});
    });

    return docs;
  },

  replaceById: function(collection, id, attrs) {
    var doc = this.getById(collection, id);

    if (doc) {
      this.__empty(doc);
      this.assign(doc, attrs, {id: id});
    }

    return doc;
  },

  removeById: function(collection, id) {
    var doc = this.getById(collection, id);

    this.__remove(collection, doc);

    return doc;
  },

  removeWhere: function(collection, predicate) {
    var self = this;
    var docs = this.filter(collection, predicate);

    docs.forEach(function(doc) {
      self.__remove(collection, doc);
    });

    return docs;
  }
};
<commit_msg>Implement new "insert" method that aborts when item is a duplicate<commit_after>// UUID
// https://gist.github.com/LeverOne/1308368
/* jshint ignore:start */
function uuid(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b}
/* jshint ignore:end */

module.exports = {
  // Empties properties
  __empty: function(doc) {
    this.forEach(doc, function(value, key) {
      delete doc[key];
    });
  },

  // Copies properties from an object to another
  __update: function(dest, src) {
    this.forEach(src, function(value, key) {
      dest[key] = value;
    });
  },

  // Removes an item from an array
  __remove: function(array, item) {
    var index = this.indexOf(array, item);
    if (index != -1) array.splice(index, 1);
  },

  __id: function() {
    var id = this.id || 'id';
    return id;
  },

  getById: function(collection, id) {
    var self = this;
    return this.find(collection, function(doc) {
      return doc[self.__id()] === id;
    });
  },

  createId: function(collection, doc) {
    return uuid();
  },

  insert: function(collection, doc) {
    doc[this.__id()] = doc[this.__id()] || this.createId(collection, doc);
    var d = this.getById(collection, doc[this.__id()]);
    if (d) throw new Error("Insert failed; duplicate id.");
    collection.push(doc);
    return doc;
  },

  upsert: function(collection, doc) {
    if (doc[this.__id()]) {
      // id is set
      var d = this.getById(collection, doc[this.__id()]);
      if (d) {
        // replace properties of existing object
        this.__empty(d);
        this.assign(d, doc);
      } else {
        // push new object
        collection.push(doc);
      }
    } else {
      // create id and push new object
      doc[this.__id()] = this.createId(collection, doc);
      collection.push(doc);
    }

    return doc;
  },

  updateById: function(collection, id, attrs) {
    var doc = this.getById(collection, id);

    if (doc) this.assign(doc, attrs, {id: id});

    return doc;
  },

  updateWhere: function(collection, predicate, attrs) {
    var self = this;
    var docs = this.filter(collection, predicate);

    docs.forEach(function(doc) {
      self.assign(doc, attrs, {id: doc.id});
    });

    return docs;
  },

  replaceById: function(collection, id, attrs) {
    var doc = this.getById(collection, id);

    if (doc) {
      this.__empty(doc);
      this.assign(doc, attrs, {id: id});
    }

    return doc;
  },

  removeById: function(collection, id) {
    var doc = this.getById(collection, id);

    this.__remove(collection, doc);

    return doc;
  },

  removeWhere: function(collection, predicate) {
    var self = this;
    var docs = this.filter(collection, predicate);

    docs.forEach(function(doc) {
      self.__remove(collection, doc);
    });

    return docs;
  }
};
<|endoftext|>


<commit_before>// Graph Editor widget for the Nubomedia Graph Editor tool
// Uses jsPlumb as the connector library

import { mapObject } from './util'

// this is the paint style for the connecting lines..
const connectorPaintStyle = {
    lineWidth: 4,
    strokeStyle: '#61B7CF',
    joinstyle: 'round',
    outlineColor: 'white',
    outlineWidth: 2
};
// .. and this is the hover style.
const connectorHoverStyle = {
    lineWidth: 4,
    strokeStyle: '#216477',
    outlineWidth: 2,
    outlineColor: 'white'
};
const endpointHoverStyle = {
    fillStyle: '#216477',
    strokeStyle: '#216477'
};
// the definition of source endpoints (the small blue ones)
const sourceEndpoint = {
    endpoint: 'Dot',
    paintStyle: {
        strokeStyle: '#7AB02C',
        fillStyle: 'transparent',
        radius: 7,
        lineWidth: 3
    },
    isSource: true,
    connector: [ 'Flowchart', { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true } ],
    connectorStyle: connectorPaintStyle,
    hoverPaintStyle: endpointHoverStyle,
    connectorHoverStyle: connectorHoverStyle,
    maxConnections: -1,
    dragOptions: {},
};
// the definition of target endpoints (will appear when the user drags a connection)
const targetEndpoint = {
    endpoint: 'Dot',
    paintStyle: { fillStyle: '#7AB02C', radius: 11 },
    hoverPaintStyle: endpointHoverStyle,
    maxConnections: -1,
    dropOptions: { hoverClass: 'hover', activeClass: 'active' },
    isTarget: true,
};

function makeUUID(node, ep) { return `${node}.${ep}` }

export default class GraphEditor {

    constructor(container, projectcb, nodecb, validatecb) {
        this.instance = jsPlumb.getInstance({
            // default drag options
            DragOptions: { cursor: 'pointer', zIndex: 2000 },
            // the overlays to decorate each connection with.  note that the label overlay uses a function to generate the label text; in this
            // case it returns the 'labelText' member that we set on each connection in the 'init' method below.
            ConnectionOverlays: [
                [ 'Arrow', { location: 1 } ],
            ]
        });
        this.instance.setContainer(container);

        // Handlers
        this.projectClickHandler = projectcb;
        this.nodeClickHandler    = nodecb;
        this.validateHandler     = validatecb;
        this.deselectNodeHandler = null;

        // Nodes
        this.nodes = [];
        this.selectedNode = null;
        this.copiedNode   = null;

        // Listener to deselected node when clicks in container
        this.instance.getContainer().addEventListener('click', (e) => {
            if (e.target == this.instance.getContainer()) {
                if (this.deselectNodeHandler) {
                    this.deselectNodeHandler();
                }
            }
        });

        // Validate on create GraphEditor
        this.validateEditor();
    }

    destroy() {
        let c = this.instance.getContainer();
        $(c).empty();
        this.nodes = [];
        this.instance.reset();
    }

    batch(cb) {
        this.instance.batch( () => {
            cb(this);
        });
    }

    createNode(type, def, id, name, x, y, savedProps) {
        // Helper function to create the endpoints for the anchors of a given node
        var _addEndpoints = (toId, anchors) => {
            anchors.map((a) => {
                var uuid = makeUUID(toId, a.name);
                this.instance.addEndpoint(toId, a.source? sourceEndpoint: targetEndpoint, {
                    anchor: a.pos, uuid: uuid, parameters: (a.source? {srcEP: a.name} : {tgtEP: a.name}) });
            });
        };
        // Helper function to select node
        var _selectNode = (node) => {
            if (this.selectedNode != node) {
                _deselectNode();
                this.selectedNode       = node;
                node.element.className += " selected";
            } else {
                _deselectNode();
            }
        };
        // Helper function to unselect node
        var _deselectNode = () => {
            if (this.selectedNode) {
                this.selectedNode.element.className = this.selectedNode.element.className.replace(/\sselected\b/,'');
                this.selectedNode = null;
            }
        };

        // Create the node DOM element and the corresponding jsPlumb endpoints
        var el = document.createElement("div");
        el.id = id;
        el.style.top = y + "px";
        el.style.left = x + "px";
        el.style.backgroundImage = `url('images/nodes/${def.image.name}.png')`;
        el.style.width = def.image.width + "px";
        el.style.height = def.image.height + "px";
        el.className = "nubogednode";// nubogednt_" + type;
        this.instance.getContainer().appendChild(el);
        _addEndpoints(id, def.anchors);
        // Prevent clicks when drag/drop
        var dragged = false;
        this.instance.draggable(el, {
            grid: [20, 20],
            drag: (e) => { dragged = true }
        });
        let props = (savedProps) ? savedProps : mapObject(def.properties, (val, key) => val.default || "");
        let node = {element: el, type:type, id:id, name: name, properties: props, anchors: def.anchors, group: def.group};
        el.addEventListener("click", (e) => {
            if (dragged) {
                dragged = false;
                return;
            }
            _selectNode(node);
        });
        el.addEventListener("dblclick", (e) => {
            this.nodeClickHandler(this, node);
        });
        // Attach container event listener
        this.deselectNodeHandler = _deselectNode;
        // Add node
        this.nodes.push(node);

        // Validate on create node
        this.validateEditor();

        return el;
    }

    deleteNode(node) {
        var _deleteEndpoints = (toId, anchors) => {
            anchors.map((a) => {
                var uuid = makeUUID(toId, a.name);
                this.instance.deleteEndpoint(uuid);
            });
        };
        let ix = this.nodes.indexOf(node);
        if (ix != -1) {
            this.nodes.splice(ix, 1);
        }
        _deleteEndpoints(node.element.id, node.anchors);
        this.instance.getContainer().removeChild(node.element);
        this.selectedNode = (this.selectedNode != node) ? this.selectedNode : null;

        // Validate on delete node
        this.validateEditor();
    }

    copyNode(node) {
        this.copiedNode = node;
    }

    createConnection(src, srcEP, tgt, tgtEP) {
        this.instance.connect({uuids: [makeUUID(src,srcEP), makeUUID(tgt, tgtEP)]});
    }

    getNodes() {
        return this.nodes.map((node) => {
            let x = parseInt(node.element.style.left, 10);
            let y = parseInt(node.element.style.top, 10);
            return {type: node.type, id: node.id, name: node.name, x: x, y: y, properties: node.properties || {}};
        });
    }

    getConnections() {
        return this.instance.getConnections().map((connection) => ({
            source: connection.sourceId,
            sourceEP: connection.getParameters().srcEP,
            target: connection.targetId,
            targetEP: connection.getParameters().tgtEP,
        }));
    }

    /**
     * This function validates all editor components.
     * Validate:
     *  - Project:
     *      - Validate if has nodes
     *  - Nodes:
     *      - Empty properties
     *      - Unused connections by group type:
     *          # filters: Validates one source and one target are
     *                     connected to another node.
     *          # hubs: At least one source and one target must be connected
     *          # elements: At least one target must be connected
     */
    validateEditor() {
        // Validator skeleton
        let validator = {
            error: false,
            nodes: {}
        };

        /*
            Nodes
         */
        this.nodes.forEach((node) => {
            // Validator node skeleton
            let nodeError = {
                anchors: {
                    sources: [],
                    targets: []
                },
                errors: {
                    properties: [],
                    connections: {
                        sources: [],
                        targets: []
                    }
                }
            };
            // Node Properties
            if(Object.keys(node.properties).length != 0) {
                for (let property in node.properties) {
                    if (node.properties.hasOwnProperty(property)) {
                        // Empty
                        if (node.properties[property] == "") {
                            nodeError.errors.properties.push({
                                property: property,
                                description: property + " is empty."
                            });
                        }
                    }
                }
            }
            // Node Anchors
            node.anchors.forEach((anchor) => {
                let anchorConnections = {
                    name: anchor.name,
                    connections: []
                };
                this.getConnections().forEach((connection) => {
                    if (connection.source == node.name && connection.sourceEP == anchor.name) {
                        anchorConnections.connections.push(connection);
                    }
                    if (connection.target == node.name && connection.targetEP == anchor.name) {
                        anchorConnections.connections.push(connection);
                    }
                });
                if (anchor.source) {
                    nodeError.anchors.sources.push(anchorConnections)
                } else {
                    nodeError.anchors.targets.push(anchorConnections);
                }
            });
            // Node connections
            switch(node.group) {
                case 'filters':
                    nodeError.anchors.sources.forEach((source) => {
                        if (source.connections.length == 0) {
                            nodeError.errors.connections.sources.push({
                                property: "Source connection",
                                description: "This filter node hasn't connection source connected to another node."
                            });
                        }
                    });
                    nodeError.anchors.targets.forEach((target) => {
                        console.log(target);
                        if (target.connections.length == 0) {
                            nodeError.errors.connections.targets.push({
                                property: "Target connection",
                                description: "This filter node hasn't connection target connected by another node."
                            });
                        }
                    });
                    break;

                case 'hubs':
                    let hubSourceHaveConnection = false;
                    let hubTargetHaveConnection = false;
                    nodeError.anchors.sources.forEach((source) => {
                        if (!hubSourceHaveConnection && source.connections.length > 0) {
                            hubSourceHaveConnection = true;
                        }
                    });
                    nodeError.anchors.targets.forEach((target) => {
                        if (!hubTargetHaveConnection && target.connections.length > 0) {
                            hubTargetHaveConnection = true;
                        }
                    });
                    if (!hubSourceHaveConnection) {
                        nodeError.errors.connections.sources.push({
                            property: "Source connection",
                            description: "This hub node needs at least be connected to another node."
                        });
                    }
                    if (!hubTargetHaveConnection) {
                        nodeError.errors.connections.targets.push({
                            property: "Target connection",
                            description: "This hub node requires at least one connection from another node to its target."
                        });
                    }
                    break;

                case 'elements':
                    let elementTargetHaveConnection = false;
                    nodeError.anchors.targets.forEach((target) => {
                        if (!elementTargetHaveConnection && target.connections.length > 0) {
                            elementTargetHaveConnection = true;
                        }
                    });
                    if (!elementTargetHaveConnection) {
                        nodeError.errors.connections.targets.push({
                            property: "Target connection",
                            description: "This element node requires at least one connection from another node to its target."
                        });
                    }
                    break;
            }

            if (nodeError.errors.properties.length > 0 ||
                nodeError.errors.connections.sources.length > 0 ||
                nodeError.errors.connections.targets.length > 0) {
                validator.nodes[node.name] = nodeError;
            }
        });

        /*
            Project
         */
        if (this.nodes.length == 0) {
            validator.nodes["project"] = {
                anchors: {
                    sources: {},
                    targets: {}
                },
                errors: {
                    properties: [{
                        property: "Editor",
                        description: "No nodes created."
                    }],
                    connections: {
                        sources: [],
                        targets: []
                    }
                }
            };
        }
        validator.error = Object.keys(validator.nodes).length != 0;
        this.validateHandler(validator);
    }
}
<commit_msg>Deleted unused logs<commit_after>// Graph Editor widget for the Nubomedia Graph Editor tool
// Uses jsPlumb as the connector library

import { mapObject } from './util'

// this is the paint style for the connecting lines..
const connectorPaintStyle = {
    lineWidth: 4,
    strokeStyle: '#61B7CF',
    joinstyle: 'round',
    outlineColor: 'white',
    outlineWidth: 2
};
// .. and this is the hover style.
const connectorHoverStyle = {
    lineWidth: 4,
    strokeStyle: '#216477',
    outlineWidth: 2,
    outlineColor: 'white'
};
const endpointHoverStyle = {
    fillStyle: '#216477',
    strokeStyle: '#216477'
};
// the definition of source endpoints (the small blue ones)
const sourceEndpoint = {
    endpoint: 'Dot',
    paintStyle: {
        strokeStyle: '#7AB02C',
        fillStyle: 'transparent',
        radius: 7,
        lineWidth: 3
    },
    isSource: true,
    connector: [ 'Flowchart', { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true } ],
    connectorStyle: connectorPaintStyle,
    hoverPaintStyle: endpointHoverStyle,
    connectorHoverStyle: connectorHoverStyle,
    maxConnections: -1,
    dragOptions: {},
};
// the definition of target endpoints (will appear when the user drags a connection)
const targetEndpoint = {
    endpoint: 'Dot',
    paintStyle: { fillStyle: '#7AB02C', radius: 11 },
    hoverPaintStyle: endpointHoverStyle,
    maxConnections: -1,
    dropOptions: { hoverClass: 'hover', activeClass: 'active' },
    isTarget: true,
};

function makeUUID(node, ep) { return `${node}.${ep}` }

export default class GraphEditor {

    constructor(container, projectcb, nodecb, validatecb) {
        this.instance = jsPlumb.getInstance({
            // default drag options
            DragOptions: { cursor: 'pointer', zIndex: 2000 },
            // the overlays to decorate each connection with.  note that the label overlay uses a function to generate the label text; in this
            // case it returns the 'labelText' member that we set on each connection in the 'init' method below.
            ConnectionOverlays: [
                [ 'Arrow', { location: 1 } ],
            ]
        });
        this.instance.setContainer(container);

        // Handlers
        this.projectClickHandler = projectcb;
        this.nodeClickHandler    = nodecb;
        this.validateHandler     = validatecb;
        this.deselectNodeHandler = null;

        // Nodes
        this.nodes = [];
        this.selectedNode = null;
        this.copiedNode   = null;

        // Listener to deselected node when clicks in container
        this.instance.getContainer().addEventListener('click', (e) => {
            if (e.target == this.instance.getContainer()) {
                if (this.deselectNodeHandler) {
                    this.deselectNodeHandler();
                }
            }
        });

        // Validate on create GraphEditor
        this.validateEditor();
    }

    destroy() {
        let c = this.instance.getContainer();
        $(c).empty();
        this.nodes = [];
        this.instance.reset();
    }

    batch(cb) {
        this.instance.batch( () => {
            cb(this);
        });
    }

    createNode(type, def, id, name, x, y, savedProps) {
        // Helper function to create the endpoints for the anchors of a given node
        var _addEndpoints = (toId, anchors) => {
            anchors.map((a) => {
                var uuid = makeUUID(toId, a.name);
                this.instance.addEndpoint(toId, a.source? sourceEndpoint: targetEndpoint, {
                    anchor: a.pos, uuid: uuid, parameters: (a.source? {srcEP: a.name} : {tgtEP: a.name}) });
            });
        };
        // Helper function to select node
        var _selectNode = (node) => {
            if (this.selectedNode != node) {
                _deselectNode();
                this.selectedNode       = node;
                node.element.className += " selected";
            } else {
                _deselectNode();
            }
        };
        // Helper function to unselect node
        var _deselectNode = () => {
            if (this.selectedNode) {
                this.selectedNode.element.className = this.selectedNode.element.className.replace(/\sselected\b/,'');
                this.selectedNode = null;
            }
        };

        // Create the node DOM element and the corresponding jsPlumb endpoints
        var el = document.createElement("div");
        el.id = id;
        el.style.top = y + "px";
        el.style.left = x + "px";
        el.style.backgroundImage = `url('images/nodes/${def.image.name}.png')`;
        el.style.width = def.image.width + "px";
        el.style.height = def.image.height + "px";
        el.className = "nubogednode";// nubogednt_" + type;
        this.instance.getContainer().appendChild(el);
        _addEndpoints(id, def.anchors);
        // Prevent clicks when drag/drop
        var dragged = false;
        this.instance.draggable(el, {
            grid: [20, 20],
            drag: (e) => { dragged = true }
        });
        let props = (savedProps) ? savedProps : mapObject(def.properties, (val, key) => val.default || "");
        let node = {element: el, type:type, id:id, name: name, properties: props, anchors: def.anchors, group: def.group};
        el.addEventListener("click", (e) => {
            if (dragged) {
                dragged = false;
                return;
            }
            _selectNode(node);
        });
        el.addEventListener("dblclick", (e) => {
            this.nodeClickHandler(this, node);
        });
        // Attach container event listener
        this.deselectNodeHandler = _deselectNode;
        // Add node
        this.nodes.push(node);

        // Validate on create node
        this.validateEditor();

        return el;
    }

    deleteNode(node) {
        var _deleteEndpoints = (toId, anchors) => {
            anchors.map((a) => {
                var uuid = makeUUID(toId, a.name);
                this.instance.deleteEndpoint(uuid);
            });
        };
        let ix = this.nodes.indexOf(node);
        if (ix != -1) {
            this.nodes.splice(ix, 1);
        }
        _deleteEndpoints(node.element.id, node.anchors);
        this.instance.getContainer().removeChild(node.element);
        this.selectedNode = (this.selectedNode != node) ? this.selectedNode : null;

        // Validate on delete node
        this.validateEditor();
    }

    copyNode(node) {
        this.copiedNode = node;
    }

    createConnection(src, srcEP, tgt, tgtEP) {
        this.instance.connect({uuids: [makeUUID(src,srcEP), makeUUID(tgt, tgtEP)]});
    }

    getNodes() {
        return this.nodes.map((node) => {
            let x = parseInt(node.element.style.left, 10);
            let y = parseInt(node.element.style.top, 10);
            return {type: node.type, id: node.id, name: node.name, x: x, y: y, properties: node.properties || {}};
        });
    }

    getConnections() {
        return this.instance.getConnections().map((connection) => ({
            source: connection.sourceId,
            sourceEP: connection.getParameters().srcEP,
            target: connection.targetId,
            targetEP: connection.getParameters().tgtEP,
        }));
    }

    /**
     * This function validates all editor components.
     * Validate:
     *  - Project:
     *      - Validate if has nodes
     *  - Nodes:
     *      - Empty properties
     *      - Unused connections by group type:
     *          # filters: Validates one source and one target are
     *                     connected to another node.
     *          # hubs: At least one source and one target must be connected
     *          # elements: At least one target must be connected
     */
    validateEditor() {
        // Validator skeleton
        let validator = {
            error: false,
            nodes: {}
        };

        /*
            Nodes
         */
        this.nodes.forEach((node) => {
            // Validator node skeleton
            let nodeError = {
                anchors: {
                    sources: [],
                    targets: []
                },
                errors: {
                    properties: [],
                    connections: {
                        sources: [],
                        targets: []
                    }
                }
            };
            // Node Properties
            if(Object.keys(node.properties).length != 0) {
                for (let property in node.properties) {
                    if (node.properties.hasOwnProperty(property)) {
                        // Empty
                        if (node.properties[property] == "") {
                            nodeError.errors.properties.push({
                                property: property,
                                description: property + " is empty."
                            });
                        }
                    }
                }
            }
            // Node Anchors
            node.anchors.forEach((anchor) => {
                let anchorConnections = {
                    name: anchor.name,
                    connections: []
                };
                this.getConnections().forEach((connection) => {
                    if (connection.source == node.name && connection.sourceEP == anchor.name) {
                        anchorConnections.connections.push(connection);
                    }
                    if (connection.target == node.name && connection.targetEP == anchor.name) {
                        anchorConnections.connections.push(connection);
                    }
                });
                if (anchor.source) {
                    nodeError.anchors.sources.push(anchorConnections)
                } else {
                    nodeError.anchors.targets.push(anchorConnections);
                }
            });
            // Node connections
            switch(node.group) {
                case 'filters':
                    nodeError.anchors.sources.forEach((source) => {
                        if (source.connections.length == 0) {
                            nodeError.errors.connections.sources.push({
                                property: "Source connection",
                                description: "This filter node hasn't connection source connected to another node."
                            });
                        }
                    });
                    nodeError.anchors.targets.forEach((target) => {
                        if (target.connections.length == 0) {
                            nodeError.errors.connections.targets.push({
                                property: "Target connection",
                                description: "This filter node hasn't connection target connected by another node."
                            });
                        }
                    });
                    break;

                case 'hubs':
                    let hubSourceHaveConnection = false;
                    let hubTargetHaveConnection = false;
                    nodeError.anchors.sources.forEach((source) => {
                        if (!hubSourceHaveConnection && source.connections.length > 0) {
                            hubSourceHaveConnection = true;
                        }
                    });
                    nodeError.anchors.targets.forEach((target) => {
                        if (!hubTargetHaveConnection && target.connections.length > 0) {
                            hubTargetHaveConnection = true;
                        }
                    });
                    if (!hubSourceHaveConnection) {
                        nodeError.errors.connections.sources.push({
                            property: "Source connection",
                            description: "This hub node needs at least be connected to another node."
                        });
                    }
                    if (!hubTargetHaveConnection) {
                        nodeError.errors.connections.targets.push({
                            property: "Target connection",
                            description: "This hub node requires at least one connection from another node to its target."
                        });
                    }
                    break;

                case 'elements':
                    let elementTargetHaveConnection = false;
                    nodeError.anchors.targets.forEach((target) => {
                        if (!elementTargetHaveConnection && target.connections.length > 0) {
                            elementTargetHaveConnection = true;
                        }
                    });
                    if (!elementTargetHaveConnection) {
                        nodeError.errors.connections.targets.push({
                            property: "Target connection",
                            description: "This element node requires at least one connection from another node to its target."
                        });
                    }
                    break;
            }

            if (nodeError.errors.properties.length > 0 ||
                nodeError.errors.connections.sources.length > 0 ||
                nodeError.errors.connections.targets.length > 0) {
                validator.nodes[node.name] = nodeError;
            }
        });

        /*
            Project
         */
        if (this.nodes.length == 0) {
            validator.nodes["project"] = {
                anchors: {
                    sources: {},
                    targets: {}
                },
                errors: {
                    properties: [{
                        property: "Editor",
                        description: "No nodes created."
                    }],
                    connections: {
                        sources: [],
                        targets: []
                    }
                }
            };
        }
        validator.error = Object.keys(validator.nodes).length != 0;
        this.validateHandler(validator);
    }
}
<|endoftext|>