Просмотр исходного кода

Merge branch 'master' into imprv/refactor-attachment

Yuki Takei 7 лет назад
Родитель
Сommit
0998cb72e5

+ 18 - 2
CHANGES.md

@@ -1,13 +1,29 @@
 CHANGES
 ========
 
+## 3.3.10-RC
+
+* Feature: PlantUML and Blockdiag on presentation
+* Improvement: Render slides of presentation with GrowiRenderer
+* Support: Use mini-css-extract-plugin instead of extract extract-text-webpack-plugin
+* Support: Use terser-webpack-plugin instead of uglifyjs-webpack-plugin
+* Support: Upgrade libs
+    * webpack
+    * webpack-assets-manifest
+    * webpack-cli
+    * webpack-merge
+
 ## 3.3.9
 
-* 
+* Fix: Import from Qiita:Team doesn't work
+    * Introduced by 3.3.0
+* Fix: Typeahead shows autocomplete wrongly
+    * Introduced by 3.3.8
+* Support: Upgrade libs
+    * react-bootstrap-typeahead
 
 ## 3.3.8
 
-* Fix: Typeahead shows autocomplete wrongly
 * Fix: Move/Duplicate don't work
     * Introduced by 3.3.7
 * Fix: Server doesn't respond when root page is restricted

+ 15 - 15
config/webpack.common.js

@@ -29,7 +29,7 @@ module.exports = (options) => {
       'js/hackmd-agent':              './src/client/js/hackmd-agent',
       'js/hackmd-styles':             './src/client/js/hackmd-styles',
       // styles
-      'styles/style':                 './src/client/styles/scss/style.scss',
+      'styles/style-app':             './src/client/styles/scss/style-app.scss',
       'styles/style-presentation':    './src/client/styles/scss/style-presentation.scss',
       // themes
       'styles/theme-default':         './src/client/styles/scss/theme/default.scss',
@@ -100,16 +100,6 @@ module.exports = (options) => {
           test: /switchery\.js$/,
           loader: 'imports-loader?module=>false,exports=>false,define=>false,this=>window'
         },
-        {
-          test: /\.css$/,
-          use: ['style-loader', 'css-loader'],
-          exclude: [helpers.root('src/client/styles')]
-        },
-        {
-          test: /\.scss$/,
-          use: ['style-loader', 'css-loader', 'sass-loader'],
-          exclude: [helpers.root('src/client/styles')]
-        },
         /*
          * File loader for supporting images, for example, in CSS files.
          */
@@ -153,8 +143,19 @@ module.exports = (options) => {
       namedModules: true,
       splitChunks: {
         cacheGroups: {
+          style_commons: {
+            test: /\.(sc|sa|c)ss$/,
+            chunks: (chunk) => {
+              // ignore patterns
+              return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-admin|legacy-presentation/);
+            },
+            name: 'styles/style-commons',
+            minSize: 1,
+            priority: 30,
+            enforce: true
+          },
           commons: {
-            test: /src/,
+            test: /src[\\/].*\.jsx?$/,
             chunks: 'initial',
             name: 'js/commons',
             minChunks: 2,
@@ -162,17 +163,16 @@ module.exports = (options) => {
             priority: 20
           },
           vendors: {
-            test: /node_modules/,
+            test: /node_modules[\\/].*\.jsx?$/,
             chunks: (chunk) => {
               // ignore patterns
               return chunk.name != null && !chunk.name.match(/legacy-presentation|ie11-polyfill|hackmd-/);
             },
             name: 'js/vendors',
-            // minChunks: 2,
             minSize: 1,
             priority: 10,
             enforce: true
-          }
+          },
         }
       },
       minimizer: options.optimization.minimizer || [],

+ 17 - 13
config/webpack.dev.js

@@ -2,14 +2,13 @@
  * @author: Yuki Takei <yuki@weseek.co.jp>
  */
 
-const path = require('path');
 const webpack = require('webpack');
 const helpers = require('../src/lib/util/helpers');
 
 /*
  * Webpack Plugins
  */
-const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
 /**
@@ -29,29 +28,34 @@ module.exports = require('./webpack.common')({
   module: {
     rules: [
       {
-        test: /\.scss$/,
+        test: /\.(css|scss)$/,
         use: [
           'style-loader',
           { loader: 'css-loader', options: { sourceMap: true } },
           { loader: 'sass-loader', options: { sourceMap: true } },
         ],
-        include: [helpers.root('src/client/styles/scss')]
+        exclude: [
+          helpers.root('src/client/styles/hackmd'),
+          helpers.root('src/client/styles/scss/style-presentation.scss'),
+        ]
       },
       { // Dump CSS for HackMD
-        test: /\.scss$/,
-        use: ExtractTextPlugin.extract({
-          use: [
-            'css-loader',
-            'sass-loader'
-          ]
-        }),
-        include: [helpers.root('src/client/styles/hackmd')]
+        test: /\.(css|scss)$/,
+        use: [
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          'sass-loader'
+        ],
+        include: [
+          helpers.root('src/client/styles/hackmd'),
+          helpers.root('src/client/styles/scss/style-presentation.scss'),
+        ]
       },
     ],
   },
   plugins: [
 
-    new ExtractTextPlugin({
+    new MiniCssExtractPlugin({
       filename: '[name].bundle.css',
     }),
 

+ 23 - 22
config/webpack.prod.js

@@ -6,8 +6,8 @@ const helpers = require('../src/lib/util/helpers');
 /**
  * Webpack Plugins
  */
-const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
-const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
@@ -26,26 +26,30 @@ module.exports = require('./webpack.common')({
   module: {
     rules: [
       {
-        test: /\.scss$/,
-        use: ExtractTextPlugin.extract({
-          use: [
-            'css-loader',
-            { loader: 'postcss-loader', options: {
-              sourceMap: false,
-              plugins: (loader) => [
-                require('autoprefixer')()
-              ]
-            } },
-            'sass-loader'
-          ]
-        }),
-        include: [helpers.root('src/client/styles/scss'), helpers.root('src/client/styles/hackmd')]
-      }
+        test: /\.(css|scss)$/,
+        use: [
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          { loader: 'postcss-loader', options: {
+            sourceMap: false,
+            plugins: (loader) => [
+              require('autoprefixer')()
+            ]
+          } },
+          'sass-loader'
+        ],
+        exclude: [helpers.root('src/client/js/legacy')]
+      },
+      {
+        test: /\.(css|scss)$/,
+        use: ['style-loader', 'css-loader', 'sass-loader'],
+        include: [helpers.root('src/client/js/legacy')]
+      },
     ]
   },
   plugins: [
 
-    new ExtractTextPlugin({
+    new MiniCssExtractPlugin({
       filename: '[name].[hash].css',
     }),
 
@@ -58,10 +62,7 @@ module.exports = require('./webpack.common')({
   ],
   optimization: {
     minimizer: [
-      new UglifyJsPlugin({
-        cache: true,
-        parallel: true,
-      }),
+      new TerserPlugin({}),
       new OptimizeCSSAssetsPlugin({})
     ],
   },

+ 8 - 8
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "3.3.9-RC",
+  "version": "3.3.10-RC",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -150,7 +150,6 @@
     "eazy-logger": "^3.0.2",
     "eslint": "^5.0.0",
     "eslint-plugin-react": "^7.7.0",
-    "extract-text-webpack-plugin": "^4.0.0-beta.0",
     "file-loader": "^2.0.0",
     "handsontable": "^6.0.1",
     "i18next-browser-languagedetector": "^2.2.0",
@@ -171,6 +170,7 @@
     "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
     "markdown-table": "^1.1.1",
     "metismenu": "^3.0.3",
+    "mini-css-extract-plugin": "^0.5.0",
     "mocha": "^5.2.0",
     "morgan": "^1.9.0",
     "node-dev": "^3.1.3",
@@ -185,7 +185,7 @@
     "postcss-loader": "^3.0.0",
     "react": "^16.7.0",
     "react-bootstrap": "^0.32.1",
-    "react-bootstrap-typeahead": "^3.3.2",
+    "react-bootstrap-typeahead": "^3.3.4",
     "react-clipboard.js": "^2.0.0",
     "react-codemirror2": "^5.1.0",
     "react-dom": "^16.4.1",
@@ -202,14 +202,14 @@
     "socket.io-client": "^2.0.3",
     "stream-to-promise": "^2.2.0",
     "style-loader": "^0.23.0",
+    "terser-webpack-plugin": "^1.2.2",
     "throttle-debounce": "^2.0.0",
     "toastr": "^2.1.2",
-    "uglifyjs-webpack-plugin": "^2.0.1",
-    "webpack": "^4.12.0",
-    "webpack-assets-manifest": "^3.0.1",
+    "webpack": "^4.29.3",
+    "webpack-assets-manifest": "^3.1.1",
     "webpack-bundle-analyzer": "^3.0.2",
-    "webpack-cli": "^3.0.8",
-    "webpack-merge": "~4.1.0"
+    "webpack-cli": "^3.2.3",
+    "webpack-merge": "^4.2.1"
   },
   "_moduleAliases": {
     "@root": ".",

+ 1 - 1
src/client/js/hackmd-styles.js

@@ -20,7 +20,7 @@ const styles = '{{styles}}';         // will be replaced by swig
 function insertStyle() {
   const element = document.createElement('style');
   element.type = 'text/css';
-  element.appendChild(document.createTextNode(styles));
+  element.appendChild(document.createTextNode(unescape(styles)));
   document.getElementsByTagName('head')[0].appendChild(element);
 }
 

+ 1 - 4
src/client/js/legacy/crowi-presentation.js

@@ -1,7 +1,5 @@
 const Reveal = require('reveal.js');
 
-require('reveal.js/css/reveal.css');
-require('reveal.js/css/theme/black.css');
 require('reveal.js/lib/js/head.min.js');
 require('reveal.js/lib/js/html5shiv.js');
 
@@ -33,10 +31,9 @@ Reveal.initialize({
 
 require.ensure([], () => {
   require('reveal.js/lib/js/classList.js');
-  require('reveal.js/plugin/markdown/marked.js');
-  require('reveal.js/plugin/markdown/markdown.js');
   require('reveal.js/plugin/zoom-js/zoom.js');
   require('reveal.js/plugin/notes/notes.js');
+  require('../util/reveal/plugins/growi-renderer.js');
 
   // fix https://github.com/weseek/crowi-plus/issues/96
   Reveal.slide(0, 0);

+ 6 - 4
src/client/js/legacy/crowi.js

@@ -2,9 +2,6 @@
 /* Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
 */
 
-/* global crowi: true */
-/* global crowiRenderer: true */
-
 import React from 'react';
 import ReactDOM from 'react-dom';
 
@@ -58,6 +55,7 @@ Crowi.setCaretLineAndFocusToEditor = function() {
     return;
   }
 
+  const crowi = window.crowi;
   const line = pageEditorDom.getAttribute('data-caret-line') || 0;
   crowi.setCaretLine(+line);
   // reset data-caret-line attribute
@@ -272,6 +270,7 @@ Crowi.getCurrentEditorMode = function() {
 };
 
 $(function() {
+  const crowi = window.crowi;
   const config = JSON.parse(document.getElementById('crowi-context-hydrate').textContent || '{}');
 
   const pageId = $('#content-main').data('page-id');
@@ -541,6 +540,8 @@ $(function() {
     const isShown = $('#view-timeline').data('shown');
 
     if (growiRendererForTimeline == null) {
+      const crowi = window.crowi;
+      const crowiRenderer = window.crowiRenderer;
       growiRendererForTimeline = new GrowiRenderer(crowi, crowiRenderer, {mode: 'timeline'});
     }
 
@@ -763,7 +764,8 @@ window.addEventListener('load', function(e) {
     }
   }
 
-  if (crowi && crowi.users || crowi.users.length == 0) {
+  const crowi = window.crowi;
+  if (crowi && crowi.users && crowi.users.length != 0) {
     const totalUsers = crowi.users.length;
     const $listLiker = $('.page-list-liker');
     $listLiker.each(function(i, liker) {

+ 111 - 0
src/client/js/util/reveal/plugins/growi-renderer.js

@@ -0,0 +1,111 @@
+import GrowiRenderer from '../../GrowiRenderer';
+
+/**
+ * reveal.js growi-renderer plugin.
+ */
+(function(root, factory) {
+  // parent window DOM (crowi.js) of presentation window.
+  let parentWindow = window.parent;
+
+  // create GrowiRenderer instance and setup.
+  let growiRenderer = new GrowiRenderer(parentWindow.crowi, parentWindow.crowiRenderer, {mode: 'editor'});
+
+  let growiRendererPlugin = factory(growiRenderer);
+  growiRendererPlugin.initialize();
+}(this, function(growiRenderer) {
+  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
+    DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
+    DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+  let marked;
+
+  /**
+   * Add data separator before lines
+   * starting with '#' to markdown.
+   */
+  function divideSlides() {
+    let sections = document.querySelectorAll('[data-markdown]');
+    for (let i = 0, len = sections.length; i < len; i++) {
+      let section = sections[i];
+      let markdown = marked.getMarkdownFromSlide(section);
+      let context = {markdown};
+      const interceptorManager = growiRenderer.crowi.interceptorManager;
+      let dataSeparator = section.getAttribute( 'data-separator' ) || DEFAULT_SLIDE_SEPARATOR;
+      // replace string '\n' to LF code.
+      dataSeparator = dataSeparator.replace(/\\n/g, '\n');
+      const replaceValue = dataSeparator + '#';
+      // detach code block.
+      interceptorManager.process('prePreProcess', context);
+      // if there is only '\n' in the first line, replace it.
+      context.markdown = context.markdown.replace(/^\n/, '');
+      // add data separator to markdown.
+      context.markdown = context.markdown.replace(/[\n]+#/g, replaceValue);
+      // restore code block.
+      interceptorManager.process('postPreProcess', context);
+      section.innerHTML = marked.createMarkdownSlide(context.markdown);
+    }
+  }
+
+  /**
+   * Converts data-markdown slides to HTML slides by GrowiRenderer.
+   */
+  function convertSlides() {
+    let sections = document.querySelectorAll('[data-markdown]');
+    let markdown;
+    const interceptorManager = growiRenderer.crowi.interceptorManager;
+
+    for (let i = 0, len = sections.length; i < len; i++) {
+      let section = sections[i];
+
+      // Only parse the same slide once
+      if (!section.getAttribute('data-markdown-parsed')) {
+        section.setAttribute('data-markdown-parsed', 'true');
+        let notes = section.querySelector( 'aside.notes' );
+        markdown = marked.getMarkdownFromSlide(section);
+        let context = { markdown };
+
+        interceptorManager.process('preRender', context)
+          .then(() => interceptorManager.process('prePreProcess', context))
+          .then(() => {
+            context.markdown = growiRenderer.preProcess(context.markdown);
+          })
+          .then(() => interceptorManager.process('postPreProcess', context))
+          .then(() => {
+            context['parsedHTML'] = growiRenderer.process(context.markdown);
+          })
+          .then(() => interceptorManager.process('prePostProcess', context))
+          .then(() => {
+            context.parsedHTML = growiRenderer.postProcess(context.parsedHTML);
+          })
+          .then(() => interceptorManager.process('postPostProcess', context))
+          .then(() => interceptorManager.process('preRenderHtml', context))
+          .then(() => interceptorManager.process('postRenderHtml', context))
+          .then(() => {
+            section.innerHTML = context.parsedHTML;
+          });
+        marked.addAttributes(   section, section, null, section.getAttribute( 'data-element-attributes' ) ||
+          section.parentNode.getAttribute( 'data-element-attributes' ) ||
+          DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+        section.getAttribute( 'data-attributes' ) ||
+          section.parentNode.getAttribute( 'data-attributes' ) ||
+          DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
+
+        // If there were notes, we need to re-add them after
+        // having overwritten the section's HTML
+        if ( notes ) {
+          section.appendChild( notes );
+        }
+      }
+    }
+  }
+
+  // API
+  return {
+    initialize: async function() {
+      growiRenderer.setup();
+      marked = require('./markdown').default(growiRenderer.process);
+      divideSlides();
+      marked.processSlides();
+      convertSlides();
+    }
+  };
+}));

+ 379 - 0
src/client/js/util/reveal/plugins/markdown.js

@@ -0,0 +1,379 @@
+/**
+ * The reveal.js markdown plugin. Handles parsing of
+ * markdown inside of presentations as well as loading
+ * of external markdown documents.
+ * Referred from The reveal.js markdown plugin.
+ * https://github.com/hakimel/reveal.js/blob/master/plugin/markdown/markdown.js
+ */
+export default function( marked ) {
+
+  const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
+    DEFAULT_NOTES_SEPARATOR = 'notes?:',
+    DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
+    DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
+
+  const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
+
+
+  /**
+   * Retrieves the markdown contents of a slide section
+   * element. Normalizes leading tabs/whitespace.
+   */
+  function getMarkdownFromSlide( section ) {
+
+    // look for a <script> or <textarea data-template> wrapper
+    let template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
+
+    // strip leading whitespace so it isn't evaluated as code
+    let text = ( template || section ).textContent;
+
+    // restore script end tags
+    text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
+
+    let leadingWs = text.match( /^\n?(\s*)/ )[1].length,
+      leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
+
+    if ( leadingTabs > 0 ) {
+      text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n' );
+    }
+    else if ( leadingWs > 1 ) {
+      text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
+    }
+
+    return text;
+
+  }
+
+  /**
+   * Given a markdown slide section element, this will
+   * return all arguments that aren't related to markdown
+   * parsing. Used to forward any other user-defined arguments
+   * to the output markdown slide.
+   */
+  function getForwardedAttributes( section ) {
+
+    let attributes = section.attributes;
+    let result = [];
+
+    for ( let i = 0, len = attributes.length; i < len; i++ ) {
+      let name = attributes[i].name,
+        value = attributes[i].value;
+
+      // disregard attributes that are used for markdown loading/parsing
+      if ( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
+
+      if ( value ) {
+        result.push( name + '="' + value + '"' );
+      }
+      else {
+        result.push( name );
+      }
+    }
+
+    return result.join( ' ' );
+
+  }
+
+  /**
+   * Inspects the given options and fills out default
+   * values for what's not defined.
+   */
+  function getSlidifyOptions( options ) {
+
+    options = options || {};
+    options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
+    options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
+    options.attributes = options.attributes || '';
+
+    return options;
+
+  }
+
+  /**
+   * Helper function for constructing a markdown slide.
+   */
+  function createMarkdownSlide( content, options ) {
+
+    options = getSlidifyOptions( options );
+
+    let notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
+
+    if ( notesMatch.length === 2 ) {
+      content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
+    }
+
+    // prevent script end tags in the content from interfering
+    // with parsing
+    content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
+
+    return '<script type="text/template">' + content + '</script>';
+
+  }
+
+  /**
+   * Parses a data string into multiple slides based
+   * on the passed in separator arguments.
+   */
+  function slidify( markdown, options ) {
+
+    options = getSlidifyOptions( options );
+
+    let separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
+      horizontalSeparatorRegex = new RegExp( options.separator );
+
+    let matches,
+      lastIndex = 0,
+      isHorizontal,
+      wasHorizontal = true,
+      content,
+      sectionStack = [];
+
+    // iterate until all blocks between separators are stacked up
+    while ( (matches = separatorRegex.exec( markdown )) != null ) {
+      // notes = null;
+
+      // determine direction (horizontal by default)
+      isHorizontal = horizontalSeparatorRegex.test( matches[0] );
+
+      if ( !isHorizontal && wasHorizontal ) {
+        // create vertical stack
+        sectionStack.push( [] );
+      }
+
+      // pluck slide content from markdown input
+      content = markdown.substring( lastIndex, matches.index );
+
+      if ( isHorizontal && wasHorizontal ) {
+        // add to horizontal stack
+        sectionStack.push( content );
+      }
+      else {
+        // add to vertical stack
+        sectionStack[sectionStack.length-1].push( content );
+      }
+
+      lastIndex = separatorRegex.lastIndex;
+      wasHorizontal = isHorizontal;
+    }
+
+    // add the remaining slide
+    ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
+
+    let markdownSections = '';
+
+    // flatten the hierarchical stack, and insert <section data-markdown> tags
+    for ( let i = 0, len = sectionStack.length; i < len; i++ ) {
+      // vertical
+      if ( sectionStack[i] instanceof Array ) {
+        markdownSections += '<section '+ options.attributes +'>';
+
+        sectionStack[i].forEach( function( child ) {
+          markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
+        } );
+
+        markdownSections += '</section>';
+      }
+      else {
+        markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
+      }
+    }
+
+    return markdownSections;
+
+  }
+
+  /**
+   * Parses any current data-markdown slides, splits
+   * multi-slide markdown into separate sections and
+   * handles loading of external markdown.
+   */
+  function processSlides() {
+
+    let sections = document.querySelectorAll( '[data-markdown]'),
+      section;
+
+    for ( let i = 0, len = sections.length; i < len; i++ ) {
+
+      section = sections[i];
+
+      if ( section.getAttribute( 'data-markdown' ).length ) {
+
+        let xhr = new XMLHttpRequest(),
+          url = section.getAttribute( 'data-markdown' );
+
+        let datacharset = section.getAttribute( 'data-charset' );
+
+        // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
+        if ( datacharset != null && datacharset != '' ) {
+          xhr.overrideMimeType( 'text/html; charset=' + datacharset );
+        }
+
+        xhr.onreadystatechange = function() {
+          if ( xhr.readyState === 4 ) {
+            // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
+            if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
+
+              section.outerHTML = slidify( xhr.responseText, {
+                separator: section.getAttribute( 'data-separator' ),
+                verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
+                notesSeparator: section.getAttribute( 'data-separator-notes' ),
+                attributes: getForwardedAttributes( section )
+              });
+
+            }
+            else {
+
+              section.outerHTML = '<section data-state="alert">' +
+                'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
+                'Check your browser\'s JavaScript console for more details.' +
+                '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
+                '</section>';
+
+            }
+          }
+        };
+
+        xhr.open( 'GET', url, false );
+
+        try {
+          xhr.send();
+        }
+        catch ( e ) {
+          alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
+        }
+
+      }
+      else if ( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
+
+        section.outerHTML = slidify( getMarkdownFromSlide( section ), {
+          separator: section.getAttribute( 'data-separator' ),
+          verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
+          notesSeparator: section.getAttribute( 'data-separator-notes' ),
+          attributes: getForwardedAttributes( section )
+        });
+
+      }
+      else {
+        section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
+      }
+    }
+
+  }
+
+  /**
+   * Check if a node value has the attributes pattern.
+   * If yes, extract it and add that value as one or several attributes
+   * the the terget element.
+   *
+   * You need Cache Killer on Chrome to see the effect on any FOM transformation
+   * directly on refresh (F5)
+   * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
+   */
+  function addAttributeInElement( node, elementTarget, separator ) {
+
+    let mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
+    let mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
+    let nodeValue = node.nodeValue;
+    let matches = mardownClassesInElementsRegex.exec( nodeValue );
+    if ( matches != null ) {
+
+      let classes = matches[1];
+      nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
+      node.nodeValue = nodeValue;
+      let matchesClass;
+      while ( (matchesClass = mardownClassRegex.exec( classes )) != null ) {
+        elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Add attributes to the parent element of a text node,
+   * or the element of an attribute node.
+   */
+  function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
+
+    if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
+      let previousParentElement = element;
+      for ( let i = 0; i < element.childNodes.length; i++ ) {
+        let childElement = element.childNodes[i];
+        if ( i > 0 ) {
+          let j = i - 1;
+          while ( j >= 0 ) {
+            let aPreviousChildElement = element.childNodes[j];
+            if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != 'BR' ) {
+              previousParentElement = aPreviousChildElement;
+              break;
+            }
+            j = j - 1;
+          }
+        }
+        let parentSection = section;
+        if ( childElement.nodeName ==  'section' ) {
+          parentSection = childElement ;
+          previousParentElement = childElement ;
+        }
+        if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
+          addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
+        }
+      }
+    }
+
+    if ( element.nodeType == Node.COMMENT_NODE ) {
+      if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
+        addAttributeInElement( element, section, separatorSectionAttributes );
+      }
+    }
+  }
+
+  /**
+   * Converts any current data-markdown slides in the
+   * DOM to HTML.
+   */
+  function convertSlides() {
+
+    let sections = document.querySelectorAll( '[data-markdown]');
+
+    for ( let i = 0, len = sections.length; i < len; i++ ) {
+
+      let section = sections[i];
+
+      // Only parse the same slide once
+      if ( !section.getAttribute( 'data-markdown-parsed' ) ) {
+
+        section.setAttribute( 'data-markdown-parsed', true );
+
+        let notes = section.querySelector( 'aside.notes' );
+        let markdown = getMarkdownFromSlide( section );
+
+        section.innerHTML = marked( markdown );
+        addAttributes(   section, section, null, section.getAttribute( 'data-element-attributes' ) ||
+                section.parentNode.getAttribute( 'data-element-attributes' ) ||
+                DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+        section.getAttribute( 'data-attributes' ) ||
+                section.parentNode.getAttribute( 'data-attributes' ) ||
+                DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
+
+        // If there were notes, we need to re-add them after
+        // having overwritten the section's HTML
+        if ( notes ) {
+          section.appendChild( notes );
+        }
+
+      }
+
+    }
+
+  }
+
+  // API
+  return {
+    getMarkdownFromSlide: getMarkdownFromSlide,
+    createMarkdownSlide: createMarkdownSlide,
+    processSlides: processSlides,
+    addAttributes: addAttributes,
+    convertSlides: convertSlides
+  };
+}

+ 2 - 0
src/client/styles/scss/_admin.scss

@@ -16,6 +16,8 @@
   }
 
   .admin-customize {
+    @import 'hljs';
+
     .ss-container img {
       padding: .5em;
       background-color: #ddd;

+ 35 - 0
src/client/styles/scss/_hljs.scss

@@ -0,0 +1,35 @@
+pre.hljs {
+  // override Highlight Js Style Border
+  border-radius: 3px;
+  &.hljs-no-border {
+    border: none;
+  }
+
+  position: relative;
+
+  cite {
+    position: absolute;
+    top: 0;
+    right: 0;
+    padding: 0 4px;
+    background: #ccc;
+    color: #333;
+    font-style: normal;
+    font-weight: bold;
+    opacity: 0.6;
+  }
+}
+
+// styles for highlightjs-line-numbers
+.hljs-ln td.hljs-ln-numbers {
+  user-select: none;
+
+  text-align: center;
+  color: #ccc;
+  border-right: 1px solid #CCC;
+  vertical-align: top;
+  padding-right: 5px;
+}
+.hljs-ln td.hljs-ln-code {
+  padding-left: 10px;
+}

+ 5 - 0
src/client/styles/scss/_on-edit.scss

@@ -288,6 +288,11 @@ body.on-edit {
   display: block;
 }
 
+// overwrite .CodeMirror pre
+.CodeMirror pre {
+  font-family: $font-family-monospace;
+}
+
 // overwrite .CodeMirror-hints
 .CodeMirror-hints {
   max-height: 30em !important;

+ 0 - 9
src/client/styles/scss/_override-hljs.scss

@@ -1,9 +0,0 @@
-// override Highlight Js Style Border
-.wiki, .admin-customize {
-  pre.hljs {
-    border-radius: 3px;
-    &.hljs-no-border {
-      border: none;
-    }
-  }
-}

+ 12 - 5
src/client/styles/scss/_search.scss

@@ -30,6 +30,7 @@
   }
   .rbt-menu {
     margin-top: 3px;
+
     li a span {
       .page-path {
         display: inline;
@@ -100,11 +101,6 @@
       width: 300px;
     }
   }
-  .rbt-menu {
-    margin-top: 33px;   // DIRTY HACK
-                        //   note: 'transform: translate3d(0px, XXpx, 0px)' calculation has failed on .search-top
-                        //         since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
-  }
 }
 .search-sidebar {
   .search-form, .form-group, .rbt-input.form-control, .input-group {
@@ -115,6 +111,17 @@
   }
 }
 
+@mixin search-typeahead-rbtmenu-mt($margin-top) {
+  .search-typeahead .rbt-menu {
+    margin-top: $margin-top;    // DIRTY HACK
+                                //   note: 'transform: translate3d(0px, XXpx, 0px)' calculation has failed
+                                //         since upgrade react-bootstrap-typeahead to v3.3.2 -- 2019.02.05 Yuki Takei
+  }
+}
+#search-top, #renamePage, #duplicatePage, .search-page-input {
+  @include search-typeahead-rbtmenu-mt(36px);
+}
+
 .search-result {
 
   .search-result-list {

+ 5 - 30
src/client/styles/scss/_wiki.scss

@@ -2,6 +2,11 @@ div.body {
   padding: 10px;
 }
 
+// hljs
+.wiki {
+  @import 'hljs';
+}
+
 .wiki {
   line-height: 1.8em;
   font-size: 15px;
@@ -108,36 +113,6 @@ div.body {
     }
   }
 
-  pre.hljs {
-    position: relative;
-
-    cite {
-      position: absolute;
-      top: 0;
-      right: 0;
-      padding: 0 4px;
-      background: #ccc;
-      color: #333;
-      font-style: normal;
-      font-weight: bold;
-      opacity: 0.6;
-    }
-  }
-
-  // styles for highlightjs-line-numbers
-  .hljs-ln td.hljs-ln-numbers {
-    user-select: none;
-
-    text-align: center;
-    color: #ccc;
-    border-right: 1px solid #CCC;
-    vertical-align: top;
-    padding-right: 5px;
-  }
-  .hljs-ln td.hljs-ln-code {
-    padding-left: 10px;
-  }
-
   p code {  // only inline code blocks
     font-family: $font-family-monospace-not-strictly;
   }

+ 0 - 3
src/client/styles/scss/style.scss → src/client/styles/scss/style-app.scss

@@ -8,9 +8,6 @@
 // vendor
 @import 'vendor';
 
-// override highlightJsStyle
-@import 'override-hljs';
-
 // override react-bootstrap-typeahead styles
 @import 'override-rbt';
 

+ 137 - 129
src/client/styles/scss/style-presentation.scss

@@ -1,129 +1,137 @@
-// import Growi variable
-@import 'variables';
-
-.reveal {
-  font-size: 32px;
-  section * {
-    font-family: Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif !important;
-  }
-
-  .slides > section {
-    //text-align: left;
-    padding: 0;
-
-    &.only.present {
-      h1, h2, h3, h4, h5, h6 {
-        font-size: 2.5em;
-      }
-    }
-
-    p {
-      line-height: 1.6;
-
-      &:first-child {
-        margin-top: 0;
-      }
-    }
-
-    pre {
-      code {
-        padding: 20px 40px;
-      }
-    }
-    blockquote {
-      width: 80%;
-      padding: 20px 60px;
-    }
-
-    ul {
-      margin-top: .2em;
-      margin-bottom: .1em;
-      > li {
-        line-height: 1.6;
-        margin-bottom: .5em;
-
-        > ul > li {
-          font-size: .85em;
-        }
-      }
-    }
-
-    h1:first-child {
-      font-size: 2.2em;
-    }
-    h2:first-child {
-      font-size: 1.8em;
-    }
-    h3, h4, h5, h6 {
-      &:first-child {
-        font-size: 1.5em;
-      }
-    }
-
-    // {{{ table (copied from bootstrap .table
-    table {
-      width: 100%;
-      margin-bottom: 1em;
-
-      border-collapse: collapse;
-      tr, td, th {
-        border-collapse: collapse;
-      }
-
-      // Cells
-      > thead,
-      > tbody,
-      > tfoot {
-        > tr {
-          > th,
-          > td {
-            padding: 1em;
-            vertical-align: top;
-            border-top: 1px solid #999;
-          }
-        }
-      }
-      // Bottom align for column headings
-      > thead > tr > th {
-        vertical-align: bottom;
-        border-bottom: 2px solid #888;
-      }
-      // Remove top border from thead by default
-      > caption + thead,
-      > colgroup + thead,
-      > thead:first-child {
-        > tr:first-child {
-          > th,
-          > td {
-            border-top: 0;
-          }
-        }
-      }
-      // Account for multiple tbody instances
-      > tbody + tbody {
-        border-top: 2px solid #888;
-      }
-
-      // .table-bordered
-      border: 1px solid #999;
-      > thead,
-      > tbody,
-      > tfoot {
-        > tr {
-          > th,
-          > td {
-            border: 1px solid #999;
-          }
-        }
-      }
-      > thead > tr {
-        > th,
-        > td {
-          border-bottom-width: 2px;
-        }
-      }
-    }
-    // }}}
-
-  }
-}
+// import Growi variable
+@import 'variables';
+
+@import "~reveal.js/css/reveal.css";
+@import "~reveal.js/css/theme/black.css";
+
+// hljs
+.reveal {
+  @import 'hljs';
+}
+
+.reveal {
+  font-size: 32px;
+  section * {
+    font-family: Lato, -apple-system, BlinkMacSystemFont, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif !important;
+  }
+
+  .slides > section {
+    //text-align: left;
+    padding: 0;
+
+    &.only.present {
+      h1, h2, h3, h4, h5, h6 {
+        font-size: 2.5em;
+      }
+    }
+
+    p {
+      line-height: 1.6;
+
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+
+    pre {
+      code {
+        padding: 20px 40px;
+      }
+    }
+    blockquote {
+      width: 80%;
+      padding: 20px 60px;
+    }
+
+    ul {
+      margin-top: .2em;
+      margin-bottom: .1em;
+      > li {
+        line-height: 1.6;
+        margin-bottom: .5em;
+
+        > ul > li {
+          font-size: .85em;
+        }
+      }
+    }
+
+    h1:first-child {
+      font-size: 2.2em;
+    }
+    h2:first-child {
+      font-size: 1.8em;
+    }
+    h3, h4, h5, h6 {
+      &:first-child {
+        font-size: 1.5em;
+      }
+    }
+
+    // {{{ table (copied from bootstrap .table
+    table {
+      width: 100%;
+      margin-bottom: 1em;
+
+      border-collapse: collapse;
+      tr, td, th {
+        border-collapse: collapse;
+      }
+
+      // Cells
+      > thead,
+      > tbody,
+      > tfoot {
+        > tr {
+          > th,
+          > td {
+            padding: 1em;
+            vertical-align: top;
+            border-top: 1px solid #999;
+          }
+        }
+      }
+      // Bottom align for column headings
+      > thead > tr > th {
+        vertical-align: bottom;
+        border-bottom: 2px solid #888;
+      }
+      // Remove top border from thead by default
+      > caption + thead,
+      > colgroup + thead,
+      > thead:first-child {
+        > tr:first-child {
+          > th,
+          > td {
+            border-top: 0;
+          }
+        }
+      }
+      // Account for multiple tbody instances
+      > tbody + tbody {
+        border-top: 2px solid #888;
+      }
+
+      // .table-bordered
+      border: 1px solid #999;
+      > thead,
+      > tbody,
+      > tfoot {
+        > tr {
+          > th,
+          > td {
+            border: 1px solid #999;
+          }
+        }
+      }
+      > thead > tr {
+        > th,
+        > td {
+          border-bottom-width: 2px;
+        }
+      }
+    }
+    // }}}
+
+  }
+}

+ 37 - 41
src/server/models/bookmark.js

@@ -2,6 +2,7 @@ module.exports = function(crowi) {
   const debug = require('debug')('growi:models:bookmark');
   const mongoose = require('mongoose');
   const ObjectId = mongoose.Schema.Types.ObjectId;
+  const bookmarkEvent = crowi.event('bookmark');
 
   let bookmarkSchema = null;
 
@@ -79,27 +80,24 @@ module.exports = function(crowi) {
     });
   };
 
-  bookmarkSchema.statics.add = function(page, user) {
+  bookmarkSchema.statics.add = async function(page, user) {
     const Bookmark = this;
 
-    return new Promise(function(resolve, reject) {
-      const newBookmark = new Bookmark;
-
-      newBookmark.page = page;
-      newBookmark.user = user;
-      newBookmark.createdAt = Date.now();
-      newBookmark.save(function(err, bookmark) {
-        debug('Bookmark.save', err, bookmark);
-        if (err) {
-          if (err.code === 11000) { // duplicate key (dummy reesponse of new object)
-            return resolve(newBookmark);
-          }
-          return reject(err);
-        }
-
-        resolve(bookmark);
-      });
-    });
+    const newBookmark = new Bookmark({ page, user, createdAt: Date.now() });
+
+    try {
+      const bookmark = await newBookmark.save();
+      bookmarkEvent.emit('create', page._id);
+      return bookmark;
+    }
+    catch (err) {
+      if (err.code === 11000) {
+        // duplicate key (dummy response of new object)
+        return newBookmark;
+      }
+      debug('Bookmark.save failed', err);
+      throw err;
+    }
   };
 
   /**
@@ -107,34 +105,32 @@ module.exports = function(crowi) {
    * used only when removing the page
    * @param {string} pageId
    */
-  bookmarkSchema.statics.removeBookmarksByPageId = function(pageId) {
+  bookmarkSchema.statics.removeBookmarksByPageId = async function(pageId) {
     const Bookmark = this;
 
-    return new Promise(function(resolve, reject) {
-      Bookmark.remove({page: pageId}, function(err, data) {
-        if (err) {
-          debug('Bookmark.remove failed (removeBookmarkByPage)', err);
-          return reject(err);
-        }
-
-        return resolve(data);
-      });
-    });
+    try {
+      const data = await Bookmark.remove({ page: pageId });
+      bookmarkEvent.emit('delete', pageId);
+      return data;
+    }
+    catch (err) {
+      debug('Bookmark.remove failed (removeBookmarkByPage)', err);
+      throw err;
+    }
   };
 
-  bookmarkSchema.statics.removeBookmark = function(page, user) {
+  bookmarkSchema.statics.removeBookmark = async function(page, user) {
     const Bookmark = this;
 
-    return new Promise(function(resolve, reject) {
-      Bookmark.findOneAndRemove({page: page, user: user}, function(err, data) {
-        if (err) {
-          debug('Bookmark.findOneAndRemove failed', err);
-          return reject(err);
-        }
-
-        return resolve(data);
-      });
-    });
+    try {
+      const data = await Bookmark.findOneAndRemove({ page, user });
+      bookmarkEvent.emit('delete', page);
+      return data;
+    }
+    catch (err) {
+      debug('Bookmark.findOneAndRemove failed', err);
+      throw err;
+    }
   };
 
   return mongoose.model('Bookmark', bookmarkSchema);

+ 1 - 2
src/server/routes/hackmd.js

@@ -7,7 +7,6 @@ const axios = require('axios');
 const ApiResponse = require('../util/apiResponse');
 
 module.exports = function(crowi, app) {
-  const config = crowi.getConfig();
   const Page = crowi.models.Page;
   const pageEvent = crowi.event('page');
 
@@ -72,7 +71,7 @@ module.exports = function(crowi, app) {
 
     // generate definitions to replace
     const definitions = {
-      styles,
+      styles: escape(styles),
     };
     // inject
     const script = stylesScriptContentTpl(definitions);

+ 29 - 34
src/server/util/createGrowiPagesFromImports.js

@@ -11,44 +11,39 @@ module.exports = crowi => {
    *    user: Object
    * }]
    */
-  const createGrowiPages = (pages) => {
-    let errors = [];
+  const createGrowiPages = async(pages) => {
+    const promises = [];
+    const errors = [];
 
-    return new Promise((resolve, reject) => {
-      const promises = pages.map(page => {
-        return new Promise(async(resolve, reject) => {
-          const path = page.path;
-          const user = page.user;
-          const body = page.body;
-          const isCreatableName = await Page.isCreatableName(path);
-          const isPageNameTaken = await Page.findPage(path, user, null, true);
+    for (let page of pages) {
+      const path = page.path;
+      const user = page.user;
+      const body = page.body;
+      const isCreatableName = await Page.isCreatableName(path);
+      const isPageNameTaken = await Page.findByPathAndViewer(path, user);
 
-          if (isCreatableName && !isPageNameTaken) {
-            try {
-              await Page.create(path, body, user, { grant: Page.GRANT_PUBLIC, grantUserGroupId: null });
-            }
-            catch (err) {
-              errors.push(err);
-            }
-          }
-          else {
-            if (!isCreatableName) {
-              errors.push(new Error(`${path} is not a creatable name in Growi`));
-            }
-            if (isPageNameTaken) {
-              errors.push(new Error(`${path} already exists in Growi`));
-            }
-          }
+      if (isCreatableName && !isPageNameTaken) {
+        try {
+          const promise = Page.create(path, body, user, { grant: Page.GRANT_PUBLIC, grantUserGroupId: null });
+          promises.push(promise);
+        }
+        catch (err) {
+          errors.push(err);
+        }
+      }
+      else {
+        if (!isCreatableName) {
+          errors.push(new Error(`${path} is not a creatable name in Growi`));
+        }
+        if (isPageNameTaken) {
+          errors.push(new Error(`${path} already exists in Growi`));
+        }
+      }
+    }
 
-          resolve();
-        });
-      });
+    await Promise.all(promises);
 
-      Promise.all(promises)
-        .then(() => {
-          resolve(errors);
-        });
-    });
+    return errors;
   };
 
   return createGrowiPages;

+ 0 - 1
src/server/util/middlewares.js

@@ -156,7 +156,6 @@ exports.swigFilters = function(crowi, app, swig) {
     swig.setFilter('presentation', function(string) {
       // 手抜き
       return string
-        .replace(/[\n]+#/g, '\n\n\n#')
         .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
     });
 

+ 1 - 1
src/server/util/search.js

@@ -786,7 +786,7 @@ SearchClient.prototype.syncPageDeleted = function(page, user) {
 SearchClient.prototype.syncBookmarkChanged = async function(pageId) {
   const Page = this.crowi.model('Page');
   const Bookmark = this.crowi.model('Bookmark');
-  const page = await Page.findPageById(pageId);
+  const page = await Page.findById(pageId);
   const bookmarkCount = await Bookmark.countByPageId(pageId);
 
   page.bookmarkCount = bookmarkCount;

+ 3 - 7
src/server/views/admin/customize.html

@@ -2,22 +2,18 @@
 
 {% block html_title %}{{ customTitle(t('Customize')) }} {% endblock %}
 
-{% block style_css_block %}
+{% block theme_css_block %}
   {% if 'kibela' === layoutType() %}
     {% if env === 'development' %}
-      <script src="{{ webpack_asset('styles/style.js') }}"></script>
       <script src="{{ webpack_asset('styles/theme-kibela.js') }}"></script>
     {% else %}
-      <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
-      <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-kibela.css') }}">
+      <link rel="stylesheet" href="{{ webpack_asset('styles/theme-kibela.css') }}">
     {% endif %}
   {% else %}
     {% if env === 'development' %}
-      <script src="{{ webpack_asset('styles/style.js') }}"></script>
       <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
     {% else %}
-      <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') }}">
+    <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
     {% endif %}
   {% endif %}
 {% endblock %}

+ 9 - 4
src/server/views/layout/layout.html

@@ -71,20 +71,25 @@
 
   <!-- styles -->
   {% block style_css_block %}
+    {% if env === 'development' %}
+      <script src="{{ webpack_asset('styles/style-commons.js') }}"></script>
+      <script src="{{ webpack_asset('styles/style-app.js') }}"></script>
+    {% else %}
+      <script src="{{ webpack_asset('styles/style-commons.js') }}"></script>
+      <link rel="stylesheet" href="{{ webpack_asset('styles/style-app.css') }}">
+    {% endif %}
+  {% endblock %}
+  {% block theme_css_block %}
     {% if 'kibela' === layoutType() %}
       {% if env === 'development' %}
-        <script src="{{ webpack_asset('styles/style.js') }}"></script>
         <script src="{{ webpack_asset('styles/theme-kibela.js') }}"></script>
       {% else %}
-        <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
         <link rel="stylesheet" href="{{ webpack_asset('styles/theme-kibela.css') }}">
       {% endif %}
     {% else %}
       {% if env === 'development' %}
-        <script src="{{ webpack_asset('styles/style.js') }}"></script>
         <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
       {% else %}
-        <link rel="stylesheet" href="{{ webpack_asset('styles/style.css') }}">
         <link rel="stylesheet" href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
       {% endif %}
     {% endif %}

+ 3 - 4
src/server/views/page_presentation.html

@@ -27,13 +27,12 @@
     {% endif %}
 
     <script src="{{ webpack_asset('js/legacy-presentation.js') }}" defer></script>
+    <link rel="stylesheet" href="{{ webpack_asset('styles/style-presentation.css') }}">
 
     <title>{{ path|path2name }} | {{ path }}</title>
 
-    <!-- styles -->
-    <link rel="stylesheet" href="{{ webpack_asset('styles/style-presentation.css') }}">
-
     {{ cdnStyleTagsByGroup('basis') }}
+    {{ cdnHighlightJsStyleTag(highlightJsStyle()) }}
 
     <style>
       {{ customCss() }}
@@ -48,7 +47,7 @@
         {% if 3 === pageBreakSeparator %}
           {% set dataSeparator = pageBreakCustomSeparator %}
         {% elseif 2 === pageBreakSeparator %}
-          {% set dataSeparator = "^-----$" %}
+          {% set dataSeparator = "\n-----\n" %}
         {% else %}
           {% set dataSeparator = "\n\n\n" %}
         {% endif %}

Разница между файлами не показана из-за своего большого размера
+ 350 - 298
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов