Browse Source

Merge branch 'master' into feat/editor-component-comment-write

yusuketk 7 years ago
parent
commit
e4a49f7352
47 changed files with 1212 additions and 522 deletions
  1. 1 0
      .babelrc
  2. 1 0
      .eslintrc.js
  3. 4 0
      .gitignore
  4. 4 0
      .vscode/settings.json
  5. 19 2
      CHANGES.md
  6. 4 5
      README.md
  7. 73 45
      config/webpack.common.js
  8. 42 85
      config/webpack.dev.js
  9. 34 0
      config/webpack.dll.js
  10. 45 96
      config/webpack.prod.js
  11. 1 1
      lib/crowi/express-init.js
  12. 1 1
      lib/crowi/index.js
  13. 150 13
      lib/locales/en-US/sandbox.md
  14. 150 13
      lib/locales/ja/sandbox.md
  15. 2 2
      lib/locales/ja/translation.json
  16. 10 19
      lib/models/page.js
  17. 9 9
      lib/routes/attachment.js
  18. 4 9
      lib/routes/page.js
  19. 21 2
      lib/service/logger/index.js
  20. 1 1
      lib/service/passport.js
  21. 1 1
      lib/views/_form.html
  22. 4 4
      lib/views/admin/customize.html
  23. 5 5
      lib/views/admin/security.html
  24. 1 1
      lib/views/admin/widget/theme-colorbox.html
  25. 1 1
      lib/views/layout/admin.html
  26. 15 14
      lib/views/layout/layout.html
  27. 5 5
      lib/views/me/external-accounts.html
  28. 5 5
      lib/views/modal/create_page.html
  29. 1 1
      lib/views/modal/shortcuts.html
  30. 6 7
      lib/views/page_presentation.html
  31. 1 1
      lib/views/widget/system-version.html
  32. 0 3
      local_modules/crowi-fileupload-local/index.js
  33. 28 20
      package.json
  34. 0 1
      public/css/.gitignore
  35. 1 1
      resource/js/app.js
  36. 2 1
      resource/js/components/Page/RevisionBody.js
  37. 0 1
      resource/js/components/PageEditor.js
  38. 7 0
      resource/js/components/PageEditor/AbstractEditor.js
  39. 37 1
      resource/js/components/PageEditor/CodeMirrorEditor.js
  40. 1 1
      resource/js/components/PageEditor/MarkdownListUtil.js
  41. 14 2
      resource/js/components/PageEditor/TextAreaEditor.js
  42. 2 0
      resource/js/ie11-polyfill.js
  43. 0 1
      resource/js/legacy/crowi-form.js
  44. 3 3
      resource/js/legacy/crowi.js
  45. 5 6
      resource/js/util/markdown-it/blockdiag.js
  46. 8 1
      resource/styles/scss/_wiki.scss
  47. 483 132
      yarn.lock

+ 1 - 0
.babelrc

@@ -1,4 +1,5 @@
 {
+  "plugins": ["lodash"],
   "presets": [
     ["env", {
       "targets": {

+ 1 - 0
.eslintrc.js

@@ -17,6 +17,7 @@ module.exports = {
     "window": true
   },
   "parserOptions": {
+    "ecmaVersion": 8,
     "ecmaFeatures": {
       "experimentalObjectRestSpread": true,
       "jsx": true

+ 4 - 0
.gitignore

@@ -18,8 +18,12 @@ package-lock.json
 
 # Dist #
 /report/
+/public/*.chunk.js
+/public/*.bundle.js
+/public/manifest.json
 /public/dll
 /public/js
+/public/styles
 /public/uploads
 /src/*/__build__/
 /__build__/**

+ 4 - 0
.vscode/settings.json

@@ -1,4 +1,8 @@
 {
   // 既定の改行文字。LF の場合には \n を CRLF の場合には \r\n を使用してください。
   "files.eol": "\n",
+  // 指定した構文に対してプロファイルを定義するか、特定の規則がある独自のプロファイルをご使用ください。
+  "emmet.syntaxProfiles": {
+    "javascript": "jsx"
+  }
 }

+ 19 - 2
CHANGES.md

@@ -1,9 +1,26 @@
 CHANGES
 ========
 
-## 3.1.6-RC
+## 3.1.8
 
-* (WIP) Feature: Support [blockdiag](http://blockdiag.com)
+* Support: Upgrade libs
+    * react-bootstrap-typeahead
+    * webpack
+
+## 3.1.7
+
+* Fix: Update hidden input 'pageForm[grant]' when save with Ctrl-S
+* Fix: Show alert message when conflict
+* Fix: `BLOCKDIAG_URI` environment variable doesn't work
+* Fix: Paste in markdown list doesn't work correctly
+* Support: Ensure to inject logger configuration from environment variables
+* Support: Upgrade libs
+    * sinon
+    * sinon-chai
+
+## 3.1.6
+
+* Feature: Support [blockdiag](http://blockdiag.com)
 * Feature: Add `BLOCKDIAG_URI` environment variable
 * Fix: Select modal for group is not shown 
 * Support: Upgrade libs

+ 4 - 5
README.md

@@ -4,8 +4,8 @@
   </a>
 </p>
 <p align="center">
-  <a href="https://github.com/weseek/crowi-plus/releases/latest"><img src="https://img.shields.io/github/release/weseek/crowi-plus.svg"></a>
-  <a href="https://growi-slackin.weseek.co.jp/"><img src="https://crowi-plus-slackin.weseek.co.jp/badge.svg"></a>
+  <a href="https://github.com/weseek/growi/releases/latest"><img src="https://img.shields.io/github/release/weseek/growi.svg"></a>
+  <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
 </p>
 
 <p align="center">
@@ -105,11 +105,10 @@ MONGO_URI=mongodb://MONGO_HOST:MONGO_PORT/growi npm start
 
 **DO NOT USE `npm install`**, use `yarn` instead.
 
-If you launch growi with Redis and ElasticSearch, add environment variables before `npm start` like following:
+If you launch growi with ElasticSearch, add environment variables before `npm start` like following:
 
 ```
 export MONGO_URI=mongodb://MONGO_HOST:MONGO_PORT/growi
-export REDIS_URL=redis://REDIS_HOST:REDIS_PORT/growi
 export ELASTICSEARCH_URI=http://ELASTICSEARCH_HOST:ELASTICSEARCH_PORT/growi
 npm start
 ```
@@ -158,8 +157,8 @@ Environment Variables
 * **Option**
     * NODE_ENV: `production` OR `development`.
     * PORT: Server port. default: `3000`
-    * REDIS_URL: URI to connect to Redis (to session store).
     * ELASTICSEARCH_URI: URI to connect to Elasticearch.
+    * REDIS_URI: URI to connect to Redis (to session store).
     * PLANTUML_URI: URI to connect to [PlantUML](http://plantuml.com/) server.
     * BLOCKDIAG_URI: URI to connect to [blockdiag](http://http://blockdiag.com/) server.
     * PASSWORD_SEED: A password seed used by password hash generator.

+ 73 - 45
config/webpack.common.js

@@ -8,32 +8,41 @@ const helpers = require('./helpers');
 /*
  * Webpack Plugins
  */
-const AssetsPlugin = require('assets-webpack-plugin');
-const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
+const WebpackAssetsManifest = require('webpack-assets-manifest');
+const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
 
 /*
  * Webpack configuration
  *
  * See: http://webpack.github.io/docs/configuration.html#cli
  */
-module.exports = function(options) {
+module.exports = (options) => {
   return {
-    entry: {
-      'app':                  './resource/js/app',
-      'legacy':               './resource/js/legacy/crowi',
-      'legacy-form':          './resource/js/legacy/crowi-form',
-      'legacy-admin':         './resource/js/legacy/crowi-admin',
-      'legacy-presentation':  './resource/js/legacy/crowi-presentation',
-      'plugin':               './resource/js/plugin',
-      'style':                './resource/styles/scss/style.scss',
-      'style-theme-default':  './resource/styles/scss/theme/default.scss',
-      'style-theme-default-dark':  './resource/styles/scss/theme/default-dark.scss',
-      'style-theme-nature':   './resource/styles/scss/theme/nature.scss',
-      'style-theme-mono-blue':   './resource/styles/scss/theme/mono-blue.scss',
-      'style-theme-future': './resource/styles/scss/theme/future.scss',
-      'style-theme-blue-night': './resource/styles/scss/theme/blue-night.scss',
-      'style-presentation':   './resource/styles/scss/style-presentation.scss',
-    },
+    mode: options.mode,
+    entry: Object.assign({
+      'js/app':                   './resource/js/app',
+      'js/legacy':                './resource/js/legacy/crowi',
+      'js/legacy-form':           './resource/js/legacy/crowi-form',
+      'js/legacy-admin':          './resource/js/legacy/crowi-admin',
+      'js/legacy-presentation':   './resource/js/legacy/crowi-presentation',
+      'js/plugin':                './resource/js/plugin',
+      'js/ie11-polyfill':         './resource/js/ie11-polyfill',
+      // styles
+      'styles/style':                './resource/styles/scss/style.scss',
+      'styles/style-presentation':   './resource/styles/scss/style-presentation.scss',
+      // themes
+      'styles/theme-default':        './resource/styles/scss/theme/default.scss',
+      'styles/theme-default-dark':   './resource/styles/scss/theme/default-dark.scss',
+      'styles/theme-nature':         './resource/styles/scss/theme/nature.scss',
+      'styles/theme-mono-blue':      './resource/styles/scss/theme/mono-blue.scss',
+      'styles/theme-future':         './resource/styles/scss/theme/future.scss',
+      'styles/theme-blue-night':     './resource/styles/scss/theme/blue-night.scss',
+    }, options.entry || {}),  // Merge with env dependent settings
+    output: Object.assign({
+      path: helpers.root('public'),
+      publicPath: '/',
+      filename: '[name].bundle.js',
+    }, options.output || {}), // Merge with env dependent settings
     externals: {
       // require("jquery") is external and available
       //  on the global var jQuery
@@ -53,7 +62,7 @@ module.exports = function(options) {
       }
     },
     module: {
-      rules: [
+      rules: options.module.rules.concat([
         {
           test: /.jsx?$/,
           exclude: {
@@ -64,10 +73,7 @@ module.exports = function(options) {
             ]
           },
           use: [{
-            loader: 'babel-loader?cacheDirectory',
-            options: {
-              plugins: ['lodash'],
-            }
+            loader: 'babel-loader?cacheDirectory'
           }]
         },
         {
@@ -98,40 +104,62 @@ module.exports = function(options) {
         */
         {
           test: /\.(eot|woff2?|svg|ttf)([?]?.*)$/,
-          use: 'file-loader',
+          use: 'null-loader',
         }
-      ]
+      ])
     },
-    plugins: [
+    plugins: options.plugins.concat([
 
-      new AssetsPlugin({
-        path: helpers.root('public/js'),
-        filename: 'webpack-assets.json',
-        prettyPrint: true,
-      }),
+      new WebpackAssetsManifest({ publicPath: true }),
 
-      new CommonsChunkPlugin({
-        name: 'commons',
-        chunks: ['app', 'legacy', 'legacy-form', 'legacy-admin'],
-        minChunks: module => /node_modules/.test(module.resource),
-      }),
-      new CommonsChunkPlugin({
-        name: 'commons',
-        chunks: ['commons', 'legacy-presentation'],
-      }),
-      new CommonsChunkPlugin({
-        name: 'commons',
-        chunks: ['commons', 'plugin'],
+      new webpack.DefinePlugin({
+        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
       }),
 
       // ignore
       new webpack.IgnorePlugin(/^\.\/lib\/deflate\.js/, /markdown-it-plantuml/),
 
+      new LodashModuleReplacementPlugin({
+        flattening: true
+      }),
+
       new webpack.ProvidePlugin({ // refs externals
         jQuery: 'jquery',
         $: 'jquery',
       }),
 
-    ]
+    ]),
+
+    devtool: options.devtool,
+    target: 'web', // Make web variables accessible to webpack, e.g. window
+    optimization: {
+      namedModules: true,
+      splitChunks: {
+        cacheGroups: {
+          commons: {
+            test: /resource/,
+            chunks: 'initial',
+            name: 'js/commons',
+            minChunks: 2,
+            minSize: 1,
+            priority: 20
+          },
+          vendors: {
+            test: /node_modules/,
+            chunks: (chunk) => {
+              return chunk.name != null && !chunk.name.match(/legacy-presentation|ie11-polyfill/);
+            },
+            name: 'js/vendors',
+            // minChunks: 2,
+            minSize: 1,
+            priority: 10,
+            enforce: true
+          }
+        }
+      },
+      minimizer: options.optimization.minimizer || [],
+    },
+    performance: options.performance || {},
+    stats: options.stats || {},
   };
 };

+ 42 - 85
config/webpack.dev.js

@@ -5,98 +5,55 @@
 const path = require('path');
 const webpack = require('webpack');
 const helpers = require('./helpers');
-const webpackMerge = require('webpack-merge');
-const webpackMergeDll = webpackMerge.strategy({plugins: 'replace'});
-const commonConfig = require('./webpack.common.js');
 
 /*
  * Webpack Plugins
  */
-const DllBundlesPlugin = require('webpack-dll-bundles-plugin').DllBundlesPlugin;
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
-/*
+/**
  * Webpack Constants
  */
 const ANALYZE = process.env.ANALYZE;
-const ENV = process.env.ENV = process.env.NODE_ENV = 'development';
-
-/*
- * Webpack configuration
- *
- * See: http://webpack.github.io/docs/configuration.html#cli
- */
-module.exports = function(options) {
-  return webpackMerge(commonConfig({ env: ENV }), {
-    devtool: 'cheap-module-eval-source-map',
-    entry: {
-      dev: './resource/js/dev',
-    },
-    output: {
-      path: helpers.root('public/js'),
-      publicPath: '/js/',
-      filename: '[name].bundle.js',
-    },
-    resolve: {
-      extensions: ['.js', '.json'],
-      modules: [helpers.root('src'), helpers.root('node_modules'), path.join(process.env.HOME, '.node_modules')],
-    },
-    module: {
-      rules: [
-        {
-          test: /\.css$/,
-          use: [
-            'style-loader',
-            { loader: 'css-loader', options: { sourceMap: true } },
-          ],
-          include: [helpers.root('resource/styles/scss')]
-        },
-        {
-          test: /\.scss$/,
-          use: [
-            'style-loader',
-            { loader: 'css-loader', options: { sourceMap: true } },
-            { loader: 'sass-loader', options: { sourceMap: true } },
-          ],
-          include: [helpers.root('resource/styles/scss')]
-        },
-      ],
-    },
-    plugins: [
-
-      new DllBundlesPlugin({
-        bundles: {
-          vendor: [
-            'axios',
-            'codemirror',
-            'date-fns',
-            'diff',
-            'diff2html',
-            'jquery-ui',
-            'markdown-it',
-            'metismenu',
-            'react',
-            'react-dom',
-            'react-bootstrap',
-            'react-bootstrap-typeahead',
-            'react-dropzone',
-            'socket.io-client',
-            'toastr',
-          ],
-        },
-        dllDir: helpers.root('public/dll'),
-        webpackConfig: webpackMergeDll(commonConfig({env: ENV}), {
-          devtool: undefined,
-          plugins: [],
-        })
-      }),
-
-      new webpack.NoEmitOnErrorsPlugin(),
-
-      new BundleAnalyzerPlugin({
-        analyzerMode: ANALYZE ? 'server' : 'disabled',
-      }),
 
-    ]
-  });
-};
+module.exports = require('./webpack.common')({
+  mode: 'development',
+  devtool: 'cheap-module-eval-source-map',
+  entry: {
+    'js/dev': './resource/js/dev',
+  },
+  resolve: {
+    // TODO merge in webpack.common.js
+    modules: [path.join(process.env.HOME, '.node_modules')],
+  },
+  module: {
+    rules: [
+      {
+        test: /\.scss$/,
+        use: [
+          'style-loader',
+          { loader: 'css-loader', options: { sourceMap: true } },
+          { loader: 'sass-loader', options: { sourceMap: true } },
+        ],
+        include: [helpers.root('resource/styles/scss')]
+      },
+    ],
+  },
+  plugins: [
+
+    new webpack.DllReferencePlugin({
+      context: helpers.root('public/dll'),
+      manifest: path.join(helpers.root('public/dll'), 'manifest.json')
+    }),
+
+    new BundleAnalyzerPlugin({
+      analyzerMode: ANALYZE ? 'server' : 'disabled',
+    }),
+
+  ],
+  optimization: {},
+  performance: {
+    hints: false
+  }
+
+});

+ 34 - 0
config/webpack.dll.js

@@ -0,0 +1,34 @@
+/**
+ * @author: Yuki Takei <yuki@weseek.co.jp>
+ */
+const path = require('path');
+const webpack = require('webpack');
+const helpers = require('./helpers');
+
+
+module.exports = {
+  mode: 'development',
+  entry: {
+    dlls: [
+      'react', 'react-dom', 'react-bootstrap', 'react-bootstrap-typeahead', 'react-i18next',
+      'codemirror', 'react-codemirror2', 'react-dropzone',
+      'jquery-slimscroll', 'jquery-ui',
+      'markdown-it',
+      'diff2html', 'lodash', 'i18next',
+    ]
+  },
+  output: {
+    path: helpers.root('public/dll'),
+    filename: 'dll.js'
+  },
+  resolve: {
+    extensions: ['.js', '.json'],
+    modules: [helpers.root('src'), helpers.root('node_modules')],
+  },
+  plugins: [
+    new webpack.DllPlugin({
+      path: path.join(helpers.root('public/dll'), 'manifest.json'),
+      name: '[name]_[hash]'
+    })
+  ]
+};

+ 45 - 96
config/webpack.prod.js

@@ -3,117 +3,66 @@
  */
 
 const helpers = require('./helpers');
-const webpack = require('webpack');
-const webpackMerge = require('webpack-merge'); // used to merge webpack configs
-const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev
 
 /**
  * Webpack Plugins
  */
-const ExtractTextPlugin = require('extract-text-webpack-plugin');
-const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
-const OptimizeJsPlugin = require('optimize-js-plugin');
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
 /**
  * Webpack Constants
  */
 const ANALYZE = process.env.ANALYZE;
-const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
 
-module.exports = function(env) {
-  return webpackMerge(commonConfig({ env: ENV }), {
-    devtool: undefined,
-    output: {
-      path: helpers.root('public/js'),
-      publicPath: '/js/',
-      filename: '[name].[chunkhash].bundle.js',
-      sourceMapFilename: '[name].[chunkhash].bundle.map',
-      chunkFilename: '[id].[chunkhash].chunk.js'
-    },
-    module: {
-      rules: [
-        {
-          test: /\.scss$/,
-          use: ExtractTextPlugin.extract({
-            fallback: 'style-loader',
-            use: [
-              { loader: 'css-loader', options: {
-                sourceMap: false,
-                minimize: true
-              } },
-              { loader: 'postcss-loader', options: {
-                sourceMap: false,
-                plugins: (loader) => [
-                  require('autoprefixer')()
-                ]
-              } },
-              { loader: 'sass-loader', options: { sourceMap: false } }
+module.exports = require('./webpack.common')({
+  mode: 'production',
+  devtool: undefined,
+  output: {
+    filename: '[name].[chunkhash].bundle.js',
+    chunkFilename: '[name].[chunkhash].chunk.js'
+  },
+  module: {
+    rules: [
+      {
+        test: /\.scss$/,
+        use: [
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          { loader: 'postcss-loader', options: {
+            sourceMap: false,
+            plugins: (loader) => [
+              require('autoprefixer')()
             ]
-          }),
-          include: [helpers.root('resource/styles/scss')]
-        }
-      ]
-    },
-    plugins: [
+          } },
+          'sass-loader'
+        ],
+        include: [helpers.root('resource/styles/scss')]
+      }
+    ]
+  },
+  plugins: [
 
-      new webpack.DefinePlugin({
-        'process.env': {
-          NODE_ENV: JSON.stringify(ENV),
-        }
-      }),
-
-      new ExtractTextPlugin('[name].[contenthash].css'),
+    new MiniCssExtractPlugin({
+      filename: '[name].[hash].css',
+    }),
 
-      new OptimizeJsPlugin({
-        sourceMap: false
-      }),
+    new BundleAnalyzerPlugin({
+      analyzerMode: ANALYZE ? 'static' : 'disabled',
+      reportFilename: helpers.root('report/bundle-analyzer.html'),
+      openAnalyzer: false,
+    }),
 
+  ],
+  optimization: {
+    minimizer: [
       new UglifyJsPlugin({
-        // beautify: true, //debug
-        // mangle: false, //debug
-        // dead_code: false, //debug
-        // unused: false, //debug
-        // deadCode: false, //debug
-        // compress: {
-        //   screw_ie8: true,
-        //   keep_fnames: true,
-        //   drop_debugger: false,
-        //   dead_code: false,
-        //   unused: false
-        // }, // debug
-        // comments: true, //debug
-
-
-        beautify: false, //prod
-        output: {
-          comments: false
-        }, //prod
-        mangle: {
-          screw_ie8: true
-        }, //prod
-        compress: {
-          screw_ie8: true,
-          warnings: false,
-          conditionals: true,
-          unused: true,
-          comparisons: true,
-          sequences: true,
-          dead_code: true,
-          evaluate: true,
-          if_return: true,
-          join_vars: true,
-          negate_iife: false // we need this for lazy v8
-        },
+        cache: true,
+        parallel: true,
       }),
-
-      new BundleAnalyzerPlugin({
-        analyzerMode: ANALYZE ? 'static' : 'disabled',
-        reportFilename: helpers.root('report/bundle-analyzer.html'),
-        openAnalyzer: false,
-      }),
-
+      new OptimizeCSSAssetsPlugin({})
     ],
-
-  });
-};
+  },
+});

+ 1 - 1
lib/crowi/express-init.js

@@ -89,7 +89,7 @@ module.exports = function(crowi, app) {
   app.use(express.static(crowi.publicDir, staticOption));
   app.engine('html', swig.renderFile);
   app.use(webpackAssets(
-    path.join(crowi.publicDir, 'js/webpack-assets.json'),
+    path.join(crowi.publicDir, 'manifest.json'),
     { devMode: (crowi.node_env === 'development') })
   );
   // app.set('view cache', false);  // Default: true in production, otherwise undefined. -- 2017.07.04 Yuki Takei

+ 1 - 1
lib/crowi/index.js

@@ -147,7 +147,7 @@ Crowi.prototype.setupSessionConfig = function() {
     , session  = require('express-session')
     , sessionConfig
     , sessionAge = (1000*3600*24*30)
-    , redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URL || null
+    , redisUrl = this.env.REDISTOGO_URL || this.env.REDIS_URI || this.env.REDIS_URL || null
     , mongoUrl = getMongoUrl(this.env)
     ;
 

+ 150 - 13
lib/locales/en-US/sandbox.md

@@ -48,7 +48,7 @@
 
 ## Br 改行
 
-改行の前に半角スペース`  `を2つ記述します。  
+改行の前に半角スペース`  `を2つ記述します。
 ***この挙動は、オプションで変更可能です***
 
 ```
@@ -58,7 +58,7 @@ piyo
 ```
 
 hoge
-fuga  
+fuga
 piyo
 
 ## Blockquotes 引用
@@ -66,12 +66,12 @@ piyo
 先頭に`>`を記述します。ネストは`>`を多重に記述します。
 
 ```
-> 引用  
+> 引用
 > 引用
 >> 多重引用
 ```
 
-> 引用  
+> 引用
 > 引用
 >> 多重引用
 
@@ -235,7 +235,7 @@ ___
 &lt;/user/admin1>
 ```
 
-[/Sandbox]  
+[/Sandbox]
 </user/admin1>
 
 ## Pukiwiki like linker
@@ -243,7 +243,7 @@ ___
 (available by [weseek/growi-plugin-pukiwiki-like-linker
 ](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
 
-最も柔軟な Linker です。  
+最も柔軟な Linker です。
 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
 
 ```
@@ -251,14 +251,14 @@ ___
 Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 ```
 
-[[../user]]  
+[[../user]]
 Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 
 # :pencil: Lists
 
 ## Ul 箇条書きリスト
 
-ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。  
+ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。
 ネストはタブで表現します。
 
 ```
@@ -281,7 +281,7 @@ Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 
 ## Ol 番号付きリスト
 
-`番号.`を先頭に記述します。ネストはタブで表現します。  
+`番号.`を先頭に記述します。ネストはタブで表現します。
 番号は自動的に採番されるため、すべての行を1.と記述するのがお勧めです。
 
 ```
@@ -567,7 +567,7 @@ class BaseClass
 namespace net.dummy #DDDDDD {
     .BaseClass <|-- Person
     Meeting o-- Person
-    
+
     .BaseClass <|- Meeting
 }
 
@@ -594,11 +594,11 @@ package "Some Group" {
   HTTP - [First Component]
   [Another Component]
 }
- 
+
 node "Other Groups" {
   FTP - [Second Component]
   [First Component] --> FTP
-} 
+}
 
 cloud {
   [Example 1]
@@ -646,10 +646,147 @@ state State3 {
 State3 --> State3 : Failed
 State3 --> [*] : Succeeded / Save Result
 State3 --> [*] : Aborted
- 
+
 @enduml
 
 <!-- Reset PlantUML -->
 <div class="clearfix"></div>
 
+# :pencil: blockdiag
+
+See [blockdiag](http://blockdiag.com/).
+
+## blockdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: blockdiag
+blockdiag {
+   A -> B -> C -> D;
+   A -> E -> F -> G;
+}
+:::
+
+</div>
+
+## seqdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: seqdiag
+seqdiag {
+  browser  -> webserver [label = "GET /index.html"];
+  browser <-- webserver;
+  browser  -> webserver [label = "POST /blog/comment"];
+              webserver  -> database [label = "INSERT comment"];
+              webserver <-- database;
+  browser <-- webserver;
+}
+:::
+
+</div>
+
+## actdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: actdiag
+actdiag {
+  write -> convert -> image
 
+  lane user {
+     label = "User"
+     write [label = "Writing reST"];
+     image [label = "Get diagram IMAGE"];
+  }
+  lane actdiag {
+     convert [label = "Convert reST to Image"];
+  }
+}
+:::
+
+</div>
+
+## nwdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: nwdiag
+nwdiag {
+  network dmz {
+      address = "210.x.x.x/24"
+
+      web01 [address = "210.x.x.1"];
+      web02 [address = "210.x.x.2"];
+  }
+  network internal {
+      address = "172.x.x.x/24";
+
+      web01 [address = "172.x.x.1"];
+      web02 [address = "172.x.x.2"];
+      db01;
+      db02;
+  }
+}
+:::
+
+</div>
+
+## rackdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: rackdiag
+rackdiag {
+  // define height of rack
+  8U;
+
+  // define rack items
+  1: UPS [2U];
+  3: DB Server
+  4: Web Server
+  5: Web Server
+  6: Web Server
+  7: Load Balancer
+  8: L3 Switch
+}
+:::
+
+</div>
+
+## packetdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: packetdiag
+packetdiag {
+  colwidth = 32
+  node_height = 72
+
+  0-15: Source Port
+  16-31: Destination Port
+  32-63: Sequence Number
+  64-95: Acknowledgment Number
+  96-99: Data Offset
+  100-105: Reserved
+  106: URG [rotate = 270]
+  107: ACK [rotate = 270]
+  108: PSH [rotate = 270]
+  109: RST [rotate = 270]
+  110: SYN [rotate = 270]
+  111: FIN [rotate = 270]
+  112-127: Window
+  128-143: Checksum
+  144-159: Urgent Pointer
+  160-191: (Options and Padding)
+  192-223: data [colheight = 3]
+}
+:::
+
+</div>

+ 150 - 13
lib/locales/ja/sandbox.md

@@ -48,7 +48,7 @@
 
 ## Br 改行
 
-改行の前に半角スペース`  `を2つ記述します。  
+改行の前に半角スペース`  `を2つ記述します。
 ***この挙動は、オプションで変更可能です***
 
 ```
@@ -58,7 +58,7 @@ piyo
 ```
 
 hoge
-fuga  
+fuga
 piyo
 
 ## Blockquotes 引用
@@ -66,12 +66,12 @@ piyo
 先頭に`>`を記述します。ネストは`>`を多重に記述します。
 
 ```
-> 引用  
+> 引用
 > 引用
 >> 多重引用
 ```
 
-> 引用  
+> 引用
 > 引用
 >> 多重引用
 
@@ -235,7 +235,7 @@ ___
 &lt;/user/admin1>
 ```
 
-[/Sandbox]  
+[/Sandbox]
 </user/admin1>
 
 ## Pukiwiki like linker
@@ -243,7 +243,7 @@ ___
 (available by [weseek/growi-plugin-pukiwiki-like-linker
 ](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
 
-最も柔軟な Linker です。  
+最も柔軟な Linker です。
 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
 
 ```
@@ -251,14 +251,14 @@ ___
 Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 ```
 
-[[../user]]  
+[[../user]]
 Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 
 # :pencil: Lists
 
 ## Ul 箇条書きリスト
 
-ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。  
+ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。
 ネストはタブで表現します。
 
 ```
@@ -281,7 +281,7 @@ Bootstrap3のExampleは[[こちら>./Bootstrap3]]
 
 ## Ol 番号付きリスト
 
-`番号.`を先頭に記述します。ネストはタブで表現します。  
+`番号.`を先頭に記述します。ネストはタブで表現します。
 番号は自動的に採番されるため、すべての行を1.と記述するのがお勧めです。
 
 ```
@@ -567,7 +567,7 @@ class BaseClass
 namespace net.dummy #DDDDDD {
     .BaseClass <|-- Person
     Meeting o-- Person
-    
+
     .BaseClass <|- Meeting
 }
 
@@ -594,11 +594,11 @@ package "Some Group" {
   HTTP - [First Component]
   [Another Component]
 }
- 
+
 node "Other Groups" {
   FTP - [Second Component]
   [First Component] --> FTP
-} 
+}
 
 cloud {
   [Example 1]
@@ -646,10 +646,147 @@ state State3 {
 State3 --> State3 : Failed
 State3 --> [*] : Succeeded / Save Result
 State3 --> [*] : Aborted
- 
+
 @enduml
 
 <!-- Reset PlantUML -->
 <div class="clearfix"></div>
 
+# :pencil: blockdiag
+
+See [blockdiag](http://blockdiag.com/).
+
+## blockdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: blockdiag
+blockdiag {
+   A -> B -> C -> D;
+   A -> E -> F -> G;
+}
+:::
+
+</div>
+
+## seqdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: seqdiag
+seqdiag {
+  browser  -> webserver [label = "GET /index.html"];
+  browser <-- webserver;
+  browser  -> webserver [label = "POST /blog/comment"];
+              webserver  -> database [label = "INSERT comment"];
+              webserver <-- database;
+  browser <-- webserver;
+}
+:::
+
+</div>
+
+## actdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: actdiag
+actdiag {
+  write -> convert -> image
 
+  lane user {
+     label = "User"
+     write [label = "Writing reST"];
+     image [label = "Get diagram IMAGE"];
+  }
+  lane actdiag {
+     convert [label = "Convert reST to Image"];
+  }
+}
+:::
+
+</div>
+
+## nwdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: nwdiag
+nwdiag {
+  network dmz {
+      address = "210.x.x.x/24"
+
+      web01 [address = "210.x.x.1"];
+      web02 [address = "210.x.x.2"];
+  }
+  network internal {
+      address = "172.x.x.x/24";
+
+      web01 [address = "172.x.x.1"];
+      web02 [address = "172.x.x.2"];
+      db01;
+      db02;
+  }
+}
+:::
+
+</div>
+
+## rackdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: rackdiag
+rackdiag {
+  // define height of rack
+  8U;
+
+  // define rack items
+  1: UPS [2U];
+  3: DB Server
+  4: Web Server
+  5: Web Server
+  6: Web Server
+  7: Load Balancer
+  8: L3 Switch
+}
+:::
+
+</div>
+
+## packetdiag
+
+<!-- Resize blockdiag -->
+<div style="max-width: 600px">
+
+::: packetdiag
+packetdiag {
+  colwidth = 32
+  node_height = 72
+
+  0-15: Source Port
+  16-31: Destination Port
+  32-63: Sequence Number
+  64-95: Acknowledgment Number
+  96-99: Data Offset
+  100-105: Reserved
+  106: URG [rotate = 270]
+  107: ACK [rotate = 270]
+  108: PSH [rotate = 270]
+  109: RST [rotate = 270]
+  110: SYN [rotate = 270]
+  111: FIN [rotate = 270]
+  112-127: Window
+  128-143: Checksum
+  144-159: Urgent Pointer
+  160-191: (Options and Padding)
+  192-223: data [colheight = 3]
+}
+:::
+
+</div>

+ 2 - 2
lib/locales/ja/translation.json

@@ -248,11 +248,11 @@
     },
     "children": {
       "label": "同一階層テンプレート",
-      "desc": "テンプレートページが存在する階層にのみ適されます"
+      "desc": "テンプレートページが存在する階層にのみ適されます"
     },
     "decendants": {
       "label": "下位層テンプレート",
-      "desc": "テンプレートページが存在する下位層のすべてのページに適されます"
+      "desc": "テンプレートページが存在する下位層のすべてのページに適されます"
     }
   },
 

+ 10 - 19
lib/models/page.js

@@ -1053,32 +1053,23 @@ module.exports = function(crowi) {
       });
   };
 
-  pageSchema.statics.updatePage = function(pageData, body, user, options) {
+  pageSchema.statics.updatePage = async function(pageData, body, user, options) {
     var Page = this
       , Revision = crowi.model('Revision')
       , grant = options.grant || null
       , grantUserGroupId = options.grantUserGroupId || null
       ;
     // update existing page
-    var newRevision = Revision.prepareRevision(pageData, body, user);
+    var newRevision = await Revision.prepareRevision(pageData, body, user);
 
-    let savedPage = undefined;
-    return Page.pushRevision(pageData, newRevision, user)
-      .then((revision) => {
-        // fetch Page
-        return Page.findPageByPath(revision.path).populate('revision');
-      })
-      .then((page) => {
-        savedPage = page;
-      })
-      .then(() => {
-        return Page.updateGrant(savedPage, grant, user, grantUserGroupId);
-      })
-      .then((data) => {
-        debug('Page grant update:', data);
-        pageEvent.emit('update', savedPage, user);
-        return savedPage;
-      });
+    const revision = await Page.pushRevision(pageData, newRevision, user);
+    const savedPage = await Page.findPageByPath(revision.path).populate('revision');
+    if (grant != null) {
+      const grantData = await Page.updateGrant(savedPage, grant, user, grantUserGroupId);
+      debug('Page grant update:', grantData);
+    }
+    pageEvent.emit('update', savedPage, user);
+    return savedPage;
   };
 
   pageSchema.statics.deletePage = function(pageData, user, options) {

+ 9 - 9
lib/routes/attachment.js

@@ -1,11 +1,11 @@
 module.exports = function(crowi, app) {
   'use strict';
 
-  var debug = require('debug')('growi:routs:attachment')
+  var debug = require('debug')('growi:routss:attachment')
+    , logger = require('@alias/logger')('growi:routes:attachment')
     , Attachment = crowi.model('Attachment')
     , User = crowi.model('User')
     , Page = crowi.model('Page')
-    , config = crowi.getConfig()
     , path = require('path')
     , fs = require('fs')
     , fileUploader = require('../util/fileUploader')(crowi, app)
@@ -42,7 +42,7 @@ module.exports = function(crowi, app) {
       })
       // not found
       .catch((err) => {
-        debug('download err', err);
+        logger.error('download err', err);
         return res.status(404).sendFile(crowi.publicDir + '/images/file-not-found.png');
       });
   };
@@ -140,7 +140,6 @@ module.exports = function(crowi, app) {
           return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
         }).then(function(data) {
           var fileUrl = data.fileUrl;
-          var config = crowi.getConfig();
 
           var result = {
             page: page.toObject(),
@@ -157,18 +156,18 @@ module.exports = function(crowi, app) {
 
           return res.json(ApiResponse.success(result));
         }).catch(function(err) {
-          debug('Error on saving attachment data', err);
+          logger.error('Error on saving attachment data', err);
           // @TODO
           // Remove from S3
 
           // delete anyway
-          fs.unlink(tmpPath, function(err) { if (err) { debug('Error while deleting tmp file.') } });
+          fs.unlink(tmpPath, function(err) { if (err) { logger.error('Error while deleting tmp file.') } });
 
           return res.json(ApiResponse.error('Error while uploading.'));
         });
-      
+
     }).catch(function(err) {
-      debug('Attachement upload error', err);
+      logger.error('Attachement upload error', err);
       return res.json(ApiResponse.error('Error.'));
     });
   };
@@ -192,10 +191,11 @@ module.exports = function(crowi, app) {
         debug('removeAttachment', data);
         return res.json(ApiResponse.success({}));
       }).catch(err => {
+        logger.error('Error', err);
         return res.status(500).json(ApiResponse.error('Error while deleting file'));
       });
     }).catch(err => {
-      debug('Error', err);
+      logger.error('Error', err);
       return res.status(404);
     });
   };

+ 4 - 9
lib/routes/page.js

@@ -643,15 +643,10 @@ module.exports = function(crowi, app) {
     .then(function(data) {
       pageData = data;
 
-      if (!req.form.isValid) {
-        debug('Form data not valid');
-        throw new Error('Form data not valid.');
-      }
-
       if (data && !data.isUpdatable(currentRevision)) {
         debug('Conflict occured');
-        req.form.errors.push('page_edit.notice.conflict');
-        throw new Error('Conflict.');
+        req.flash('dangerMessage', 'Conflict occured');
+        return res.redirect(req.headers.referer);
       }
 
       if (data) {
@@ -839,8 +834,8 @@ module.exports = function(crowi, app) {
         throw new Error('Revision error.');
       }
 
-      var grantOption = {grant: pageData.grant};
-      if (grant !== null) {
+      var grantOption = {};
+      if (grant != null) {
         grantOption.grant = grant;
       }
       if (grantUserGroupId != null) {

+ 21 - 2
lib/service/logger/index.js

@@ -4,13 +4,32 @@ const minimatch = require('minimatch');
 const isBrowser = typeof window !== 'undefined';
 const isProd = process.env.NODE_ENV === 'production';
 
-const config = require('@root/config').logger;
-
+let config = require('@root/config').logger;
 let stream = isProd ? require('./stream.prod') : require('./stream.dev');
 
 // logger store
 let loggers = {};
 
+
+// merge configuration from environment variables
+const envLevelMap = {
+  INFO:   'info',
+  DEBUG:  'debug',
+  WARN:   'warn',
+  TRACE:  'trace',
+  ERROR:  'error',
+};
+Object.keys(envLevelMap).forEach(envName => {   // ['INFO', 'DEBUG', ...].forEach
+  const envVars = process.env[envName];         // process.env.DEBUG should have a value like 'growi:routes:page,growi:models.page,...'
+  if (envVars != null) {
+    const level = envLevelMap[envName];
+    envVars.split(',').forEach(ns => {          // ['growi:routes:page', 'growi:models.page', ...].forEach
+      config[ns.trim()] = level;
+    });
+  }
+});
+
+
 /**
  * determine logger level
  * @param {string} name Logger name

+ 1 - 1
lib/service/passport.js

@@ -181,7 +181,7 @@ class PassportService {
 
     // parse serverUrl
     // see: https://regex101.com/r/0tuYBB/1
-    const match = serverUrl.match(/(ldaps?:\/\/[^\/]+)\/(.*)?/);
+    const match = serverUrl.match(/(ldaps?:\/\/[^/]+)\/(.*)?/);
     if (match == null || match.length < 1) {
       debug('LdapStrategy: serverUrl is invalid');
       return (req, callback) => { callback({ message: 'serverUrl is invalid'}) };

+ 1 - 1
lib/views/_form.html

@@ -1,5 +1,5 @@
 {% block html_head_loading_legacy %}
-  <script src="{{ webpack_asset('legacy-form').js }}" defer></script>
+  <script src="{{ webpack_asset('js/legacy-form.js') }}" defer></script>
   {% parent %}
 {% endblock %}
 

+ 4 - 4
lib/views/admin/customize.html

@@ -4,11 +4,11 @@
 
 {% block style_css_block %}
   {% if env === 'development' %}
-    <script src="{{ webpack_asset('style').js }}"></script>
-    <script src="{{ webpack_asset('style-theme-' + theme()).js }}"></script>
+    <script src="{{ webpack_asset('styles/style.js') }}"></script>
+    <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
   {% else %}
-    <link rel="stylesheet" href="{{ webpack_asset('style').css }}">
-    <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('style-theme-' + theme()).css }}">
+    <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
+    <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
   {% endif %}
 {% endblock %}
 

+ 5 - 5
lib/views/admin/security.html

@@ -230,19 +230,19 @@
           </p>
           <ul class="nav nav-tabs" role="tablist" {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
             <li class="active">
-              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="icon-organization"></i> LDAP</a>
+              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
             </li>
             <li>
-              <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="icon-social-google"></i> Google OAuth</a>
+              <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="fa fa-google"></i> (TBD) Google OAuth</a>
             </li>
             <li>
-              <a href="#passport-facebook" data-toggle="tab" role="tab"><i class="icon-social-facebook"></i> Facebook</a>
+              <a href="#passport-facebook" data-toggle="tab" role="tab"><i class="fa fa-facebook"></i> (TBD) Facebook</a>
             </li>
             <li>
-              <a href="#passport-twitter" data-toggle="tab" role="tab"><i class="icon-social-twitter"></i> Twitter</a>
+              <a href="#passport-twitter" data-toggle="tab" role="tab"><i class="fa fa-twitter"></i> (TBD) Twitter</a>
             </li>
             <li>
-              <a href="#passport-github" data-toggle="tab" role="tab"><i class="icon-social-github"></i> Github</a>
+              <a href="#passport-github" data-toggle="tab" role="tab"><i class="fa fa-github"></i> (TBD) Github</a>
             </li>
           </ul>
 

+ 1 - 1
lib/views/admin/widget/theme-colorbox.html

@@ -1,7 +1,7 @@
 <a id="theme-option-{{name}}" href="#"
     class="{{name}} {% if name === settingForm['customize:theme'] %}active{% endif %}"
     onclick="selectTheme('{{name}}')"
-    data-theme="{{ webpack_asset('style-theme-' + name).css }}">
+    data-theme="{{ webpack_asset('styles/theme-' + name + '.css') }}">
 
   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
     <title>{{name}}</title>

+ 1 - 1
lib/views/layout/admin.html

@@ -6,7 +6,7 @@
 
 {% block html_additional_headers %}
   {% parent %}
-  <script src="{{ webpack_asset('legacy-admin').js }}" defer></script>
+  <script src="{{ webpack_asset('js/legacy-admin.js') }}" defer></script>
 {% endblock %}
 
 {# disable custom script in admin page #}

+ 15 - 14
lib/views/layout/layout.html

@@ -16,20 +16,19 @@
 
   {{ customHeader() }}
 
-
   <!-- polyfills for IE11 -->
   <script>
     var userAgent = window.navigator.userAgent.toLowerCase();
     if (userAgent.indexOf('msie') != -1 || userAgent.indexOf('trident') != -1) {
       var scriptElement = document.createElement('script');
-      scriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.23.0/polyfill.min.js';
+      scriptElement.src = '{{ webpack_asset("js/ie11-polyfill.js") }}';
       var headElement = document.getElementsByTagName('head')[0];
       headElement.appendChild(scriptElement);
     }
   </script>
 
-  <!-- jQuery, emojione -->
-  <script src="https://cdn.jsdelivr.net/combine/npm/emojione@3.1.2,npm/jquery@3.3.1"></script>
+  <!-- jQuery, emojione, bootstrap -->
+  <script src="https://cdn.jsdelivr.net/combine/npm/emojione@3.1.2,npm/jquery@3.3.1,npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
   <!-- highlight.js -->
   <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.12.0/build/highlight.min.js"></script>
   <script src="https://cdn.jsdelivr.net/combine/
@@ -63,33 +62,35 @@ gh/highlightjs/cdn-release@9.12.0/build/languages/yaml.min.js
   {% endif %}
 
   {% if env === 'development' %}
-    <script src="/dll/vendor.dll.js"></script>
-    <script src="{{ webpack_asset('dev').js }}" async></script>
+    <script src="/dll/dll.js"></script>
+    <script src="{{ webpack_asset('js/dev.js') }}" async></script>
     <!-- Browsersync -->
     <script id="__bs_script__">//<![CDATA[
       document.write("<script async src='http://HOST:3001/browser-sync/browser-sync-client.js?v=2.23.6'><\/script>".replace("HOST", location.hostname));
     //]]></script>
   {% endif %}
 
-  <script src="{{ webpack_asset('commons').js }}" defer></script>
+  <script src="{{ webpack_asset('js/vendors.js') }}" defer></script>
+  <script src="{{ webpack_asset('js/commons.js') }}" defer></script>
   {% if isEnabledPlugins() %}
-  <script src="{{ webpack_asset('plugin').js }}" defer></script>
+  <script src="{{ webpack_asset('js/plugin.js') }}" defer></script>
   {% endif %}
   {% block html_head_loading_legacy %}
-  <script src="{{ webpack_asset('legacy').js }}" defer></script>
+  <script src="{{ webpack_asset('js/legacy.js') }}" defer></script>
   {% endblock %}
   {% block html_head_loading_app %}
-  <script src="{{ webpack_asset('app').js }}" defer></script>
+  <script src="{{ webpack_asset('js/app.js') }}" defer></script>
   {% endblock %}
 
   <!-- styles -->
   {% block style_css_block %}
     {% if env === 'development' %}
-      <script src="{{ webpack_asset('style').js }}"></script>
-      <script src="{{ webpack_asset('style-theme-' + theme()).js }}"></script>
+      <script src="{{ webpack_asset('styles/style.js') }}"></script>
+      <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
     {% else %}
-      <link rel="stylesheet" href="{{ webpack_asset('style').css }}">
-      <link rel="stylesheet" href="{{ webpack_asset('style-theme-' + theme()).css }}">
+      <link rel="stylesheet" href="{{ webpack_asset('js/vendors.css') }}">
+      <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
+      <link rel="stylesheet" href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
     {% endif %}
   {% endblock %}
 

+ 5 - 5
lib/views/me/external-accounts.html

@@ -120,19 +120,19 @@
 
           <ul class="nav nav-tabs passport-settings" role="tablist">
             <li class="active">
-              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="icon-organization"></i> LDAP</a>
+              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
             </li>
             <li>
-              <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="icon-social-google"></i> Google OAuth</a>
+              <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="fa fa-google"></i> (TBD) Google OAuth</a>
             </li>
             <li>
-              <a href="#passport-facebook" data-toggle="tab" role="tab"><i class="icon-social-facebook"></i> Facebook</a>
+              <a href="#passport-facebook" data-toggle="tab" role="tab"><i class="fa fa-facebook"></i> (TBD) Facebook</a>
             </li>
             <li>
-              <a href="#passport-twitter" data-toggle="tab" role="tab"><i class="icon-social-twitter"></i> Twitter</a>
+              <a href="#passport-twitter" data-toggle="tab" role="tab"><i class="fa fa-twitter"></i> (TBD) Twitter</a>
             </li>
             <li>
-              <a href="#passport-github" data-toggle="tab" role="tab"><i class="icon-social-github"></i> Github</a>
+              <a href="#passport-github" data-toggle="tab" role="tab"><i class="fa fa-github"></i> (TBD) Github</a>
             </li>
           </ul>
 

+ 5 - 5
lib/views/modal/create_page.html

@@ -78,13 +78,13 @@
       pagePath = pagePath.slice(0, -1);
   };
 
-  $.get(`/_api/pages.templates?path=${pagePath}`)
-    .then(templateInfo => {
-      buttonTextChildren = templateInfo.childrenTemplateExists ? `{{ t('Edit') }}` : `{{ t('Create') }}`;
-      buttonTextDecendants = templateInfo.decendantsTemplateExists ? `{{ t('Edit') }}` : `{{ t('Create') }}`;
+  $.get('/_api/pages.templates?path=' + pagePath)   // don't use template literal(`...${}`) for IE11
+    .then(function(templateInfo) {                  // don't use arrow function for IE11
+      buttonTextChildren = templateInfo.childrenTemplateExists ? '{{ t("Edit") }}' : '{{ t("Create") }}';
+      buttonTextDecendants = templateInfo.decendantsTemplateExists ? '{{ t("Edit") }}' : '{{ t("Create") }}';
     });
 
-  $("#template-type").on("change", () => {
+  $("#template-type").on("change", function() {
     // enable button
     $('#link-to-template').removeClass("disabled");
 

+ 1 - 1
lib/views/modal/shortcuts.html

@@ -67,7 +67,7 @@
     var platform = navigator.platform.toLowerCase();
     var isMac = (platform.indexOf('mac') > -1);
 
-    document.querySelectorAll('#shortcuts-modal .cmd-key').forEach((element) => {
+    document.querySelectorAll('#shortcuts-modal .cmd-key').forEach(function(element) {
       if (isMac) {
         element.classList.add('mac');
       }

+ 6 - 7
lib/views/page_presentation.html

@@ -35,19 +35,18 @@ gh/highlightjs/cdn-release@9.12.0/build/languages/yaml.min.js
 " defer></script>
 
     {% if env === 'development' %}
-      <script src="/dll/vendor.dll.js"></script>
-      <script src="{{ webpack_asset('dev').js }}" async></script>
+      <script src="/dll/dll.js"></script>
+      <script src="{{ webpack_asset('js/dev.js') }}" async></script>
     {% endif %}
 
-    <script src="{{ webpack_asset('commons').js }}" defer></script>
-    <script src="{{ webpack_asset('legacy-presentation').js }}" defer></script>
+    <script src="{{ webpack_asset('js/legacy-presentation.js') }}" defer></script>
 
     <title>{{ path|path2name }} | {{ path }}</title>
 
     <!-- styles -->
-    <link rel="stylesheet" href="{{ webpack_asset('style').css }}">
-    <link rel="stylesheet" href="{{ webpack_asset('style-theme-default').css }}">
-    <link rel="stylesheet" href="{{ webpack_asset('style-presentation').css }}">
+    <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
+    <link rel="stylesheet" href="{{ webpack_asset('styles/theme-default.css') }}">
+    <link rel="stylesheet" href="{{ webpack_asset('styles/style-presentation.css') }}">
 
     <!-- Google Fonts -->
     <link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'>

+ 1 - 1
lib/views/widget/system-version.html

@@ -13,7 +13,7 @@
   var platform = navigator.platform.toLowerCase();
   var isMac = (platform.indexOf('mac') > -1);
 
-  document.querySelectorAll('.system-version .cmd-key').forEach((element) => {
+  document.querySelectorAll('.system-version .cmd-key').forEach(function(element) {
     if (isMac) {
       element.classList.add('mac');
     }

+ 0 - 3
local_modules/crowi-fileupload-local/index.js

@@ -7,8 +7,6 @@ module.exports = function(crowi) {
     , fs = require('fs')
     , path = require('path')
     , mkdir = require('mkdirp')
-    , Config = crowi.model('Config')
-    , config = crowi.getConfig()
     , lib = {}
     , basePath = path.posix.join(crowi.publicDir, 'uploads'); // TODO: to configurable
 
@@ -17,7 +15,6 @@ module.exports = function(crowi) {
     return new Promise(function(resolve, reject) {
       fs.unlink(path.posix.join(basePath, filePath), function(err) {
         if (err) {
-          debug(err);
           return reject(err);
         }
 

+ 28 - 20
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.1.6-RC",
+  "version": "3.1.8-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -20,13 +20,17 @@
     "url": "https://github.com/weseek/growi/issues"
   },
   "scripts": {
-    "build:dev:analyze": "cross-env ANALYZE=1 npm run build:dev",
-    "build:dev:watch": "npm run build:dev -- --watch",
-    "build:dev": "npm run clean:js && webpack --config config/webpack.dev.js --progress --profile",
+    "build:dev:analyze": "npm-run-all -s build:dev:dll build:dev:app:analyze",
+    "build:dev:app:analyze": "cross-env ANALYZE=1 npm run build:dev:app:watch -- --profile",
+    "build:dev:app:watch": "npm run build:dev:app -- --watch",
+    "build:dev:app": "npm run clean:app && env-cmd config/env.dev.js webpack --config config/webpack.dev.js --progress",
+    "build:dev:dll": "webpack --config config/webpack.dll.js",
+    "build:dev:watch": "npm-run-all -s build:dev:dll build:dev:app:watch",
+    "build:dev": "npm-run-all -s build:dev:dll build:dev:app",
     "build:prod:analyze": "cross-env ANALYZE=1 npm run build:prod",
-    "build:prod": "npm run clean && webpack --config config/webpack.prod.js --progress --profile --bail",
+    "build:prod": "npm run clean && env-cmd config/env.prod.js webpack --config config/webpack.prod.js --profile --bail",
     "build": "npm run build:dev:watch",
-    "clean:js": "rimraf -- public/js",
+    "clean:app": "rimraf -- public/js public/styles",
     "clean:dll": "rimraf -- public/dll",
     "clean:report": "rimraf -- report",
     "clean": "npm-run-all -p clean:*",
@@ -35,13 +39,11 @@
     "lint": "eslint .",
     "mkdirp": "mkdirp",
     "plugin:def": "node bin/generate-plugin-definitions-source.js",
-    "prebuild:dev": "env-cmd config/env.dev.js npm run plugin:def",
+    "prebuild:dev:app": "env-cmd config/env.dev.js npm run plugin:def",
     "prebuild:prod": "npm run plugin:def",
     "prestart": "npm run build:prod",
-    "postserver:prod:container": "echo ---------------------------------------- && echo [WARNING] && echo   'server:prod:container' is deprecated. && echo   Please use 'sever:prod' && echo ----------------------------------------",
     "server:debug": "env-cmd config/env.dev.js node-dev --inspect app.js",
     "server:dev": "env-cmd config/env.dev.js node-dev --respawn app.js",
-    "server:prod:container": "npm run server:prod",
     "server:prod:ci": "npm run server:prod -- --ci",
     "server:prod": "env-cmd config/env.prod.js node app.js",
     "server": "npm run server:dev",
@@ -83,6 +85,7 @@
     "i18next-express-middleware": "^1.1.1",
     "i18next-node-fs-backend": "^1.0.0",
     "i18next-sprintf-postprocessor": "^0.2.2",
+    "markdown-it-blockdiag": "^1.0.2",
     "md5": "^2.2.1",
     "method-override": "^2.3.10",
     "mkdirp": "~0.5.1",
@@ -97,9 +100,6 @@
     "passport": "^0.4.0",
     "passport-ldapauth": "^2.0.0",
     "passport-local": "^1.0.0",
-    "react": "^16.2.0",
-    "react-dom": "^16.2.0",
-    "react-i18next": "^7.6.1",
     "rimraf": "^2.6.1",
     "slack-node": "^0.1.8",
     "socket.io": "^2.0.3",
@@ -110,11 +110,11 @@
   },
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.0.16",
-    "assets-webpack-plugin": "^3.6.0",
     "autoprefixer": "^8.2.0",
     "babel-core": "^6.25.0",
     "babel-loader": "^7.1.1",
     "babel-plugin-lodash": "^3.3.2",
+    "babel-polyfill": "^6.26.0",
     "babel-preset-env": "^1.6.0",
     "babel-preset-react": "^6.24.1",
     "bootstrap-sass": "~3.3.6",
@@ -135,13 +135,13 @@
     "eazy-logger": "^3.0.2",
     "eslint": "^4.19.1",
     "eslint-plugin-react": "^7.7.0",
-    "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^1.1.0",
     "i18next-browser-languagedetector": "^2.2.0",
     "jquery-slimscroll": "^1.3.8",
     "jquery-ui": "^1.12.1",
     "jquery.cookie": "~1.4.1",
     "load-css-file": "^1.0.0",
+    "lodash-webpack-plugin": "^0.11.5",
     "markdown-it": "^8.4.0",
     "markdown-it-blockdiag": "^1.0.0",
     "markdown-it-emoji": "^1.4.0",
@@ -153,33 +153,41 @@
     "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
     "markdown-table": "^1.1.1",
     "metismenu": "^2.7.4",
+    "mini-css-extract-plugin": "^0.4.0",
     "mocha": "^5.0.0",
     "morgan": "^1.9.0",
     "node-dev": "^3.1.3",
     "node-sass": "^4.5.0",
+    "nodelist-foreach-polyfill": "^1.2.0",
     "normalize-path": "^3.0.0",
+    "null-loader": "^0.1.1",
     "on-headers": "^1.0.1",
-    "optimize-js-plugin": "0.0.4",
+    "optimize-css-assets-webpack-plugin": "^4.0.2",
     "plantuml-encoder": "^1.2.5",
     "postcss-loader": "^2.1.3",
+    "react": "^16.2.0",
     "react-bootstrap": "^0.32.1",
-    "react-bootstrap-typeahead": "^3.0.3",
+    "react-bootstrap-typeahead": "^3.1.4",
     "react-clipboard.js": "^2.0.0",
     "react-codemirror2": "^5.0.0",
+    "react-dom": "^16.2.0",
     "react-dropzone": "^4.2.7",
+    "react-i18next": "^7.6.1",
     "reveal.js": "^3.5.0",
     "sass-loader": "^7.0.1",
     "simple-load-script": "^1.0.2",
-    "sinon": "^5.0.2",
-    "sinon-chai": "^3.0.0",
+    "sinon": "^6.0.0",
+    "sinon-chai": "^3.2.0",
     "socket.io-client": "^2.0.3",
     "style-loader": "^0.21.0",
     "throttle-debounce": "^2.0.0",
     "toastr": "^2.1.2",
+    "uglifyjs-webpack-plugin": "^1.2.5",
     "url-join": "^4.0.0",
-    "webpack": "3.11.0",
+    "webpack": "^4.12.0",
+    "webpack-assets-manifest": "^3.0.1",
     "webpack-bundle-analyzer": "^2.9.0",
-    "webpack-dll-bundles-plugin": "^1.0.0-beta.5",
+    "webpack-cli": "^3.0.8",
     "webpack-merge": "~4.1.0"
   },
   "_moduleAliases": {

+ 0 - 1
public/css/.gitignore

@@ -1 +0,0 @@
-

+ 1 - 1
resource/js/app.js

@@ -146,7 +146,7 @@ if (pageEditorElem) {
   // create onSave event handler
   const onSaveSuccess = function(page) {
     // modify the revision id value to pass checking id when updating
-    crowi.getCrowiForJquery().updateCurrentRevision(page.revision._id);
+    crowi.getCrowiForJquery().updatePageForm(page);
     // re-render Page component if exists
     if (componentInstances.page != null) {
       componentInstances.page.setMarkdown(page.revision.body);

+ 2 - 1
resource/js/components/Page/RevisionBody.js

@@ -43,6 +43,7 @@ export default class RevisionBody extends React.Component {
   }
 
   render() {
+    const additionalClassName = this.props.additionalClassName || '';
     return (
       <div
         ref={(elm) => {
@@ -51,7 +52,7 @@ export default class RevisionBody extends React.Component {
             this.props.inputRef(elm);
           }
         }}
-        className={'wiki ' + this.props.additionalClassName} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
+        className={`wiki ${additionalClassName}`} dangerouslySetInnerHTML={this.generateInnerHtml(this.props.html)}>
       </div>
     );
   }

+ 0 - 1
resource/js/components/PageEditor.js

@@ -311,7 +311,6 @@ export default class PageEditor extends React.Component {
   }
 
   apiErrorHandler(error) {
-    console.error(error);
     toastr.error(error.message, 'Error occured', {
       closeButton: true,
       progressBar: true,

+ 7 - 0
resource/js/components/PageEditor/AbstractEditor.js

@@ -55,6 +55,13 @@ export default class AbstractEditor extends React.Component {
     throw new Error('this method should be impelemented in subclass');
   }
 
+  /**
+   * return strings from BOL(beginning of line) to current position
+   */
+  getStrFromBolToSelectedUpperPos() {
+    throw new Error('this method should be impelemented in subclass');
+  }
+
   /**
    * replace Beggining Of Line to current position with param 'text'
    * @param {string} text

+ 37 - 1
resource/js/components/PageEditor/CodeMirrorEditor.js

@@ -184,12 +184,22 @@ export default class CodeMirrorEditor extends AbstractEditor {
     return editor.getDoc().getRange(curPos, this.getEol());
   }
 
+  /**
+   * @inheritDoc
+   */
+  getStrFromBolToSelectedUpperPos() {
+    const editor = this.getCodeMirror();
+    const pos = this.selectUpperPos(editor.getCursor('from'), editor.getCursor('to'));
+    return editor.getDoc().getRange(this.getBol(), pos);
+  }
+
   /**
    * @inheritDoc
    */
   replaceBolToCurrentPos(text) {
     const editor = this.getCodeMirror();
-    editor.getDoc().replaceRange(text, this.getBol(), editor.getCursor());
+    const pos = this.selectLowerPos(editor.getCursor('from'), editor.getCursor('to'));
+    editor.getDoc().replaceRange(text, this.getBol(), pos);
   }
 
   /**
@@ -219,6 +229,32 @@ export default class CodeMirrorEditor extends AbstractEditor {
     return { line: curPos.line, ch: lineLength };
   }
 
+  /**
+   * select the upper position of pos1 and pos2
+   * @param {{line: number, ch: number}} pos1
+   * @param {{line: number, ch: number}} pos2
+   */
+  selectUpperPos(pos1, pos2) {
+    // if both is in same line
+    if (pos1.line === pos2.line) {
+      return (pos1.ch < pos2.ch) ? pos1 : pos2;
+    }
+    return (pos1.line < pos2.line) ? pos1 : pos2;
+  }
+
+  /**
+   * select the lower position of pos1 and pos2
+   * @param {{line: number, ch: number}} pos1
+   * @param {{line: number, ch: number}} pos2
+   */
+  selectLowerPos(pos1, pos2) {
+    // if both is in same line
+    if (pos1.line === pos2.line) {
+      return (pos1.ch < pos2.ch) ? pos2 : pos1;
+    }
+    return (pos1.line < pos2.line) ? pos2 : pos1;
+  }
+
   loadCss(source) {
     return new Promise((resolve) => {
       loadCssSync(source);

+ 1 - 1
resource/js/components/PageEditor/MarkdownListUtil.js

@@ -42,7 +42,7 @@ class MarkdownListUtil {
    */
   pasteText(editor, event, text) {
     // get strings from BOL(beginning of line) to current position
-    const strFromBol = editor.getStrFromBol();
+    const strFromBol = editor.getStrFromBolToSelectedUpperPos();
 
     // when match indentAndMarkOnlyRE
     // (this means the current position is the beginning of the list item)

+ 14 - 2
resource/js/components/PageEditor/TextAreaEditor.js

@@ -117,12 +117,24 @@ export default class TextAreaEditor extends AbstractEditor {
     return this.textarea.value.substring(currentPos, this.getEolPos());
   }
 
+  /**
+   * @inheritDoc
+   */
+  getStrFromBolToSelectedUpperPos() {
+    const startPos = this.textarea.selectionStart;
+    const endPos = this.textarea.selectionEnd;
+    const upperPos = (startPos < endPos) ? startPos : endPos;
+    return this.textarea.value.substring(this.getBolPos(), upperPos);
+  }
+
   /**
    * @inheritDoc
    */
   replaceBolToCurrentPos(text) {
-    const currentPos = this.textarea.selectionStart;
-    this.replaceValue(text, this.getBolPos(), currentPos);
+    const startPos = this.textarea.selectionStart;
+    const endPos = this.textarea.selectionEnd;
+    const lowerPos = (startPos < endPos) ? endPos : startPos;
+    this.replaceValue(text, this.getBolPos(), lowerPos);
   }
 
   getBolPos() {

+ 2 - 0
resource/js/ie11-polyfill.js

@@ -0,0 +1,2 @@
+import 'nodelist-foreach-polyfill';
+import 'babel-polyfill';

+ 0 - 1
resource/js/legacy/crowi-form.js

@@ -2,7 +2,6 @@ var pageId = $('#content-main').data('page-id');
 var pagePath= $('#content-main').data('path');
 
 require('bootstrap-select');
-require('bootstrap-sass');
 
 // show/hide
 function FetchPagesUpdatePostAndInsert(path) {

+ 3 - 3
resource/js/legacy/crowi.js

@@ -16,7 +16,6 @@ import Page from '../components/Page';
 const io = require('socket.io-client');
 const entities = require('entities');
 const escapeStringRegexp = require('escape-string-regexp');
-require('bootstrap-sass');
 require('jquery.cookie');
 
 require('./thirdparty-js/agile-admin');
@@ -137,8 +136,9 @@ Crowi.modifyScrollTop = function() {
   }, timeout);
 };
 
-Crowi.updateCurrentRevision = function(revisionId) {
-  $('#page-form [name="pageForm[currentRevision]"]').val(revisionId);
+Crowi.updatePageForm = function(page) {
+  $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id);
+  $('#page-form [name="pageForm[grant]"]').val(page.grant);
 };
 
 Crowi.handleKeyEHandler = (event) => {

+ 5 - 6
resource/js/util/markdown-it/blockdiag.js

@@ -4,14 +4,13 @@ export default class BlockdiagConfigurer {
     this.crowi = crowi;
     const config = crowi.getConfig();
 
-    this.generateSourceUrl = config.env.BLOCKDIAG_URL || 'https://blockdiag-api.com/';
+    this.generateSourceUrl = config.env.BLOCKDIAG_URI || 'https://blockdiag-api.com/';
   }
 
   configure(md) {
-    //// disable temporary because this breaks /Sandbox -- 2018.06.08 Yuki Takei
-    // md.use(require('markdown-it-blockdiag'), {
-    //   generateSourceUrl: this.generateSourceUrl,
-    //   marker: ':::',
-    // });
+    md.use(require('markdown-it-blockdiag'), {
+      generateSourceUrl: this.generateSourceUrl,
+      marker: ':::',
+    });
   }
 }

+ 8 - 1
resource/styles/scss/_wiki.scss

@@ -152,6 +152,10 @@ div.body {
     h1, h2, h3, h4, h5, h6 {
       margin-top: 1.6em * $ratio;
       margin-bottom: .8em * $ratio;
+
+      &:first-child {
+        margin-top: 15px;
+      }
     }
 
     h1 {
@@ -178,8 +182,11 @@ div.body {
     }
 
     ul, ol {
+      padding-left: 15px;
+      margin: 10px 0;
+
       li {
-        line-height: 1.8em * $ratio;
+        line-height: 1.1em;
       }
     }
 

File diff suppressed because it is too large
+ 483 - 132
yarn.lock


Some files were not shown because too many files changed in this diff