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

Merge pull request #1606 from weseek/fix/theme-preview

Fix/theme preview
Yuki Takei 6 лет назад
Родитель
Сommit
3eaccc4ab2

+ 1 - 2
config/webpack.common.js

@@ -23,7 +23,6 @@ module.exports = (options) => {
       'js/app':                       './src/client/js/app',
       'js/installer':                 './src/client/js/installer',
       'js/legacy':                    './src/client/js/legacy/crowi',
-      'js/legacy-admin':              './src/client/js/legacy/crowi-admin',
       'js/legacy-presentation':       './src/client/js/legacy/crowi-presentation',
       'js/plugin':                    './src/client/js/plugin',
       'js/ie11-polyfill':             './src/client/js/ie11-polyfill',
@@ -158,7 +157,7 @@ module.exports = (options) => {
             test: /\.(sc|sa|c)ss$/,
             chunks: (chunk) => {
               // ignore patterns
-              return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-admin|legacy-presentation/);
+              return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-presentation/);
             },
             name: 'styles/style-commons',
             minSize: 1,

+ 21 - 20
src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx

@@ -36,21 +36,26 @@ class CustomizeHighlightSetting extends React.Component {
     }
   }
 
-  getDemoFunction() {
-    return `function $initHighlight(block, cls) {
-    try {
+  renderHljsDemo() {
+    const { adminCustomizeContainer } = this.props;
 
-      if (cls.search(/\bno\-highlight\b/) !== -1) {
-        return \`\${process(block, true, 0x0F)} class="\${cls}"\`;
-      }
-    }
-    catch (e) {
-      /* handle exception */
-    }
-    for (let i = 0 / 2; i < classes.length; i++) {
-      if (checkCondition(classes[i]) === undefined) { console.log('undefined') }
-    }
-  };`;
+    /* eslint-disable max-len */
+    const html = `<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MersenneTwister</span>(<span class="hljs-params">seed</span>) </span>{
+  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">arguments</span>.length == <span class="hljs-number">0</span>) {
+    seed = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime();
+  }
+
+  <span class="hljs-keyword">this</span>._mt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">624</span>);
+  <span class="hljs-keyword">this</span>.setSeed(seed);
+}</span>`;
+    /* eslint-enable max-len */
+
+    return (
+      <pre className={`hljs ${!adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled && 'hljs-no-border'}`}>
+        {/* eslint-disable-next-line react/no-danger */}
+        <code dangerouslySetInnerHTML={{ __html: html }}></code>
+      </pre>
+    );
   }
 
   render() {
@@ -65,7 +70,7 @@ class CustomizeHighlightSetting extends React.Component {
 
       menuItem.push(
         <li key={styleId} role="presentation" type="button" onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}>
-          <a role="menuitem">{styleName}</a>
+          <a role="button">{styleName}</a>
         </li>,
       );
     });
@@ -115,11 +120,7 @@ class CustomizeHighlightSetting extends React.Component {
         <div className="help-block">
           <label>Examples:</label>
           <div className="wiki">
-            <pre className={`hljs ${!adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled && 'hljs-no-border'}`}>
-              <code className="highlightjs-demo">
-                {this.getDemoFunction()}
-              </code>
-            </pre>
+            {this.renderHljsDemo()}
           </div>
         </div>
 

+ 0 - 5
src/client/js/legacy/crowi-admin.js

@@ -1,5 +0,0 @@
-require('./thirdparty-js/jQuery.style.switcher');
-
-$(() => {
-  $('#styleOptions').styleSwitcher();
-});

+ 0 - 233
src/client/js/legacy/thirdparty-js/jQuery.style.switcher.js

@@ -1,233 +0,0 @@
-/**
-@author Cameron Manavian
-jQuery Style Switcher
-
-The MIT License (MIT)
-
-Copyright (c) 2014 Cameron Manavian
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-**/
-(function ($) {
-	var jStyleSwitcher,
-		_defaultOptions = {
-			hasPreview: true,
-			defaultThemeId: 'jssDefault',
-			fullPath: 'css/',
-			cookie: {
-				expires: 30,
-				isManagingLoad: true
-			}
-		},
-		// private
-		_cookieKey = 'jss_selected',
-		_docCookies = {};
-
-	/*\
-	|*|
-	|*|  :: cookies.js ::
-	|*|
-	|*|  A complete cookies reader/writer framework with full unicode support.
-	|*|
-	|*|  revision #1
-	|*|
-	|*|  https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
-	|*|
-	|*|  This framework is released under the GNU Public License, version 3 or later.
-	|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
-	|*|
-	|*|  Syntaxes:
-	|*|
-	|*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
-	|*|  * docCookies.getItem(name)
-	|*|  * docCookies.removeItem(name[, path[, domain]])
-	|*|  * docCookies.hasItem(name)
-	|*|  * docCookies.keys()
-	|*|
-	\*/
-	_docCookies = {
-		getItem: function (sKey) {
-			if (!sKey) {
-				return null;
-			}
-			return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
-		},
-		setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
-			if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
-				return false;
-			}
-			var sExpires = "";
-			if (vEnd) {
-				switch (vEnd.constructor) {
-					case Number:
-						sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
-						break;
-					case String:
-						sExpires = "; expires=" + vEnd;
-						break;
-					case Date:
-						sExpires = "; expires=" + vEnd.toUTCString();
-						break;
-				}
-			}
-			document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
-			return true;
-		},
-		removeItem: function (sKey, sPath, sDomain) {
-			if (!this.hasItem(sKey)) {
-				return false;
-			}
-			document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
-			return true;
-		},
-		hasItem: function (sKey) {
-			if (!sKey) {
-				return false;
-			}
-			return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
-		},
-		keys: function () {
-			var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
-			for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
-				aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
-			}
-			return aKeys;
-		}
-	};
-
-	jStyleSwitcher = function ($root, config) {
-		return this.init($root, config);
-	};
-
-	jStyleSwitcher.prototype = {
-
-		/**
-		 * {Object} DOM reference to style option list
-		 */
-		$root: null,
-
-		/**
-		 * {Object} configs for the style switcher
-		 */
-		config: {},
-
-		/**
-		 * {Object} jQuery reference to <link> tag for swapping CSS
-		 */
-		$themeCss: null,
-
-		/**
-		 * {String} default theme page was loaded with
-		 */
-		defaultTheme: null,
-
-		init: function ($root, config) {
-			this.$root = $root;
-			this.config = config ? config : {};
-			this.setDefaultTheme();
-			if(this.defaultTheme) {
-				// try cookies
-				if (this.config.cookie) {
-					this.checkCookie();
-				}
-				// try hover
-				if (this.config.hasPreview) {
-					this.addHoverEvents();
-				}
-				// finally, add click events
-				this.addClickEvents();
-			} else {
-				this.$root.addClass('jssError error level0');
-			}
-		},
-
-		setDefaultTheme: function () {
-			this.$themeCss = $('link[id=' + this.config.defaultThemeId + ']');
-			if(this.$themeCss.length) {
-				this.defaultTheme = this.$themeCss.attr('href');
-			}
-		},
-
-		resetStyle: function() {
-			this.updateStyle(this.defaultTheme);
-		},
-
-		updateStyle: function(newStyle) {
-			this.$themeCss.attr('href', newStyle);
-		},
-
-		getFullAssetPath: function(asset) {
-			return this.config.fullPath + asset;
-		},
-
-		checkCookie: function () {
-			var styleCookie;
-			// if using cookies and using JavaScript to load css
-			if (this.config.cookie && this.config.cookie.isManagingLoad) {
-				// check if css is set in cookie
-				styleCookie = _docCookies.getItem(_cookieKey);
-				if (styleCookie) {
-					newStyle = this.getFullAssetPath(styleCookie);
-					// update link tag
-					this.updateStyle(newStyle);
-					// update default ref
-					this.defaultTheme = newStyle;
-				}
-			}
-		},
-
-		addHoverEvents: function () {
-			var self = this;
-			this.$root.find('a').hover(
-				function () {
-					var asset = $(this).data('theme'),
-						newStyle = self.getFullAssetPath(asset);
-					// update link tag
-					self.updateStyle(newStyle);
-				},
-				function () {
-					// reset link tag
-					self.resetStyle();
-				}
-			);
-		},
-
-		addClickEvents: function () {
-			var self = this;
-			this.$root.find('a').click(
-				function () {
-					var asset = $(this).data('theme'),
-            newStyle = self.getFullAssetPath(asset);
-					// update link tag
-					self.updateStyle(newStyle);
-					// update default ref
-					self.defaultTheme = newStyle;
-					// try to store cookie
-					if (self.config.cookie) {
-						_docCookies.setItem(_cookieKey, asset, self.config.cookie.expires, '/');
-					}
-				}
-			);
-		}
-	};
-
-	$.fn.styleSwitcher = function (options) {
-		return new jStyleSwitcher(this, $.extend(true, _defaultOptions, options));
-	};
-})(jQuery);

+ 36 - 1
src/client/js/services/AdminCustomizeContainer.js

@@ -109,6 +109,11 @@ export default class AdminCustomizeContainer extends Container {
       return;
     }
     this.setState({ currentTheme: themeName });
+
+    // preview if production
+    if (process.env.NODE_ENV !== 'development') {
+      this.previewTheme(themeName);
+    }
   }
 
   /**
@@ -161,6 +166,8 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentHighlightJsStyleName: styleName });
     // recommended settings are applied
     this.setState({ isHighlightJsStyleBorderEnabled: isBorderEnable });
+
+    this.previewHighlightJsStyle(styleId);
   }
 
   /**
@@ -198,6 +205,34 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentCustomizeScript: inpuValue });
   }
 
+  /**
+   * Preview theme
+   * @param {string} themeName
+   */
+  async previewTheme(themeName) {
+    try {
+      // get theme asset path
+      const response = await this.appContainer.apiv3.get('/customize-setting/layout-theme/asset-path', { themeName });
+      const { assetPath } = response.data;
+
+      const themeLink = document.getElementById('grw-theme-link');
+      themeLink.setAttribute('href', assetPath);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }
+
+  /**
+   * Preview hljs style
+   * @param {string} styleId
+   */
+  previewHighlightJsStyle(styleId) {
+    const styleLInk = document.querySelectorAll('#grw-hljs-container-for-demo link')[0];
+    // replace css url
+    // see https://regex101.com/r/gBNZYu/4
+    styleLInk.href = styleLInk.href.replace(/[^/]+\.css$/, `${styleId}.css`);
+  }
 
   /**
    * Update layout
@@ -205,7 +240,7 @@ export default class AdminCustomizeContainer extends Container {
    * @return {Array} Appearance
    */
   async updateCustomizeLayoutAndTheme() {
-    const response = await this.appContainer.apiv3.put('/customize-setting/layoutTheme', {
+    const response = await this.appContainer.apiv3.put('/customize-setting/layout-theme', {
       layoutType: this.state.currentLayout,
       themeType: this.state.currentTheme,
     });

+ 47 - 4
src/server/routes/apiv3/customize-setting.js

@@ -7,7 +7,7 @@ const express = require('express');
 
 const router = express.Router();
 
-const { body } = require('express-validator/check');
+const { body, query } = require('express-validator');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 /**
@@ -92,6 +92,11 @@ module.exports = (crowi) => {
   const { ApiV3FormValidator } = crowi.middlewares;
 
   const validator = {
+    themeAssetPath: [
+      query('themeName').isString().isIn([
+        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'default-dark', 'future', 'blue-night', 'halloween', 'spring',
+      ]),
+    ],
     layoutTheme: [
       body('layoutType').isString().isIn(['growi', 'kibela', 'crowi']),
       body('themeType').isString().isIn([
@@ -173,11 +178,49 @@ module.exports = (crowi) => {
   /**
    * @swagger
    *
-   *    /customize-setting/layoutTheme:
+   *    /customize-setting/layout-theme/asset-path:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        operationId: getLayoutThemeAssetPath
+   *        summary: /customize-setting/layout-theme/asset-path
+   *        description: Get layout theme asset path
+   *        parameters:
+   *          - name: themeName
+   *            in: query
+   *            required: true
+   *            schema:
+   *              type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to update layout and theme
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    assetPath:
+   *                      type: string
+   */
+  router.get('/layout-theme/asset-path', loginRequiredStrictly, adminRequired, validator.themeAssetPath, ApiV3FormValidator, async(req, res) => {
+    const themeName = req.query.themeName;
+
+    const webpackAssetKey = `styles/theme-${themeName}.css`;
+    const assetPath = res.locals.webpack_asset(webpackAssetKey);
+
+    if (assetPath == null) {
+      return res.apiv3Err(new ErrorV3(`The asset for '${webpackAssetKey}' is undefined.`, 'invalid-asset'));
+    }
+
+    return res.apiv3({ assetPath });
+  });
+
+  /**
+   * @swagger
+   *
+   *    /customize-setting/layout-theme:
    *      put:
    *        tags: [CustomizeSetting]
    *        operationId: updateLayoutThemeCustomizeSetting
-   *        summary: /customize-setting/layoutTheme
+   *        summary: /customize-setting/layout-theme
    *        description: Update layout and theme
    *        requestBody:
    *          required: true
@@ -193,7 +236,7 @@ module.exports = (crowi) => {
    *                schema:
    *                  $ref: '#/components/schemas/CustomizeLayoutTheme'
    */
-  router.put('/layoutTheme', loginRequiredStrictly, adminRequired, csrf, validator.layoutTheme, ApiV3FormValidator, async(req, res) => {
+  router.put('/layout-theme', loginRequiredStrictly, adminRequired, csrf, validator.layoutTheme, ApiV3FormValidator, async(req, res) => {
     const requestParams = {
       'customize:layout': req.body.layoutType,
       'customize:theme': req.body.themeType,

+ 4 - 10
src/server/views/admin/customize.html

@@ -1,16 +1,6 @@
 {% extends '../layout/admin.html' %}
 {% block html_title %}{{ customizeService.generateCustomTitle(t('Customize')) }}{% endblock %}
 
-{% block theme_css_block %}
-{% set themeName = getConfig('crowi', 'customize:theme') %}
-
-{% if env === 'development' %}
-<script src="{{ webpack_asset('styles/theme-' + themeName + '.js') }}"></script>
-{% else %}
-<link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-' + themeName + '.css') }}" />
-{% endif %}
-{% endblock %}
-
 {% block html_additional_headers %}
 {% parent %}
 <!-- CodeMirror -->
@@ -45,6 +35,10 @@
   </div>
   {% endif %}
 
+  <div id="grw-hljs-container-for-demo">
+    {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}
+  </div>
+
   <div class="row">
     <div class="col-md-3">
       {% include './widget/menu.html' with {current: 'customize'} %}

+ 0 - 1
src/server/views/layout/admin.html

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

+ 1 - 1
src/server/views/widget/headers/styles-theme.html

@@ -1,5 +1,5 @@
 {% if env === 'development' %}
   <script src="{{ webpack_asset('styles/theme-' + themeName + '.js') }}"></script>
 {% else %}
-  <link rel="stylesheet" href="{{ webpack_asset('styles/theme-' + themeName + '.css') }}">
+  <link rel="stylesheet" id="grw-theme-link" href="{{ webpack_asset('styles/theme-' + themeName + '.css') }}">
 {% endif %}