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

Merge branch 'master' into fix/106996-private-legacy-style

Yuken Tezuka 3 лет назад
Родитель
Сommit
836aa8324d
48 измененных файлов с 289 добавлено и 568 удалено
  1. 0 2
      packages/app/public/static/locales/en_US/admin.json
  2. 11 0
      packages/app/public/static/locales/en_US/commons.json
  3. 0 4
      packages/app/public/static/locales/en_US/translation.json
  4. 0 2
      packages/app/public/static/locales/ja_JP/admin.json
  5. 11 0
      packages/app/public/static/locales/ja_JP/commons.json
  6. 0 4
      packages/app/public/static/locales/ja_JP/translation.json
  7. 0 2
      packages/app/public/static/locales/zh_CN/admin.json
  8. 11 0
      packages/app/public/static/locales/zh_CN/commons.json
  9. 0 4
      packages/app/public/static/locales/zh_CN/translation.json
  10. 0 73
      packages/app/src/client/services/AdminCustomizeContainer.js
  11. 23 30
      packages/app/src/client/services/PageContainer.js
  12. 0 4
      packages/app/src/components/Admin/Customize/Customize.jsx
  13. 1 1
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  14. 0 148
      packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx
  15. 1 1
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  16. 1 0
      packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx
  17. 11 6
      packages/app/src/components/InstallerForm.tsx
  18. 6 0
      packages/app/src/components/Layout/AdminLayout.tsx
  19. 1 1
      packages/app/src/components/Layout/RawLayout.tsx
  20. 43 30
      packages/app/src/components/Me/EditorSettings.tsx
  21. 11 13
      packages/app/src/components/Page/DisplaySwitcher.tsx
  22. 1 1
      packages/app/src/components/PageComment.tsx
  23. 1 1
      packages/app/src/components/PageEditor.tsx
  24. 15 0
      packages/app/src/components/PageEditorByHackmd.tsx
  25. 42 42
      packages/app/src/components/Theme/utils/ThemeProvider.tsx
  26. 0 22
      packages/app/src/interfaces/customize.ts
  27. 1 1
      packages/app/src/interfaces/editor-settings.ts
  28. 9 18
      packages/app/src/pages/[[...path]].page.tsx
  29. 2 2
      packages/app/src/pages/installer.page.tsx
  30. 0 17
      packages/app/src/server/routes/admin.js
  31. 11 5
      packages/app/src/stores/editor.tsx
  32. 4 8
      packages/app/src/stores/middlewares/sync-to-storage.ts
  33. 33 43
      packages/app/src/stores/ui.tsx
  34. 4 4
      packages/app/src/styles/_editor.scss
  35. 0 37
      packages/app/src/styles/_hljs.scss
  36. 2 2
      packages/app/src/styles/_layout.scss
  37. 1 1
      packages/app/src/styles/_old-ios.scss
  38. 1 1
      packages/app/src/styles/_variables.scss
  39. 0 5
      packages/app/src/styles/_wiki.scss
  40. 2 6
      packages/app/src/styles/style-next.scss
  41. 14 14
      packages/app/src/styles/style-themes.scss
  42. 1 1
      packages/app/src/styles/theme/_apply-colors-light.scss
  43. 2 2
      packages/app/src/styles/theme/_apply-colors.scss
  44. 2 2
      packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts
  45. 2 2
      packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts
  46. 2 2
      packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts
  47. 1 1
      packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts
  48. 5 3
      packages/app/test/cypress/integration/60-home/home.spec.ts

+ 0 - 2
packages/app/public/static/locales/en_US/admin.json

@@ -467,8 +467,6 @@
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
       "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
       "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
     },
     },
-    "code_highlight": "Code highlight",
-    "nocdn_desc": "This function is disabled when the environment variable <code>NO_CDN=true</code>.<br>Github style has been forcibly applied.",
     "custom_title": "Custom title",
     "custom_title": "Custom title",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - The site name of this wiki.",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - The site name of this wiki.",

+ 11 - 0
packages/app/public/static/locales/en_US/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "English"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/en_US/translation.json

@@ -523,10 +523,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
   "toaster": {
   "toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-    "update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "File upload succeeded.",
     "file_upload_succeeded": "File upload succeeded.",
     "file_upload_failed": "File upload failed.",
     "file_upload_failed": "File upload failed.",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",

+ 0 - 2
packages/app/public/static/locales/ja_JP/admin.json

@@ -499,8 +499,6 @@
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
       "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
     },
     },
-    "code_highlight": "コードハイライト",
-    "nocdn_desc": "この機能は、環境変数 <code>NO_CDN=true</code> の時は無効化されます。<br>GitHub スタイルが適用されています。",
     "custom_title": "カスタム Title",
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - この Wiki のサイト名",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - この Wiki のサイト名",

+ 11 - 0
packages/app/public/static/locales/ja_JP/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "日本語"
+  },
+  "toaster": {
+    "create_succeeded": "新しい{{target}}が作成されました",
+    "create_failed": "{{target}}の作成に失敗しました",
+    "update_successed": "{{target}}を更新しました",
+    "update_failed": "{{target}}の更新に失敗しました"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/ja_JP/translation.json

@@ -514,10 +514,6 @@
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   },
   "toaster": {
   "toaster": {
-    "create_succeeded": "新しい{{target}}が作成されました",
-    "create_failed": "{{target}}の作成に失敗しました",
-    "update_successed": "{{target}}を更新しました",
-    "update_failed": "{{target}}の更新に失敗しました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "initialize_successed": "{{target}}を初期化しました",
     "initialize_successed": "{{target}}を初期化しました",

+ 0 - 2
packages/app/public/static/locales/zh_CN/admin.json

@@ -464,8 +464,6 @@
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
       "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
     },
     },
-    "code_highlight": "代码突出显示",
-    "nocdn_desc": "当强制应用环境变量<code>NO_CDN=true</code><br>Github样式时,此函数被禁用。",
     "custom_title": "自定义标题",
     "custom_title": "自定义标题",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",

+ 11 - 0
packages/app/public/static/locales/zh_CN/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "简体中文"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/zh_CN/translation.json

@@ -496,10 +496,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
 	"toaster": {
 	"toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-		"update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "文件上传成功",
     "file_upload_succeeded": "文件上传成功",
     "file_upload_failed": "文件上传失败",
     "file_upload_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",

+ 0 - 73
packages/app/src/client/services/AdminCustomizeContainer.js

@@ -35,26 +35,10 @@ export default class AdminCustomizeContainer extends Container {
       isEnabledStaleNotification: false,
       isEnabledStaleNotification: false,
       isAllReplyShown: false,
       isAllReplyShown: false,
       isSearchScopeChildrenAsDefault: false,
       isSearchScopeChildrenAsDefault: false,
-      currentHighlightJsStyleId: '',
-      isHighlightJsStyleBorderEnabled: false,
       currentCustomizeTitle: '',
       currentCustomizeTitle: '',
       currentCustomizeHeader: '',
       currentCustomizeHeader: '',
       currentCustomizeCss: '',
       currentCustomizeCss: '',
       currentCustomizeScript: '',
       currentCustomizeScript: '',
-      /* eslint-disable quote-props, no-multi-spaces */
-      highlightJsCssSelectorOptions: {
-        'github':           { name: '[Light] GitHub',         border: false },
-        'github-gist':      { name: '[Light] GitHub Gist',    border: true },
-        'atom-one-light':   { name: '[Light] Atom One Light', border: true },
-        'xcode':            { name: '[Light] Xcode',          border: true },
-        'vs':               { name: '[Light] Vs',             border: true },
-        'atom-one-dark':    { name: '[Dark] Atom One Dark',   border: false },
-        'hybrid':           { name: '[Dark] Hybrid',          border: false },
-        'monokai':          { name: '[Dark] Monokai',         border: false },
-        'tomorrow-night':   { name: '[Dark] Tomorrow Night',  border: false },
-        'vs2015':           { name: '[Dark] Vs 2015',         border: false },
-      },
-      /* eslint-enable quote-props, no-multi-spaces */
     };
     };
     this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
     this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
     this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
     this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
@@ -88,16 +72,11 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
-        currentHighlightJsStyleId: customizeParams.styleName,
-        isHighlightJsStyleBorderEnabled: customizeParams.styleBorder,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeHeader: customizeParams.customizeHeader,
         currentCustomizeHeader: customizeParams.customizeHeader,
         currentCustomizeCss: customizeParams.customizeCss,
         currentCustomizeCss: customizeParams.customizeCss,
         currentCustomizeScript: customizeParams.customizeScript,
         currentCustomizeScript: customizeParams.customizeScript,
       });
       });
-
-      // search style name from object for display
-      this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[customizeParams.styleName].name });
     }
     }
     catch (err) {
     catch (err) {
       this.setState({ retrieveError: err });
       this.setState({ retrieveError: err });
@@ -171,25 +150,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
     this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
   }
   }
 
 
-  /**
-   * Switch highlightJsStyle
-   */
-  switchHighlightJsStyle(styleId, styleName, isBorderEnable) {
-    this.setState({ currentHighlightJsStyleId: styleId });
-    this.setState({ currentHighlightJsStyleName: styleName });
-    // recommended settings are applied
-    this.setState({ isHighlightJsStyleBorderEnabled: isBorderEnable });
-
-    this.previewHighlightJsStyle(styleId);
-  }
-
-  /**
-   * Switch highlightJsStyleBorder
-   */
-  switchHighlightJsStyleBorder() {
-    this.setState({ isHighlightJsStyleBorderEnabled: !this.state.isHighlightJsStyleBorderEnabled });
-  }
-
   /**
   /**
    * Change customize Title
    * Change customize Title
    */
    */
@@ -218,17 +178,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentCustomizeScript: inpuValue });
     this.setState({ currentCustomizeScript: inpuValue });
   }
   }
 
 
-  /**
-   * 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 function
    * Update function
@@ -266,28 +215,6 @@ export default class AdminCustomizeContainer extends Container {
     }
     }
   }
   }
 
 
-  /**
-   * Update code highlight
-   * @memberOf AdminCustomizeContainer
-   */
-  async updateHighlightJsStyle() {
-    try {
-      const response = await apiv3Put('/customize-setting/highlight', {
-        highlightJsStyle: this.state.currentHighlightJsStyleId,
-        highlightJsStyleBorder: this.state.isHighlightJsStyleBorderEnabled,
-      });
-      const { customizedParams } = response.data;
-      this.setState({
-        highlightJsStyle: customizedParams.highlightJsStyle,
-        highlightJsStyleBorder: customizedParams.highlightJsStyleBorder,
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      throw new Error('Failed to update data');
-    }
-  }
-
   /**
   /**
    * Update customTitle
    * Update customTitle
    * @memberOf AdminCustomizeContainer
    * @memberOf AdminCustomizeContainer

+ 23 - 30
packages/app/src/client/services/PageContainer.js

@@ -174,38 +174,31 @@ export default class PageContainer extends Container {
    */
    */
   updateStateAfterSave(page, tags, revision, editorMode) {
   updateStateAfterSave(page, tags, revision, editorMode) {
     // update state of PageContainer
     // update state of PageContainer
-    const newState = {
-      pageId: page._id,
-      revisionId: revision._id,
-      revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
-      remoteRevisionId: revision._id,
-      revisionAuthor: revision.author,
-      revisionIdHackmdSynced: page.revisionHackmdSynced,
-      hasDraftOnHackmd: page.hasDraftOnHackmd,
-      markdown: revision.body,
-      createdAt: page.createdAt,
-      updatedAt: page.updatedAt,
-    };
-    if (tags != null) {
-      newState.tags = tags;
-    }
-    this.setState(newState);
-
-    // Update PageEditor component
-    if (editorMode !== EditorMode.Editor) {
-      // eslint-disable-next-line no-undef
-      globalEmitter.emit('updateEditorValue', newState.markdown);
-    }
+    // const newState = {
+    //   pageId: page._id,
+    //   revisionId: revision._id,
+    //   revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
+    //   remoteRevisionId: revision._id,
+    //   revisionAuthor: revision.author,
+    //   revisionIdHackmdSynced: page.revisionHackmdSynced,
+    //   hasDraftOnHackmd: page.hasDraftOnHackmd,
+    //   markdown: revision.body,
+    //   createdAt: page.createdAt,
+    //   updatedAt: page.updatedAt,
+    // };
+    // if (tags != null) {
+    //   newState.tags = tags;
+    // }
+    // this.setState(newState);
 
 
     // PageEditorByHackmd component
     // PageEditorByHackmd component
-    const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-    if (pageEditorByHackmd != null) {
-      // reset
-      if (editorMode !== EditorMode.HackMD) {
-        pageEditorByHackmd.reset();
-      }
-    }
-
+    // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
+    // if (pageEditorByHackmd != null) {
+    //   // reset
+    //   if (editorMode !== EditorMode.HackMD) {
+    //     pageEditorByHackmd.reset();
+    //   }
+    // }
   }
   }
 
 
   /**
   /**

+ 0 - 4
packages/app/src/components/Admin/Customize/Customize.jsx

@@ -13,7 +13,6 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeHeaderSetting from './CustomizeHeaderSetting';
 import CustomizeHeaderSetting from './CustomizeHeaderSetting';
-import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
@@ -56,9 +55,6 @@ function Customize(props) {
       <div className="mb-5">
       <div className="mb-5">
         <CustomizeFunctionSetting />
         <CustomizeFunctionSetting />
       </div>
       </div>
-      <div className="mb-5">
-        <CustomizeHighlightSetting />
-      </div>
       <div className="mb-5">
       <div className="mb-5">
         <CustomizeTitle />
         <CustomizeTitle />
       </div>
       </div>

+ 1 - 1
packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx

@@ -44,7 +44,7 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
           </Card>
           </Card>
           <div className="form-text text-muted">
           <div className="form-text text-muted">
             { t('Example') }:
             { t('Example') }:
-            <pre className="hljs">
+            <pre>
               {/* eslint-disable-next-line react/no-unescaped-entities */}
               {/* eslint-disable-next-line react/no-unescaped-entities */}
               <code className="text-wrap">&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js"
               <code className="text-wrap">&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js"
                 defer&gt;&lt;/script&gt;
                 defer&gt;&lt;/script&gt;

+ 0 - 148
packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx

@@ -1,148 +0,0 @@
-/* eslint-disable no-useless-escape */
-import React, { useCallback, useState } from 'react';
-
-
-import { useTranslation } from 'next-i18next';
-import {
-  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
-} from 'reactstrap';
-
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { IHighlightJsCssSelectorOptions } from '~/interfaces/customize';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-
-type Props = {
-  adminCustomizeContainer: AdminCustomizeContainer
-}
-
-type HljsDemoProps = {
-  isHighlightJsStyleBorderEnabled: boolean
-}
-
-const HljsDemo = React.memo((props: HljsDemoProps): JSX.Element => {
-
-  const { isHighlightJsStyleBorderEnabled } = props;
-
-  /* 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 ${!isHighlightJsStyleBorderEnabled && 'hljs-no-border'}`}>
-      {/* eslint-disable-next-line react/no-danger */}
-      <code dangerouslySetInnerHTML={{ __html: html }}></code>
-    </pre>
-  );
-});
-HljsDemo.displayName = 'HljsDemo';
-
-const CustomizeHighlightSetting = (props: Props): JSX.Element => {
-  const { adminCustomizeContainer } = props;
-  const { t } = useTranslation();
-  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-  const options: IHighlightJsCssSelectorOptions = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
-
-  const onToggleDropdown = useCallback(() => {
-    setIsDropdownOpen(!isDropdownOpen);
-  }, [isDropdownOpen]);
-
-  const onClickSubmit = useCallback(async() => {
-    try {
-      await adminCustomizeContainer.updateHighlightJsStyle();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.code_highlight') }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [t, adminCustomizeContainer]);
-
-  const renderMenuItems = useCallback(() => {
-
-    const items = Object.entries(options).map((option) => {
-      const styleId = option[0];
-      const styleName = option[1].name;
-      const isBorderEnable = option[1].border;
-
-      return (
-        <DropdownItem
-          key={styleId}
-          role="presentation"
-          onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}
-        >
-          <a role="menuitem">{styleName}</a>
-        </DropdownItem>
-      );
-    });
-    return items;
-  }, [adminCustomizeContainer, options]);
-
-  return (
-    <React.Fragment>
-      <div className="row">
-        <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_settings.code_highlight')}</h2>
-
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <div className="my-0">
-                <label>{t('admin:customize_settings.theme')}</label>
-              </div>
-              <Dropdown isOpen={isDropdownOpen} toggle={onToggleDropdown}>
-                <DropdownToggle className="text-right col-6" caret>
-                  <span className="float-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
-                </DropdownToggle>
-                <DropdownMenu className="dropdown-menu" role="menu">
-                  {renderMenuItems()}
-                </DropdownMenu>
-              </Dropdown>
-              <p className="form-text text-warning">
-                {/* eslint-disable-next-line react/no-danger */}
-                <span dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.nocdn_desc') }} />
-              </p>
-            </div>
-          </div>
-
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <div className="custom-control custom-switch custom-checkbox-success">
-                <input
-                  type="checkbox"
-                  className="custom-control-input"
-                  id="highlightBorder"
-                  checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
-                  onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
-                />
-                <label className="custom-control-label" htmlFor="highlightBorder">
-                  <strong>Border</strong>
-                </label>
-              </div>
-            </div>
-          </div>
-
-          <div className="form-text text-muted">
-            <label>Examples:</label>
-            <div className="wiki">
-              <HljsDemo isHighlightJsStyleBorderEnabled={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled} />
-            </div>
-          </div>
-
-          <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
-        </div>
-      </div>
-    </React.Fragment>
-  );
-};
-
-const CustomizeHighlightSettingWrapper = withUnstatedContainers(CustomizeHighlightSetting, [AdminCustomizeContainer]);
-
-export default CustomizeHighlightSettingWrapper;

+ 1 - 1
packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -79,7 +79,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
 
 
           <div className="form-text text-muted">
           <div className="form-text text-muted">
             Examples:
             Examples:
-            <pre className="hljs"><code>{getExampleCode()}</code></pre>
+            <pre><code className='language-javascript'>{getExampleCode()}</code></pre>
           </div>
           </div>
 
 
           <div className="form-group">
           <div className="form-group">

+ 1 - 0
packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx

@@ -1,4 +1,5 @@
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
+
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 const SwitchToMirrorMode = (props) => {
 const SwitchToMirrorMode = (props) => {

+ 11 - 6
packages/app/src/components/InstallerForm.tsx

@@ -2,8 +2,7 @@ import {
   FormEventHandler, memo, useCallback, useState,
   FormEventHandler, memo, useCallback, useState,
 } from 'react';
 } from 'react';
 
 
-import i18next from 'i18next';
-import { useTranslation, i18n } from 'next-i18next';
+import { useTranslation } from 'next-i18next';
 
 
 import { i18n as i18nConfig } from '^/config/next-i18next.config';
 import { i18n as i18nConfig } from '^/config/next-i18next.config';
 
 
@@ -11,10 +10,11 @@ import { toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
 
 
 const InstallerForm = memo((): JSX.Element => {
 const InstallerForm = memo((): JSX.Element => {
-  const { t } = useTranslation();
+  const { t, i18n } = useTranslation();
 
 
   const [isValidUserName, setValidUserName] = useState(true);
   const [isValidUserName, setValidUserName] = useState(true);
   const [isSubmittingDisabled, setSubmittingDisabled] = useState(false);
   const [isSubmittingDisabled, setSubmittingDisabled] = useState(false);
+  const [currentLocale, setCurrentLocale] = useState('en_US');
 
 
   const checkUserName = useCallback(async(event) => {
   const checkUserName = useCallback(async(event) => {
     const axios = require('axios').create({
     const axios = require('axios').create({
@@ -28,6 +28,11 @@ const InstallerForm = memo((): JSX.Element => {
     setValidUserName(res.data.valid);
     setValidUserName(res.data.valid);
   }, []);
   }, []);
 
 
+  const onClickLanguageItem = useCallback((locale) => {
+    i18n.changeLanguage(locale);
+    setCurrentLocale(locale);
+  }, [i18n]);
+
   const submitHandler: FormEventHandler = useCallback(async(e: any) => {
   const submitHandler: FormEventHandler = useCallback(async(e: any) => {
     e.preventDefault();
     e.preventDefault();
 
 
@@ -59,7 +64,7 @@ const InstallerForm = memo((): JSX.Element => {
         name,
         name,
         email,
         email,
         password,
         password,
-        'app:globalLang': formData['registerForm[app:globalLang]'].value,
+        'app:globalLang': currentLocale,
       },
       },
     };
     };
 
 
@@ -78,7 +83,7 @@ const InstallerForm = memo((): JSX.Element => {
 
 
       toastError(t('installer.failed_to_install'));
       toastError(t('installer.failed_to_install'));
     }
     }
-  }, [isSubmittingDisabled, t]);
+  }, [isSubmittingDisabled, t, currentLocale]);
 
 
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const unavailableUserId = isValidUserName
   const unavailableUserId = isValidUserName
@@ -132,7 +137,7 @@ const InstallerForm = memo((): JSX.Element => {
                         data-testid={`dropdownLanguageMenu-${locale}`}
                         data-testid={`dropdownLanguageMenu-${locale}`}
                         className="dropdown-item"
                         className="dropdown-item"
                         type="button"
                         type="button"
-                        onClick={() => { i18next.changeLanguage(locale) }}
+                        onClick={() => { onClickLanguageItem(locale) }}
                       >
                       >
                         {fixedT?.('meta.display_name')}
                         {fixedT?.('meta.display_name')}
                       </button>
                       </button>

+ 6 - 0
packages/app/src/components/Layout/AdminLayout.tsx

@@ -8,6 +8,9 @@ import { RawLayout } from './RawLayout';
 
 
 import styles from './Admin.module.scss';
 import styles from './Admin.module.scss';
 
 
+
+const HotkeysManager = dynamic(() => import('../Hotkeys/HotkeysManager'), { ssr: false });
+
 const AdminNotFoundPage = dynamic(() => import('../Admin/NotFoundPage').then(mod => mod.AdminNotFoundPage), { ssr: false });
 const AdminNotFoundPage = dynamic(() => import('../Admin/NotFoundPage').then(mod => mod.AdminNotFoundPage), { ssr: false });
 
 
 
 
@@ -54,6 +57,9 @@ const AdminLayout = ({
 
 
         <SystemVersion />
         <SystemVersion />
       </div>
       </div>
+
+      <HotkeysManager />
+
     </RawLayout>
     </RawLayout>
   );
   );
 };
 };

+ 1 - 1
packages/app/src/components/Layout/RawLayout.tsx

@@ -21,7 +21,7 @@ type Props = {
 }
 }
 
 
 export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
 export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
-  const classNames: string[] = ['wrapper'];
+  const classNames: string[] = ['layout-root'];
   if (className != null) {
   if (className != null) {
     classNames.push(className);
     classNames.push(className);
   }
   }

+ 43 - 30
packages/app/src/components/Me/EditorSettings.tsx

@@ -1,12 +1,13 @@
 import React, {
 import React, {
-  Dispatch,
+  Dispatch, memo,
   FC, SetStateAction, useCallback, useEffect, useState,
   FC, SetStateAction, useCallback, useEffect, useState,
+  useMemo,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
+import { useEditorSettings } from '~/stores/editor';
 
 
 
 
 type EditorSettingsBodyProps = Record<string, never>;
 type EditorSettingsBodyProps = Record<string, never>;
@@ -194,51 +195,62 @@ const RuleListGroup: FC<RuleListGroupProps> = ({
   );
   );
 };
 };
 
 
+const createRulesFromDefaultList = (rule: { name: string }) => (
+  {
+    name: rule.name,
+    isEnabled: true,
+  }
+);
+
 
 
-export const EditorSettings: FC<EditorSettingsBodyProps> = () => {
+export const EditorSettings = memo((): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const [textlintRules, setTextlintRules] = useState<LintRule[]>([]);
   const [textlintRules, setTextlintRules] = useState<LintRule[]>([]);
 
 
-  const initializeEditorSettings = useCallback(async() => {
-    const { data } = await apiv3Get('/personal-setting/editor-settings');
-    const retrievedRules: LintRule[] = data?.textlintSettings?.textlintRules;
+  const { data: dataEditorSettings, update: updateEditorSettings } = useEditorSettings();
 
 
-    // If database is empty, add default rules to state
-    if (retrievedRules != null && retrievedRules.length > 0) {
-      setTextlintRules(retrievedRules);
-    }
-    else {
-      const createRulesFromDefaultList = (rule: { name: string }) => (
-        {
-          name: rule.name,
-          isEnabled: true,
-        }
-      );
+  const defaultRules = useMemo(() => {
+    const defaultCommonRules = commonRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+    const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+
+    return [...defaultCommonRules, ...defaultJapaneseRules];
+  }, []);
 
 
-      const defaultCommonRules = commonRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
-      const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
-      setTextlintRules([...defaultCommonRules, ...defaultJapaneseRules]);
+  const initializeEditorSettings = useCallback(() => {
+    if (dataEditorSettings == null) {
+      return;
     }
     }
 
 
-  }, []);
+    const retrievedRules: LintRule[] | undefined = dataEditorSettings?.textlintSettings?.textlintRules;
 
 
-  useEffect(() => {
-    initializeEditorSettings();
-  }, [initializeEditorSettings]);
+    // If database is empty, add default rules to state
+    if (retrievedRules != null && retrievedRules.length > 0) {
+      setTextlintRules(retrievedRules);
+      return;
+    }
+    setTextlintRules(defaultRules);
+  }, [dataEditorSettings, defaultRules]);
 
 
-  const updateRulesHandler = async() => {
+  const updateRulesHandler = useCallback(async() => {
     try {
     try {
-      const { data } = await apiv3Put('/personal-setting/editor-settings', { textlintSettings: { textlintRules: [...textlintRules] } });
-      setTextlintRules(data.textlintSettings.textlintRules);
+      await updateEditorSettings({ textlintSettings: { textlintRules } });
       toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
       toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  };
+  }, [t, textlintRules, updateEditorSettings]);
+
+  useEffect(() => {
+    initializeEditorSettings();
+  }, [initializeEditorSettings]);
 
 
   if (textlintRules == null) {
   if (textlintRules == null) {
-    return <></>;
+    return (
+      <div className="text-muted text-center">
+        <i className="fa fa-2x fa-spinner fa-pulse"></i>
+      </div>
+    );
   }
   }
 
 
   return (
   return (
@@ -270,4 +282,5 @@ export const EditorSettings: FC<EditorSettingsBodyProps> = () => {
       </div>
       </div>
     </div>
     </div>
   );
   );
-};
+});
+EditorSettings.displayName = 'EditorSettings';

+ 11 - 13
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -3,8 +3,8 @@ import React, { useMemo } from 'react';
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
+import { Link } from 'react-scroll';
 
 
-// import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
   useCurrentPagePath, useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
 } from '~/stores/context';
 } from '~/stores/context';
@@ -79,18 +79,18 @@ const PageView = React.memo((): JSX.Element => {
             </div>
             </div>
 
 
             {/* Comments */}
             {/* Comments */}
-            {/* { getCommentListDom != null && !isTopPagePath && ( */}
             { !isTopPagePath && (
             { !isTopPagePath && (
               <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
               <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                <button
-                  type="button"
-                  className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-                  // onClick={() => smoothScrollIntoView(getCommentListDom, WIKI_HEADER_LINK)}
-                >
-                  <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
-                  <span>Comments</span>
-                  <CountBadge count={currentPage?.commentCount} />
-                </button>
+                <Link to={'page-comments'} smooth="easeOutQuart" offset={-100} duration={800}>
+                  <button
+                    type="button"
+                    className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
+                  >
+                    <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
+                    <span>Comments</span>
+                    <CountBadge count={currentPage?.commentCount} />
+                  </button>
+                </Link>
               </div>
               </div>
             ) }
             ) }
 
 
@@ -109,8 +109,6 @@ PageView.displayName = 'PageView';
 
 
 
 
 const DisplaySwitcher = React.memo((): JSX.Element => {
 const DisplaySwitcher = React.memo((): JSX.Element => {
-  // get element for smoothScroll
-  // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);
 
 
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
 
 

+ 1 - 1
packages/app/src/components/PageComment.tsx

@@ -152,7 +152,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
 
 
   return (
   return (
     <>
     <>
-      <div className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
+      <div id="page-comments" className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
         <div className="container-lg">
         <div className="container-lg">
           <div className="page-comments">
           <div className="page-comments">
             <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>
             <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>

+ 1 - 1
packages/app/src/components/PageEditor.tsx

@@ -202,7 +202,7 @@ const PageEditor = React.memo((): JSX.Element => {
       // when if created newly
       // when if created newly
       if (res.pageCreated) {
       if (res.pageCreated) {
         logger.info('Page is created', res.page._id);
         logger.info('Page is created', res.page._id);
-        // pageContainer.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
+        globalEmitter.emit('resetInitializedHackMdStatus');
         mutateGrant(res.page.grant);
         mutateGrant(res.page.grant);
       }
       }
     }
     }

+ 15 - 0
packages/app/src/components/PageEditorByHackmd.tsx

@@ -131,6 +131,21 @@ export const PageEditorByHackmd = (): JSX.Element => {
     };
     };
   }, [saveAndReturnToViewHandler]);
   }, [saveAndReturnToViewHandler]);
 
 
+  const resetInitializedStatusHandler = useCallback(() => {
+    setIsInitialized(false);
+  }, []);
+
+
+  // set handler to save and reload Page
+  useEffect(() => {
+    globalEmitter.on('resetInitializedHackMdStatus', resetInitializedStatusHandler);
+
+    return function cleanup() {
+      globalEmitter.removeListener('resetInitializedHackMdStatus', resetInitializedStatusHandler);
+    };
+  }, [resetInitializedStatusHandler]);
+
+
   const isResume = useCallback(() => {
   const isResume = useCallback(() => {
     const isPageExistsOnHackmd = (pageIdOnHackmd != null);
     const isPageExistsOnHackmd = (pageIdOnHackmd != null);
     return (isPageExistsOnHackmd && hasDraftOnHackmd) || isHackmdDraftUpdatingInRealtime;
     return (isPageExistsOnHackmd && hasDraftOnHackmd) || isHackmdDraftUpdatingInRealtime;

+ 42 - 42
packages/app/src/components/Theme/utils/ThemeProvider.tsx

@@ -7,21 +7,21 @@ import { GrowiThemes } from '~/interfaces/theme';
 import { Themes } from '~/stores/use-next-themes';
 import { Themes } from '~/stores/use-next-themes';
 
 
 
 
-const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
-const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
-const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
+// const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
+// const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
+// const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
 const ThemeDefault = dynamic(() => import('../ThemeDefault'));
 const ThemeDefault = dynamic(() => import('../ThemeDefault'));
-const ThemeFireRed = dynamic(() => import('../ThemeFireRed'));
-const ThemeFuture = dynamic(() => import('../ThemeFuture'));
-const ThemeHalloween = dynamic(() => import('../ThemeHalloween'));
-const ThemeHufflepuff = dynamic(() => import('../ThemeHufflepuff'));
-const ThemeIsland = dynamic(() => import('../ThemeIsland'));
-const ThemeJadeGreen = dynamic(() => import('../ThemeJadeGreen'));
-const ThemeKibela = dynamic(() => import('../ThemeKibela'));
-const ThemeMonoBlue = dynamic(() => import('../ThemeMonoBlue'));
-const ThemeNature = dynamic(() => import('../ThemeNature'));
-const ThemeSpring = dynamic(() => import('../ThemeSpring'));
-const ThemeWood = dynamic(() => import('../ThemeWood'));
+// const ThemeFireRed = dynamic(() => import('../ThemeFireRed'));
+// const ThemeFuture = dynamic(() => import('../ThemeFuture'));
+// const ThemeHalloween = dynamic(() => import('../ThemeHalloween'));
+// const ThemeHufflepuff = dynamic(() => import('../ThemeHufflepuff'));
+// const ThemeIsland = dynamic(() => import('../ThemeIsland'));
+// const ThemeJadeGreen = dynamic(() => import('../ThemeJadeGreen'));
+// const ThemeKibela = dynamic(() => import('../ThemeKibela'));
+// const ThemeMonoBlue = dynamic(() => import('../ThemeMonoBlue'));
+// const ThemeNature = dynamic(() => import('../ThemeNature'));
+// const ThemeSpring = dynamic(() => import('../ThemeSpring'));
+// const ThemeWood = dynamic(() => import('../ThemeWood'));
 
 
 
 
 type Props = {
 type Props = {
@@ -32,34 +32,34 @@ type Props = {
 
 
 export const ThemeProvider = ({ theme, children, colorScheme }: Props): JSX.Element => {
 export const ThemeProvider = ({ theme, children, colorScheme }: Props): JSX.Element => {
   switch (theme) {
   switch (theme) {
-    case GrowiThemes.ANTARCTIC:
-      return <ThemeAntarctic colorScheme={colorScheme}>{children}</ThemeAntarctic>;
-    case GrowiThemes.BLACKBOARD:
-      return <ThemeBlackboard>{children}</ThemeBlackboard>;
-    case GrowiThemes.CHRISTMAS:
-      return <ThemeChristmas colorScheme={colorScheme}>{children}</ThemeChristmas>;
-    case GrowiThemes.FIRE_RED:
-      return <ThemeFireRed>{children}</ThemeFireRed>;
-    case GrowiThemes.FUTURE:
-      return <ThemeFuture>{children}</ThemeFuture>;
-    case GrowiThemes.HALLOWEEN:
-      return <ThemeHalloween colorScheme={colorScheme}>{children}</ThemeHalloween>;
-    case GrowiThemes.HUFFLEPUFF:
-      return <ThemeHufflepuff colorScheme={colorScheme}>{children}</ThemeHufflepuff>;
-    case GrowiThemes.ISLAND:
-      return <ThemeIsland colorScheme={colorScheme}>{children}</ThemeIsland>;
-    case GrowiThemes.JADE_GREEN:
-      return <ThemeJadeGreen>{children}</ThemeJadeGreen>;
-    case GrowiThemes.KIBELA:
-      return <ThemeKibela>{children}</ThemeKibela>;
-    case GrowiThemes.MONO_BLUE:
-      return <ThemeMonoBlue>{children}</ThemeMonoBlue>;
-    case GrowiThemes.NATURE:
-      return <ThemeNature>{children}</ThemeNature>;
-    case GrowiThemes.SPRING:
-      return <ThemeSpring colorScheme={colorScheme}>{children}</ThemeSpring>;
-    case GrowiThemes.WOOD:
-      return <ThemeWood colorScheme={colorScheme}>{children}</ThemeWood>;
+    // case GrowiThemes.ANTARCTIC:
+    //   return <ThemeAntarctic colorScheme={colorScheme}>{children}</ThemeAntarctic>;
+    // case GrowiThemes.BLACKBOARD:
+    //   return <ThemeBlackboard>{children}</ThemeBlackboard>;
+    // case GrowiThemes.CHRISTMAS:
+    //   return <ThemeChristmas colorScheme={colorScheme}>{children}</ThemeChristmas>;
+    // case GrowiThemes.FIRE_RED:
+    //   return <ThemeFireRed>{children}</ThemeFireRed>;
+    // case GrowiThemes.FUTURE:
+    //   return <ThemeFuture>{children}</ThemeFuture>;
+    // case GrowiThemes.HALLOWEEN:
+    //   return <ThemeHalloween colorScheme={colorScheme}>{children}</ThemeHalloween>;
+    // case GrowiThemes.HUFFLEPUFF:
+    //   return <ThemeHufflepuff colorScheme={colorScheme}>{children}</ThemeHufflepuff>;
+    // case GrowiThemes.ISLAND:
+    //   return <ThemeIsland colorScheme={colorScheme}>{children}</ThemeIsland>;
+    // case GrowiThemes.JADE_GREEN:
+    //   return <ThemeJadeGreen>{children}</ThemeJadeGreen>;
+    // case GrowiThemes.KIBELA:
+    //   return <ThemeKibela>{children}</ThemeKibela>;
+    // case GrowiThemes.MONO_BLUE:
+    //   return <ThemeMonoBlue>{children}</ThemeMonoBlue>;
+    // case GrowiThemes.NATURE:
+    //   return <ThemeNature>{children}</ThemeNature>;
+    // case GrowiThemes.SPRING:
+    //   return <ThemeSpring colorScheme={colorScheme}>{children}</ThemeSpring>;
+    // case GrowiThemes.WOOD:
+    //   return <ThemeWood colorScheme={colorScheme}>{children}</ThemeWood>;
     default:
     default:
       return <ThemeDefault>{children}</ThemeDefault>;
       return <ThemeDefault>{children}</ThemeDefault>;
   }
   }

+ 0 - 22
packages/app/src/interfaces/customize.ts

@@ -1,25 +1,3 @@
-const IHighlightJsCssSelectorThemes = {
-  GITHUB: 'github',
-  GITHUB_GIST: 'github-gist',
-  ATOM_ONE_LIGHT: 'atom-one-light',
-  XCIDE: 'xcode',
-  VS: 'vs',
-  ATOM_ONE_DARK: 'atom-one-dark',
-  HYBRID: 'hybrid',
-  MONOKAI: 'monokai',
-  TOMMORROW_NIGHT: 'tomorrow-night',
-  VS_2015: 'vs2015',
-} as const;
-
-type IHighlightJsCssSelectorThemes = typeof IHighlightJsCssSelectorThemes[keyof typeof IHighlightJsCssSelectorThemes];
-
-export type IHighlightJsCssSelectorOptions = {
-  [theme in IHighlightJsCssSelectorThemes]: {
-    name: string,
-    border: boolean
-  }
-}
-
 export type IResLayoutSetting = {
 export type IResLayoutSetting = {
   isContainerFluid: boolean,
   isContainerFluid: boolean,
 };
 };

+ 1 - 1
packages/app/src/interfaces/editor-settings.ts

@@ -5,7 +5,7 @@ export interface ILintRule {
 }
 }
 
 
 export interface ITextlintSettings {
 export interface ITextlintSettings {
-  neverAskBeforeDownloadLargeFiles: boolean;
+  neverAskBeforeDownloadLargeFiles?: boolean;
   textlintRules: ILintRule[];
   textlintRules: ILintRule[];
 }
 }
 
 

+ 9 - 18
packages/app/src/pages/[[...path]].page.tsx

@@ -39,7 +39,9 @@ import type { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import { useRedirectFrom } from '~/stores/page-redirect';
 import { useRedirectFrom } from '~/stores/page-redirect';
 import {
 import {
-  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth, useSelectedGrant,
+  EditorMode,
+  useEditorMode, useSelectedGrant,
+  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -230,8 +232,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   useIsUploadableFile(props.editorConfig.upload.isUploadableFile);
   useIsUploadableFile(props.editorConfig.upload.isUploadableFile);
   useIsUploadableImage(props.editorConfig.upload.isUploadableImage);
   useIsUploadableImage(props.editorConfig.upload.isUploadableImage);
 
 
-  // const { data: editorMode } = useEditorMode();
-
   const { pageWithMeta, userUISettings } = props;
   const { pageWithMeta, userUISettings } = props;
 
 
   const pageId = pageWithMeta?.data._id;
   const pageId = pageWithMeta?.data._id;
@@ -246,13 +246,13 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   useEditingMarkdown(pageWithMeta?.data.revision?.body ?? '');
   useEditingMarkdown(pageWithMeta?.data.revision?.body ?? '');
-
-  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
   const { data: dataPageInfo } = useSWRxPageInfo(pageId);
   const { data: dataPageInfo } = useSWRxPageInfo(pageId);
-
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
 
 
+  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
+  const { getClassNamesByEditorMode } = useEditorMode();
+
   const shouldRenderPutbackPageModal = pageWithMeta != null
   const shouldRenderPutbackPageModal = pageWithMeta != null
     ? _isTrashPage(pageWithMeta.data.path)
     ? _isTrashPage(pageWithMeta.data.path)
     : false;
     : false;
@@ -271,17 +271,9 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   }, [props.currentPathname, router]);
   }, [props.currentPathname, router]);
 
 
   const classNames: string[] = [];
   const classNames: string[] = [];
-  // switch (editorMode) {
-  //   case EditorMode.Editor:
-  //     classNames.push('on-edit', 'builtin-editor');
-  //     break;
-  //   case EditorMode.HackMD:
-  //     classNames.push('on-edit', 'hackmd');
-  //     break;
-  // }
-  // if (page == null) {
-  //   classNames.push('not-found-page');
-  // }
+
+  const isSidebar = pagePath === '/Sidebar';
+  classNames.push(...getClassNamesByEditorMode(isSidebar));
 
 
   const isTopPagePath = isTopPage(pageWithMeta?.data.path ?? '');
   const isTopPagePath = isTopPage(pageWithMeta?.data.path ?? '');
 
 
@@ -301,7 +293,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
         {renderHighlightJsStyleTag(props.highlightJsStyle)}
         {renderHighlightJsStyleTag(props.highlightJsStyle)}
         */}
         */}
       </Head>
       </Head>
-      {/* <BasicLayout title={useCustomTitle(props, t('GROWI'))} className={classNames.join(' ')}> */}
       <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')} expandContainer={isContainerFluid}>
       <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')} expandContainer={isContainerFluid}>
         <div className="h-100 d-flex flex-column justify-content-between">
         <div className="h-100 d-flex flex-column justify-content-between">
           <header className="py-0 position-relative">
           <header className="py-0 position-relative">

+ 2 - 2
packages/app/src/pages/installer.page.tsx

@@ -23,7 +23,7 @@ import {
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 
 
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired, true);
   props._nextI18Next = nextI18NextConfig._nextI18Next;
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 }
 
 
@@ -65,7 +65,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
 
   const props: Props = result.props as Props;
   const props: Props = result.props as Props;
 
 
-  injectNextI18NextConfigurations(context, props, ['translation']);
+  await injectNextI18NextConfigurations(context, props, ['translation']);
 
 
   return {
   return {
     props,
     props,

+ 0 - 17
packages/app/src/server/routes/admin.js

@@ -124,25 +124,8 @@ module.exports = function(crowi, app) {
   actions.customize.index = function(req, res) {
   actions.customize.index = function(req, res) {
     const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
     const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
 
 
-    // TODO delete after apiV3
-    /* eslint-disable quote-props, no-multi-spaces */
-    const highlightJsCssSelectorOptions = {
-      'github':           { name: '[Light] GitHub',         border: false },
-      'github-gist':      { name: '[Light] GitHub Gist',    border: true },
-      'atom-one-light':   { name: '[Light] Atom One Light', border: true },
-      'xcode':            { name: '[Light] Xcode',          border: true },
-      'vs':               { name: '[Light] Vs',             border: true },
-      'atom-one-dark':    { name: '[Dark] Atom One Dark',   border: false },
-      'hybrid':           { name: '[Dark] Hybrid',          border: false },
-      'monokai':          { name: '[Dark] Monokai',         border: false },
-      'tomorrow-night':   { name: '[Dark] Tomorrow Night',  border: false },
-      'vs2015':           { name: '[Dark] Vs 2015',         border: false },
-    };
-    /* eslint-enable quote-props, no-multi-spaces */
-
     return res.render('admin/customize', {
     return res.render('admin/customize', {
       settingForm,
       settingForm,
-      highlightJsCssSelectorOptions,
     });
     });
   };
   };
 
 

+ 11 - 5
packages/app/src/stores/editor.tsx

@@ -10,16 +10,19 @@ import { SlackChannels } from '~/interfaces/user-trigger-notification';
 import {
 import {
   useCurrentUser, useDefaultIndentSize, useIsGuestUser,
   useCurrentUser, useDefaultIndentSize, useIsGuestUser,
 } from './context';
 } from './context';
-import { localStorageMiddleware } from './middlewares/sync-to-storage';
+// import { localStorageMiddleware } from './middlewares/sync-to-storage';
 import { useSWRxTagsInfo } from './page';
 import { useSWRxTagsInfo } from './page';
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
 
 
 
 
 type EditorSettingsOperation = {
 type EditorSettingsOperation = {
-  update: (updateData: Partial<IEditorSettings>) => void,
+  update: (updateData: Partial<IEditorSettings>) => Promise<void>,
   turnOffAskingBeforeDownloadLargeFiles: () => void,
   turnOffAskingBeforeDownloadLargeFiles: () => void,
 }
 }
 
 
+// TODO: Enable localStorageMiddleware
+//   - Unabling localStorageMiddleware occurrs a flickering problem when loading theme.
+//   - see: https://github.com/weseek/growi/pull/6781#discussion_r1000285786
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, IEditorSettings, Error> => {
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, IEditorSettings, Error> => {
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
@@ -27,11 +30,14 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
   const swrResult = useSWRImmutable<IEditorSettings>(
   const swrResult = useSWRImmutable<IEditorSettings>(
     isGuestUser ? null : ['/personal-setting/editor-settings', currentUser?.username],
     isGuestUser ? null : ['/personal-setting/editor-settings', currentUser?.username],
     endpoint => apiv3Get(endpoint).then(result => result.data),
     endpoint => apiv3Get(endpoint).then(result => result.data),
-    { use: [localStorageMiddleware] }, // store to localStorage for initialization fastly
+    {
+      // use: [localStorageMiddleware], // store to localStorage for initialization fastly
+      // fallbackData: undefined,
+    },
   );
   );
 
 
   return withUtils<EditorSettingsOperation, IEditorSettings, Error>(swrResult, {
   return withUtils<EditorSettingsOperation, IEditorSettings, Error>(swrResult, {
-    update: (updateData) => {
+    update: async(updateData) => {
       const { data, mutate } = swrResult;
       const { data, mutate } = swrResult;
 
 
       if (data == null) {
       if (data == null) {
@@ -41,7 +47,7 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
       mutate({ ...data, ...updateData }, false);
       mutate({ ...data, ...updateData }, false);
 
 
       // invoke API
       // invoke API
-      apiv3Put('/personal-setting/editor-settings', updateData);
+      await apiv3Put('/personal-setting/editor-settings', updateData);
     },
     },
     turnOffAskingBeforeDownloadLargeFiles: async() => {
     turnOffAskingBeforeDownloadLargeFiles: async() => {
       const { data, mutate } = swrResult;
       const { data, mutate } = swrResult;

+ 4 - 8
packages/app/src/stores/middlewares/sync-to-storage.ts

@@ -33,14 +33,10 @@ export const createSyncToStorageMiddlware = (
         initData = storageSerializer.deserialize(itemInStorage);
         initData = storageSerializer.deserialize(itemInStorage);
       }
       }
 
 
-      const swrNext = useSWRNext(key, fetcher, {
-        fallbackData: initData,
-        ...config,
-      });
+      config.fallbackData = initData;
+      const swrNext = useSWRNext(key, fetcher, config);
 
 
-      return {
-        ...swrNext,
-        // override mutate
+      return Object.assign(swrNext, {
         mutate: (data, shouldRevalidate) => {
         mutate: (data, shouldRevalidate) => {
           return swrNext.mutate(data, shouldRevalidate)
           return swrNext.mutate(data, shouldRevalidate)
             .then((value) => {
             .then((value) => {
@@ -48,7 +44,7 @@ export const createSyncToStorageMiddlware = (
               return value;
               return value;
             });
             });
         },
         },
-      };
+      });
     };
     };
   };
   };
 };
 };

+ 33 - 43
packages/app/src/stores/ui.tsx

@@ -1,4 +1,4 @@
-import { RefObject, useEffect } from 'react';
+import { RefObject, useCallback, useEffect } from 'react';
 
 
 import {
 import {
   isClient, isServer, pagePathUtils, Nullable, PageGrant,
   isClient, isServer, pagePathUtils, Nullable, PageGrant,
@@ -72,29 +72,21 @@ export const useIsMobile = (): SWRResponse<boolean, Error> => {
   return useStaticSWR<boolean, Error>(key, undefined, configuration);
   return useStaticSWR<boolean, Error>(key, undefined, configuration);
 };
 };
 
 
-const updateBodyClassesByEditorMode = (newEditorMode: EditorMode, isSidebar = false) => {
-  const bodyElement = document.getElementsByTagName('body')[0];
-  if (bodyElement == null) {
-    logger.warn('The body tag was not successfully obtained');
-    return;
-  }
-  switch (newEditorMode) {
-    case EditorMode.View:
-      bodyElement.classList.remove('on-edit', 'builtin-editor', 'hackmd', 'editing-sidebar');
-      break;
+const getClassNamesByEditorMode = (editorMode: EditorMode | undefined, isSidebar = false): string[] => {
+  const classNames: string[] = [];
+  switch (editorMode) {
     case EditorMode.Editor:
     case EditorMode.Editor:
-      bodyElement.classList.add('on-edit', 'builtin-editor');
-      bodyElement.classList.remove('hackmd');
-      // editing /Sidebar
+      classNames.push('editing', 'builtin-editor');
       if (isSidebar) {
       if (isSidebar) {
-        bodyElement.classList.add('editing-sidebar');
+        classNames.push('editing-sidebar');
       }
       }
       break;
       break;
     case EditorMode.HackMD:
     case EditorMode.HackMD:
-      bodyElement.classList.add('on-edit', 'hackmd');
-      bodyElement.classList.remove('builtin-editor', 'editing-sidebar');
+      classNames.push('editing', 'hackmd');
       break;
       break;
   }
   }
+
+  return classNames;
 };
 };
 
 
 const updateHashByEditorMode = (newEditorMode: EditorMode) => {
 const updateHashByEditorMode = (newEditorMode: EditorMode) => {
@@ -130,8 +122,11 @@ export const determineEditorModeByHash = (): EditorMode => {
   }
   }
 };
 };
 
 
-let isEditorModeLoaded = false;
-export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
+type EditorModeUtils = {
+  getClassNamesByEditorMode: (isEditingSidebar: boolean) => string[],
+}
+
+export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
   const { data: _isEditable } = useIsEditable();
   const { data: _isEditable } = useIsEditable();
 
 
   const editorModeByHash = determineEditorModeByHash();
   const editorModeByHash = determineEditorModeByHash();
@@ -140,36 +135,31 @@ export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
   const isEditable = !isLoading && _isEditable;
   const isEditable = !isLoading && _isEditable;
   const initialData = isEditable ? editorModeByHash : EditorMode.View;
   const initialData = isEditable ? editorModeByHash : EditorMode.View;
 
 
-  const { data: currentPagePath } = useCurrentPagePath();
-  const isSidebar = currentPagePath === '/Sidebar';
-
-  const swrResponse = useSWRImmutable(
+  const swrResponse = useSWRImmutable<EditorMode>(
     isLoading ? null : ['editorMode', isEditable],
     isLoading ? null : ['editorMode', isEditable],
     null,
     null,
     { fallbackData: initialData },
     { fallbackData: initialData },
   );
   );
 
 
-  // initial updating
-  if (!isEditorModeLoaded && !isLoading && swrResponse.data != null) {
-    if (isEditable) {
-      updateBodyClassesByEditorMode(swrResponse.data, isSidebar);
+  // construct overriding mutate method
+  const mutateOriginal = swrResponse.mutate;
+  const mutate = useCallback((editorMode: EditorMode, shouldRevalidate?: boolean) => {
+    if (!isEditable) {
+      return Promise.resolve(EditorMode.View); // fixed if not editable
     }
     }
-    isEditorModeLoaded = true;
-  }
-
-  return {
-    ...swrResponse,
-
-    // overwrite mutate
-    mutate: (editorMode: EditorMode, shouldRevalidate?: boolean) => {
-      if (!isEditable) {
-        return Promise.resolve(EditorMode.View); // fixed if not editable
-      }
-      updateBodyClassesByEditorMode(editorMode, isSidebar);
-      updateHashByEditorMode(editorMode);
-      return swrResponse.mutate(editorMode, shouldRevalidate);
-    },
-  };
+    updateHashByEditorMode(editorMode);
+    return mutateOriginal(editorMode, shouldRevalidate);
+  }, [isEditable, mutateOriginal]);
+
+  // construct getClassNamesByEditorMode method
+  const getClassNames = useCallback((isEditingSidebar: boolean) => {
+    return getClassNamesByEditorMode(swrResponse.data, isEditingSidebar);
+  }, [swrResponse.data]);
+
+  return Object.assign(swrResponse, {
+    mutate,
+    getClassNamesByEditorMode: getClassNames,
+  });
 };
 };
 
 
 export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {
 export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {

+ 4 - 4
packages/app/src/styles/_on-edit.scss → packages/app/src/styles/_editor.scss

@@ -4,7 +4,7 @@
 @import 'sidebar-wiki';
 @import 'sidebar-wiki';
 
 
 // global imported
 // global imported
-body.on-edit {
+.layout-root.editing {
   overflow-y: hidden !important;
   overflow-y: hidden !important;
 
 
   .grw-navbar {
   .grw-navbar {
@@ -279,14 +279,14 @@ body.on-edit {
   }
   }
 }
 }
 
 
-body.on-edit {
-  .wrapper:not(.growi-layout-fluid) .page-editor-preview-body {
+.layout-root.editing {
+  &:not(.growi-layout-fluid) .page-editor-preview-body {
     .wiki {
     .wiki {
       max-width: 980px;
       max-width: 980px;
       margin: 0 auto;
       margin: 0 auto;
     }
     }
   }
   }
-  .wrapper.growi-layout-fluid .page-editor-preview-body {
+  &.growi-layout-fluid .page-editor-preview-body {
     .wiki {
     .wiki {
       margin: 0 auto;
       margin: 0 auto;
     }
     }

+ 0 - 37
packages/app/src/styles/_hljs.scss

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

+ 2 - 2
packages/app/src/styles/_layout.scss

@@ -6,11 +6,11 @@ body {
   overscroll-behavior-y: none;
   overscroll-behavior-y: none;
 }
 }
 
 
-.wrapper:not(.growi-layout-fluid) .grw-container-convertible {
+.layout-root:not(.growi-layout-fluid) .grw-container-convertible {
   @extend .container-lg;
   @extend .container-lg;
 }
 }
 
 
-.wrapper.growi-layout-fluid .grw-container-convertible {
+.layout-root.growi-layout-fluid .grw-container-convertible {
   @extend .container-fluid;
   @extend .container-fluid;
 }
 }
 
 

+ 1 - 1
packages/app/src/styles/_old-ios.scss

@@ -1,4 +1,4 @@
-html[old-ios] body:not(.on-edit) {
+html[old-ios] .layout-root:not(.editing) {
   .grw-navbar {
   .grw-navbar {
     position: initial !important;
     position: initial !important;
     top: 0 !important;
     top: 0 !important;

+ 1 - 1
packages/app/src/styles/_variables.scss

@@ -35,7 +35,7 @@ $grw-logo-width: $grw-sidebar-nav-width;
 $grw-logomark-width: 36px;
 $grw-logomark-width: 36px;
 
 
 // fix tab width to 95 pixels
 // fix tab width to 95 pixels
-// see also '_on-edit.scss'
+// see also '_editor.scss'
 $grw-nav-main-left-tab-width: 95px;
 $grw-nav-main-left-tab-width: 95px;
 $grw-nav-main-left-tab-width-mobile: 50px;
 $grw-nav-main-left-tab-width-mobile: 50px;
 $grw-nav-main-tab-height: 42px;
 $grw-nav-main-tab-height: 42px;

+ 0 - 5
packages/app/src/styles/_wiki.scss

@@ -1,11 +1,6 @@
 @use './variables' as var;
 @use './variables' as var;
 @use './bootstrap/init' as bs;
 @use './bootstrap/init' as bs;
 
 
-// hljs
-// .wiki {
-//   @import 'hljs';
-// }
-
 .wiki {
 .wiki {
   @mixin add-left-border($width) {
   @mixin add-left-border($width) {
     &:before {
     &:before {

+ 2 - 6
packages/app/src/styles/style-next.scss

@@ -45,20 +45,16 @@
 // @import 'comment_growi';
 // @import 'comment_growi';
 // @import 'create-page';
 // @import 'create-page';
 // @import 'draft';
 // @import 'draft';
-// @import 'editor-attachment';
-// @import 'editor-navbar';
-// @import 'page-content-footer';
+@import 'editor';
 // @import 'handsontable';
 // @import 'handsontable';
 @import 'layout';
 @import 'layout';
 // @import 'login';
 // @import 'login';
 // @import 'me';
 // @import 'me';
-// @import 'mirror_mode';
+@import 'mirror_mode';
 @import 'modal';
 @import 'modal';
 // @import 'navbar';
 // @import 'navbar';
 // @import 'old-ios';
 // @import 'old-ios';
-@import 'on-edit';
 // @import 'page-duplicate-modal';
 // @import 'page-duplicate-modal';
-
 // @import 'page-path';
 // @import 'page-path';
 // @import 'page-tree';
 // @import 'page-tree';
 // @import 'page';
 // @import 'page';

+ 14 - 14
packages/app/src/styles/style-themes.scss

@@ -1,15 +1,15 @@
-@import '~/components/Theme/ThemeAntarctic.global.scss';
-@import '~/components/Theme/ThemeBlackboard.global.scss';
-@import '~/components/Theme/ThemeChristmas.global.scss';
+// @import '~/components/Theme/ThemeAntarctic.global.scss';
+// @import '~/components/Theme/ThemeBlackboard.global.scss';
+// @import '~/components/Theme/ThemeChristmas.global.scss';
 @import '~/components/Theme/ThemeDefault.global.scss';
 @import '~/components/Theme/ThemeDefault.global.scss';
-@import '~/components/Theme/ThemeFireRed.global.scss';
-@import '~/components/Theme/ThemeFuture.global.scss';
-@import '~/components/Theme/ThemeHalloween.global.scss';
-@import '~/components/Theme/ThemeHufflepuff.global.scss';
-@import '~/components/Theme/ThemeIsland.global.scss';
-@import '~/components/Theme/ThemeJadeGreen.global.scss';
-@import '~/components/Theme/ThemeKibela.global.scss';
-@import '~/components/Theme/ThemeMonoBlue.global.scss';
-@import '~/components/Theme/ThemeNature.global.scss';
-@import '~/components/Theme/ThemeSpring.global.scss';
-@import '~/components/Theme/ThemeWood.global.scss';
+// @import '~/components/Theme/ThemeFireRed.global.scss';
+// @import '~/components/Theme/ThemeFuture.global.scss';
+// @import '~/components/Theme/ThemeHalloween.global.scss';
+// @import '~/components/Theme/ThemeHufflepuff.global.scss';
+// @import '~/components/Theme/ThemeIsland.global.scss';
+// @import '~/components/Theme/ThemeJadeGreen.global.scss';
+// @import '~/components/Theme/ThemeKibela.global.scss';
+// @import '~/components/Theme/ThemeMonoBlue.global.scss';
+// @import '~/components/Theme/ThemeNature.global.scss';
+// @import '~/components/Theme/ThemeSpring.global.scss';
+// @import '~/components/Theme/ThemeWood.global.scss';

+ 1 - 1
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -252,7 +252,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 }
 }
 
 
 /*
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
  */
 .grw-editor-navbar-bottom {
 .grw-editor-navbar-bottom {
   background-color: $gray-50;
   background-color: $gray-50;

+ 2 - 2
packages/app/src/styles/theme/_apply-colors.scss

@@ -503,9 +503,9 @@ ul.pagination {
 }
 }
 
 
 /*
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
  */
-body.on-edit {
+.layout-root.editing {
   .main {
   .main {
     background-color: darken($bgcolor-global, 2%);
     background-color: darken($bgcolor-global, 2%);
 
 

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts

@@ -18,8 +18,8 @@ context('Access to page', () => {
   it('/Sandbox with anchor hash is successfully loaded', () => {
   it('/Sandbox with anchor hash is successfully loaded', () => {
     cy.visit('/Sandbox#Headers');
     cy.visit('/Sandbox#Headers');
 
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
 
     // remove animation for screenshot
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated
     // remove 'blink' class because ::after element cannot be operated

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts

@@ -54,7 +54,7 @@ context('Modal for page operation', () => {
     });
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('.layout-root').should('not.have.class', 'editing');
 
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-today-page`);
     cy.screenshot(`${ssPrefix}create-today-page`);
@@ -72,7 +72,7 @@ context('Modal for page operation', () => {
     });
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-page-under-specific-page`);
     cy.screenshot(`${ssPrefix}create-page-under-specific-page`);

+ 2 - 2
packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts

@@ -14,8 +14,8 @@ context('Access to page by guest', () => {
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(500);
     cy.wait(500);
 
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
 
     cy.screenshot(`${ssPrefix}-sandbox-headers`);
     cy.screenshot(`${ssPrefix}-sandbox-headers`);
   });
   });

+ 1 - 1
packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts

@@ -60,7 +60,7 @@ context('Access to sidebar', () => {
     cy.get('.CodeMirror textarea').type(content, {force: true});
     cy.get('.CodeMirror textarea').type(content, {force: true});
     cy.screenshot(`${ssPrefix}custom-sidebar-2-custom-sidebar-editor`);
     cy.screenshot(`${ssPrefix}custom-sidebar-2-custom-sidebar-editor`);
     cy.getByTestid('save-page-btn').click();
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
 
     // What to do when UserUISettings is not saved in time
     // What to do when UserUISettings is not saved in time
     cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').then(($el) => {
     cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').then(($el) => {

+ 5 - 3
packages/app/test/cypress/integration/60-home/home.spec.ts

@@ -15,7 +15,9 @@ context('Access Home', () => {
     cy.getByTestid('grw-personal-dropdown').click();
     cy.getByTestid('grw-personal-dropdown').click();
     cy.getByTestid('grw-personal-dropdown').find('.dropdown-menu .btn-group > .btn-outline-secondary:eq(0)').click();
     cy.getByTestid('grw-personal-dropdown').find('.dropdown-menu .btn-group > .btn-outline-secondary:eq(0)').click();
 
 
-    cy.getByTestid('grw-user-page').should('be.visible');
+    cy.get('.grw-users-info').should('be.visible');
+    // for check download toc data
+    cy.get('.toc-link').should('be.visible');
 
 
     cy.screenshot(`${ssPrefix}-visit-home`);
     cy.screenshot(`${ssPrefix}-visit-home`);
   });
   });
@@ -34,8 +36,8 @@ context('Access User settings', () => {
     // collapse sidebar
     // collapse sidebar
     cy.collapseSidebar(true);
     cy.collapseSidebar(true);
     cy.visit('/me');
     cy.visit('/me');
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
   });
   });
 
 
   it('Access User information', () => {
   it('Access User information', () => {