<commit_before>'use strict'

const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const paths = require('./paths')
const getClientEnvironment = require('./env')
const argv = require('minimist')(process.argv.slice(2))
const rxPaths = require('rxjs/_esm2015/path-mapping')
const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const JsConfigPathsPlugin = require('jsconfig-paths-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const tsImportPluginFactory = require('ts-import-plugin')

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './'
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1)
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl)

// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
  throw new Error('Production builds must have NODE_ENV=production.')
}

// Note: defined here because it will be used more than once.
const cssFilename = '[name].css'

// ExtractTextPlugin expects the build output to be flat.
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
// However, our output is structured with css, js and media folders.
// To have this structure working with relative paths, we have to use custom options.
const extractTextPluginOptions = shouldUseRelativeAssetPaths
  ? // Making sure that the publicPath goes back to to build folder.
    { publicPath: Array(cssFilename.split('/').length).join('../') }
  : {}

// Every folder in app src would generate a entry
// except 'manifest', 'assets','components', 'typings' and name starts with '_'
const entries = fs.readdirSync(paths.appSrc)
  .filter(name => !name.startsWith('_'))
  .map(name => ({name, dirPath: path.join(paths.appSrc, name)}))
  .filter(({name, dirPath}) => !/^assets|components|manifest|typings|app-config$/.test(name) && fs.lstatSync(dirPath).isDirectory())

const entriesWithHTML = entries.map(({name, dirPath}) => ({name, dirPath, template: path.join(dirPath, 'index.html')}))
  .filter(({template}) => fs.existsSync(template))

// add dictionary styles
const dictStyleEntries = {}
fs.readdirSync(path.join(paths.appSrc, 'components/dictionaries')).forEach(name => {
  if (name === 'helpers.ts') { return }
  dictStyleEntries[`dicts/${name}`] = path.join(paths.appSrc, 'components/dictionaries', name, '_style.scss')
})

// This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file.
module.exports = {
  // Don't attempt to continue if there are any errors.
  bail: true,
  // We generate sourcemaps in production. This is slow but gives good results.
  // You can exclude the *.map files from the build during deployment.
  devtool: shouldUseSourceMap ? 'source-map' : false,
  // In production, we only want to load the polyfills and the app code.
  entry: entries.reduce((result, {name, dirPath}) => {
    const names = fs.readdirSync(dirPath)
    const indexFile = names.find(name => /index\.(ts|tsx|js|jsx|css|scss)$/.test(name))
    if (!indexFile) { throw new Error(`Missing entry file for ${dirPath}`) }
    result[name] = indexFile.endsWith('css')
      ? path.join(dirPath, indexFile)
      : [require.resolve('./polyfills'), path.join(dirPath, indexFile)]
    return result
  }, dictStyleEntries),
  output: {
    // The build folder.
    path: paths.appBuild,
    // Generated JS file names (with nested folders).
    // There will be one main bundle, and one file per asynchronous chunk.
    // We don't currently advertise code splitting but Webpack supports it.
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
    // We inferred the "public path" (such as / or /my-project) from homepage.
    publicPath: publicPath,
    // Point sourcemap entries to original disk location (format as URL on Windows)
    devtoolModuleFilenameTemplate: info =>
      path
        .relative(paths.appSrc, info.absoluteResourcePath)
        .replace(/\\/g, '/'),
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ['node_modules', paths.appNodeModules].concat(
      // It is guaranteed to exist because we tweak it in `env.js`
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    // `web` extension prefixes have been added for better support
    // for React Native Web.
    extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', '.vue'],
    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      'react-native': 'react-native-web',
      'vue$': 'vue/dist/vue.runtime.esm.js',
      ...rxPaths(),
    },
    plugins: [
      // Prevents users from importing files from outside of src/ (or node_modules/).
      // This often causes confusion because we only process files within src/ with babel.
      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
      // please link the files into your node_modules/ and let module-resolution kick in.
      // Make sure your source files are compiled, as they will not be processed in any way.
      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
      new JsConfigPathsPlugin(),
      new TsConfigPathsPlugin(),
    ],
  },
  module: {
    strictExportPresence: true,
    rules: [
      // TODO: Disable require.ensure as it's not a standard language feature.
      // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
      // { parser: { requireEnsure: false } },

      {
        // "oneOf" will traverse all following loaders until one will
        // match the requirements. When no loader matches it will fall
        // back to the "file" loader at the end of the loader list.
        oneOf: [
          // "url" loader works just like "file" loader but it also embeds
          // assets smaller than specified size as data URLs to avoid requests.
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/[name].[ext]',
            },
          },
          {
            test: /\.tsx?$/,
            include: paths.appSrc,
            use: [
              {
                loader: require.resolve('babel-loader'),
                options: {
                  compact: true,
                },
              },
              {
                loader: require.resolve('ts-loader'),
                options: {
                  appendTsSuffixTo: [/\.vue$/],
                  // transpileOnly: argv.devbuild,
                  transpileOnly: !!argv.notypecheck,
                  getCustomTransformers: () => ({
                    before: [ tsImportPluginFactory([
                      {
                        libraryName: 'antd',
                        libraryDirectory: 'es',
                        style: 'css',
                      },
                    ]) ]
                  }),
                }
              }
            ],
          },
          // Process JS with Babel.
          {
            test: /\.jsx?$/,
            include: paths.appSrc,
            loader: require.resolve('babel-loader'),
            options: {
              compact: true,
            },
          },
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
              loaders: {
                css: genStyleLoaders('css', true),
                scss: genStyleLoaders('scss', true),
              },
              cssSourceMap: shouldUseSourceMap,
              // If you have problems debugging vue-files in devtools,
              // set this to false - it *may* help
              // https://vue-loader.vuejs.org/en/options.html#cachebusting
              cacheBusting: true,
              transformToRequire: {
                video: ['src', 'poster'],
                source: 'src',
                img: 'src',
                image: 'xlink:href'
              },
              esModule: true
            }
          },
          {
            test: /\.scss$/,
            loader: genStyleLoaders('scss', false),
          },
          {
            test: /\.css$/,
            loader: genStyleLoaders('css', false),
          },
          // "file" loader makes sure assets end up in the `build` folder.
          // When you `import` an asset, you get its filename.
          // This loader doesn't use a "test" so it will catch all modules
          // that fall through the other loaders.
          {
            loader: require.resolve('file-loader'),
            // Exclude `js` files to keep "css" loader working as it injects
            // it's runtime that would otherwise processed through "file" loader.
            // Also exclude `html` and `json` extensions so they get processed
            // by webpacks internal loaders.
            exclude: [/\.js$/, /\.html$/, /\.json$/],
            options: {
              name: 'static/[name].[ext]',
            },
          },
          // ** STOP ** Are you adding a new loader?
          // Make sure to add the new loader(s) before the "file" loader.
        ],
      },
    ],
  },
  plugins: [
    argv.notypecheck
    ? null
    : new ForkTsCheckerWebpackPlugin({tslint: true, async: false}),
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In production, it will be an empty string unless you specify "homepage"
    // in `package.json`, in which case it will be the pathname of that URL.
    new InterpolateHtmlPlugin(env.raw),
    // extract commons chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'page-commons',
      filename: 'page-commons.js',
      chunks: entriesWithHTML.map(x => x.name),
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'wordpage-commons',
      filename: 'wordpage-commons.js',
      chunks: ['notebook', 'history'],
    }),
    // Generates an `index.html` file with the <script> injected.
    ...entriesWithHTML.map(({name, template}) => new HtmlWebpackPlugin({
      inject: true,
      filename: name + '.html',
      chunks: /^(notebook|history)$/.test(name)
        ? ['page-commons', 'wordpage-commons', name]
        : ['page-commons', name],
      chunksSortMode: 'manual',
      template,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    })),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
    // It is absolutely essential that NODE_ENV was set to production here.
    // Otherwise React will be compiled in the very slow development mode.
    new webpack.DefinePlugin(env.stringified),
    // RxJs https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
    argv.devbuild || argv.analyze
    ? null
    : new webpack.optimize.ModuleConcatenationPlugin(),
    // Minify the code.
    argv.devbuild
    ? null
    : new UglifyJsPlugin({
      uglifyOptions: {
        ie8: false,
        ecma: 7,
        compress: {
          // Harmony Bug
          // https://github.com/mishoo/UglifyJS2/issues/2842
          inline: false,
          warnings: false,
          // Disabled because of an issue with Uglify breaking seemingly valid code:
          // https://github.com/facebookincubator/create-react-app/issues/2376
          // Pending further investigation:
          // https://github.com/mishoo/UglifyJS2/issues/2011
          comparisons: false,
        },
        output: {
          comments: false,
          // Turned on because emoji and regex is not minified properly using default
          // https://github.com/facebookincubator/create-react-app/issues/2488
          ascii_only: true,
        },
      },
      sourceMap: shouldUseSourceMap,
    }),
    // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
    new ExtractTextPlugin({
      filename: cssFilename,
    }),
    // Tailor locales
    new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en|zh-cn|zh-tw)$/),
    !process.env.CI && argv.analyze
    ? new BundleAnalyzerPlugin()
    : null,
  ].filter(Boolean),
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty',
  },
}

// The notation here is somewhat confusing.
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader normally turns CSS into JS modules injecting <style>,
// but unlike in development configuration, we do something different.
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
// (second argument), then grabs the result CSS and puts it into a
// separate file in our build process. This way we actually ship
// a single CSS file in production instead of JS code injecting <style>
// tags. If you use code splitting, however, any async bundles will still
// use the "style" loader inside the async code so CSS from them won't be
// in the main CSS file.
function genStyleLoaders (format, isVue) {
  var use = [
    {
      loader: 'css-loader',
      options: {
        sourceMap: shouldUseSourceMap,
        minimize: !argv.devbuild,
      },
    },
    {
      loader: 'postcss-loader',
      options: { sourceMap: shouldUseSourceMap },
    },
  ]

  if (format === 'scss') {
    use.push({
      loader: 'sass-loader',
      options: {
        sourceMap: shouldUseSourceMap,
        includePaths: [path.resolve(__dirname, '../node_modules')],
      },
    })
  }

  return ExtractTextPlugin.extract(
    Object.assign(
      {
        fallback: {
          loader: require.resolve(isVue ? 'vue-style-loader' : 'style-loader'),
          options: {
            hmr: false,
          },
        },
        use,
      },
      extractTextPluginOptions
    )
  )
  // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
}

<commit_msg>build: remove sync ts check<commit_after>'use strict'

const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const paths = require('./paths')
const getClientEnvironment = require('./env')
const argv = require('minimist')(process.argv.slice(2))
const rxPaths = require('rxjs/_esm2015/path-mapping')
const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const JsConfigPathsPlugin = require('jsconfig-paths-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const tsImportPluginFactory = require('ts-import-plugin')

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './'
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1)
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl)

// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
  throw new Error('Production builds must have NODE_ENV=production.')
}

// Note: defined here because it will be used more than once.
const cssFilename = '[name].css'

// ExtractTextPlugin expects the build output to be flat.
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
// However, our output is structured with css, js and media folders.
// To have this structure working with relative paths, we have to use custom options.
const extractTextPluginOptions = shouldUseRelativeAssetPaths
  ? // Making sure that the publicPath goes back to to build folder.
    { publicPath: Array(cssFilename.split('/').length).join('../') }
  : {}

// Every folder in app src would generate a entry
// except 'manifest', 'assets','components', 'typings' and name starts with '_'
const entries = fs.readdirSync(paths.appSrc)
  .filter(name => !name.startsWith('_'))
  .map(name => ({name, dirPath: path.join(paths.appSrc, name)}))
  .filter(({name, dirPath}) => !/^assets|components|manifest|typings|app-config$/.test(name) && fs.lstatSync(dirPath).isDirectory())

const entriesWithHTML = entries.map(({name, dirPath}) => ({name, dirPath, template: path.join(dirPath, 'index.html')}))
  .filter(({template}) => fs.existsSync(template))

// add dictionary styles
const dictStyleEntries = {}
fs.readdirSync(path.join(paths.appSrc, 'components/dictionaries')).forEach(name => {
  if (name === 'helpers.ts') { return }
  dictStyleEntries[`dicts/${name}`] = path.join(paths.appSrc, 'components/dictionaries', name, '_style.scss')
})

// This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file.
module.exports = {
  // Don't attempt to continue if there are any errors.
  bail: true,
  // We generate sourcemaps in production. This is slow but gives good results.
  // You can exclude the *.map files from the build during deployment.
  devtool: shouldUseSourceMap ? 'source-map' : false,
  // In production, we only want to load the polyfills and the app code.
  entry: entries.reduce((result, {name, dirPath}) => {
    const names = fs.readdirSync(dirPath)
    const indexFile = names.find(name => /index\.(ts|tsx|js|jsx|css|scss)$/.test(name))
    if (!indexFile) { throw new Error(`Missing entry file for ${dirPath}`) }
    result[name] = indexFile.endsWith('css')
      ? path.join(dirPath, indexFile)
      : [require.resolve('./polyfills'), path.join(dirPath, indexFile)]
    return result
  }, dictStyleEntries),
  output: {
    // The build folder.
    path: paths.appBuild,
    // Generated JS file names (with nested folders).
    // There will be one main bundle, and one file per asynchronous chunk.
    // We don't currently advertise code splitting but Webpack supports it.
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
    // We inferred the "public path" (such as / or /my-project) from homepage.
    publicPath: publicPath,
    // Point sourcemap entries to original disk location (format as URL on Windows)
    devtoolModuleFilenameTemplate: info =>
      path
        .relative(paths.appSrc, info.absoluteResourcePath)
        .replace(/\\/g, '/'),
  },
  resolve: {
    // This allows you to set a fallback for where Webpack should look for modules.
    // We placed these paths second because we want `node_modules` to "win"
    // if there are any conflicts. This matches Node resolution mechanism.
    // https://github.com/facebookincubator/create-react-app/issues/253
    modules: ['node_modules', paths.appNodeModules].concat(
      // It is guaranteed to exist because we tweak it in `env.js`
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    // These are the reasonable defaults supported by the Node ecosystem.
    // We also include JSX as a common component filename extension to support
    // some tools, although we do not recommend using it, see:
    // https://github.com/facebookincubator/create-react-app/issues/290
    // `web` extension prefixes have been added for better support
    // for React Native Web.
    extensions: ['.ts', '.tsx', '.js', '.json', '.jsx', '.vue'],
    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      'react-native': 'react-native-web',
      'vue$': 'vue/dist/vue.runtime.esm.js',
      ...rxPaths(),
    },
    plugins: [
      // Prevents users from importing files from outside of src/ (or node_modules/).
      // This often causes confusion because we only process files within src/ with babel.
      // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
      // please link the files into your node_modules/ and let module-resolution kick in.
      // Make sure your source files are compiled, as they will not be processed in any way.
      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
      new JsConfigPathsPlugin(),
      new TsConfigPathsPlugin(),
    ],
  },
  module: {
    strictExportPresence: true,
    rules: [
      // TODO: Disable require.ensure as it's not a standard language feature.
      // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
      // { parser: { requireEnsure: false } },

      {
        // "oneOf" will traverse all following loaders until one will
        // match the requirements. When no loader matches it will fall
        // back to the "file" loader at the end of the loader list.
        oneOf: [
          // "url" loader works just like "file" loader but it also embeds
          // assets smaller than specified size as data URLs to avoid requests.
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/[name].[ext]',
            },
          },
          {
            test: /\.tsx?$/,
            include: paths.appSrc,
            use: [
              {
                loader: require.resolve('babel-loader'),
                options: {
                  compact: true,
                },
              },
              {
                loader: require.resolve('ts-loader'),
                options: {
                  appendTsSuffixTo: [/\.vue$/],
                  // transpileOnly: argv.devbuild,
                  transpileOnly: !!argv.notypecheck,
                  getCustomTransformers: () => ({
                    before: [ tsImportPluginFactory([
                      {
                        libraryName: 'antd',
                        libraryDirectory: 'es',
                        style: 'css',
                      },
                    ]) ]
                  }),
                }
              }
            ],
          },
          // Process JS with Babel.
          {
            test: /\.jsx?$/,
            include: paths.appSrc,
            loader: require.resolve('babel-loader'),
            options: {
              compact: true,
            },
          },
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
              loaders: {
                css: genStyleLoaders('css', true),
                scss: genStyleLoaders('scss', true),
              },
              cssSourceMap: shouldUseSourceMap,
              // If you have problems debugging vue-files in devtools,
              // set this to false - it *may* help
              // https://vue-loader.vuejs.org/en/options.html#cachebusting
              cacheBusting: true,
              transformToRequire: {
                video: ['src', 'poster'],
                source: 'src',
                img: 'src',
                image: 'xlink:href'
              },
              esModule: true
            }
          },
          {
            test: /\.scss$/,
            loader: genStyleLoaders('scss', false),
          },
          {
            test: /\.css$/,
            loader: genStyleLoaders('css', false),
          },
          // "file" loader makes sure assets end up in the `build` folder.
          // When you `import` an asset, you get its filename.
          // This loader doesn't use a "test" so it will catch all modules
          // that fall through the other loaders.
          {
            loader: require.resolve('file-loader'),
            // Exclude `js` files to keep "css" loader working as it injects
            // it's runtime that would otherwise processed through "file" loader.
            // Also exclude `html` and `json` extensions so they get processed
            // by webpacks internal loaders.
            exclude: [/\.js$/, /\.html$/, /\.json$/],
            options: {
              name: 'static/[name].[ext]',
            },
          },
          // ** STOP ** Are you adding a new loader?
          // Make sure to add the new loader(s) before the "file" loader.
        ],
      },
    ],
  },
  plugins: [
    argv.notypecheck
    ? null
    : new ForkTsCheckerWebpackPlugin({tslint: true}),
    // Makes some environment variables available in index.html.
    // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    // In production, it will be an empty string unless you specify "homepage"
    // in `package.json`, in which case it will be the pathname of that URL.
    new InterpolateHtmlPlugin(env.raw),
    // extract commons chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'page-commons',
      filename: 'page-commons.js',
      chunks: entriesWithHTML.map(x => x.name),
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'wordpage-commons',
      filename: 'wordpage-commons.js',
      chunks: ['notebook', 'history'],
    }),
    // Generates an `index.html` file with the <script> injected.
    ...entriesWithHTML.map(({name, template}) => new HtmlWebpackPlugin({
      inject: true,
      filename: name + '.html',
      chunks: /^(notebook|history)$/.test(name)
        ? ['page-commons', 'wordpage-commons', name]
        : ['page-commons', name],
      chunksSortMode: 'manual',
      template,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    })),
    // Makes some environment variables available to the JS code, for example:
    // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
    // It is absolutely essential that NODE_ENV was set to production here.
    // Otherwise React will be compiled in the very slow development mode.
    new webpack.DefinePlugin(env.stringified),
    // RxJs https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
    argv.devbuild || argv.analyze
    ? null
    : new webpack.optimize.ModuleConcatenationPlugin(),
    // Minify the code.
    argv.devbuild
    ? null
    : new UglifyJsPlugin({
      uglifyOptions: {
        ie8: false,
        ecma: 7,
        compress: {
          // Harmony Bug
          // https://github.com/mishoo/UglifyJS2/issues/2842
          inline: false,
          warnings: false,
          // Disabled because of an issue with Uglify breaking seemingly valid code:
          // https://github.com/facebookincubator/create-react-app/issues/2376
          // Pending further investigation:
          // https://github.com/mishoo/UglifyJS2/issues/2011
          comparisons: false,
        },
        output: {
          comments: false,
          // Turned on because emoji and regex is not minified properly using default
          // https://github.com/facebookincubator/create-react-app/issues/2488
          ascii_only: true,
        },
      },
      sourceMap: shouldUseSourceMap,
    }),
    // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
    new ExtractTextPlugin({
      filename: cssFilename,
    }),
    // Tailor locales
    new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en|zh-cn|zh-tw)$/),
    !process.env.CI && argv.analyze
    ? new BundleAnalyzerPlugin()
    : null,
  ].filter(Boolean),
  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty',
  },
}

// The notation here is somewhat confusing.
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader normally turns CSS into JS modules injecting <style>,
// but unlike in development configuration, we do something different.
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
// (second argument), then grabs the result CSS and puts it into a
// separate file in our build process. This way we actually ship
// a single CSS file in production instead of JS code injecting <style>
// tags. If you use code splitting, however, any async bundles will still
// use the "style" loader inside the async code so CSS from them won't be
// in the main CSS file.
function genStyleLoaders (format, isVue) {
  var use = [
    {
      loader: 'css-loader',
      options: {
        sourceMap: shouldUseSourceMap,
        minimize: !argv.devbuild,
      },
    },
    {
      loader: 'postcss-loader',
      options: { sourceMap: shouldUseSourceMap },
    },
  ]

  if (format === 'scss') {
    use.push({
      loader: 'sass-loader',
      options: {
        sourceMap: shouldUseSourceMap,
        includePaths: [path.resolve(__dirname, '../node_modules')],
      },
    })
  }

  return ExtractTextPlugin.extract(
    Object.assign(
      {
        fallback: {
          loader: require.resolve(isVue ? 'vue-style-loader' : 'style-loader'),
          options: {
            hmr: false,
          },
        },
        use,
      },
      extractTextPluginOptions
    )
  )
  // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
}

<|endoftext|>


<commit_before>describe('Mocha order', function () {
  before(function () {
    console.log('before');
  });

  after(function () {
    console.log('after');
  });

  beforeEach(function () {
    console.log('beforeEach');
  });
  afterEach(function () {
    console.log('afterEach');
  });

  it('test1', function () {
    console.log('test1');
  });
  it('test2', function () {
    console.log('test2');
  });
});

//   Mocha order
// before
// beforeEach
// test1
//     ✓ test1
// afterEach
// beforeEach
// test2
//     ✓ test2
// afterEach
// after
//
<commit_msg>[JavaScript][Mocha] order of nested function.<commit_after>describe('Mocha order', function () {
  before(function () {
    console.log('before');
  });

  after(function () {
    console.log('after');
  });

  beforeEach(function () {
    console.log('beforeEach');
  });
  afterEach(function () {
    console.log('afterEach');
  });

  it('test1', function () {
    console.log('test1');
  });
  it('test2', function () {
    console.log('test2');
  });

  describe('Mocha order nested', function () {
    before(function () {
      console.log('nested before');
    });

    after(function () {
      console.log('nested after');
    });

    beforeEach(function () {
      console.log('nested beforeEach');
    });
    afterEach(function () {
      console.log('nested afterEach');
    });

    it('test1', function () {
      console.log('nested test1');
    });
    it('test2', function () {
      console.log('nested test2');
    });
  });
});

//   Mocha order
// before
// beforeEach
// test1
//     ✓ test1
// afterEach
// beforeEach
// test2
//     ✓ test2
// afterEach
//     Mocha order nested
// nested before
// beforeEach
// nested beforeEach
// nested test1
//       ✓ test1
// nested afterEach
// afterEach
// beforeEach
// nested beforeEach
// nested test2
//       ✓ test2
// nested afterEach
// afterEach
// nested after
// after
<|endoftext|>


<commit_before>/**
 * RcmPeopleSlider
 *
 * JS for editing RcmPeopleSlider
 *
 * PHP version 5.3
 *
 * LICENSE: No License yet
 *
 * @category  Reliv
 * @package   RcmPlugins\RcmPeopleSlider
 * @author    Rod McNew <rmcnew@relivinc.com>
 * @copyright 2012 Reliv International
 * @license   License.txt New BSD License
 * @version   GIT: <git_id>
 * @link      http://ci.reliv.com/confluence
 */
var RcmPeopleSliderEdit = function (instanceId, container) {

    /**
     * Always refers to this object unlike the 'this' JS variable;
     *
     * @type {RcmPeopleSliderEdit}
     */
    var me = this;

    /**
     * The the object used by non-admins to view this plugin
     * @type {RcmPeopleSlider}
     */
    var peopleSlider = window['RcmPeopleSliders'][instanceId];

    var personTemplate = {
        largeImage: '/modules/rcm/images/no-image.png',
        smallImage: '/modules/rcm/images/no-image.png',
        shortDesc: 'Short Description',
        longDesc: 'Long description'
    };

    var newPersonId = 10000;

    var sortable = false;

    /**
     * Called by content management system to make this plugin user-editable
     */
    me.initEdit = function(){

        var contSel = rcm.getPluginContainerSelector(instanceId);

        //Add right click menu
        rcmEdit.pluginContextMenu({
            selector: contSel + ' .person, ' + contSel + ' .personDetails',
            //Here are the right click menu options
            items:{
                sort:{
                    name:'Toggle Sort Mode (Allows drag and drop sorting but ' +
                        'disables text editing)',
                    icon:'edit',
                    callback:function(){
                        if(sortable){
                            sortable=false;
                            container.find('.previews').sortable('destroy');
                        }else{
                            sortable=true;
                            container.find('.previews').sortable();
                        }
                    }
                },
                line1:'-',
                create:{
                    name:'Create Person',
                    icon:'edit',
                    callback:me.createPerson
                },
                edit:{
                    name:'Edit Person Images',
                    icon:'edit',
                    callback:me.editPersonImages
                },
                line2:'-',
                delete:{
                    name:'Delete Person',
                    icon:'delete',
                    callback:me.deletePerson
                }
            }
        });

        me.makePeopleEditable();
    };

    me.makePeopleEditable = function(){
        $.each(
            container.find('.person'),
            function(){
                me.makePersonEditable($(this).attr('data-personId'));
            }
        );
    };

    me.makePersonEditable = function(personId){
        var personEles = peopleSlider.getPersonElements(personId);
        if(!personEles.shortDesc.attr('contenteditable')){

            //Make the shortDesc editable
            personEles.shortDesc.attr('contenteditable', true);
            personEles.shortDesc.css('cursor', 'text');

            // We load all images when in the editor to make saving easier
            peopleSlider.loadDelayedImage(personEles.largeImage);

            //Allow double click to edit images
            personEles.smallImage.dblclick(me.editPersonImages);
            personEles.largeImage.dblclick(me.editPersonImages);

            // This fails when the plugin is brand new because we can't start ck
            // editors on elements that are not in the dom
            try{
                rcmEditor.convertToHtml5Editor(personEles.longDesc);
            }catch(e){

            }
        }
    };

    me.createPerson = function(){
        newPersonId+=1;

        container.find('.previews').append(
            peopleSlider.buildPersonPreview(newPersonId, personTemplate)
        );

        container.find('.peopleDetails').append(
            peopleSlider.buildPersonDetails(newPersonId, personTemplate)
        );

        //Makes the slide recalculate the frame count
        peopleSlider.apertureSlider.init();

        peopleSlider.selectPerson(newPersonId);

        peopleSlider.attachClickEvents();

        me.makePersonEditable(newPersonId);
    };

    me.deletePerson = function(){
        var personEles = peopleSlider.getPersonElements(
            $(this).attr('data-personId')
        );
        personEles.details.remove();
        personEles.preview.remove();

        //Select first person in case we deleted the selected person
        peopleSlider.selectPerson($('.person').attr('data-personId'));

        //Makes the slide recalculate the frame count
        peopleSlider.apertureSlider.init();
    };

    me.editPersonImages = function(){
        var clickedEle = $(this);
        var personId = clickedEle.attr('data-personId');

        //Used for double clicking on images
        if(typeof(personId)=='undefined'){
            personId = clickedEle
                .closest('[data-personId]').attr('data-personId');
        }

        var personEles = peopleSlider.getPersonElements(personId);

        var smallImage = $.dialogIn(
            'image',
            'Small Preview Image (150px by 100px)',
            personEles.smallImage.attr('src')
        );
        var largeImage = $.dialogIn(
            'image',
            'Large Details Image (200px wide)',
            personEles.largeImage.attr('src')
        );

        var form = $('<form></form>')
            .addClass('simple')
            .append(smallImage, largeImage)
            .dialog({
                title:'Properties',
                modal:true,
                width:620,
                buttons:{
                    Cancel:function () {
                        $(this).dialog("close");
                    },
                    Ok:function () {

                        //Get user-entered data from form
                        personEles.smallImage.attr('src', smallImage.val());
                        personEles.largeImage.attr('src', largeImage.val());
                        personEles.largeImage.attr(
                            'delayed-src', largeImage.val()
                        );

                        $(this).dialog('close');
                    }
                }
            }
        );
    };

    /**
     * Called by content management system to get this plugins data for saving
     * on the server
     *
     * @return {Object}
     */
    me.getSaveData = function () {

        var people = [];

        $.each(
            container.find('.person'),
            function(){
                var personEles = peopleSlider.getPersonElements(
                    $(this).attr('data-personId')
                );
                people.push(
                    {
                        'smallImage' :personEles.smallImage.attr('src'),
                        'largeImage' : personEles.largeImage.attr('src'),
                        'shortDesc' : personEles.shortDesc.html(),
                        'longDesc' : personEles.longDesc.html()
                    }
                );
            }
        );

        return {
            people:people
        }
    }

};<commit_msg>finish people slider editing<commit_after>/**
 * RcmPeopleSlider
 *
 * JS for editing RcmPeopleSlider
 *
 * PHP version 5.3
 *
 * LICENSE: No License yet
 *
 * @category  Reliv
 * @package   RcmPlugins\RcmPeopleSlider
 * @author    Rod McNew <rmcnew@relivinc.com>
 * @copyright 2012 Reliv International
 * @license   License.txt New BSD License
 * @version   GIT: <git_id>
 * @link      http://ci.reliv.com/confluence
 */
var RcmPeopleSliderEdit = function (instanceId, container) {

    /**
     * Always refers to this object unlike the 'this' JS variable;
     *
     * @type {RcmPeopleSliderEdit}
     */
    var me = this;

    /**
     * The the object used by non-admins to view this plugin
     * @type {RcmPeopleSlider}
     */
    var peopleSlider = window['RcmPeopleSliders'][instanceId];

    var personTemplate = {
        largeImage: '/modules/rcm/images/no-image.png',
        smallImage: '/modules/rcm/images/no-image.png',
        shortDesc: 'Short Description',
        longDesc: 'Long description'
    };

    var newPersonId = 10000;

    var sortable = false;

    /**
     * Called by content management system to make this plugin user-editable
     */
    me.initEdit = function(){

        var contSel = rcm.getPluginContainerSelector(instanceId);

        //Add right click menu
        rcmEdit.pluginContextMenu({
            selector: contSel + ' .person, ' + contSel + ' .personDetails',
            //Here are the right click menu options
            items:{
                sort:{
                    name:'Toggle Sort Mode (Allows drag and drop sorting but ' +
                        'disables text editing)',
                    icon:'edit',
                    callback:function(){
                        if(sortable){
                            sortable=false;
                            container.find('.previews').sortable('destroy');
                        }else{
                            sortable=true;
                            container.find('.previews').sortable();
                        }
                    }
                },
                line1:'-',
                create:{
                    name:'Create Person',
                    icon:'edit',
                    callback:me.createPerson
                },
                edit:{
                    name:'Edit Person Images',
                    icon:'edit',
                    callback:me.editPersonImages
                },
                line2:'-',
                delete:{
                    name:'Delete Person',
                    icon:'delete',
                    callback:me.deletePerson
                }
            }
        });

        me.makePeopleEditable();
    };

    me.makePeopleEditable = function(){
        $.each(
            container.find('.person'),
            function(){
                me.makePersonEditable($(this).attr('data-personId'));
            }
        );
        peopleSlider.attachClickEvents();
    };

    me.makePersonEditable = function(personId){
        var personEles = peopleSlider.getPersonElements(personId);
        if(!personEles.shortDesc.attr('contenteditable')){

            //Make the shortDesc editable
            personEles.shortDesc.attr('contenteditable', true);
            personEles.shortDesc.css('cursor', 'text');

            // We load all images when in the editor to make saving easier
            peopleSlider.loadDelayedImage(personEles.largeImage);

            //Allow double click to edit images
            personEles.smallImage.dblclick(me.editPersonImages);
            personEles.largeImage.dblclick(me.editPersonImages);

            // This fails when the plugin is brand new because we can't start ck
            // editors on elements that are not in the dom
            try{
                window['rcmEditor'].convertToHtml5Editor(personEles.longDesc);
            }catch(e){

            }
        }
    };

    me.createPerson = function(){
        newPersonId+=1;

        container.find('.previews').append(
            peopleSlider.buildPersonPreview(newPersonId, personTemplate)
        );

        container.find('.peopleDetails').append(
            peopleSlider.buildPersonDetails(newPersonId, personTemplate)
        );

        //Makes the slide recalculate the frame count
        peopleSlider.apertureSlider.init();

        peopleSlider.selectPerson(newPersonId);

        peopleSlider.attachClickEvents();

        me.makePersonEditable(newPersonId);
    };

    me.deletePerson = function(){
        var personEles = peopleSlider.getPersonElements(
            $(this).attr('data-personId')
        );
        personEles.details.remove();
        personEles.preview.remove();

        //Select first person in case we deleted the selected person
        peopleSlider.selectPerson($('.person').attr('data-personId'));

        //Makes the slide recalculate the frame count
        peopleSlider.apertureSlider.init();
    };

    me.editPersonImages = function(){
        var clickedEle = $(this);
        var personId = clickedEle.attr('data-personId');

        //Used for double clicking on images
        if(typeof(personId)=='undefined'){
            personId = clickedEle
                .closest('[data-personId]').attr('data-personId');
        }

        var personEles = peopleSlider.getPersonElements(personId);

        var smallImage = $.dialogIn(
            'image',
            'Small Preview Image (150px by 100px)',
            personEles.smallImage.attr('src')
        );
        var largeImage = $.dialogIn(
            'image',
            'Large Details Image (200px wide)',
            personEles.largeImage.attr('src')
        );

        var form = $('<form></form>')
            .addClass('simple')
            .append(smallImage, largeImage)
            .dialog({
                title:'Properties',
                modal:true,
                width:620,
                buttons:{
                    Cancel:function () {
                        $(this).dialog("close");
                    },
                    Ok:function () {

                        //Get user-entered data from form
                        personEles.smallImage.attr('src', smallImage.val());
                        personEles.largeImage.attr('src', largeImage.val());
                        personEles.largeImage.attr(
                            'delayed-src', largeImage.val()
                        );

                        $(this).dialog('close');
                    }
                }
            }
        );
    };

    /**
     * Called by content management system to get this plugins data for saving
     * on the server
     *
     * @return {Object}
     */
    me.getSaveData = function () {

        var people = [];

        $.each(
            container.find('.person'),
            function(){
                var personEles = peopleSlider.getPersonElements(
                    $(this).attr('data-personId')
                );
                people.push(
                    {
                        'smallImage' :personEles.smallImage.attr('src'),
                        'largeImage' : personEles.largeImage.attr('src'),
                        'shortDesc' : personEles.shortDesc.html(),
                        'longDesc' : personEles.longDesc.html()
                    }
                );
            }
        );

        return {
            people:people
        }
    }

};<|endoftext|>


<commit_before>import emailLib from '../../lib/email';
import Promise from 'bluebird';
import config from 'config';
import request from 'request-promise';
import _ from 'lodash';
import crypto from 'crypto';
import debug from 'debug';
import models, { sequelize, Op } from '../../models';
import errors from '../../lib/errors';

const debugEmail = debug('email');
const debugWebhook = debug('webhook');

export const unsubscribe = (req, res, next) => {
  const { type, email, slug, token } = req.params;

  const identifier = `${email}.${slug || 'any'}.${type}.${
    config.keys.opencollective.secret
  }`;
  const computedToken = crypto
    .createHash('md5')
    .update(identifier)
    .digest('hex');
  if (token !== computedToken) {
    return next(new errors.BadRequest('Invalid token'));
  }

  Promise.all([
    models.Collective.findOne({ where: { slug } }),
    models.User.findOne({ where: { email } }),
  ])
    .then(results => {
      if (!results[1])
        throw new errors.NotFound(`Cannot find a user with email "${email}"`);

      return results[1].unsubscribe(results[0] && results[0].id, type, 'email');
    })
    .then(() => res.send({ response: 'ok' }))
    .catch(next);
};

// TODO: move to emailLib.js
const sendEmailToList = (to, email) => {
  debugEmail('sendEmailToList', to, 'email data: ', email);
  const { mailinglist, collectiveSlug, type } = getNotificationType(to);
  email.from =
    email.from ||
    `${collectiveSlug} collective <hello@${collectiveSlug}.opencollective.com>`;
  email.collective = email.collective || { slug: collectiveSlug }; // used for the unsubscribe url

  return models.Notification.getSubscribersUsers(collectiveSlug, mailinglist)
    .tap(subscribers => {
      if (subscribers.length === 0)
        throw new errors.NotFound(
          `No subscribers found in ${collectiveSlug} for email type ${type}`,
        );
    })
    .then(results => results.map(r => r.email))
    .then(recipients => {
      debugEmail(
        `Sending email from ${email.from} to ${to} (${
          recipients.length
        } recipient(s))`,
      );
      return Promise.map(recipients, recipient => {
        if (email.template) {
          return emailLib.send(email.template, to, email, {
            from: email.from,
            bcc: recipient,
            type,
          });
        } else {
          email.body += '\n<!-- OpenCollective.com -->\n'; // watermark to identify if email has already been processed
          return emailLib.sendMessage(to, email.subject, email.body, {
            from: email.from,
            bcc: recipient,
            type,
          });
        }
      });
    })
    .catch(e => {
      console.error('error in sendEmailToList', e);
      throw e;
    });
};

export const approve = (req, res, next) => {
  const { messageId } = req.query;
  const approverEmail = req.query.approver;
  const mailserver = req.query.mailserver || 'so';

  let approver, sender;
  let email = {};

  const fetchSenderAndApprover = email => {
    const where = {
      [Op.or]: [{ email: approverEmail }, { email: email.sender }],
    };
    sender = { name: email.From, email: email.sender }; // default value
    return models.User.findAll({ where })
      .then(users => {
        users.map(user => {
          if (approverEmail === user.email) approver = user;
          if (email.sender === user.email) sender = user;
        });
      })
      .catch(e => {
        console.error('err: ', e);
      });
  };

  const requestOptions = {
    json: true,
    auth: {
      user: 'api',
      pass: config.mailgun.api_key,
    },
  };

  return request
    .get(
      `https://${mailserver}.api.mailgun.net/v3/domains/opencollective.com/messages/${messageId}`,
      requestOptions,
    )
    .then(json => {
      email = json;
      return email;
    })
    .then(fetchSenderAndApprover)
    .then(() => {
      const emailData = {
        template: 'email.message',
        subject: email.Subject,
        body: email['body-html'] || email['body-plain'],
        to: email.To,
        sender: _.pick(sender, ['email', 'name', 'image']),
      };
      if (approver && approver.email !== sender.email)
        emailData.approver = _.pick(approver, ['email', 'name', 'image']);

      return sendEmailToList(email.To, emailData);
    })
    .then(() =>
      res.send(
        `Email from ${email.sender} with subject "${
          email.Subject
        }" approved for the ${email.To} mailing list`,
      ),
    )
    .catch(e => {
      if (e.statusCode === 404)
        return next(
          new errors.NotFound(
            `Message ${messageId} not found on the ${mailserver} server`,
          ),
        );
      else return next(e);
    });
};

export const getNotificationType = email => {
  debugEmail('getNotificationType', email);
  let tokens;
  if (email.match(/<.+@.+\..+>/)) {
    tokens = email.match(/<(.+)@(.+)\.opencollective\.com>/i);
  } else {
    tokens = email.match(/(.+)@(.+)\.opencollective\.com/i);
  }
  if (!tokens) return {};
  const collectiveSlug = tokens[2];
  let mailinglist = tokens[1];
  if (
    ['info', 'hello', 'members', 'admins', 'admins'].indexOf(mailinglist) !== -1
  ) {
    mailinglist = 'admins';
  }
  const type = `mailinglist.${mailinglist}`;
  const res = { collectiveSlug, mailinglist, type };
  debugEmail('getNotificationType', res);
  return res;
};

export const webhook = (req, res, next) => {
  const email = req.body;
  const { recipient } = email;
  debugWebhook('>>> webhook received', JSON.stringify(email));
  const { mailinglist, collectiveSlug } = getNotificationType(recipient);

  if (!collectiveSlug) {
    return res.send(`Invalid recipient (${recipient}), skipping`);
  }

  debugWebhook(
    `email received for ${mailinglist} mailinglist of ${collectiveSlug}`,
  );
  const body = email['body-html'] || email['body-plain'];

  let collective;

  // If receive an email that has already been processed, we skip it
  // (it happens since we send the approved email to the mailing list and add the recipients in /bcc)
  if (body.indexOf('<!-- OpenCollective.com -->') !== -1) {
    debugWebhook(
      `Email from ${email.from} with subject ${
        email.subject
      } already processed, skipping`,
    );
    return res.send('Email already processed, skipping');
  }

  // If an email is sent to [info|hello|members|admins|organizers]@:collectiveSlug.opencollective.com,
  // we simply forward it to admins who subscribed to that mailinglist (no approval process)
  if (mailinglist === 'admins') {
    return sendEmailToList(recipient, {
      subject: email.subject,
      body,
      from: email.from,
    })
      .then(() => res.send('ok'))
      .catch(e => {
        debugWebhook('Error: ', e);
        next(e);
      });
  }

  // If the email is sent to :tierSlug or :eventSlug@:collectiveSlug.opencollective.com
  // We leave the original message on the mailgun server
  // and we send the email to the admins of the collective for approval
  // once approved, we will fetch the original email from the server and send it to all recipients
  let subscribers;
  models.Collective.find({ where: { slug: collectiveSlug } })
    .tap(g => {
      if (!g) throw new Error('collective_not_found');
      collective = g;
    })
    // We fetch all the recipients of that mailing list to give a preview in the approval email
    .then(collective => models.Notification.getSubscribersCollectives(collective.slug, mailinglist))
    .tap(results => {
      debugWebhook('getSubscribers', mailinglist, results);
      if (results.length === 0) throw new Error('no_subscribers');
      subscribers = results.map(s => {
        if (s.image) {
          s.roundedAvatar = `https://res.cloudinary.com/opencollective/image/fetch/c_thumb,g_face,h_48,r_max,w_48,bo_3px_solid_white/c_thumb,h_48,r_max,w_48,bo_2px_solid_rgb:66C71A/e_trim/f_auto/${encodeURIComponent(
            s.image,
          )}`;
        } else {
          s.roundedAvatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(s.name)}&rounded=true&size=48`;
        }
        return s;
      });
    })
    // We fetch all the admins of the collective to whom we will send the email to approve
    .then(() => {
      return sequelize.query(
        `
        SELECT * FROM "Users" u
        LEFT JOIN "Members" m ON m."CreatedByUserId"=u.id
        WHERE m."CollectiveId"=:collectiveid AND m.role=:role AND m."deletedAt" IS NULL
      `,
        {
          replacements: { collectiveid: collective.id, role: 'ADMIN' },
          model: models.User,
        },
      );
    })
    .tap(admins => {
      if (admins.length === 0) throw new Error('no_admins');
    })
    .then(admins => {
      const messageId = email['message-url'].substr(
        email['message-url'].lastIndexOf('/') + 1,
      );
      const mailserver = email['message-url'].substring(
        8,
        email['message-url'].indexOf('.'),
      );
      const getData = user => {
        return {
          from: email.from,
          subject: email.subject,
          body: email['body-html'] || email['body-plain'],
          subscribers,
          latestSubscribers: subscribers.slice(0, 15),
          approve_url: `${
            config.host.website
          }/api/services/email/approve?mailserver=${mailserver}&messageId=${messageId}&approver=${encodeURIComponent(
            user.email,
          )}`,
        };
      };
      // We send the email to each admin with
      // to: admins@:collectiveSlug.opencollective.com
      // bcc: admin.email
      // body: includes mailing list, recipients, preview of the email and approve button
      return Promise.map(admins, admin =>
        emailLib.send(
          'email.approve',
          `admins@${collectiveSlug}.opencollective.com`,
          getData(admin),
          { bcc: admin.email },
        ),
      );
    })
    .then(() => res.send('Mailgun webhook processed successfully'))
    .catch(e => {
      switch (e.message) {
        case 'no_subscribers':
          debugWebhook('No subscribers');
          /**
           * TODO
           * If there is no such mailing list,
           * - if the sender is a ADMIN, we send an email to confirm to create the mailing list
           *   with the people in /cc as initial subscribers
           * - if the sender is unknown, we return an email suggesting to contact info@:collectiveSlug.opencollective.com
           */
          return res.send({
            error: { message: `There is no user subscribed to ${recipient}` },
          });

        case 'mailinglist_not_found':
          debugWebhook('Mailing list not found');
          return res.send({
            error: {
              message: `Invalid mailing list address ${mailinglist}@${collectiveSlug}.opencollective.com`,
            },
          });

        case 'collective_not_found':
          debugWebhook('Collective not found');
          /**
           * TODO
           * If there is no such collective, we send an email to confirm to create the collective
           * with the people in /cc as initial admins
           */
          return res.send({
            error: {
              message: `There is no collective with slug ${collectiveSlug}`,
            },
          });

        case 'no_admins':
          return res.send({
            error: {
              message: `There is no admins to approve emails sent to ${
                email.recipient
              }`,
            },
          });

        default:
          debugWebhook('Unknown error:', e);
          return next(e);
      }
    });
};
<commit_msg>fix(email.message): fix email sent by [object Promise]<commit_after>import emailLib from '../../lib/email';
import Promise from 'bluebird';
import config from 'config';
import request from 'request-promise';
import { pick } from 'lodash';
import crypto from 'crypto';
import debug from 'debug';
import models, { sequelize, Op } from '../../models';
import errors from '../../lib/errors';

const debugEmail = debug('email');
const debugWebhook = debug('webhook');

export const unsubscribe = (req, res, next) => {
  const { type, email, slug, token } = req.params;

  const identifier = `${email}.${slug || 'any'}.${type}.${
    config.keys.opencollective.secret
  }`;
  const computedToken = crypto
    .createHash('md5')
    .update(identifier)
    .digest('hex');
  if (token !== computedToken) {
    return next(new errors.BadRequest('Invalid token'));
  }

  Promise.all([
    models.Collective.findOne({ where: { slug } }),
    models.User.findOne({ where: { email } }),
  ])
    .then(results => {
      if (!results[1])
        throw new errors.NotFound(`Cannot find a user with email "${email}"`);

      return results[1].unsubscribe(results[0] && results[0].id, type, 'email');
    })
    .then(() => res.send({ response: 'ok' }))
    .catch(next);
};

// TODO: move to emailLib.js
const sendEmailToList = (to, email) => {
  debugEmail('sendEmailToList', to, 'email data: ', email);
  const { mailinglist, collectiveSlug, type } = getNotificationType(to);
  email.from =
    email.from ||
    `${collectiveSlug} collective <hello@${collectiveSlug}.opencollective.com>`;
  email.collective = email.collective || { slug: collectiveSlug }; // used for the unsubscribe url

  return models.Notification.getSubscribersUsers(collectiveSlug, mailinglist)
    .tap(subscribers => {
      if (subscribers.length === 0)
        throw new errors.NotFound(
          `No subscribers found in ${collectiveSlug} for email type ${type}`,
        );
    })
    .then(results => results.map(r => r.email))
    .then(recipients => {
      debugEmail(
        `Sending email from ${email.from} to ${to} (${
          recipients.length
        } recipient(s))`,
      );
      return Promise.map(recipients, recipient => {
        if (email.template) {
          return emailLib.send(email.template, to, email, {
            from: email.from,
            bcc: recipient,
            type,
          });
        } else {
          email.body += '\n<!-- OpenCollective.com -->\n'; // watermark to identify if email has already been processed
          return emailLib.sendMessage(to, email.subject, email.body, {
            from: email.from,
            bcc: recipient,
            type,
          });
        }
      });
    })
    .catch(e => {
      console.error('error in sendEmailToList', e);
      throw e;
    });
};

export const approve = (req, res, next) => {
  const { messageId } = req.query;
  const approverEmail = req.query.approver;
  const mailserver = req.query.mailserver || 'so';

  let approver, sender;
  let email = {};

  const fetchSenderAndApprover = email => {
    sender = { name: email.From, email: email.sender }; // default value
    const where = {
      [Op.or]: [{ email: approverEmail }, { email: email.sender }],
    };
    return models.User.findAll({
      attributes: ['email', 'CollectiveId'],
      where,
      include: { model: models.Collective, as: 'collective' },
    })
      .then(users => {
        users.map(user => {
          if (approverEmail === user.email) {
            approver = pick(user.collective, ['name', 'image']);
            approver.email = user.email;
          }
          if (email.sender === user.email) {
            sender = pick(user.collective, ['name', 'image']);
            sender.email = user.email;
          }
        });
      })
      .catch(e => {
        console.error('err: ', e);
      });
  };

  const requestOptions = {
    json: true,
    auth: {
      user: 'api',
      pass: config.mailgun.api_key,
    },
  };

  return request
    .get(
      `https://${mailserver}.api.mailgun.net/v3/domains/opencollective.com/messages/${messageId}`,
      requestOptions,
    )
    .then(json => {
      email = json;
      return email;
    })
    .then(fetchSenderAndApprover)
    .then(() => {
      const emailData = {
        template: 'email.message',
        subject: email.Subject,
        body: email['body-html'] || email['body-plain'],
        to: email.To,
        sender: pick(sender, ['email', 'name', 'image']),
      };
      if (approver && approver.email !== sender.email)
        emailData.approver = pick(approver, ['email', 'name', 'image']);

      return sendEmailToList(email.To, emailData);
    })
    .then(() =>
      res.send(
        `Email from ${email.sender} with subject "${
          email.Subject
        }" approved for the ${email.To} mailing list`,
      ),
    )
    .catch(e => {
      if (e.statusCode === 404)
        return next(
          new errors.NotFound(
            `Message ${messageId} not found on the ${mailserver} server`,
          ),
        );
      else return next(e);
    });
};

export const getNotificationType = email => {
  debugEmail('getNotificationType', email);
  let tokens;
  if (email.match(/<.+@.+\..+>/)) {
    tokens = email.match(/<(.+)@(.+)\.opencollective\.com>/i);
  } else {
    tokens = email.match(/(.+)@(.+)\.opencollective\.com/i);
  }
  if (!tokens) return {};
  const collectiveSlug = tokens[2];
  let mailinglist = tokens[1];
  if (
    ['info', 'hello', 'members', 'admins', 'admins'].indexOf(mailinglist) !== -1
  ) {
    mailinglist = 'admins';
  }
  const type = `mailinglist.${mailinglist}`;
  const res = { collectiveSlug, mailinglist, type };
  debugEmail('getNotificationType', res);
  return res;
};

export const webhook = (req, res, next) => {
  const email = req.body;
  const { recipient } = email;
  debugWebhook('>>> webhook received', JSON.stringify(email));
  const { mailinglist, collectiveSlug } = getNotificationType(recipient);

  if (!collectiveSlug) {
    return res.send(`Invalid recipient (${recipient}), skipping`);
  }

  debugWebhook(
    `email received for ${mailinglist} mailinglist of ${collectiveSlug}`,
  );
  const body = email['body-html'] || email['body-plain'];

  let collective;

  // If receive an email that has already been processed, we skip it
  // (it happens since we send the approved email to the mailing list and add the recipients in /bcc)
  if (body.indexOf('<!-- OpenCollective.com -->') !== -1) {
    debugWebhook(
      `Email from ${email.from} with subject ${
        email.subject
      } already processed, skipping`,
    );
    return res.send('Email already processed, skipping');
  }

  // If an email is sent to [info|hello|members|admins|organizers]@:collectiveSlug.opencollective.com,
  // we simply forward it to admins who subscribed to that mailinglist (no approval process)
  if (mailinglist === 'admins') {
    return sendEmailToList(recipient, {
      subject: email.subject,
      body,
      from: email.from,
    })
      .then(() => res.send('ok'))
      .catch(e => {
        debugWebhook('Error: ', e);
        next(e);
      });
  }

  // If the email is sent to :tierSlug or :eventSlug@:collectiveSlug.opencollective.com
  // We leave the original message on the mailgun server
  // and we send the email to the admins of the collective for approval
  // once approved, we will fetch the original email from the server and send it to all recipients
  let subscribers;
  models.Collective.find({ where: { slug: collectiveSlug } })
    .tap(g => {
      if (!g) throw new Error('collective_not_found');
      collective = g;
    })
    // We fetch all the recipients of that mailing list to give a preview in the approval email
    .then(collective => models.Notification.getSubscribersCollectives(collective.slug, mailinglist))
    .tap(results => {
      debugWebhook('getSubscribers', mailinglist, results);
      if (results.length === 0) throw new Error('no_subscribers');
      subscribers = results.map(s => {
        if (s.image) {
          s.roundedAvatar = `https://res.cloudinary.com/opencollective/image/fetch/c_thumb,g_face,h_48,r_max,w_48,bo_3px_solid_white/c_thumb,h_48,r_max,w_48,bo_2px_solid_rgb:66C71A/e_trim/f_auto/${encodeURIComponent(
            s.image,
          )}`;
        } else {
          s.roundedAvatar = `https://ui-avatars.com/api/?name=${encodeURIComponent(s.name)}&rounded=true&size=48`;
        }
        return s;
      });
    })
    // We fetch all the admins of the collective to whom we will send the email to approve
    .then(() => {
      return sequelize.query(
        `
        SELECT * FROM "Users" u
        LEFT JOIN "Members" m ON m."CreatedByUserId"=u.id
        WHERE m."CollectiveId"=:collectiveid AND m.role=:role AND m."deletedAt" IS NULL
      `,
        {
          replacements: { collectiveid: collective.id, role: 'ADMIN' },
          model: models.User,
        },
      );
    })
    .tap(admins => {
      if (admins.length === 0) throw new Error('no_admins');
    })
    .then(admins => {
      const messageId = email['message-url'].substr(
        email['message-url'].lastIndexOf('/') + 1,
      );
      const mailserver = email['message-url'].substring(
        8,
        email['message-url'].indexOf('.'),
      );
      const getData = user => {
        return {
          from: email.from,
          subject: email.subject,
          body: email['body-html'] || email['body-plain'],
          subscribers,
          latestSubscribers: subscribers.slice(0, 15),
          approve_url: `${
            config.host.website
          }/api/services/email/approve?mailserver=${mailserver}&messageId=${messageId}&approver=${encodeURIComponent(
            user.email,
          )}`,
        };
      };
      // We send the email to each admin with
      // to: admins@:collectiveSlug.opencollective.com
      // bcc: admin.email
      // body: includes mailing list, recipients, preview of the email and approve button
      return Promise.map(admins, admin =>
        emailLib.send(
          'email.approve',
          `admins@${collectiveSlug}.opencollective.com`,
          getData(admin),
          { bcc: admin.email },
        ),
      );
    })
    .then(() => res.send('Mailgun webhook processed successfully'))
    .catch(e => {
      switch (e.message) {
        case 'no_subscribers':
          debugWebhook('No subscribers');
          /**
           * TODO
           * If there is no such mailing list,
           * - if the sender is a ADMIN, we send an email to confirm to create the mailing list
           *   with the people in /cc as initial subscribers
           * - if the sender is unknown, we return an email suggesting to contact info@:collectiveSlug.opencollective.com
           */
          return res.send({
            error: { message: `There is no user subscribed to ${recipient}` },
          });

        case 'mailinglist_not_found':
          debugWebhook('Mailing list not found');
          return res.send({
            error: {
              message: `Invalid mailing list address ${mailinglist}@${collectiveSlug}.opencollective.com`,
            },
          });

        case 'collective_not_found':
          debugWebhook('Collective not found');
          /**
           * TODO
           * If there is no such collective, we send an email to confirm to create the collective
           * with the people in /cc as initial admins
           */
          return res.send({
            error: {
              message: `There is no collective with slug ${collectiveSlug}`,
            },
          });

        case 'no_admins':
          return res.send({
            error: {
              message: `There is no admins to approve emails sent to ${
                email.recipient
              }`,
            },
          });

        default:
          debugWebhook('Unknown error:', e);
          return next(e);
      }
    });
};
<|endoftext|>


<commit_before>const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    'window.$': 'jquery',
    jQuery: 'jquery',
    'window.jQuery': 'jquery',
    _: 'lodash',
  })
)

environment.plugins.append('CommonChunkVendor',
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor', // Vendor code
    minChunks: (module) => module.context && module.context.indexOf('node_modules') !== -1
  })
)

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
environment.plugins.insert('BundleAnalyzerPlugin', new BundleAnalyzerPlugin())

module.exports = environment
<commit_msg>comment out bundle analyzer<commit_after>const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    'window.$': 'jquery',
    jQuery: 'jquery',
    'window.jQuery': 'jquery',
    _: 'lodash',
  })
)

environment.plugins.append('CommonChunkVendor',
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor', // Vendor code
    minChunks: (module) => module.context && module.context.indexOf('node_modules') !== -1
  })
)

// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// environment.plugins.insert('BundleAnalyzerPlugin', new BundleAnalyzerPlugin())

module.exports = environment
<|endoftext|>


<commit_before>var Rect = (function () {
    /**
     * @class Rect
     * @constructor
     * @param {number} x
     * @param {number} y
     * @param {number} w
     * @param {number} h
     */
    function Rect( x, y, w, h ) {
        this.x = typeof x === 'number' ? x : 0.0;
        this.y = typeof y === 'number' ? y : 0.0;
        this.width = typeof w === 'number' ? w : 0.0;
        this.height = typeof h === 'number' ? h : 0.0;
    }
    JS.setClassName('Fire.Rect', Rect);

    /**
     * @method fromMinMax
     * @param {Vec2} v1
     * @param {Vec2} v2
     * @return {Rect}
     */
    Rect.fromMinMax = function ( v1, v2 ) {
        var min_x = Math.min( v1.x, v2.x );
        var min_y = Math.min( v1.y, v2.y );
        var max_x = Math.max( v1.x, v2.x );
        var max_y = Math.max( v1.y, v2.y );

        return new Rect ( min_x, min_y, max_x - min_x, max_y - min_y );
    };

    /**
     * @method fromVec2
     * @param {Vec2} leftTop
     * @param {Vec2} size
     * @return {Rect}
     */
    Rect.fromVec2 = function ( leftTop, size ) {
        return new Rect ( leftTop.x, leftTop.y, size.x, size.y );
    };

    /**
     * Check if rect contains
     * @method contain
     * @param a {Rect} Rect a
     * @param b {Rect} Rect b
     * @return {Number} The contains result, 1 is a contains b, -1 is b contains a, 0 is no contains
     */
    Rect.contain = function _Contain ( a, b ) {
        if ( a.x <= b.x &&
             a.x + a.width >= b.x + b.width &&
             a.y <= b.y &&
             a.y + a.height >= b.y + b.height )
        {
            // a contains b
            return 1;
        }
        if ( b.x <= a.x &&
             b.x + b.width >= a.x + a.width &&
             b.y <= a.y &&
             b.y + b.height >= a.y + a.height )
        {
            // b contains a
            return -1;
        }
        return 0;
    };

    Rect.prototype.clone = function () {
        return new Rect(this.x, this.y, this.width, this.height);
    };

    Rect.prototype.equals = function (other) {
        return this.x === other.x &&
               this.y === other.y &&
               this.width === other.width &&
               this.height === other.height;
    };

    Rect.prototype.toString = function () {
        return '(' + this.x.toFixed(2) + ', ' + this.y.toFixed(2) + ', ' + this.width.toFixed(2) +
               ', ' + this.height.toFixed(2) + ')';
    };

    Object.defineProperty(Rect.prototype, 'xMax', {
        get: function () { return this.x + this.width; }
    });

    Object.defineProperty(Rect.prototype, 'yMax', {
        get: function () { return this.y + this.height; }
    });

    Object.defineProperty(Rect.prototype, 'center', {
        get: function () {
            return new Fire.Vec2( this.x + this.width * 0.5,
                                  this.y + this.height * 0.5 );
        }
    });

    Rect.prototype.intersects = function ( rect ) {
        return Fire.Intersection.rectRect( this, rect );
    };

    Rect.prototype.contains = function ( point ) {
        if ( this.x <= point.x &&
             this.x + this.width >= point.x &&
             this.y <= point.y &&
             this.y + this.height >= point.y )
        {
            return true;
        }
        return false;
    };

    Rect.prototype.containsRect = function ( rect ) {
        if ( this.x <= rect.x &&
             this.x + this.width >= rect.x + rect.width &&
             this.y <= rect.y &&
             this.y + this.height >= rect.y + rect.height )
        {
            return true;
        }
        return false;
    };

    return Rect;
})();

Fire.Rect = Rect;

/**
 * The convenience method to create a new Rect
 * @method Fire.rect
 * @param {number} [x=0]
 * @param {number} [y=0]
 * @param {number} [w=0]
 * @param {number} [h=0]
 * @return {Rect}
 */
Fire.rect = function rect (x, y, w, h) {
    if (Array.isArray(x)) {
        return new Rect(x[0], x[1], x[2], x[3]);
    }
    else {
        return new Rect(x, y, w, h);
    }
};
<commit_msg>add Rect.xMin, yMin<commit_after>var Rect = (function () {
    /**
     * @class Rect
     * @constructor
     * @param {number} x
     * @param {number} y
     * @param {number} w
     * @param {number} h
     */
    function Rect( x, y, w, h ) {
        this.x = typeof x === 'number' ? x : 0.0;
        this.y = typeof y === 'number' ? y : 0.0;
        this.width = typeof w === 'number' ? w : 0.0;
        this.height = typeof h === 'number' ? h : 0.0;
    }
    JS.setClassName('Fire.Rect', Rect);

    /**
     * @method fromMinMax
     * @param {Vec2} v1
     * @param {Vec2} v2
     * @return {Rect}
     */
    Rect.fromMinMax = function ( v1, v2 ) {
        var min_x = Math.min( v1.x, v2.x );
        var min_y = Math.min( v1.y, v2.y );
        var max_x = Math.max( v1.x, v2.x );
        var max_y = Math.max( v1.y, v2.y );

        return new Rect ( min_x, min_y, max_x - min_x, max_y - min_y );
    };

    /**
     * @method fromVec2
     * @param {Vec2} leftTop
     * @param {Vec2} size
     * @return {Rect}
     */
    Rect.fromVec2 = function ( leftTop, size ) {
        return new Rect ( leftTop.x, leftTop.y, size.x, size.y );
    };

    /**
     * Check if rect contains
     * @method contain
     * @param a {Rect} Rect a
     * @param b {Rect} Rect b
     * @return {Number} The contains result, 1 is a contains b, -1 is b contains a, 0 is no contains
     */
    Rect.contain = function _Contain ( a, b ) {
        if ( a.x <= b.x &&
             a.x + a.width >= b.x + b.width &&
             a.y <= b.y &&
             a.y + a.height >= b.y + b.height )
        {
            // a contains b
            return 1;
        }
        if ( b.x <= a.x &&
             b.x + b.width >= a.x + a.width &&
             b.y <= a.y &&
             b.y + b.height >= a.y + a.height )
        {
            // b contains a
            return -1;
        }
        return 0;
    };

    Rect.prototype.clone = function () {
        return new Rect(this.x, this.y, this.width, this.height);
    };

    Rect.prototype.equals = function (other) {
        return this.x === other.x &&
               this.y === other.y &&
               this.width === other.width &&
               this.height === other.height;
    };

    Rect.prototype.toString = function () {
        return '(' + this.x.toFixed(2) + ', ' + this.y.toFixed(2) + ', ' + this.width.toFixed(2) +
               ', ' + this.height.toFixed(2) + ')';
    };

    Object.defineProperty(Rect.prototype, 'xMin', {
        get: function () { return this.x; },
        set: function (value) {
            this.width += this.x - value;
            this.x = value;
        }
    });

    Object.defineProperty(Rect.prototype, 'yMin', {
        get: function () { return this.y; },
        set: function (value) {
            this.height += this.y - value;
            this.y = value;
        }
    });

    Object.defineProperty(Rect.prototype, 'xMax', {
        get: function () { return this.x + this.width; },
        set: function (value) { this.width = value - this.x; }
    });

    Object.defineProperty(Rect.prototype, 'yMax', {
        get: function () { return this.y + this.height; },
        set: function (value) { this.height = value - this.y; }
    });

    Object.defineProperty(Rect.prototype, 'center', {
        get: function () {
            return new Fire.Vec2( this.x + this.width * 0.5,
                                  this.y + this.height * 0.5 );
        },
        set: function (value) {
            this.x = value.x - this.width * 0.5;
            this.y = value.y - this.height * 0.5;
        }
    });

    Rect.prototype.intersects = function ( rect ) {
        return Fire.Intersection.rectRect( this, rect );
    };

    Rect.prototype.contains = function ( point ) {
        if ( this.x <= point.x &&
             this.x + this.width >= point.x &&
             this.y <= point.y &&
             this.y + this.height >= point.y )
        {
            return true;
        }
        return false;
    };

    Rect.prototype.containsRect = function ( rect ) {
        if ( this.x <= rect.x &&
             this.x + this.width >= rect.x + rect.width &&
             this.y <= rect.y &&
             this.y + this.height >= rect.y + rect.height )
        {
            return true;
        }
        return false;
    };

    return Rect;
})();

Fire.Rect = Rect;

/**
 * The convenience method to create a new Rect
 * @method Fire.rect
 * @param {number} [x=0]
 * @param {number} [y=0]
 * @param {number} [w=0]
 * @param {number} [h=0]
 * @return {Rect}
 */
Fire.rect = function rect (x, y, w, h) {
    if (Array.isArray(x)) {
        return new Rect(x[0], x[1], x[2], x[3]);
    }
    else {
        return new Rect(x, y, w, h);
    }
};
<|endoftext|>


<commit_before>const r = require('./../../../db');
const client = require('./../../');

function flatten(array) {
	return array.reduce((acc, val) =>
		acc.concat(Array.isArray(val) ? flatten(val) : val), []);
}

function choice(array) {
	// Flatten the array
	const arr = flatten(array);
	// Select a random item in the array. That's it.
	return arr[Math.floor(Math.random() * arr.length)];
}

function generate(user, callback) {
	r.table('markov')
		.filter({ name: user })
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('Fuck!');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('Fuck!');

				// Find a first word
				let word = choice(result[0].start);

				// Add the first word to the array
				const title = [word];

				// If there's a next word available,
				// while (wordstats[word]) {
				while (Object.keys(result[0].stats).includes(word)) {
					// Select a random next word
					word = choice(result[0].stats[word]);

					// Add the next word to the array
					title.push(word);

					// If the word is a word that ends the sentence, stop
					if (result[0].end.includes(word)) break;
				}

				const author = client.users.get(result[0].author);

				let embed = {};

				if (author) {
					embed = {
						content: `If I recall correctly, ${author.username} said:`,
						embed: {
							description: title.join(' '),
							author: {
								name: `${author.username}#${author.discriminator}`,
								icon_url: author.avatarURL
							}
						}
					};
				} else {
					embed = {
						content: 'Seems like this person is offline, or has deleted themselves from all of eternity.'
					};
				}

				// Join the sentence and call the callback.
				callback(embed);
			});
		});
}

function save(message) {
	const keys = [];
	const stats = {};
	const words = message.content.split(/\s+/);
	// Get the first word
	const start = words[0];
	// Get the last word
	const end = words[words.length - 1];

	function callback() {
		r.table('markov')
			.filter({ author: message.author.id })
			.update({
				start: r.row('start').append(start),
				end: r.row('end').append(end),
				stats: r.row('stats').do(statistics =>
					r.expr(stats).keys().map(key =>
						r.expr([key, statistics(key).default([]).append(r.expr(stats)(key))])
					).coerceTo('object')
				)
			})
			.run(r.conn, (err) => {
				if (err) throw new Error('Fuck!');
			});
	}

	// For every word, make an object with every word said.
	words.forEach((word, i) => {
		// If it's the last word, don't try to continue.
		if (words.length === (i + 1)) {
			callback();
		} else if (keys.includes(word)) {
			// Push the next word into the statistics bin
			stats[word].push(words[i + 1]);
		} else {
			// Otherwise, create the array and then add the word.
			stats[word] = [words[i + 1]];
			keys.push(word);
		}
	});
}

function register(id, name, callback) {
	r.table('markov')
		.filter(
			r.row('author').eq(id).or(r.row('name').eq(name))
		)
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('An error occured while searching for preexisting IDs and names in the Markov database.');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('An error occured while converting the preexisting IDs and names in the Markov database.');
				if (result.length === 0) {
					r.table('markov')
						.insert({
							author: id,
							name,
							start: [],
							end: [],
							stats: {}
						})
						.run(r.conn, (err3) => {
							if (err3) throw new Error('An error occured while writing a registration to the Markov database.');
							callback(`You have successfully registered with the Markov database. After sending many messages to a server with ${config.get('name')} inside, you can run \`${name}pls\` to generate a random message.`);
						});
				} else {
					callback('You or your selected name has already been registered.');
				}
			});
		});
}

function unregister(id, callback) {
	r.table('markov')
		.filter({ author: id })
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('An error occured while searching for preexisting IDs and names in the Markov database.');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('An error occured while converting the preexisting IDs and names in the Markov database.');
				if (result.length === 0) {
					callback('You or your selected name has already been registered.');
				} else {
					r.table('markov')
						.filter({ author: id })
						.delete()
						.run(r.conn, (err3) => {
							if (err3) throw new Error('An error occured while removing a registration to the Markov database.');
							callback('You have successfully unregistered with the Markov database.');
						});
				}
			});
		});
}

exports.choice = choice;
exports.generate = generate;
exports.save = save;
exports.register = register;
exports.unregister = unregister;

// Maek new markovs.

// r.db("discord").table("markov").insert({
//   author: "190519304972664832",
//   name: "coil",
//   start: [],
//   end: [],
//   stats: {}
// })
<commit_msg>Remember config<commit_after>const r = require('./../../../db');
const client = require('./../../');
const config = require('config');

function flatten(array) {
	return array.reduce((acc, val) =>
		acc.concat(Array.isArray(val) ? flatten(val) : val), []);
}

function choice(array) {
	// Flatten the array
	const arr = flatten(array);
	// Select a random item in the array. That's it.
	return arr[Math.floor(Math.random() * arr.length)];
}

function generate(user, callback) {
	r.table('markov')
		.filter({ name: user })
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('Fuck!');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('Fuck!');

				// Find a first word
				let word = choice(result[0].start);

				// Add the first word to the array
				const title = [word];

				// If there's a next word available,
				// while (wordstats[word]) {
				while (Object.keys(result[0].stats).includes(word)) {
					// Select a random next word
					word = choice(result[0].stats[word]);

					// Add the next word to the array
					title.push(word);

					// If the word is a word that ends the sentence, stop
					if (result[0].end.includes(word)) break;
				}

				const author = client.users.get(result[0].author);

				let embed = {};

				if (author) {
					embed = {
						content: `If I recall correctly, ${author.username} said:`,
						embed: {
							description: title.join(' '),
							author: {
								name: `${author.username}#${author.discriminator}`,
								icon_url: author.avatarURL
							}
						}
					};
				} else {
					embed = {
						content: 'Seems like this person is offline, or has deleted themselves from all of eternity.'
					};
				}

				// Join the sentence and call the callback.
				callback(embed);
			});
		});
}

function save(message) {
	const keys = [];
	const stats = {};
	const words = message.content.split(/\s+/);
	// Get the first word
	const start = words[0];
	// Get the last word
	const end = words[words.length - 1];

	function callback() {
		r.table('markov')
			.filter({ author: message.author.id })
			.update({
				start: r.row('start').append(start),
				end: r.row('end').append(end),
				stats: r.row('stats').do(statistics =>
					r.expr(stats).keys().map(key =>
						r.expr([key, statistics(key).default([]).append(r.expr(stats)(key))])
					).coerceTo('object')
				)
			})
			.run(r.conn, (err) => {
				if (err) throw new Error('Fuck!');
			});
	}

	// For every word, make an object with every word said.
	words.forEach((word, i) => {
		// If it's the last word, don't try to continue.
		if (words.length === (i + 1)) {
			callback();
		} else if (keys.includes(word)) {
			// Push the next word into the statistics bin
			stats[word].push(words[i + 1]);
		} else {
			// Otherwise, create the array and then add the word.
			stats[word] = [words[i + 1]];
			keys.push(word);
		}
	});
}

function register(id, name, callback) {
	r.table('markov')
		.filter(
			r.row('author').eq(id).or(r.row('name').eq(name))
		)
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('An error occured while searching for preexisting IDs and names in the Markov database.');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('An error occured while converting the preexisting IDs and names in the Markov database.');
				if (result.length === 0) {
					r.table('markov')
						.insert({
							author: id,
							name,
							start: [],
							end: [],
							stats: {}
						})
						.run(r.conn, (err3) => {
							if (err3) throw new Error('An error occured while writing a registration to the Markov database.');
							callback(`You have successfully registered with the Markov database. After sending many messages to a server with ${config.get('name')} inside, you can run \`${name}pls\` to generate a random message.`);
						});
				} else {
					callback('You or your selected name has already been registered.');
				}
			});
		});
}

function unregister(id, callback) {
	r.table('markov')
		.filter({ author: id })
		.run(r.conn, (err1, cursor) => {
			if (err1) throw new Error('An error occured while searching for preexisting IDs and names in the Markov database.');
			cursor.toArray((err2, result) => {
				if (err2) throw new Error('An error occured while converting the preexisting IDs and names in the Markov database.');
				if (result.length === 0) {
					callback('You or your selected name has already been registered.');
				} else {
					r.table('markov')
						.filter({ author: id })
						.delete()
						.run(r.conn, (err3) => {
							if (err3) throw new Error('An error occured while removing a registration to the Markov database.');
							callback('You have successfully unregistered with the Markov database.');
						});
				}
			});
		});
}

exports.choice = choice;
exports.generate = generate;
exports.save = save;
exports.register = register;
exports.unregister = unregister;

// Maek new markovs.

// r.db("discord").table("markov").insert({
//   author: "190519304972664832",
//   name: "coil",
//   start: [],
//   end: [],
//   stats: {}
// })
<|endoftext|>