Ver Fonte

Merge branch 'dev/7.0.x' into feat/page-bulk-export

Futa Arai há 2 anos atrás
pai
commit
a92c1f9c4d
100 ficheiros alterados com 1383 adições e 7291 exclusões
  1. 0 1
      _obsolete/packages/.eslintignore
  2. 0 1
      _obsolete/packages/hackmd/.eslintignore
  3. 0 1
      _obsolete/packages/hackmd/.gitignore
  4. 0 25
      _obsolete/packages/hackmd/package.json
  5. 0 152
      _obsolete/packages/hackmd/src/hackmd-agent.js
  6. 0 41
      _obsolete/packages/hackmd/src/hackmd-styles.ts
  7. 0 16
      _obsolete/packages/hackmd/src/index.ts
  8. 0 21
      _obsolete/packages/hackmd/src/style.scss
  9. 0 14
      _obsolete/packages/hackmd/tsconfig.json
  10. 0 30
      _obsolete/packages/hackmd/vite.config.js
  11. 0 33
      apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts
  12. 0 51
      apps/app/_obsolete/src/client/util/codemirror/autorefresh.ext.js
  13. 0 47
      apps/app/_obsolete/src/client/util/codemirror/drawio-fold.ext.js
  14. 0 19
      apps/app/_obsolete/src/client/util/codemirror/gfm-growi.mode.js
  15. 0 41
      apps/app/_obsolete/src/client/util/codemirror/update-display-util.ext.js
  16. 0 50
      apps/app/_obsolete/src/components/Navbar/GlobalSearch.module.scss
  17. 0 144
      apps/app/_obsolete/src/components/Navbar/GlobalSearch.tsx
  18. 0 114
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss
  19. 0 141
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx
  20. 0 103
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss
  21. 0 55
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx
  22. 0 41
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss
  23. 0 97
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  24. 0 144
      apps/app/_obsolete/src/components/PageEditor/AbstractEditor.tsx
  25. 0 50
      apps/app/_obsolete/src/components/PageEditor/Editor.module.scss
  26. 0 365
      apps/app/_obsolete/src/components/PageEditor/Editor.tsx
  27. 0 159
      apps/app/_obsolete/src/components/PageEditor/EditorIcon.jsx
  28. 0 62
      apps/app/_obsolete/src/components/PageEditor/EmojiPicker.tsx
  29. 0 116
      apps/app/_obsolete/src/components/PageEditor/EmojiPickerHelper.ts
  30. 0 46
      apps/app/_obsolete/src/components/PageEditor/MarkdownLinkUtil.js
  31. 0 95
      apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js
  32. 0 44
      apps/app/_obsolete/src/components/PageEditor/PasteHelper.js
  33. 0 44
      apps/app/_obsolete/src/components/PageEditor/PreventMarkdownListInterceptor.js
  34. 0 274
      apps/app/_obsolete/src/components/PageEditor/TextAreaEditor.jsx
  35. 0 521
      apps/app/_obsolete/src/components/PageEditorByHackmd.tsx
  36. 0 115
      apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx
  37. 0 101
      apps/app/_obsolete/src/components/UncontrolledCodeMirror.tsx
  38. 0 16
      apps/app/_obsolete/src/interfaces/hackmd.ts
  39. 0 346
      apps/app/_obsolete/src/server/routes/hackmd.js
  40. 0 22
      apps/app/_obsolete/src/stores/hackmd.ts
  41. 0 170
      apps/app/_obsolete/src/styles/_override.scss
  42. 0 32
      apps/app/_obsolete/src/styles/theme/_hsl-functions.scss
  43. 0 108
      apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss
  44. 0 29
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss
  45. 0 22
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss
  46. 0 60
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss
  47. 0 38
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss
  48. 0 52
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss
  49. 0 74
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss
  50. 0 3
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss
  51. 0 103
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  52. 0 9
      apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss
  53. 0 23
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss
  54. 0 146
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss
  55. 0 72
      apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss
  56. 0 22
      apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss
  57. 0 159
      apps/app/bin/cdn/cdn-resources-downloader.ts
  58. 0 33
      apps/app/bin/download-cdn-resources.ts
  59. 0 1
      apps/app/config/logger/config.dev.js
  60. 11 4
      apps/app/config/next-i18next.config.js
  61. 1 0
      apps/app/next-env.d.ts
  62. 1 1
      apps/app/next.config.js
  63. 13 17
      apps/app/package.json
  64. 3 0
      apps/app/public/images/icons/slack/slack-logo-background.svg
  65. 3 0
      apps/app/public/images/icons/slack/slack-logo-dark-background.svg
  66. 2 1
      apps/app/public/static/locales/en_US/translation.json
  67. 1 0
      apps/app/public/static/locales/ja_JP/translation.json
  68. 1 0
      apps/app/public/static/locales/zh_CN/translation.json
  69. 0 221
      apps/app/resource/cdn-manifests.js
  70. 0 253
      apps/app/resource/locales/en_US/sandbox-bootstrap4.md
  71. 169 0
      apps/app/resource/locales/en_US/sandbox-bootstrap5.md
  72. 7 10
      apps/app/resource/locales/en_US/sandbox-diagrams.md
  73. 110 391
      apps/app/resource/locales/en_US/sandbox.md
  74. 46 59
      apps/app/resource/locales/en_US/welcome.md
  75. 0 253
      apps/app/resource/locales/ja_JP/sandbox-bootstrap4.md
  76. 258 0
      apps/app/resource/locales/ja_JP/sandbox-bootstrap5.md
  77. 12 3
      apps/app/resource/locales/ja_JP/sandbox-diagrams.md
  78. 4 4
      apps/app/resource/locales/ja_JP/sandbox-math.md
  79. 192 329
      apps/app/resource/locales/ja_JP/sandbox.md
  80. 44 53
      apps/app/resource/locales/ja_JP/welcome.md
  81. 0 253
      apps/app/resource/locales/zh_CN/sandbox-bootstrap4.md
  82. 169 0
      apps/app/resource/locales/zh_CN/sandbox-bootstrap5.md
  83. 7 10
      apps/app/resource/locales/zh_CN/sandbox-diagrams.md
  84. 110 389
      apps/app/resource/locales/zh_CN/sandbox.md
  85. 46 59
      apps/app/resource/locales/zh_CN/welcome.md
  86. 0 5
      apps/app/src/client/services/page-operation.ts
  87. 47 11
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  88. 47 11
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  89. 30 9
      apps/app/src/client/services/side-effects/page-updated.ts
  90. 22 0
      apps/app/src/client/services/update-page/conflict.tsx
  91. 9 0
      apps/app/src/client/services/update-page/index.ts
  92. 2 1
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  93. 3 2
      apps/app/src/components/Admin/AuditLog/AuditLogDisableMode.tsx
  94. 1 1
      apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  95. 6 6
      apps/app/src/components/Admin/AuditLogManagement.tsx
  96. 0 6
      apps/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  97. 3 2
      apps/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  98. 0 6
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  99. 0 6
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  100. 3 1
      apps/app/src/components/Admin/ElasticsearchManagement/ReconnectControls.tsx

+ 0 - 1
_obsolete/packages/.eslintignore

@@ -1 +0,0 @@
-**/*

+ 0 - 1
_obsolete/packages/hackmd/.eslintignore

@@ -1 +0,0 @@
-/dist/**

+ 0 - 1
_obsolete/packages/hackmd/.gitignore

@@ -1 +0,0 @@
-/dist

+ 0 - 25
_obsolete/packages/hackmd/package.json

@@ -1,25 +0,0 @@
-{
-  "name": "@growi/hackmd",
-  "version": "7.0.0-RC.0",
-  "description": "GROWI js and css files to use hackmd",
-  "license": "MIT",
-  "type": "module",
-  "main": "dist/index.cjs",
-  "module": "dist/index.js",
-  "types": "dist/index.d.ts",
-  "scripts": {
-    "build": "vite build",
-    "clean": "shx rm -rf dist",
-    "dev": "vite build --mode dev",
-    "watch": "yarn dev -w --emptyOutDir=false",
-    "lint:js": "yarn eslint **/*.{js,ts}",
-    "lint:typecheck": "tsc",
-    "lint": "npm-run-all -p lint:*",
-    "version": "yarn version --no-git-tag-version --preid=RC"
-  },
-  "dependencies": {},
-  "devDependencies": {
-    "penpal": "^4.0.0",
-    "throttle-debounce": "^5.0.0"
-  }
-}

+ 0 - 152
_obsolete/packages/hackmd/src/hackmd-agent.js

@@ -1,152 +0,0 @@
-/**
- * GROWI agent for HackMD
- *
- * This file will be transpiled as a single JS
- *  and should be load from HackMD head via 'routes/hackmd.js' route
- *
- * USAGE:
- *  <script src="${hostname of GROWI}/_hackmd/load-agent"></script>
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-import connectToParent from 'penpal/lib/connectToParent';
-import { debounce } from 'throttle-debounce';
-
-const DEBUG_PENPAL = false;
-
-/* eslint-disable no-console  */
-
-const allowedOrigin = '<%= origin %>'; // will be replaced by ejs
-
-
-/**
- * return the value of CodeMirror
- */
-function getValueOfCodemirror() {
-  // get CodeMirror instance
-  const editor = window.editor;
-  return editor.doc.getValue();
-}
-
-/**
- * set the specified document to CodeMirror
- * @param {string} value
- */
-function setValueToCodemirror(value) {
-  // get CodeMirror instance
-  const editor = window.editor;
-  editor.doc.setValue(value);
-}
-
-/**
- * set the specified document to CodeMirror on window loaded
- * @param {string} value
- */
-function setValueToCodemirrorOnInit(newValue) {
-  if (window.cmClient != null) {
-    setValueToCodemirror(newValue);
-    return;
-  }
-
-  const intervalId = setInterval(() => {
-    if (window.cmClient != null) {
-      clearInterval(intervalId);
-      setValueToCodemirror(newValue);
-    }
-  }, 250);
-
-}
-
-/**
- * postMessage to GROWI to notify body changes
- * @param {string} body
- */
-function postParentToNotifyBodyChanges(body) {
-  window.growi.notifyBodyChanges(body);
-}
-// generate debounced function
-const debouncedPostParentToNotifyBodyChanges = debounce(800, postParentToNotifyBodyChanges);
-
-/**
- * postMessage to GROWI to save with shortcut
- * @param {string} document
- */
-function postParentToSaveWithShortcut(document) {
-  window.growi.saveWithShortcut(document);
-}
-
-function addEventListenersToCodemirror() {
-  // get CodeMirror instance
-  const codemirror = window.CodeMirror;
-  // get CodeMirror editor instance
-  const editor = window.editor;
-
-  // e.g. 404 not found
-  if (codemirror == null || editor == null) {
-    return;
-  }
-
-  // == change event
-  editor.on('change', (cm, change) => {
-    if (change.origin === 'ignoreHistory') {
-      // do nothing because this operation triggered by other user
-      return;
-    }
-    debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
-  });
-
-  // == save event
-  // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
-  codemirror.commands.save = function(cm) {
-    postParentToSaveWithShortcut(cm.doc.getValue());
-  };
-  delete editor.options.extraKeys['Cmd-S'];
-  delete editor.options.extraKeys['Ctrl-S'];
-}
-
-function connectToParentWithPenpal() {
-  const connection = connectToParent({
-    parentOrigin: allowedOrigin,
-    // Methods child is exposing to parent
-    methods: {
-      getValue() {
-        return getValueOfCodemirror();
-      },
-      setValue(newValue) {
-        setValueToCodemirror(newValue);
-      },
-      setValueOnInit(newValue) {
-        setValueToCodemirrorOnInit(newValue);
-      },
-    },
-    debug: DEBUG_PENPAL,
-  });
-  connection.promise
-    .then((parent) => {
-      window.growi = parent;
-    })
-    .catch((err) => {
-      console.log(err);
-    });
-}
-
-/**
- * main
- */
-(function() {
-  // check HackMD is in iframe
-  if (window === window.parent) {
-    console.log('[GROWI] Loading agent for HackMD is not processed because currently not in iframe');
-    return;
-  }
-
-  console.log('[HackMD] Loading GROWI agent for HackMD...');
-
-  window.addEventListener('load', () => {
-    addEventListenersToCodemirror();
-  });
-
-  connectToParentWithPenpal();
-
-  console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
-}());

+ 0 - 41
_obsolete/packages/hackmd/src/hackmd-styles.ts

@@ -1,41 +0,0 @@
-/**
- * GROWI styles loader for HackMD
- *
- * This file will be transpiled as a single JS
- *  and should be load from HackMD head via 'routes/hackmd.js' route
- *
- * USAGE:
- *  <script src="${hostname of GROWI}/_hackmd/load-styles"></script>
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-
-/* eslint-disable no-console  */
-
-const styles = '<%= styles %>'; // will be replaced by ejs
-
-/**
- * Insert link tag to load style file
- */
-function insertStyle() {
-  const element = document.createElement('style');
-  element.appendChild(document.createTextNode(unescape(styles)));
-  document.getElementsByTagName('head')[0].appendChild(element);
-}
-
-/**
- * main
- */
-(function() {
-  // check HackMD is in iframe
-  if (window === window.parent) {
-    console.log('[GROWI] Loading styles for HackMD is not processed because currently not in iframe');
-    return;
-  }
-
-  console.log('[HackMD] Loading GROWI styles for HackMD...');
-
-  insertStyle();
-
-  console.log('[HackMD] GROWI styles for HackMD has successfully loaded.');
-}());

+ 0 - 16
_obsolete/packages/hackmd/src/index.ts

@@ -1,16 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-const isProduction = process.env.NODE_ENV === 'production';
-const dirPath = isProduction ? '.' : '../dist';
-const stylesJSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/hackmd-styles.js`));
-const agentJSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/hackmd-agent.js`));
-const stylesCSSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/style.css`));
-
-// export to app as string
-const hackmdFiles = {
-  stylesJS: stylesJSFile.toString(),
-  agentJS: agentJSFile.toString(),
-  stylesCSS: stylesCSSFile.toString().replace(/(\r\n|\n|\r)/gm, ''), // https://stackoverflow.com/questions/10805125/how-to-remove-all-line-breaks-from-a-string
-};
-export default hackmdFiles;

+ 0 - 21
_obsolete/packages/hackmd/src/style.scss

@@ -1,21 +0,0 @@
-.navbar-header {
-  .navbar-brand {
-    display: none;
-  }
-}
-
-.navbar-form {
-  margin-left: 15px;
-}
-
-.navbar-right {
-  .ui-new, .ui-publish {
-    display: none;
-  }
-}
-
-.CodeMirror pre.CodeMirror-line {
-  font-family: Osaka-Mono, 'MS Gothic', Monaco, Menlo, Consolas, 'Courier New', monospace;
-  font-size: 14px;
-  line-height: 20px;
-}

+ 0 - 14
_obsolete/packages/hackmd/tsconfig.json

@@ -1,14 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "isolatedModules": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  },
-  "include": [
-    "src"
-  ]
-}

+ 0 - 30
_obsolete/packages/hackmd/vite.config.js

@@ -1,30 +0,0 @@
-import { defineConfig } from 'vite';
-import dts from 'vite-plugin-dts';
-
-
-// https://vitejs.dev/config/
-export default defineConfig({
-  plugins: [
-    dts({ copyDtsFiles: true }),
-  ],
-  build: {
-    outDir: 'dist',
-    lib: {
-      entry: [
-        'src/index.ts',
-        'src/hackmd-styles.ts',
-        'src/hackmd-agent.js',
-        'src/style.scss',
-      ],
-      name: 'hackmd-libs',
-      formats: ['es', 'cjs'],
-    },
-    rollupOptions: {
-      external: [
-        'node:fs',
-        'node:path',
-      ],
-    },
-    sourcemap: true,
-  },
-});

+ 0 - 33
apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts

@@ -1,33 +0,0 @@
-import { useCallback, useEffect } from 'react';
-
-import { SocketEventName } from '~/interfaces/websocket';
-import { useIsHackmdDraftUpdatingInRealtime } from '~/stores/hackmd';
-import { useCurrentPageId } from '~/stores/page';
-import { useGlobalSocket } from '~/stores/websocket';
-
-export const useHackmdDraftUpdatedEffect = (): void => {
-
-  const { data: currentPageId } = useCurrentPageId();
-  const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
-
-  const { data: socket } = useGlobalSocket();
-
-  const setIsHackmdDraftUpdatingInRealtime = useCallback((data) => {
-    const { s2cMessagePageUpdated } = data;
-    if (s2cMessagePageUpdated.pageId === currentPageId) {
-      mutateIsHackmdDraftUpdatingInRealtime(true);
-    }
-  }, [currentPageId, mutateIsHackmdDraftUpdatingInRealtime]);
-
-  // listen socket for hackmd saved
-  useEffect(() => {
-
-    if (socket == null) { return }
-
-    socket.on(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
-
-    return () => {
-      socket.off(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
-    };
-  }, [setIsHackmdDraftUpdatingInRealtime, socket]);
-};

+ 0 - 51
apps/app/_obsolete/src/client/util/codemirror/autorefresh.ext.js

@@ -1,51 +0,0 @@
-/**
- * extends codemirror/addon/display/autorefresh
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- * @see https://codemirror.net/addon/display/autorefresh.js
- * @see https://github.com/scniro/react-codemirror2/issues/83#issuecomment-398825212
- */
-/* eslint-disable */
-
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  mod(require("codemirror"));
-})(function(CodeMirror) {
-  "use strict"
-
-  CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
-    if (cm.state.autoRefresh) {
-      stopListening(cm, cm.state.autoRefresh)
-      cm.state.autoRefresh = null
-    }
-    if (val && (val.force || cm.display.wrapper.offsetHeight == 0))
-      startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
-  })
-
-  function startListening(cm, state) {
-    function check() {
-      if (cm.display.wrapper.offsetHeight) {
-        stopListening(cm, state)
-        if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
-          cm.refresh()
-      } else {
-        state.timeout = setTimeout(check, state.delay)
-      }
-    }
-    state.timeout = setTimeout(check, state.delay)
-    state.hurry = function() {
-      clearTimeout(state.timeout)
-      state.timeout = setTimeout(check, 50)
-    }
-    CodeMirror.on(window, "mouseup", state.hurry)
-    CodeMirror.on(window, "keyup", state.hurry)
-  }
-
-  function stopListening(_cm, state) {
-    clearTimeout(state.timeout)
-    CodeMirror.off(window, "mouseup", state.hurry)
-    CodeMirror.off(window, "keyup", state.hurry)
-  }
-});

+ 0 - 47
apps/app/_obsolete/src/client/util/codemirror/drawio-fold.ext.js

@@ -1,47 +0,0 @@
-/* eslint-disable */
-
-import mdu from '../../../components/PageEditor/MarkdownDrawioUtil.js';
-
-(function(mod) {
-  mod(require("codemirror"));
-})(function(CodeMirror) {
-  "use strict"
-
-  CodeMirror.registerGlobalHelper('fold', 'drawio', function (mode, cm) {
-    return true;
-  }, function(cm, start) {
-    function isBeginningOfDrawio(lineNo) {
-      let line = cm.getLine(lineNo);
-      let match = mdu.lineBeginPartOfDrawioRE.exec(line);
-      if (match) {
-        return true;
-      }
-      return false;
-    }
-    function isEndOfDrawio(lineNo) {
-      let line = cm.getLine(lineNo);
-      let match = mdu.lineEndPartOfDrawioRE.exec(line);
-      if (match) {
-        return true;
-      }
-      return false;
-    }
-
-    let drawio = isBeginningOfDrawio(start.line);
-    if (drawio === false) { return; }
-
-    let lastLine = cm.lastLine();
-    let end = start.line;
-    while(end < lastLine) {
-      end += 1;
-      if (isEndOfDrawio(end)) {
-        break;
-      }
-    }
-
-    return {
-      from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
-      to: CodeMirror.Pos(end, cm.getLine(end).length)
-    };
-  });
-});

+ 0 - 19
apps/app/_obsolete/src/client/util/codemirror/gfm-growi.mode.js

@@ -1,19 +0,0 @@
-// https://discuss.codemirror.net/t/cm-header-margin-padding-height/75/5
-window.CodeMirror.defineMode('gfm-growi', (cmConfig, modeCfg) => {
-  // based on Markdown (GitHub-flavour) mode
-  // https://codemirror.net/doc/manual.html#option_mode
-  // https://codemirror.net/mode/index.html
-  modeCfg.name = 'gfm';
-  modeCfg.highlightFormatting = true;
-  const mode = window.CodeMirror.getMode(cmConfig, modeCfg);
-
-  const origToken = mode.token;
-  mode.token = function(stream, state) {
-    let classes = origToken(stream, state) || '';
-    // https://regex101.com/r/Fep0w2/1
-    classes = classes.replace(/(^| )header(\S*)/g, '$1header$2 line-grw-cm-header-line');
-    return /^\s*$/.test(classes) ? null : classes;
-  };
-
-  return mode;
-});

+ 0 - 41
apps/app/_obsolete/src/client/util/codemirror/update-display-util.ext.js

@@ -1,41 +0,0 @@
-import { sawCollapsedSpans } from 'codemirror/src/line/saw_special_spans';
-import { getLine } from 'codemirror/src/line/utils_line';
-import { heightAtLine, visualLineEndNo, visualLineNo } from 'codemirror/src/line/spans';
-import { DisplayUpdate } from 'codemirror/src/display/update_display';
-import { adjustView } from 'codemirror/src/display/view_tracking';
-
-class UpdateDisplayUtil {
-
-  /**
-   * Transplant 'updateDisplayIfNeeded' method to fix weseek/growi#703
-   *
-   * @see https://github.com/weseek/growi/issues/703
-   * @see https://github.com/codemirror/CodeMirror/blob/5.42.0/src/display/update_display.js#L125
-   *
-   * @param {CodeMirror} cm
-   */
-  static forceUpdateViewOffset(cm) {
-    const doc = cm.doc;
-    const display = cm.display;
-
-    const update = new DisplayUpdate(cm, cm.getViewport());
-
-    // Compute a suitable new viewport (from & to)
-    const end = doc.first + doc.size;
-    let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
-    let to = Math.min(end, update.visible.to + cm.options.viewportMargin);
-    if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
-    if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
-    if (sawCollapsedSpans) {
-      from = visualLineNo(cm.doc, from);
-      to = visualLineEndNo(cm.doc, to);
-    }
-    adjustView(cm, from, to);
-
-    display.viewOffset = heightAtLine(getLine(doc, display.viewFrom));
-  }
-
-}
-
-
-export default UpdateDisplayUtil;

+ 0 - 50
apps/app/_obsolete/src/components/Navbar/GlobalSearch.module.scss

@@ -1,50 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-// input styles
-.grw-global-search :global {
-  .dropdown-toggle {
-    min-width: 95px;
-    padding-left: 1.5rem;
-  }
-
-  .search-typeahead {
-    .rbt-menu {
-      right: 0;
-      left: auto;
-
-      @include bs.media-breakpoint-up(md) {
-        right: auto;
-        left: 0;
-      }
-
-      @include bs.media-breakpoint-down(sm) {
-        left: auto !important;
-        width: 90vw;
-      }
-    }
-  }
-
-  // using react-bootstrap-typeahead
-  // see: https://github.com/ericgio/react-bootstrap-typeahead
-  .rbt-input.form-control {
-    height: 30px;
-    .rbt-input-wrapper {
-      margin-left: 8px;
-    }
-  }
-
-  .grw-shortcut-key-indicator {
-    position: absolute;
-    top: 0;
-    right: 4px;
-
-    display: flex;
-    align-items: center;
-    height: 30px;
-
-    code {
-      padding-right: 0.4rem;
-      padding-left: 0.4rem;
-    }
-  }
-}

+ 0 - 144
apps/app/_obsolete/src/components/Navbar/GlobalSearch.tsx

@@ -1,144 +0,0 @@
-import React, {
-  useState, useCallback, useRef, useEffect,
-} from 'react';
-
-import assert from 'assert';
-
-import { pathUtils } from '@growi/core/dist/utils';
-import { useTranslation } from 'next-i18next';
-import { useRouter } from 'next/router';
-
-import { IFocusable } from '~/client/interfaces/focusable';
-import { useKeywordManager } from '~/client/services/search-operation';
-import { IPageWithSearchMeta } from '~/interfaces/search';
-import {
-  useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
-} from '~/stores/context';
-import { useCurrentPagePath } from '~/stores/page';
-import { useGlobalSearchFormRef } from '~/stores/ui';
-
-import SearchForm from '../../../../src/components/SearchForm';
-
-import styles from './GlobalSearch.module.scss';
-
-
-export type GlobalSearchProps = {
-  dropup?: boolean,
-}
-
-export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
-  const { t } = useTranslation('commons');
-
-  const { dropup } = props;
-
-  const { returnPathForURL } = pathUtils;
-
-  const router = useRouter();
-
-  const globalSearchFormRef = useRef<IFocusable>(null);
-
-  useGlobalSearchFormRef(globalSearchFormRef);
-
-  const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
-  const { data: isSearchScopeChildrenAsDefault } = useIsSearchScopeChildrenAsDefault();
-  const { data: currentPagePath } = useCurrentPagePath();
-
-  const [text, setText] = useState('');
-  const [isScopeChildren, setScopeChildren] = useState<boolean|undefined>(isSearchScopeChildrenAsDefault ?? false);
-  const [isFocused, setFocused] = useState<boolean>(false);
-
-  const { pushState } = useKeywordManager();
-
-  useEffect(() => {
-    setScopeChildren(isSearchScopeChildrenAsDefault);
-  }, [isSearchScopeChildrenAsDefault]);
-
-
-  const gotoPage = useCallback((data: IPageWithSearchMeta[]) => {
-    assert(data.length > 0);
-
-    const page = data[0].data; // should be single page selected
-
-    // navigate to page
-    if (page != null) {
-      router.push(returnPathForURL(page.path, page._id));
-    }
-  }, [returnPathForURL, router]);
-
-  const search = useCallback(() => {
-    // construct search query
-    let q = text;
-    if (isScopeChildren) {
-      q += ` prefix:${currentPagePath ?? window.location.pathname}`;
-    }
-
-    pushState(q);
-  }, [currentPagePath, isScopeChildren, router, text]);
-
-  const scopeLabel = isScopeChildren
-    ? t('header_search_box.label.This tree')
-    : t('header_search_box.label.All pages');
-
-  const isIndicatorShown = !isFocused && (text.length === 0);
-
-
-  if (isScopeChildren == null || isSearchServiceReachable == null) {
-    return <></>;
-  }
-
-  return (
-    <div className={`grw-global-search ${styles['grw-global-search']} mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
-      <div className="input-group flex-nowrap">
-        <div className={` ${dropup ? 'dropup' : ''}`}>
-          <button
-            className="btn btn-secondary dropdown-toggle py-0"
-            type="button"
-            data-bs-toggle="dropdown"
-            aria-haspopup="true"
-            data-testid="select-search-scope"
-          >
-            {scopeLabel}
-          </button>
-          <div className="dropdown-menu">
-            <button
-              className="dropdown-item"
-              type="button"
-              onClick={() => {
-                setScopeChildren(false);
-                globalSearchFormRef.current?.focus();
-              }}
-            >
-              { t('header_search_box.item_label.All pages') }
-            </button>
-            <button
-              data-tesid="search-current-tree"
-              className="dropdown-item"
-              type="button"
-              onClick={() => {
-                setScopeChildren(true);
-                globalSearchFormRef.current?.focus();
-              }}
-            >
-              { t('header_search_box.item_label.This tree') }
-            </button>
-          </div>
-        </div>
-        <SearchForm
-          ref={globalSearchFormRef}
-          isSearchServiceReachable={isSearchServiceReachable || false}
-          dropup={dropup}
-          onChange={gotoPage}
-          onBlur={() => setFocused(false)}
-          onFocus={() => setFocused(true)}
-          onInputChange={text => setText(text)}
-          onSubmit={search}
-        />
-        { isIndicatorShown && (
-          <span className="grw-shortcut-key-indicator">
-            <code className="bg-transparent text-muted">/</code>
-          </span>
-        ) }
-      </div>
-    </div>
-  );
-};

+ 0 - 114
apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss

@@ -1,114 +0,0 @@
-@use '~/styles/variables' as var;
-@use '~/styles/bootstrap/init' as bs;
-@use '~/styles/mixins';
-
-.grw-navbar :global {
-
-  .confidential {
-    font-weight: bold;
-  }
-
-}
-
-.grw-navbar :global {
-  top: #{-1 * var.$grw-navbar-height} !important;
-
-  z-index: var.$grw-navbar-z-index !important;
-  max-height: var.$grw-navbar-height + var.$grw-navbar-border-width;
-  border-top: 0;
-  border-right: 0;
-  border-bottom: var.$grw-navbar-border-width solid;
-  border-left: 0;
-
-  .grw-app-title {
-    @include mixins.variable-font-size(24px);
-  }
-
-  .grw-navbar-search {
-    position: absolute;
-    left: 50%;
-    transform: translate(-50%, 0%);
-  }
-
-  .nav-link,
-  .nav-item.confidential {
-    display: flex;
-    align-items: center;
-    min-height: var.$grw-navbar-height;
-    padding: 0 1rem;
-  }
-
-  .nav-link {
-    &:hover {
-      background: rgba(0, 0, 0, 0.1);
-    }
-
-    &:focus {
-      background: rgba(0, 0, 0, 0);
-    }
-  }
-  .nav-item.confidential {
-    :not(i) {
-      @include mixins.variable-font-size(14px);
-    }
-
-    @include bs.media-breakpoint-only(md) {
-      max-width: 100px;
-    }
-
-    max-width: 120px;
-    max-height: var.$grw-navbar-height;
-    overflow: hidden;
-    background: rgba(0, 0, 0, 0.2);
-  }
-
-  .grw-notification-dropdown {
-    .dropdown-menu {
-      max-width: 70vw;
-    }
-  }
-}
-
-// layout for GlobalSearch
-.grw-navbar :global {
-  .grw-global-search-container {
-    // centering on navbar
-    top: var.$grw-navbar-height / 2;
-    left: 50vw;
-    z-index: bs.$zindex-fixed + 1;
-    transform: translate(-50%, -50%);
-
-    .rbt-input.form-control {
-      width: 200px;
-      transition: 0.3s ease-out;
-
-      // focus
-      &.focus {
-        width: 300px;
-      }
-
-      @include bs.media-breakpoint-up(md) {
-        width: 300px;
-      }
-      @include bs.media-breakpoint-up(lg) {
-        // focus
-        &.focus {
-          width: 400px;
-        }
-      }
-      @include bs.media-breakpoint-up(xl) {
-        width: 350px;
-        // focus
-        &.focus {
-          width: 450px;
-        }
-      }
-    }
-  }
-}
-
-.grw-notification-badge {
-  position: absolute;
-  top: 6px;
-  right: 3.5px;
-}

+ 0 - 141
apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx

@@ -1,141 +0,0 @@
-import React, {
-  FC, memo, useMemo, useRef,
-} from 'react';
-
-import { useTranslation } from 'next-i18next';
-import dynamic from 'next/dynamic';
-import { useRipple } from 'react-use-ripple';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import {
-  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
-} from '~/stores/context';
-import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
-import { useIsDeviceSmallerThanMd } from '~/stores/ui';
-
-
-import { GlobalSearchProps } from './GlobalSearch';
-
-import styles from './GrowiNavbar.module.scss';
-
-const NavbarRight = memo((): JSX.Element => {
-  const { t } = useTranslation();
-
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: isGuestUser } = useIsGuestUser();
-  const { data: isReadOnlyUser } = useIsReadOnlyUser();
-
-  // ripple
-  const newButtonRef = useRef(null);
-  useRipple(newButtonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  const { open: openCreateModal } = usePageCreateModal();
-
-  const isAuthenticated = isGuestUser === false;
-
-  const authenticatedNavItem = useMemo(() => {
-    return (
-      <>
-        {!isReadOnlyUser
-          && (
-            <li className="nav-item d-none d-md-block">
-              <button
-                className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
-                type="button"
-                ref={newButtonRef}
-                data-testid="newPageBtn"
-                onClick={() => openCreateModal(currentPagePath || '')}
-              >
-                <span className="material-symbols-outlined">edit</span>
-                <span className="d-none d-lg-block">{ t('commons:New') }</span>
-              </button>
-            </li>
-          )
-        }
-      </>
-    );
-  }, [isReadOnlyUser, t, openCreateModal, currentPagePath]);
-
-  const notAuthenticatedNavItem = useMemo(() => {
-    return (
-      <>
-        <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>
-      </>
-    );
-  }, []);
-
-  return (
-    <>
-      {isAuthenticated ? authenticatedNavItem : notAuthenticatedNavItem}
-    </>
-  );
-});
-NavbarRight.displayName = 'NavbarRight';
-
-type ConfidentialProps = {
-  confidential?: string,
-}
-const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX.Element => {
-  const { confidential } = props;
-
-  if (confidential == null || confidential.length === 0) {
-    return <></>;
-  }
-
-  return (
-    <li className="nav-item confidential text-light">
-      <i id="confidentialTooltip"></i><span className="material-symbols-outlined d-md-none">info</span>
-      <span className="d-none d-md-inline">
-        {confidential}
-      </span>
-      <UncontrolledTooltip
-        placement="bottom"
-        target="confidentialTooltip"
-        className="d-md-none"
-      >
-        {confidential}
-      </UncontrolledTooltip>
-    </li>
-  );
-});
-Confidential.displayName = 'Confidential';
-
-type Props = {
-  isGlobalSearchHidden?: boolean
-}
-
-export const GrowiNavbar = (props: Props): JSX.Element => {
-
-  const { isGlobalSearchHidden } = props;
-
-  const GlobalSearch = dynamic<GlobalSearchProps>(() => import('./GlobalSearch').then(mod => mod.GlobalSearch), { ssr: false });
-
-  const { data: appTitle } = useAppTitle();
-  const { data: confidential } = useConfidential();
-  const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-  const { data: isSearchPage } = useIsSearchPage();
-
-  return (
-    <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
-
-      <div className="grw-app-title d-none d-md-block">
-        {appTitle}
-      </div>
-
-      {/* Navbar Right  */}
-      <ul className="navbar-nav ms-auto">
-        <NavbarRight />
-        <Confidential confidential={confidential} />
-      </ul>
-
-      <div className="grw-global-search-container position-absolute">
-        { !isGlobalSearchHidden && isSearchServiceConfigured && !isDeviceSmallerThanMd && !isSearchPage && (
-          <GlobalSearch />
-        ) }
-      </div>
-    </nav>
-  );
-
-};

+ 0 - 103
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -1,103 +0,0 @@
-@use '~/styles/variables' as var;
-@use '@growi/core/scss/bootstrap/init' as bs;
-@use '~/styles/mixins';
-
-%subnav-buttons-height {
-  height: 40px;
-}
-
-%compact-subnav-buttons-height {
-  height: 32px;
-}
-
-// https://github.com/css-modules/css-modules/issues/295#issuecomment-404873976
-// workaround to use '&' in global scope
-.grw-subnav {
-  :global {
-    min-height: var.$grw-subnav-min-height;
-    padding-top: 8px;
-    padding-bottom: 8px;
-
-    @include bs.media-breakpoint-up(md) {
-      min-height: var.$grw-subnav-min-height-md;
-    }
-
-    h1 {
-      @include mixins.variable-font-size(32px);
-      line-height: 1.4em;
-    }
-
-    .btn-copy {
-      &:not(:hover):not(:active) {
-        background-color: transparent !important;
-      }
-      opacity: 0.5;
-    }
-
-    .btn-subscribe {
-      @extend %subnav-buttons-height;
-      font-size: 20px;
-    }
-
-    .btn-like,
-    .btn-bookmark,
-    .btn-seen-user {
-      @extend %subnav-buttons-height;
-      padding-right: 6px;
-      padding-left: 8px;
-      font-size: 20px;
-      svg {
-        width: 20px;
-        height: 20px;
-      }
-    }
-    .total-likes,
-    .total-bookmarks {
-      display: flex;
-      align-items: flex-end;
-      padding-right: 8px;
-      padding-left: 6px;
-      font-size: 14px;
-      font-weight: bs.$font-weight-bold;
-    }
-    .seen-user-count {
-      padding-right: 6px;
-      padding-left: 6px;
-      font-size: 14px;
-      font-weight: bs.$font-weight-bold;
-      vertical-align: bottom;
-    }
-
-    .btn-page-item-control {
-      height: 40px;
-      font-size: 16px;
-    }
-
-    .user-list-popover {
-      max-width: 200px;
-
-      .user-list-content {
-        direction: rtl;
-
-        .liker-user-count,
-        .seen-user-count {
-          font-size: 12px;
-          font-weight: bolder;
-        }
-      }
-      .cls-1 {
-        isolation: isolate;
-      }
-    }
-  }
-
-  &:global {
-    &:hover {
-      .btn-copy,
-      .btn-edit-tags {
-        // change button opacity
-        opacity: unset;
-      }
-    }
-  }
-}

+ 0 - 55
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx

@@ -1,55 +0,0 @@
-import React from 'react';
-
-import {
-  EditorMode, useEditorMode,
-} from '~/stores/ui';
-
-import PagePathNav from '../PagePathNav';
-
-
-import styles from './GrowiSubNavigation.module.scss';
-
-
-export type GrowiSubNavigationProps = {
-  pagePath?: string,
-  pageId?: string,
-  isNotFound?: boolean,
-  isTagLabelsDisabled?: boolean,
-  tags?: string[],
-  rightComponent?: React.FunctionComponent,
-  additionalClasses?: string[],
-}
-
-export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element => {
-
-  const { data: editorMode } = useEditorMode();
-
-  const {
-    pageId, pagePath,
-    rightComponent: RightComponent,
-    additionalClasses = [],
-  } = props;
-
-  const isViewMode = editorMode === EditorMode.View;
-  const isEditorMode = !isViewMode;
-
-  return (
-    <div className={`
-      grw-subnav ${styles['grw-subnav']} d-flex align-items-center justify-content-between
-      ${additionalClasses.join(' ')}`}
-    >
-      {/* Left side */}
-      <div className="d-flex grw-subnav-start-side">
-        <div className="grw-path-nav-container">
-          { pagePath != null && (
-            <PagePathNav pageId={pageId} pagePath={pagePath} isSingleLineMode={isEditorMode} />
-          ) }
-        </div>
-      </div>
-      {/* Right side. */}
-      { RightComponent && (
-        <RightComponent />
-      ) }
-    </div>
-  );
-};

+ 0 - 41
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss

@@ -1,41 +0,0 @@
-@use '~/styles/variables' as var;
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-/*
- * Fixed ver
- */
-$easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
-
-.grw-subnav-fixed-container {
-  top: var.$grw-navbar-border-width;
-  z-index: bs.$zindex-sticky - 5;
-}
-
-/*
- * Switching show/hide
- */
-.grw-subnav-switcher {
-  :global {
-    .grw-subnav-fixed-container {
-      transition: transform 150ms $easeInOutCubic;
-    }
-
-    /*
-    * shadow
-    */
-    .grw-subnav-append-shadow-container {
-      .grw-subnav {
-        box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
-      }
-    }
-  }
-
-  &:global {
-    &.grw-subnav-switcher-hidden {
-      .grw-subnav-fixed-container {
-        transition: unset;
-        transform: translateY(-100%);
-      }
-    }
-  }
-}

+ 0 - 97
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -1,97 +0,0 @@
-import React, {
-  useState, useRef, useEffect, useCallback,
-} from 'react';
-
-import { debounce } from 'throttle-debounce';
-
-import { useSticky } from '~/client/services/side-effects/use-sticky';
-import { useSWRxCurrentPage } from '~/stores/page';
-import { useSidebarCollapsed } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
-
-import styles from './GrowiSubNavigationSwitcher.module.scss';
-
-const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
-
-export type GrowiSubNavigationSwitcherProps = {
-  isLinkSharingDisabled: boolean,
-}
-
-/**
- * GrowiSubNavigation
- *
- * needs:
- *   #grw-subnav-fixed-container element
- *   #grw-subnav-sticky-trigger element
- */
-export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProps): JSX.Element => {
-  const { isLinkSharingDisabled } = props;
-
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: isSidebarCollapsed } = useSidebarCollapsed();
-
-  const [width, setWidth] = useState<number>(0);
-
-  // use more specific type HTMLDivElement for avoid assertion error.
-  // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
-  const fixedContainerRef = useRef<HTMLDivElement>(null);
-  const clientWidth = fixedContainerRef.current?.parentElement?.clientWidth;
-
-  // Get sticky status
-  const isSticky = useSticky('#grw-subnav-sticky-trigger');
-
-  // Do not use clientWidth as useCallback deps, resizing events will not work in production builds.
-  const initWidth = useCallback(() => {
-    if (fixedContainerRef.current != null && fixedContainerRef.current.parentElement != null) {
-      // get parent elements width
-      const { clientWidth } = fixedContainerRef.current.parentElement;
-      setWidth(clientWidth);
-    }
-  }, []);
-
-  // setup effect by resizing event
-  useEffect(() => {
-    const resizeHandler = debounce(100, initWidth);
-    window.addEventListener('resize', resizeHandler);
-
-    // return clean up handler
-    return () => {
-      window.removeEventListener('resize', resizeHandler);
-    };
-  }, [initWidth]);
-
-  // update width when sidebar collapsing changed
-  useEffect(() => {
-    if (isSidebarCollapsed != null) {
-      setTimeout(initWidth, 300);
-    }
-  }, [isSidebarCollapsed, initWidth]);
-
-  /*
-   * initialize width.
-   * Since width is not recalculated at production build first rendering,
-   * make initWidth execution dependent on clientWidth.
-   */
-  useEffect(() => {
-    if (clientWidth != null) initWidth();
-  }, [initWidth, clientWidth]);
-
-  if (currentPage == null) {
-    return <></>;
-  }
-
-  return (
-    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher">
-      <div
-        id="grw-subnav-fixed-container"
-        className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
-        ref={fixedContainerRef}
-        style={{ width }}
-      >
-        <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled={isLinkSharingDisabled} />
-      </div>
-    </div>
-  );
-};

+ 0 - 144
apps/app/_obsolete/src/components/PageEditor/AbstractEditor.tsx

@@ -1,144 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import React from 'react';
-
-import { ICodeMirror } from 'react-codemirror2';
-
-
-export interface AbstractEditorProps extends ICodeMirror {
-  value?: string;
-  isGfmMode?: boolean;
-  onScrollCursorIntoView?: (line: number) => void;
-  onSave?: () => Promise<void>;
-  onPasteFiles?: (event: Event) => void;
-  onCtrlEnter?: (event: Event) => void;
-}
-
-interface defaultProps {
-  isGfmMode: true,
-}
-
-export default class AbstractEditor<T extends AbstractEditorProps> extends React.Component<T, Record<string, unknown>> {
-
-  constructor(props: Readonly<T>) {
-    super(props);
-
-    this.forceToFocus = this.forceToFocus.bind(this);
-    this.setCaretLine = this.setCaretLine.bind(this);
-    this.setScrollTopByLine = this.setScrollTopByLine.bind(this);
-
-    this.getStrFromBol = this.getStrFromBol.bind(this);
-    this.getStrToEol = this.getStrToEol.bind(this);
-    this.insertText = this.insertText.bind(this);
-    this.insertLinebreak = this.insertLinebreak.bind(this);
-
-    this.dispatchSave = this.dispatchSave.bind(this);
-  }
-
-  public static defaultProps: defaultProps = {
-    isGfmMode: true,
-  };
-
-  forceToFocus(): void {}
-
-  /**
-   * set new value
-   */
-  setValue(_newValue: string): void {}
-
-  /**
-   * Enable/Disable GFM mode
-   * @param {bool} _bool
-   */
-  setGfmMode(_bool: boolean): void {}
-
-  /**
-   * set caret position of codemirror
-   * @param {string} number
-   */
-  setCaretLine(_line: number): void {}
-
-  /**
-   * scroll
-   * @param {number} _line
-   */
-  setScrollTopByLine(_line: number): void {}
-
-  /**
-   * return strings from BOL(beginning of line) to current position
-   */
-  getStrFromBol(): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * return strings from current position to EOL(end of line)
-   */
-  getStrToEol(): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * return strings from BOL(beginning of line) to current position
-   */
-  getStrFromBolToSelectedUpperPos(): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * replace Beggining Of Line to current position with param 'text'
-   * @param {string} _text
-   */
-  replaceBolToCurrentPos(_text: string): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * replace the current line with param 'text'
-   * @param {string} _text
-   */
-  replaceLine(_text: string): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * insert text
-   * @param {string} _text
-   */
-  insertText(_text: string): Error {
-    throw new Error('this method should be impelemented in subclass');
-  }
-
-  /**
-   * insert line break to the current position
-   */
-  insertLinebreak(): void {
-    this.insertText('\n');
-  }
-
-  /**
-   * dispatch onSave event
-   */
-  dispatchSave(): void {
-    if (this.props.onSave != null) {
-      this.props.onSave();
-    }
-  }
-
-  /**
-   * dispatch onPasteFiles event
-   * @param {object} event
-   */
-  dispatchPasteFiles(event: Event): void {
-    if (this.props.onPasteFiles != null) {
-      this.props.onPasteFiles(event);
-    }
-  }
-
-  /**
-   * returns items(an array of react elements) in navigation bar for editor
-   */
-  getNavbarItems(): null {
-    return null;
-  }
-
-}

+ 0 - 50
apps/app/_obsolete/src/components/PageEditor/Editor.module.scss

@@ -1,50 +0,0 @@
-@use '~/styles/mixins' as ms;
-@use '@growi/core/scss/bootstrap/init' as bs;
-@use './page-editor-inheritance';
-
-
-.editor-container :global {
-
-  .btn.btn-open-dropzone {
-    z-index: 2;
-    padding-top: 3px;
-    padding-bottom: 3px;
-    font-size: small;
-    border: none;
-    border-top: 1px dotted bs.$gray-300;
-    border-bottom: none;
-
-    &:hover,
-    &:focus {
-      border-bottom: none;
-    }
-  }
-
-  // for Navbar editor
-  .navbar-editor {
-    height: page-editor-inheritance.$navbar-editor-height;
-    padding: 0;
-
-    border-bottom: 1px solid transparent;
-
-    li {
-      display: inline-block;
-      i {
-        font-size: 16px;
-      }
-    }
-
-    button {
-      padding: 0px;
-      margin: 0 2px;
-      font-size: 1rem;
-      line-height: 1;
-      background-color: transparent;
-      border: none;
-    }
-
-    img {
-      vertical-align: bottom;
-    }
-  }
-}

+ 0 - 365
apps/app/_obsolete/src/components/PageEditor/Editor.tsx

@@ -1,365 +0,0 @@
-import type { ForwardRefRenderFunction } from 'react';
-import React, {
-  useState, useRef, useImperativeHandle, useCallback, forwardRef,
-  memo,
-  useEffect,
-} from 'react';
-
-import type { EditorSettings } from '@growi/editor';
-import Dropzone from 'react-dropzone';
-import { useTranslation } from 'react-i18next';
-import {
-  Modal, ModalHeader, ModalBody,
-} from 'reactstrap';
-
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { useDefaultIndentSize } from '~/stores/context';
-import { useEditorSettings } from '~/stores/editor';
-import { useIsMobile } from '~/stores/ui';
-
-import type { IEditorMethods } from '../../interfaces/editor-methods';
-
-import type AbstractEditor from './AbstractEditor';
-import { Cheatsheet } from './Cheatsheet';
-// import CodeMirrorEditor from './CodeMirrorEditor';
-import pasteHelper from './PasteHelper';
-import TextAreaEditor from './TextAreaEditor';
-
-
-import styles from './Editor.module.scss';
-
-export type EditorPropsType = {
-  value?: string,
-  isGfmMode?: boolean,
-  noCdn?: boolean,
-  isUploadable?: boolean,
-  isUploadAllFileAllowed?: boolean,
-  onChange?: (newValue: string, isClean?: boolean) => void,
-  onUpload?: (file) => void,
-  editorSettings?: EditorSettings,
-  indentSize?: number,
-  onDragEnter?: (event: any) => void,
-  onMarkdownHelpButtonClicked?: () => void,
-  onAddAttachmentButtonClicked?: () => void,
-  onScroll?: (line: { line: number }) => void,
-  onScrollCursorIntoView?: (line: number) => void,
-  onSave?: () => Promise<void>,
-  onPasteFiles?: (event: Event) => void,
-  onCtrlEnter?: (event: Event) => void,
-  isComment?: boolean,
-}
-
-type DropzoneRef = {
-  open: () => void
-}
-
-const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props, ref): JSX.Element => {
-  const {
-    onUpload, isUploadable, isUploadAllFileAllowed, indentSize, isGfmMode = true,
-  } = props;
-
-  const [dropzoneActive, setDropzoneActive] = useState(false);
-  const [isUploading, setIsUploading] = useState(false);
-  const [isCheatsheetModalShown, setIsCheatsheetModalShown] = useState(false);
-
-  const [navBarItems, setNavBarItems] = useState<JSX.Element[]>([]);
-
-  const { t } = useTranslation();
-  const { data: editorSettings } = useEditorSettings();
-  const { data: defaultIndentSize } = useDefaultIndentSize();
-  const { data: isMobile } = useIsMobile();
-
-  const dropzoneRef = useRef<DropzoneRef>(null);
-  // CodeMirrorEditor ref
-  const cmEditorRef = useRef<AbstractEditor<any>>(null);
-  const taEditorRef = useRef<TextAreaEditor>(null);
-
-  const editorSubstance = useCallback(() => {
-    return isMobile ? taEditorRef.current : cmEditorRef.current;
-  }, [isMobile]);
-
-  // methods for ref
-  useImperativeHandle(ref, () => ({
-    forceToFocus: () => {
-      editorSubstance()?.forceToFocus();
-    },
-    setValue: (newValue: string) => {
-      editorSubstance()?.setValue(newValue);
-    },
-    setGfmMode: (bool: boolean) => {
-      editorSubstance()?.setGfmMode(bool);
-    },
-    setCaretLine: (line: number) => {
-      editorSubstance()?.setCaretLine(line);
-    },
-    setScrollTopByLine: (line: number) => {
-      editorSubstance()?.setScrollTopByLine(line);
-    },
-    insertText: (text: string) => {
-      editorSubstance()?.insertText(text);
-    },
-    /**
-     * remove overlay and set isUploading to false
-     */
-    terminateUploadingState: () => {
-      setDropzoneActive(false);
-      setIsUploading(false);
-    },
-  }));
-
-  /**
-   * dispatch onUpload event
-   */
-  const dispatchUpload = useCallback((files) => {
-    if (onUpload != null) {
-      onUpload(files);
-    }
-  }, [onUpload]);
-
-  /**
-   * get acceptable(uploadable) file type
-   */
-  const getAcceptableType = useCallback(() => {
-    let accept = 'null'; // reject all
-    if (isUploadable) {
-      if (!isUploadAllFileAllowed) {
-        accept = 'image/*'; // image only
-      }
-      else {
-        accept = ''; // allow all
-      }
-    }
-
-    return accept;
-  }, [isUploadable, isUploadAllFileAllowed]);
-
-  const pasteFilesHandler = useCallback((event) => {
-    const items = event.clipboardData.items || event.clipboardData.files || [];
-
-    // abort if length is not 1
-    if (items.length < 1) {
-      return;
-    }
-
-    for (let i = 0; i < items.length; i++) {
-      try {
-        const file = items[i].getAsFile();
-        // check file type (the same process as Dropzone)
-        if (file != null && pasteHelper.isAcceptableType(file, getAcceptableType())) {
-          dispatchUpload(file);
-          setIsUploading(true);
-        }
-      }
-      catch (e) {
-        toastError(t('toaster.file_upload_failed'));
-      }
-    }
-  }, [dispatchUpload, getAcceptableType, t]);
-
-  const dragEnterHandler = useCallback((event) => {
-    const dataTransfer = event.dataTransfer;
-
-    // do nothing if contents is not files
-    if (!dataTransfer.types.includes('Files')) {
-      return;
-    }
-
-    setDropzoneActive(true);
-  }, []);
-
-  const dropHandler = useCallback((accepted) => {
-    // rejected
-    if (accepted.length !== 1) { // length should be 0 or 1 because `multiple={false}` is set
-      setDropzoneActive(false);
-      return;
-    }
-
-    const file = accepted[0];
-    dispatchUpload(file);
-    setIsUploading(true);
-  }, [dispatchUpload]);
-
-  const addAttachmentHandler = useCallback(() => {
-    if (dropzoneRef.current == null) { return }
-    dropzoneRef.current.open();
-  }, []);
-
-  const getDropzoneClassName = useCallback((isDragAccept: boolean, isDragReject: boolean) => {
-    let className = 'dropzone';
-    if (!isUploadable) {
-      className += ' dropzone-unuploadable';
-    }
-    else {
-      className += ' dropzone-uploadable';
-
-      if (isUploadAllFileAllowed) {
-        className += ' dropzone-uploadablefile';
-      }
-    }
-
-    // uploading
-    if (isUploading) {
-      className += ' dropzone-uploading';
-    }
-
-    if (isDragAccept) {
-      className += ' dropzone-accepted';
-    }
-
-    if (isDragReject) {
-      className += ' dropzone-rejected';
-    }
-
-    return className;
-  }, [isUploadable, isUploading, isUploadAllFileAllowed]);
-
-  const renderDropzoneOverlay = useCallback(() => {
-    return (
-      <div className="overlay overlay-dropzone-active">
-        {isUploading
-          && (
-            <span className="overlay-content">
-              <div className="speeding-wheel d-inline-block"></div>
-              <span className="visually-hidden">Uploading...</span>
-            </span>
-          )
-        }
-        {!isUploading && <span className="overlay-content"></span>}
-      </div>
-    );
-  }, [isUploading]);
-
-  const renderNavbar = () => {
-    return (
-      <div className="m-0 navbar navbar-default navbar-editor" data-testid="navbar-editor" style={{ minHeight: 'unset' }}>
-        <ul className="ps-2 nav nav-navbar">
-          { navBarItems.map((item, idx) => {
-            // eslint-disable-next-line react/no-array-index-key
-            return <li key={`navbarItem-${idx}`}>{item}</li>;
-          }) }
-        </ul>
-      </div>
-    );
-  };
-
-  const renderCheatsheetModal = useCallback(() => {
-    const hideCheatsheetModal = () => {
-      setIsCheatsheetModalShown(false);
-    };
-
-    return (
-      <Modal isOpen={isCheatsheetModalShown} toggle={hideCheatsheetModal} className={`modal-gfm-cheatsheet ${styles['modal-gfm-cheatsheet']}`} size="lg">
-        <ModalHeader tag="h4" toggle={hideCheatsheetModal} className="bg-primary text-light">
-          <span className="material-symbols-outlined me-1">help</span>Markdown help
-        </ModalHeader>
-        <ModalBody>
-          <Cheatsheet />
-        </ModalBody>
-      </Modal>
-    );
-  }, [isCheatsheetModalShown]);
-
-  const isReadyToRenderEditor = editorSettings != null;
-
-  // https://redmine.weseek.co.jp/issues/111731
-  useEffect(() => {
-    const editorRef = editorSubstance();
-    if (isReadyToRenderEditor && editorRef != null) {
-      const editorNavBarItems = editorRef.getNavbarItems() ?? [];
-      setNavBarItems(editorNavBarItems);
-    }
-  }, [editorSubstance, isReadyToRenderEditor]);
-
-  if (!isReadyToRenderEditor) {
-    return <></>;
-  }
-
-  const flexContainer: React.CSSProperties = {
-    height: '100%',
-    display: 'flex',
-    flexDirection: 'column',
-  };
-
-  return (
-    <>
-      <div style={flexContainer} className={`editor-container ${styles['editor-container']}`}>
-        <Dropzone
-          ref={dropzoneRef}
-          accept={getAcceptableType()}
-          noClick
-          noKeyboard
-          multiple={false}
-          onDragLeave={() => { setDropzoneActive(false) }}
-          onDrop={dropHandler}
-        >
-          {({
-            getRootProps,
-            getInputProps,
-            isDragAccept,
-            isDragReject,
-          }) => {
-            return (
-              <div className={getDropzoneClassName(isDragAccept, isDragReject)} {...getRootProps()}>
-                { dropzoneActive && renderDropzoneOverlay() }
-
-                { renderNavbar() }
-
-                {/* for PC */}
-                { !isMobile && (
-                  // <CodeMirrorEditor
-                  //   ref={cmEditorRef}
-                  //   indentSize={indentSize ?? defaultIndentSize}
-                  //   onPasteFiles={pasteFilesHandler}
-                  //   onDragEnter={dragEnterHandler}
-                  //   onMarkdownHelpButtonClicked={() => { setIsCheatsheetModalShown(true) }}
-                  //   onAddAttachmentButtonClicked={addAttachmentHandler}
-                  //   editorSettings={editorSettings}
-                  //   isGfmMode={isGfmMode}
-                  //   {...props}
-                  // />
-                  <></>
-                )}
-
-                {/* for mobile */}
-                { isMobile && (
-                  <TextAreaEditor
-                    ref={taEditorRef}
-                    onPasteFiles={pasteFilesHandler}
-                    onDragEnter={dragEnterHandler}
-                    {...props}
-                  />
-                )}
-
-                <input {...getInputProps()} />
-              </div>
-            );
-          }}
-        </Dropzone>
-
-        { isUploadable
-          && (
-            <button
-              type="button"
-              className="btn btn-outline-secondary btn-open-dropzone"
-              onClick={addAttachmentHandler}
-            >
-              <span className="material-symbols-outlined" aria-hidden="true">attachment</span>&nbsp;
-              Attach files
-              <span className="d-none d-sm-inline">
-              &nbsp;by dragging &amp; dropping,&nbsp;
-                <span className="btn-link">selecting them</span>,&nbsp;
-                or pasting from the clipboard.
-              </span>
-
-            </button>
-          )
-        }
-
-        { renderCheatsheetModal() }
-
-      </div>
-    </>
-  );
-};
-
-export default memo(forwardRef(Editor));

+ 0 - 159
apps/app/_obsolete/src/components/PageEditor/EditorIcon.jsx

@@ -1,159 +0,0 @@
-/* eslint-disable max-len */
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-const EditorIcon = (props) => {
-
-  switch (props.icon) {
-    case 'Bold':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M9.71,21.14V8.86A.84.84,0,0,1,10.59,8h4.46c2.41,0,4.05,1.41,4.05,3.52a3.17,3.17,0,0,1-2.44,3.08v.07a3.39,3.39,0,0,1,3.15,3.47c0,2.48-1.78,4-4.78,4H10.59A.84.84,0,0,1,9.71,21.14ZM14.11,14c2.08,0,3.21-.83,3.21-2.36s-1-2.16-2.67-2.16H11.47V14Zm.66,6.46c2.12,0,3.23-.86,3.23-2.49s-1.15-2.46-3.4-2.46H11.47v4.95Z" />
-        </svg>
-      );
-    case 'Italic':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M18.55,8a.48.48,0,0,1,.45.5.45.45,0,0,1-.39.5H16.75L14.51,21h1.62a.45.45,0,0,1,.46.5.47.47,0,0,1-.54.5h-4.6a.53.53,0,0,1-.47-.5.47.47,0,0,1,.48-.5h2L15.83,9H14a.52.52,0,0,1-.5-.5A.51.51,0,0,1,14,8Z" />
-        </svg>
-      );
-    case 'Strikethrough':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M22.5,14H7.5a.47.47,0,0,0-.5.5.46.46,0,0,0,.5.5h15a.5.5,0,0,0,0-1Z" />
-          <path d="M18,17a2.21,2.21,0,0,1,.6,1.88c-.07.51-.53,2.18-3.31,2.18a5.35,5.35,0,0,1-4.21-1.76L11,18.5c-.05-.3-.21-.5-.5-.5s-.45.17-.5.5v1A5.79,5.79,0,0,0,15,22c3.75,0,4.41-2.11,4.53-2.53A3.12,3.12,0,0,0,19.28,17Z" />
-          <path d="M12.21,13h1.91c-1.27-.44-2.37-1.52-2.1-2.5.18-.65,1-1.59,3.27-1.59a4.21,4.21,0,0,1,3.44,1.41l.07.37a.55.55,0,1,0,1.08-.19l-.09-.5-.08-.2A6.28,6.28,0,0,0,15,8c-3.11,0-3.95,1.74-4,2.33A2.32,2.32,0,0,0,12.21,13Z" />
-        </svg>
-      );
-    case 'Heading':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M10,21V9.11a.61.61,0,1,1,1.22,0v5.24h7.55V9.11a.59.59,0,0,1,.62-.64.58.58,0,0,1,.61.64V21a.58.58,0,0,1-.61.63.59.59,0,0,1-.62-.63V15.46H11.22V21A.61.61,0,1,1,10,21Z" />
-        </svg>
-      );
-    case 'InlineCode':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M11,19.57a.54.54,0,0,1-.36-.14L7.05,15.79a.49.49,0,0,1,0-.67l4.09-5a.5.5,0,0,1,.71-.07.5.5,0,0,1,.07.7L8.08,15.41l3.31,3.31a.5.5,0,0,1,0,.71A.54.54,0,0,1,11,19.57Z" /><path d="M18.5,20a.51.51,0,0,1-.32-.12.5.5,0,0,1-.07-.7l3.81-4.63-3.36-3.36a.5.5,0,0,1,0-.71.51.51,0,0,1,.71,0L23,14.21a.49.49,0,0,1,0,.67l-4.09,5A.52.52,0,0,1,18.5,20Z" /><path d="M13,21.5a.41.41,0,0,1-.16,0,.5.5,0,0,1-.32-.63l4-12a.5.5,0,0,1,.63-.31.49.49,0,0,1,.32.63l-4,12A.49.49,0,0,1,13,21.5Z" />
-        </svg>
-      );
-    case 'Quote':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M11.5,14h9a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-9a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,14Z" /><path d="M8.5,9h11a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5H8.5A.5.5,0,0,1,8,9.5H8A.5.5,0,0,1,8.5,9Z" /><path d="M11.5,19h7a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-7a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,19Z" /><path d="M8,20.5v-8a.5.5,0,0,1,.5-.5h0a.5.5,0,0,1,.5.5v8a.5.5,0,0,1-.5.5h0A.5.5,0,0,1,8,20.5Z" />
-        </svg>
-      );
-    case 'List':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <circle cx="8.5" cy="9.5" r="1" /><circle cx="8.5" cy="14.5" r="1" /><circle cx="8.5" cy="19.5" r="1" /><path d="M11.5,9h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,9Z" /><path d="M11.5,14h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,14Z" /><path d="M11.5,19h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,19Z" />
-        </svg>
-      );
-    case 'NumberedList':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M11.5,9h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,9Z" /><path d="M11.5,19h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,19Z" /><path d="M11.5,14h10a.5.5,0,0,1,.5.5h0a.5.5,0,0,1-.5.5h-10a.5.5,0,0,1-.5-.5h0A.5.5,0,0,1,11.5,14Z" /><path d="M7.44,12h.85V8.62L7.58,9h-.1a.24.24,0,0,1-.29-.19.25.25,0,0,1,.19-.3l.83-.41A.77.77,0,0,1,8.5,8h.08a.29.29,0,0,1,.29.27V12H9.6a.27.27,0,1,1,.1.53H7.44a.27.27,0,0,1,0-.53Z" /><path d="M7.61,17.13a.26.26,0,0,1-.26.26h0c-.14,0-.27-.09-.26-.32v-.4a.48.48,0,0,1,.27-.42,1.93,1.93,0,0,1,1-.25,1.3,1.3,0,0,1,1.42,1.15v.22a2.54,2.54,0,0,1-1,1.74l-1,.94H9.58a.27.27,0,0,1,0,.53H7.32A.32.32,0,0,1,7,20.26v0a.47.47,0,0,1,.2-.34l1.2-1.17a2.12,2.12,0,0,0,.79-1.36.8.8,0,0,0-.75-.85H8.35a1.32,1.32,0,0,0-.7.2Z" />
-        </svg>
-      );
-    case 'CheckList':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M20.5,12.5a.57.57,0,0,1,.5.5v8a1.17,1.17,0,0,1-1,1H10a1.28,1.28,0,0,1-1-1V11a1.28,1.28,0,0,1,1-1h6a.57.57,0,0,1,.5.5.57.57,0,0,1-.5.5H10V21H20V13A.58.58,0,0,1,20.5,12.5ZM12,15.25a.41.41,0,0,0,0,.58L14,18a.78.78,0,0,0,1,0l5.9-9c.09-.16.09-.5-.16-.58a.41.41,0,0,0-.58.08L14.5,17.16l-1.91-1.91A.41.41,0,0,0,12,15.25Z" />
-        </svg>
-      );
-    case 'Link':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M12.12,18a.29.29,0,0,0,.43,0l5.3-5.3c.07-.08.3-.25.06-.51a.32.32,0,0,0-.53,0l-5.22,5.28C12.09,17.57,11.85,17.73,12.12,18Zm2.34-.9a1.74,1.74,0,0,1,0,1,3.69,3.69,0,0,1-.43.78L12.2,20.5a1.69,1.69,0,0,1-1.31.61A1.77,1.77,0,0,1,9.5,20.5a1.65,1.65,0,0,1-.61-1.31A1.74,1.74,0,0,1,9.5,17.8L11.15,16a1.74,1.74,0,0,1,1.92-.43l.69-.7a2.67,2.67,0,0,0-1.21-.26,2.89,2.89,0,0,0-2,.78L8.89,17.19a2.8,2.8,0,0,0-.09,3.92l0,0a2.62,2.62,0,0,0,2.05.83,2.79,2.79,0,0,0,2-.87l1.74-1.66A3.93,3.93,0,0,0,15.42,18a2.56,2.56,0,0,0-.26-1.56Zm6.61-8.18a2.11,2.11,0,0,0-.87-.69,2.69,2.69,0,0,0-3,.69l-1.83,1.66a2.68,2.68,0,0,0-.78,1.56,2.72,2.72,0,0,0,.26,1.66l.69-.7a2,2,0,0,1,0-1,2,2,0,0,1,.44-1l1.83-1.66L18,9.27l.35-.17.34-.18h.44a1.67,1.67,0,0,1,1.3.61,1.74,1.74,0,0,1,.61,1.4,1.62,1.62,0,0,1-.61,1.3l-1.74,1.83-.6.35a1.37,1.37,0,0,1-.79.17H17l-.69.7a8.58,8.58,0,0,0,1,.22,2.9,2.9,0,0,0,1.21-.22l.87-.61,1.74-1.83a2.6,2.6,0,0,0,.23-3.69Z" />
-        </svg>
-      );
-    case 'Image':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M22,8H8A1,1,0,0,0,7,9V21a1,1,0,0,0,1,1H22a1,1,0,0,0,1-1V9A1,1,0,0,0,22,8Zm0,13H8V18l4.07-4.06,4.07,4a.41.41,0,0,0,.33.18.4.4,0,0,0,.32-.18l1.7-1.55,3.17,3.25L22,20Zm0-2.25-3.1-3.34a.89.89,0,0,0-.33-.17.89.89,0,0,0-.28.14l-1.83,1.49-4-3.9a.49.49,0,0,0-.32-.16.5.5,0,0,0-.41.16L8,16.75V9H22ZM19.5,12.5a1,1,0,1,1-1-1A1,1,0,0,1,19.5,12.5Z" />
-        </svg>
-      );
-    case 'Grid':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
-          <rect width="30" height="30" fill="none" />
-          <g transform="translate(-375 -415)">
-            <g transform="translate(382 422)">
-              <path d="M5,7H1A.945.945,0,0,1,0,6V1A.945.945,0,0,1,1,0H5A.945.945,0,0,1,6,1V6A.945.945,0,0,1,5,7ZM1,1V6H5V1ZM1,.5V1H1Z" />
-            </g>
-            <g transform="translate(390 422)">
-              <path d="M7,7H1A.945.945,0,0,1,0,6V1A.945.945,0,0,1,1,0H7A.945.945,0,0,1,8,1V6A.945.945,0,0,1,7,7ZM1,1V6H7V1ZM1,.5V1H1Z" />
-            </g>
-            <g transform="translate(382 431)">
-              <path d="M9,7H1A.945.945,0,0,1,0,6V1A.945.945,0,0,1,1,0H9a.945.945,0,0,1,1,1V6A.945.945,0,0,1,9,7ZM1,1V6H9V1ZM1,.5V1H1Z" />
-            </g>
-            <g transform="translate(394 431)">
-              <path d="M3,7H1A.945.945,0,0,1,0,6V1A.945.945,0,0,1,1,0H3A.945.945,0,0,1,4,1V6A.945.945,0,0,1,3,7ZM1,1V6H3V1ZM1,.5V1H1Z" />
-            </g>
-          </g>
-        </svg>
-      );
-    case 'Table':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M8,22a1,1,0,0,1-1-1V9A1,1,0,0,1,8,8H22c1,0,1,.47,1,1V21a1,1,0,0,1-1,1Zm10-4v3h4V18Zm-5,0v3h4V18ZM8,18v3h4V18Zm10-4v3h4V14Zm-5,0v3h4V14ZM8,14v3h4V14Zm10-4v3h4V10Zm-5,0v3h4V10ZM8,10v3h4V10Z" />
-        </svg>
-      );
-    case 'Drawio':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M22.12,17H19.75l-3.12-4H18a1,1,0,0,0,1-1V8a1,1,0,0,0-1-1H12a1,1,0,0,0-1,1v4a1,1,0,0,0,1,1h1.38l-2.92,4H7.88A.94.94,0,0,0,7,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,14,22V18a.94.94,0,0,0-.88-1H11.63l3.13-4h.47l3.13,4H16.88A.94.94,0,0,0,16,18v4a.94.94,0,0,0,.88,1h5.24A.94.94,0,0,0,23,22V18A.94.94,0,0,0,22.12,17ZM13,22H8V18h5ZM12,8h6v4H12ZM22,22H17V18h5Z" />
-        </svg>
-      );
-    case 'Attachment':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 0 30 30">
-          <rect fillOpacity="0" width="30" height="30" />
-          <path d="M9.71,22.5a2.57,2.57,0,0,1-1.85-.79,2.79,2.79,0,0,1,0-4l9-9.23a3.21,3.21,0,0,1,1.59-.87,3.39,3.39,0,0,1,1.81.1,4.38,4.38,0,0,1,1.7,1.05,4.15,4.15,0,0,1,.46.56,3.73,3.73,0,0,1,.35.65,4.25,4.25,0,0,1,.2.72,3.91,3.91,0,0,1,.07.76,3.71,3.71,0,0,1-1.12,2.67l-6.79,7a.48.48,0,0,1-.34.16.51.51,0,0,1-.35-.13.48.48,0,0,1,0-.7l6.78-7a2.8,2.8,0,0,0,.84-2,2.58,2.58,0,0,0-.79-2,3.63,3.63,0,0,0-1.11-.75,2.41,2.41,0,0,0-1.31-.17,2.19,2.19,0,0,0-1.25.62l-9,9.22A1.8,1.8,0,0,0,8,19.69,1.78,1.78,0,0,0,8.58,21a1.81,1.81,0,0,0,.57.39,1.48,1.48,0,0,0,.66.1,2,2,0,0,0,1.28-.62l7.12-7.35.15-.16a1.15,1.15,0,0,0,.15-.2.9.9,0,0,0,.12-.24,1.17,1.17,0,0,0,.07-.25.52.52,0,0,0-.05-.27.75.75,0,0,0-.19-.26.73.73,0,0,0-.58-.27,1.29,1.29,0,0,0-.67.38l-5.36,5.53a.5.5,0,0,1-.22.13.46.46,0,0,1-.26,0,.48.48,0,0,1-.22-.12A.41.41,0,0,1,11,17.5a.5.5,0,0,1,.14-.35L16.5,11.6a2.19,2.19,0,0,1,1.29-.67,1.69,1.69,0,0,1,1.37.55,1.54,1.54,0,0,1,.53,1.31,2.26,2.26,0,0,1-.76,1.42L11.8,21.58a3.06,3.06,0,0,1-2,.91H9.71Z" />
-        </svg>
-      );
-    case 'Emoji':
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
-          <g transform="translate(-435 -392)">
-            <rect width="30" height="30" transform="translate(435 392)" fillOpacity="0" />
-            <path d="M8,1a7,7,0,1,0,7,7A7.008,7.008,0,0,0,8,1M8,0A8,8,0,1,1,0,8,8,8,0,0,1,8,0Z" transform="translate(442 399)" />
-            <circle cx="1" cy="1" r="1" transform="translate(446 403)" />
-            <circle cx="1" cy="1" r="1" transform="translate(452 403)" />
-            <g transform="translate(445 406.5)">
-              <path d="M5,5.5a5.006,5.006,0,0,1-5-5,.5.5,0,1,1,1,0,4,4,0,0,0,8,0,.5.5,0,0,1,1,0A5.006,5.006,0,0,1,5,5.5Z" />
-            </g>
-          </g>
-        </svg>
-      );
-    case 'Template':
-      // TODO: fix
-      return (
-        <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" className="bi bi-filetype-md" viewBox="-2 -3 28 21">
-          <path fillRule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2H9v-1h3a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5ZM.706 13.189v2.66H0V11.85h.806l1.14 2.596h.026l1.14-2.596h.8v3.999h-.716v-2.66h-.038l-.946 2.159h-.516l-.952-2.16H.706Zm3.919 2.66V11.85h1.459c.406 0 .741.078 1.005.234.263.157.46.383.589.68.13.297.196.655.196 1.075 0 .422-.066.784-.196 1.084-.131.301-.33.53-.595.689-.264.158-.597.237-1 .237H4.626Zm1.353-3.354h-.562v2.707h.562c.186 0 .347-.028.484-.082a.8.8 0 0 0 .334-.252 1.14 1.14 0 0 0 .196-.422c.045-.168.067-.365.067-.592a2.1 2.1 0 0 0-.117-.753.89.89 0 0 0-.354-.454c-.159-.102-.362-.152-.61-.152Z" />
-        </svg>
-      );
-  }
-
-
-};
-
-EditorIcon.propTypes = {
-  icon: PropTypes.string.isRequired,
-};
-
-export default EditorIcon;

+ 0 - 62
apps/app/_obsolete/src/components/PageEditor/EmojiPicker.tsx

@@ -1,62 +0,0 @@
-import React, { FC, useCallback } from 'react';
-
-import { Picker } from 'emoji-mart';
-import { Modal } from 'reactstrap';
-
-import { useNextThemes } from '~/stores/use-next-themes';
-
-import EmojiPickerHelper, { getEmojiTranslation } from './EmojiPickerHelper';
-
-
-import 'emoji-mart/css/emoji-mart.css';
-
-
-type Props = {
-  onClose: () => void,
-  onSelected: (emoji: string) => void,
-  emojiSearchText: string,
-  emojiPickerHelper: EmojiPickerHelper,
-  isOpen: boolean
-}
-
-const EmojiPicker: FC<Props> = (props: Props) => {
-
-  const {
-    onClose, onSelected, emojiSearchText, emojiPickerHelper, isOpen,
-  } = props;
-
-  const { resolvedTheme } = useNextThemes();
-
-  // Set search emoji input and trigger search
-  const searchEmoji = useCallback(() => {
-    const input = window.document.querySelector('[id^="emoji-mart-search"]') as HTMLInputElement;
-    const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
-    valueSetter?.call(input, emojiSearchText);
-    const event = new Event('input', { bubbles: true });
-    input.dispatchEvent(event);
-    input.focus();
-  }, [emojiSearchText]);
-
-  const selectEmoji = useCallback((emoji) => {
-    onSelected(emoji);
-    onClose();
-  }, [onClose, onSelected]);
-
-
-  const translation = getEmojiTranslation();
-
-  return (
-    <Modal isOpen={isOpen} toggle={onClose} onOpened={searchEmoji} backdropClassName="emoji-picker-modal" fade={false}>
-      <Picker
-        onSelect={selectEmoji}
-        i18n={translation}
-        title={translation.title}
-        emojiTooltip
-        style={emojiPickerHelper.setStyle()}
-        theme={resolvedTheme}
-      />
-    </Modal>
-  );
-};
-
-export default EmojiPicker;

+ 0 - 116
apps/app/_obsolete/src/components/PageEditor/EmojiPickerHelper.ts

@@ -1,116 +0,0 @@
-import { CSSProperties } from 'react';
-
-import { Position } from 'codemirror';
-import i18n from 'i18next';
-
-// https://regex101.com/r/x5LbOZ/1
-const EMOJI_PATTERN = new RegExp(/^:[a-z0-9-+_]+$/);
-
-export default class EmojiPickerHelper {
-
-  editor;
-
-  pattern: string;
-
-  constructor(editor) {
-    this.editor = editor;
-  }
-
-  setStyle = (): CSSProperties => {
-    const offset = 20;
-    const emojiPickerHeight = 420;
-    const cursorPos = this.editor.cursorCoords(true);
-    const editorPos = this.editor.getWrapperElement().getBoundingClientRect();
-    // Emoji Picker bottom position exceed editor's bottom position
-    if (cursorPos.bottom + emojiPickerHeight > editorPos.bottom) {
-      return {
-        top: editorPos.bottom - emojiPickerHeight,
-        left: cursorPos.left + offset,
-        position: 'fixed',
-      };
-    }
-    return {
-      top: cursorPos.top + offset,
-      left: cursorPos.left + offset,
-      position: 'fixed',
-    };
-  };
-
-  shouldModeTurnOn = (char: string): Position | null | undefined => {
-    if (char !== ':') {
-      return null;
-    }
-
-    const currentPos = this.editor.getCursor();
-    const sc = this.editor.getSearchCursor(':', currentPos, { multiline: false });
-    if (sc.findPrevious()) {
-      return sc.pos.from;
-    }
-  };
-
-  shouldOpen = (startPos: Position): boolean => {
-    const currentPos = this.editor.getCursor();
-    const rangeStr = this.editor.getRange(startPos, currentPos);
-
-    return EMOJI_PATTERN.test(rangeStr);
-  };
-
-  getInitialSearchingText = (startPos: Position): void => {
-    const currentPos = this.editor.getCursor();
-    const rangeStr = this.editor.getRange(startPos, currentPos);
-
-    return rangeStr.slice(1); // return without the heading ':'
-  };
-
-  addEmoji = (emoji: { colons: string }, startPosToReplace: Position|null): void => {
-    const currentPos = this.editor.getCursor();
-
-    const from = startPosToReplace ?? currentPos;
-    const to = currentPos;
-
-    const doc = this.editor.getDoc();
-    doc.replaceRange(`${emoji.colons} `, from, to);
-    this.editor.focus();
-    this.editor.refresh();
-  };
-
-}
-
-export const getEmojiTranslation = () => {
-
-  const categories = {};
-  [
-    'search',
-    'recent',
-    'smileys',
-    'people',
-    'nature',
-    'foods',
-    'activity',
-    'places',
-    'objects',
-    'symbols',
-    'flags',
-    'custom',
-  ].forEach((category) => {
-    categories[category] = i18n.t(`emoji.categories.${category}`);
-  });
-
-  const skintones = {};
-  (Array.from(Array(6).keys())).forEach((tone) => {
-    skintones[tone + 1] = i18n.t(`emoji.skintones.${tone + 1}`);
-  });
-
-  const translation = {
-    search: i18n.t('emoji.search'),
-    clear: i18n.t('emoji.clear'),
-    notfound: i18n.t('emoji.notfound'),
-    skintext: i18n.t('emoji.skintext'),
-    categories,
-    categorieslabel: i18n.t('emoji.categorieslabel'),
-    skintones,
-    title: i18n.t('emoji.title'),
-  };
-
-  return translation;
-};

+ 0 - 46
apps/app/_obsolete/src/components/PageEditor/MarkdownLinkUtil.js

@@ -1,46 +0,0 @@
-import Linker from '@growi/editor/src/services/link-util/Linker';
-/**
- * Utility for markdown link
- */
-class MarkdownLinkUtil {
-
-  constructor() {
-    this.getMarkdownLink = this.getMarkdownLink.bind(this);
-    this.isInLink = this.isInLink.bind(this);
-    this.replaceFocusedMarkdownLinkWithEditor = this.replaceFocusedMarkdownLinkWithEditor.bind(this);
-  }
-
-  // return an instance of Linker from cursor position or selected text.
-  getMarkdownLink(editor) {
-    if (!this.isInLink(editor)) {
-      return Linker.fromMarkdownString(editor.getDoc().getSelection());
-    }
-    const curPos = editor.getCursor();
-    return Linker.fromLineWithIndex(editor.getDoc().getLine(curPos.line), curPos.ch);
-  }
-
-  isInLink(editor) {
-    const curPos = editor.getCursor();
-    const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(editor.getDoc().getLine(curPos.line), curPos.ch);
-    return beginningOfLink >= 0 && endOfLink >= 0;
-  }
-
-  // replace link(link is an instance of Linker)
-  replaceFocusedMarkdownLinkWithEditor(editor, linkText) {
-    const curPos = editor.getCursor();
-    if (!this.isInLink(editor)) {
-      editor.getDoc().replaceSelection(linkText);
-    }
-    else {
-      const line = editor.getDoc().getLine(curPos.line);
-      const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(line, curPos.ch);
-      editor.getDoc().replaceRange(linkText, { line: curPos.line, ch: beginningOfLink }, { line: curPos.line, ch: endOfLink });
-    }
-  }
-
-}
-
-// singleton pattern
-const instance = new MarkdownLinkUtil();
-Object.freeze(instance);
-export default instance;

+ 0 - 95
apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js

@@ -1,95 +0,0 @@
-import { BasicInterceptor } from '@growi/core/dist/utils';
-
-import MarkdownTable from '~/client/models/MarkdownTable';
-
-import {
-  getStrFromBot, addRowToMarkdownTable, getStrToEot, isEndOfLine, mergeMarkdownTable, replaceFocusedMarkdownTableWithEditor,
-  isInTable, emptyLineOfTableRE,
-} from '../../../../src/components/PageEditor/markdown-table-util-for-editor';
-
-/**
- * Interceptor for markdown table
- */
-export default class MarkdownTableInterceptor extends BasicInterceptor {
-
-  /**
-   * @inheritdoc
-   */
-  isInterceptWhen(contextName) {
-    return (
-      contextName === 'preHandleEnter'
-    );
-  }
-
-  /**
-   * return boolean value whether processable parallel
-   */
-  isProcessableParallel() {
-    return false;
-  }
-
-  addRow(cm) {
-    // get lines all of table from current position to beginning of table
-    const strFromBot = getStrFromBot(cm);
-    let table = MarkdownTable.fromMarkdownString(strFromBot);
-
-    addRowToMarkdownTable(table);
-
-    const strToEot = getStrToEot(cm);
-    const tableBottom = MarkdownTable.fromMarkdownString(strToEot);
-    if (tableBottom.table.length > 0) {
-      table = mergeMarkdownTable([table, tableBottom]);
-    }
-
-    replaceFocusedMarkdownTableWithEditor(cm, table);
-  }
-
-  reformTable(cm) {
-    const tableStr = getStrFromBot(cm) + getStrToEot(cm);
-    const table = MarkdownTable.fromMarkdownString(tableStr);
-    replaceFocusedMarkdownTableWithEditor(cm, table);
-  }
-
-  removeRow(editor) {
-    editor.replaceLine('\n');
-  }
-
-  /**
-   * @inheritdoc
-   */
-  async process(contextName, ...args) {
-    const context = Object.assign(args[0]); // clone
-    const editor = context.editor; // AbstractEditor instance
-    // "autoFormatMarkdownTable" may be undefined, so it is compared to true and converted to bool.
-    const noIntercept = (context.autoFormatMarkdownTable === false);
-
-    // do nothing if editor is not a CodeMirrorEditor or no intercept
-    if (editor == null || editor.getCodeMirror() == null || noIntercept) {
-      return context;
-    }
-
-    const cm = editor.getCodeMirror();
-
-    const isLastRow = getStrToEot(cm) === editor.getStrToEol();
-
-    if (isInTable(cm)) {
-      // at EOL in the table
-      if (isEndOfLine(cm)) {
-        this.addRow(cm);
-      }
-      // last empty row
-      else if (isLastRow && emptyLineOfTableRE.test(editor.getStrFromBol() + editor.getStrToEol())) {
-        this.removeRow(editor);
-      }
-      else {
-        this.reformTable(cm);
-      }
-
-      // report to manager that handling was done
-      context.handlers.push(this.className);
-      return context;
-    }
-
-  }
-
-}

+ 0 - 44
apps/app/_obsolete/src/components/PageEditor/PasteHelper.js

@@ -1,44 +0,0 @@
-import accepts from 'attr-accept';
-
-import markdownListUtil from './MarkdownListUtil';
-
-class PasteHelper {
-
-  constructor() {
-    this.pasteText = this.pasteText.bind(this);
-  }
-
-  /**
-   * paste text
-   * @param {any} editor An editor instance of CodeMirror
-   * @param {any} event
-   */
-  pasteText(editor, event) {
-    // get data in clipboard
-    const text = event.clipboardData.getData('text/plain');
-
-    if (text.length === 0) {
-      return;
-    }
-
-    markdownListUtil.pasteText(editor, event, text);
-  }
-
-  // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
-  /**
-   * transplanted from react-dropzone
-   * @see https://github.com/react-dropzone/react-dropzone/blob/master/src/utils/index.js
-   *
-   * @param {*} file
-   * @param {*} accept
-   */
-  isAcceptableType(file, accept) {
-    return file.type === 'application/x-moz-file' || accepts(file, accept);
-  }
-
-}
-
-// singleton pattern
-const instance = new PasteHelper();
-Object.freeze(instance);
-export default instance;

+ 0 - 44
apps/app/_obsolete/src/components/PageEditor/PreventMarkdownListInterceptor.js

@@ -1,44 +0,0 @@
-import { BasicInterceptor } from '@growi/core/dist/utils';
-
-import mlu from './MarkdownListUtil';
-
-export default class PreventMarkdownListInterceptor extends BasicInterceptor {
-
-  /**
-   * @inheritdoc
-   */
-  isInterceptWhen(contextName) {
-    return (
-      contextName === 'preHandleEnter'
-    );
-  }
-
-  /**
-   * return boolean value whether processable parallel
-   */
-  isProcessableParallel() {
-    return false;
-  }
-
-  /**
-   * @inheritdoc
-   */
-  process(contextName, ...args) {
-    const context = Object.assign(args[0]); // clone
-    const editor = context.editor; // AbstractEditor instance
-
-    // get strings from current position to EOL(end of line) before break the line
-    const strToEol = editor.getStrToEol();
-    if (mlu.indentAndMarkRE.test(strToEol)) {
-      // newline simply
-      editor.insertLinebreak();
-
-      // report to manager that handling was done
-      context.handlers.push(this.className);
-    }
-
-    // resolve
-    return Promise.resolve(context);
-  }
-
-}

+ 0 - 274
apps/app/_obsolete/src/components/PageEditor/TextAreaEditor.jsx

@@ -1,274 +0,0 @@
-import React from 'react';
-// import PropTypes from 'prop-types';
-
-import { Input } from 'reactstrap';
-
-import InterceptorManager from '~/services/interceptor-manager';
-import loggerFactory from '~/utils/logger';
-
-
-import AbstractEditor from './AbstractEditor';
-import mlu from './MarkdownListUtil';
-import pasteHelper from './PasteHelper';
-import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
-
-export default class TextAreaEditor extends AbstractEditor {
-
-  constructor(props) {
-    super(props);
-    this.logger = loggerFactory('growi:PageEditor:TextAreaEditor');
-
-    this.state = {
-      value: this.props.value,
-      isGfmMode: this.props.isGfmMode,
-    };
-
-    this.textarea = React.createRef();
-
-    this.init();
-
-    this.handleEnterKey = this.handleEnterKey.bind(this);
-
-    this.keyPressHandler = this.keyPressHandler.bind(this);
-    this.pasteHandler = this.pasteHandler.bind(this);
-    this.dragEnterHandler = this.dragEnterHandler.bind(this);
-  }
-
-  init() {
-    this.interceptorManager = new InterceptorManager();
-    this.interceptorManager.addInterceptors([
-      new PreventMarkdownListInterceptor(),
-    ]);
-  }
-
-  componentDidMount() {
-    // initialize caret line
-    this.setCaretLine(0);
-
-    // set event handlers
-    this.textarea.addEventListener('keypress', this.keyPressHandler);
-    this.textarea.addEventListener('paste', this.pasteHandler);
-    this.textarea.addEventListener('dragenter', this.dragEnterHandler);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  forceToFocus() {
-    setTimeout(() => {
-      this.textarea.focus();
-    }, 150);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setValue(newValue) {
-    this.setState({ value: newValue });
-    this.textarea.value = newValue;
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setGfmMode(bool) {
-    this.setState({
-      isGfmMode: bool,
-    });
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setCaretLine(line) {
-    if (Number.isNaN(line)) {
-      return;
-    }
-
-    // scroll to bottom
-    this.textarea.scrollTop = this.textarea.scrollHeight;
-
-    const lines = this.textarea.value.split('\n').slice(0, line);
-    /* eslint-disable no-param-reassign, no-return-assign */
-    const pos = lines
-      .map((lineStr) => { return lineStr.length + 1 }) // correct length+1 of each lines
-      .reduce((a, x) => { return a += x }, 0) //          sum
-        - 1; //                                           -1
-    /* eslint-enable no-param-reassign, no-return-assign */
-
-    this.textarea.setSelectionRange(pos, pos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setScrollTopByLine(line) {
-    // do nothing
-  }
-
-  /**
-   * @inheritDoc
-   */
-  insertText(text) {
-    const startPos = this.textarea.selectionStart;
-    const endPos = this.textarea.selectionEnd;
-    this.replaceValue(text, startPos, endPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBol() {
-    const currentPos = this.textarea.selectionStart;
-    return this.textarea.value.substring(this.getBolPos(), currentPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrToEol() {
-    const currentPos = this.textarea.selectionStart;
-    return this.textarea.value.substring(currentPos, this.getEolPos());
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBolToSelectedUpperPos() {
-    const startPos = this.textarea.selectionStart;
-    const endPos = this.textarea.selectionEnd;
-    const upperPos = (startPos < endPos) ? startPos : endPos;
-    return this.textarea.value.substring(this.getBolPos(), upperPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceBolToCurrentPos(text) {
-    const startPos = this.textarea.selectionStart;
-    const endPos = this.textarea.selectionEnd;
-    const lowerPos = (startPos < endPos) ? endPos : startPos;
-    this.replaceValue(text, this.getBolPos(), lowerPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceLine(text) {
-    this.replaceValue(text, this.getBolPos(), this.getEolPos());
-  }
-
-  getBolPos() {
-    const currentPos = this.textarea.selectionStart;
-    return this.textarea.value.lastIndexOf('\n', currentPos - 1) + 1;
-  }
-
-  getEolPos() {
-    const currentPos = this.textarea.selectionStart;
-    const pos = this.textarea.value.indexOf('\n', currentPos);
-    if (pos < 0) { // not found but EOF
-      return this.textarea.value.length;
-    }
-    return pos;
-  }
-
-  replaceValue(text, startPos, endPos) {
-    // create new value
-    const value = this.textarea.value;
-    const newValue = value.substring(0, startPos) + text + value.substring(endPos, value.length);
-    // calculate new position
-    const newPos = startPos + text.length;
-
-    this.textarea.value = newValue;
-    this.textarea.setSelectionRange(newPos, newPos);
-  }
-
-  /**
-   * keypress event handler
-   * @param {string} event
-   */
-  keyPressHandler(event) {
-    const key = event.key.toLowerCase();
-    if (key === 'enter') {
-      if (event.ctrlKey || event.altKey || event.metaKey) {
-        return;
-      }
-
-      this.handleEnterKey(event);
-    }
-  }
-
-  /**
-   * handle ENTER key
-   * @param {string} event
-   */
-  handleEnterKey(event) {
-    if (!this.state.isGfmMode) {
-      return; // do nothing
-    }
-
-    const context = {
-      handlers: [], // list of handlers which process enter key
-      editor: this,
-    };
-
-    const interceptorManager = this.interceptorManager;
-    interceptorManager.process('preHandleEnter', context)
-      .then(() => {
-        event.preventDefault();
-        if (context.handlers.length === 0) {
-          mlu.newlineAndIndentContinueMarkdownList(this);
-        }
-      });
-  }
-
-  /**
-   * paste event handler
-   * @param {any} event
-   */
-  pasteHandler(event) {
-    const types = event.clipboardData.types;
-
-    // files
-    if (types.includes('Files')) {
-      event.preventDefault();
-      this.dispatchPasteFiles(event);
-    }
-    // text
-    else if (types.includes('text/plain')) {
-      pasteHelper.pasteText(this, event);
-    }
-  }
-
-  dragEnterHandler(event) {
-    this.dispatchDragEnter(event);
-  }
-
-  dispatchDragEnter(event) {
-    if (this.props.onDragEnter != null) {
-      this.props.onDragEnter(event);
-    }
-  }
-
-  render() {
-    return (
-      <React.Fragment>
-        <Input
-          type="textarea"
-          className="textarea-editor shadow-none"
-          innerRef={(c) => { this.textarea = c }}
-          defaultValue={this.state.value}
-          onChange={(e) => {
-            if (this.props.onChange != null) {
-              this.props.onChange(e.target.value);
-            }
-          }}
-        />
-      </React.Fragment>
-    );
-  }
-
-}
-
-TextAreaEditor.propTypes = Object.assign({
-}, AbstractEditor.propTypes);

+ 0 - 521
apps/app/_obsolete/src/components/PageEditorByHackmd.tsx

@@ -1,521 +0,0 @@
-import React, {
-  useCallback, useRef, useState, useEffect, useMemo,
-} from 'react';
-
-import EventEmitter from 'events';
-
-import { pathUtils } from '@growi/core/dist/utils';
-import Link from 'next/link';
-import { useRouter } from 'next/router';
-import { useTranslation } from 'react-i18next';
-import urljoin from 'url-join';
-
-import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
-import { apiPost } from '~/client/util/apiv1-client';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { IResHackmdIntegrated, IResHackmdDiscard } from '~/interfaces/hackmd';
-import { OptionsToSave } from '~/interfaces/page-operation';
-import {
-  useCurrentPathname, useHackmdUri,
-} from '~/stores/context';
-import {
-  useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning, useWaitingSaveProcessing,
-} from '~/stores/editor';
-import {
-  usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useIsHackmdDraftUpdatingInRealtime,
-} from '~/stores/hackmd';
-import {
-  useCurrentPagePath, useSWRMUTxCurrentPage, useSWRxCurrentPage, useSWRxTagsInfo, useCurrentPageId, useIsNotFound,
-} from '~/stores/page';
-import { mutatePageTree } from '~/stores/page-listing';
-import { useRemoteRevisionId } from '~/stores/remote-latest-page';
-import {
-  EditorMode,
-  useEditorMode, useSelectedGrant,
-} from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
-
-const logger = loggerFactory('growi:PageEditorByHackmd');
-
-
-declare global {
-  // eslint-disable-next-line vars-on-top, no-var
-  var globalEmitter: EventEmitter;
-}
-
-
-type HackEditorRef = {
-  getValue: () => Promise<string>
-};
-
-export const PageEditorByHackmd = (): JSX.Element => {
-
-  const { t } = useTranslation();
-  const router = useRouter();
-
-  const { data: isNotFound } = useIsNotFound();
-  const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
-  const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: currentPathname } = useCurrentPathname();
-  const { data: isSlackEnabled } = useIsSlackEnabled();
-  const { data: pageId } = useCurrentPageId();
-  const { data: pageTags } = usePageTagsForEditors(pageId);
-  const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
-  const { data: grantData } = useSelectedGrant();
-  const { data: hackmdUri } = useHackmdUri();
-  const saveOrUpdate = useSaveOrUpdate();
-
-  const { returnPathForURL } = pathUtils;
-
-  // pageData
-  const { data: pageData } = useSWRxCurrentPage();
-  const { trigger: mutatePageData } = useSWRMUTxCurrentPage();
-  const revision = pageData?.revision;
-
-  const [isInitialized, setIsInitialized] = useState(false);
-  const [isInitializing, setIsInitializing] = useState(false);
-  // for error
-  const [hasError, setHasError] = useState(false);
-  const [errorMessage, setErrorMessage] = useState('');
-  const [errorReason, setErrorReason] = useState('');
-
-  // state from pageContainer
-  const { data: pageIdOnHackmd, mutate: mutatePageIdOnHackmd } = usePageIdOnHackmd();
-  const { data: hasDraftOnHackmd, mutate: mutateHasDraftOnHackmd } = useHasDraftOnHackmd();
-  const { data: revisionIdHackmdSynced, mutate: mutateRevisionIdHackmdSynced } = useRevisionIdHackmdSynced();
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
-  const { data: isHackmdDraftUpdatingInRealtime, mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
-  const { data: remoteRevisionId, mutate: mutateRemoteRevisionId } = useRemoteRevisionId();
-
-  const updateStateAfterSave = useUpdateStateAfterSave(pageId);
-
-  const hackmdEditorRef = useRef<HackEditorRef>(null);
-
-  const optionsToSave = useMemo((): OptionsToSave | undefined => {
-    if (grantData == null) {
-      return;
-    }
-    const userRelatedGrantedGroups = grantData.userRelatedGrantedGroups?.map((group) => {
-      return { item: group.id, type: group.type };
-    });
-    const optionsToSave = {
-      isSlackEnabled: isSlackEnabled ?? false,
-      slackChannels: '', // set in save method by opts in SavePageControlls.tsx
-      grant: grantData.grant,
-      pageTags: pageTags ?? [],
-      userRelatedGrantUserGroupIds: userRelatedGrantedGroups,
-    };
-    return optionsToSave;
-  }, [grantData, isSlackEnabled, pageTags]);
-
-  const saveAndReturnToViewHandler = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}) => {
-    if (editorMode !== EditorMode.HackMD) { return }
-
-    try {
-      if (currentPathname == null || revision == null || hackmdEditorRef.current == null || revisionIdHackmdSynced == null || optionsToSave == null) {
-        throw new Error('Some materials to save are invalid');
-      }
-
-      mutateWaitingSaveProcessing(true);
-
-      const options = Object.assign(optionsToSave, opts, { isSyncRevisionToHackmd: true });
-
-      const markdown = await hackmdEditorRef.current.getValue();
-
-      const { page } = await saveOrUpdate(markdown, { pageId, path: currentPagePath || currentPathname, revisionId: revisionIdHackmdSynced }, options);
-
-      if (page == null) {
-        return;
-      }
-      if (isNotFound) {
-        await router.push(`/${page._id}`);
-      }
-      else {
-        updateStateAfterSave?.();
-        mutateIsHackmdDraftUpdatingInRealtime(false);
-
-        // to sync revision id with page tree: https://github.com/weseek/growi/pull/7227
-        mutatePageTree();
-      }
-      setIsInitialized(false);
-      mutateEditorMode(EditorMode.View);
-    }
-    catch (error) {
-      logger.error('failed to save', error);
-      toastError(error.message);
-    }
-    finally {
-      mutateWaitingSaveProcessing(false);
-    }
-
-  // eslint-disable-next-line max-len
-  }, [
-    pageId, currentPagePath, isNotFound, router,
-    editorMode, currentPathname, revision, revisionIdHackmdSynced, optionsToSave,
-    saveOrUpdate, mutateEditorMode, updateStateAfterSave, mutateIsHackmdDraftUpdatingInRealtime, mutateWaitingSaveProcessing,
-  ]);
-
-  // set handler to save and reload Page
-  useEffect(() => {
-    globalEmitter.on('saveAndReturnToView', saveAndReturnToViewHandler);
-
-    return function cleanup() {
-      globalEmitter.removeListener('saveAndReturnToView', 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]);
-
-  useEffect(() => {
-    // for page translation: https://github.com/weseek/growi/pull/7100
-    setIsInitialized(false);
-  }, [pageId]);
-
-
-  const isResume = useCallback(() => {
-    const isPageExistsOnHackmd = (pageIdOnHackmd != null);
-    return (isPageExistsOnHackmd && hasDraftOnHackmd) || isHackmdDraftUpdatingInRealtime;
-  }, [hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime, pageIdOnHackmd]);
-
-  const startToEdit = useCallback(async() => {
-
-    if (hackmdUri == null) {
-      // do nothing
-      return;
-    }
-
-    setIsInitialized(false);
-    setIsInitializing(true);
-
-    try {
-      const res = await apiPost<IResHackmdIntegrated>('/hackmd.integrate', { pageId });
-
-      if (!res.ok) {
-        throw new Error(res.error);
-      }
-
-      mutatePageIdOnHackmd(res.pageIdOnHackmd);
-      mutateRevisionIdHackmdSynced(res.revisionIdHackmdSynced);
-    }
-    catch (err) {
-      toastError(err.message);
-
-      setHasError(true);
-      setErrorMessage('GROWI server failed to connect to HackMD.');
-      setErrorReason(err.toString());
-    }
-
-    setIsInitialized(true);
-    setIsInitializing(false);
-  }, [pageId, hackmdUri, mutatePageIdOnHackmd, mutateRevisionIdHackmdSynced]);
-
-  /**
-   * Start to edit w/o any api request
-   */
-  const resumeToEdit = useCallback(() => {
-    setIsInitialized(true);
-  }, []);
-
-  const discardChanges = useCallback(async() => {
-
-    if (pageId == null) { return }
-
-    try {
-      const res = await apiPost<IResHackmdDiscard>('/hackmd.discard', { pageId });
-
-      if (!res.ok) {
-        throw new Error(res.error);
-      }
-
-      mutateIsHackmdDraftUpdatingInRealtime(false);
-      mutateHasDraftOnHackmd(false);
-      mutatePageIdOnHackmd(res.pageIdOnHackmd);
-      mutateRemoteRevisionId(res.revisionIdHackmdSynced);
-      mutateRevisionIdHackmdSynced(res.revisionIdHackmdSynced);
-
-
-    }
-    catch (err) {
-      logger.error(err);
-      toastError(err.message);
-    }
-  }, [mutateIsHackmdDraftUpdatingInRealtime, mutateHasDraftOnHackmd, mutatePageIdOnHackmd, mutateRevisionIdHackmdSynced, mutateRemoteRevisionId, pageId]);
-
-  /**
-   * save and update state of containers
-   * @param {string} markdown
-   */
-  const onSaveWithShortcut = useCallback(async(markdown) => {
-    try {
-      mutateWaitingSaveProcessing(true);
-
-      const currentPagePathOrPathname = currentPagePath || currentPathname;
-      if (
-        pageId == null || revisionIdHackmdSynced == null || currentPagePathOrPathname == null || optionsToSave == null
-      ) { throw new Error('Some materials to save are invalid') }
-
-      const options = Object.assign(optionsToSave, { isSyncRevisionToHackmd: true });
-
-      const res = await saveOrUpdate(markdown, { pageId, path: currentPagePathOrPathname, revisionId: revisionIdHackmdSynced }, options);
-
-      // update pageData
-      mutatePageData(res);
-
-      // set updated data
-      updateStateAfterSave?.();
-      mutateTagsInfo();
-
-      // to sync revision id with page tree: https://github.com/weseek/growi/pull/7227
-      mutatePageTree();
-
-      mutateIsEnabledUnsavedWarning(false);
-
-      logger.debug('success to save');
-
-      toastSuccess(t('successfully_saved_the_page'));
-    }
-    catch (error) {
-      logger.error('failed to save', error);
-      toastError(error.message);
-    }
-    finally {
-      mutateWaitingSaveProcessing(false);
-    }
-
-  // eslint-disable-next-line max-len
-  }, [
-    currentPagePath, currentPathname, pageId, revisionIdHackmdSynced, optionsToSave,
-    saveOrUpdate,
-    mutateWaitingSaveProcessing, mutatePageData, updateStateAfterSave, mutateTagsInfo, mutateIsEnabledUnsavedWarning, t,
-  ]);
-
-  /**
-   * onChange event of HackmdEditor handler
-   */
-  const hackmdEditorChangeHandler = useCallback(async(body) => {
-
-    if (hackmdUri == null || pageId == null) {
-      // do nothing
-      return;
-    }
-
-    if (revision?.body === body) {
-      return;
-    }
-
-    mutateIsEnabledUnsavedWarning(true);
-
-    try {
-      await apiPost('/hackmd.saveOnHackmd', { pageId });
-    }
-    catch (err) {
-      logger.error(err);
-    }
-  }, [hackmdUri, pageId, revision?.body, mutateIsEnabledUnsavedWarning]);
-
-  const penpalErrorOccuredHandler = useCallback((error) => {
-    toastError(error.message);
-
-    setHasError(true);
-    setErrorMessage(t('hackmd.fail_to_connect'));
-    setErrorReason(error.toString());
-  }, [t]);
-
-  const renderPreInitContent = useCallback(() => {
-    const isPageNotFound = pageId == null;
-
-    let content;
-
-    /*
-     * HackMD is not setup
-     */
-    if (hackmdUri == null) {
-      content = (
-        <div>
-          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> { t('hackmd.not_set_up')}</p>
-          {/* eslint-disable-next-line react/no-danger */}
-          <p dangerouslySetInnerHTML={{ __html: t('hackmd.need_to_associate_with_growi_to_use_hackmd_refer_to_this') }} />
-        </div>
-      );
-    }
-
-    /*
-    * used HackMD from NotFound Page
-    */
-    else if (isPageNotFound) {
-      content = (
-        <div className="text-center">
-          <p className="hackmd-status-label">
-            <span className="material-symbols-outlined">description</span>
-            { t('hackmd.used_for_not_found') }
-          </p>
-          {/* eslint-disable-next-line react/no-danger */}
-          <p dangerouslySetInnerHTML={{ __html: t('hackmd.need_to_make_page') }} />
-        </div>
-      );
-    }
-    /*
-     * Resume to edit or discard changes
-     */
-    else if (isResume()) {
-      const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
-
-      content = (
-        <div>
-          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
-          <p className="text-center"><strong>{t('hackmd.unsaved_draft')}</strong></p>
-
-          { isHackmdDocumentOutdated && (
-            <div className="card border-warning">
-              <div className="card-header bg-warning text-dark"><span className="material-symbols-outlined">info</span> {t('hackmd.draft_outdated')}</div>
-              <div className="card-body text-center">
-                {t('hackmd.based_on_revision')}&nbsp;
-                { pageData != null && (
-                  <Link href={urljoin(returnPathForURL(pageData.path, pageData._id), `?revisionId=${revisionIdHackmdSynced}`)} prefetch={false}>
-                    <span className="badge bg-primary">{revisionIdHackmdSynced?.substr(-8)}</span>
-                  </Link>
-                )}
-                <div className="text-center mt-3">
-                  <button
-                    className="btn btn-link btn-view-outdated-draft p-0"
-                    type="button"
-                    disabled={isInitializing}
-                    onClick={resumeToEdit}
-                  >
-                    {t('hackmd.view_outdated_draft')}
-                  </button>
-                </div>
-              </div>
-            </div>
-          ) }
-
-          { !isHackmdDocumentOutdated && (
-            <div className="text-center hackmd-resume-button-container mb-3">
-              <button
-                className="btn btn-success btn-lg waves-effect waves-light"
-                type="button"
-                disabled={isInitializing}
-                onClick={resumeToEdit}
-              >
-                <span className="btn-label"></span><span className="material-symbols-outlined">skip_next</span>
-                <span className="btn-text">{t('hackmd.resume_to_edit')}</span>
-              </button>
-            </div>
-          ) }
-
-          <div className="text-center hackmd-discard-button-container mb-3">
-            <button
-              className="btn btn-outline-secondary btn-lg waves-effect waves-light"
-              type="button"
-              onClick={discardChanges}
-            >
-              <span className="btn-label"></span><span className="material-symbols-outlined">play_arrow</span>
-              <span className="btn-text">{t('hackmd.discard_changes')}</span>
-            </button>
-          </div>
-
-        </div>
-      );
-    }
-    /*
-     * Start to edit
-     */
-    else {
-      const isRevisionOutdated = revision?._id !== remoteRevisionId;
-
-      content = (
-        <div>
-          <p className="text-muted text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
-          <div className="text-center hackmd-start-button-container mb-3">
-            <button
-              className="btn btn-info btn-lg waves-effect waves-light"
-              type="button"
-              disabled={isRevisionOutdated || isInitializing}
-              onClick={startToEdit}
-            >
-              <span className="btn-label"></span><span className="material-symbols-outlined">send</span>
-              {t('hackmd.start_to_edit')}
-            </button>
-          </div>
-          <p className="text-center">{t('hackmd.clone_page_content')}</p>
-        </div>
-      );
-    }
-
-    return (
-      <div className="hackmd-preinit d-flex justify-content-center align-items-center">
-        {content}
-      </div>
-    );
-  // eslint-disable-next-line max-len
-  }, [pageId, hackmdUri, isResume, t, revisionIdHackmdSynced, remoteRevisionId, pageData, returnPathForURL, isInitializing, resumeToEdit, discardChanges, revision?._id, startToEdit]);
-
-  if (editorMode == null || revision == null) {
-    return <></>;
-  }
-
-  let content;
-
-  // TODO: typescriptize
-  // using any because ref cann't used between FC and class conponent with type safe
-  const AnyEditor = HackmdEditor as any;
-
-  if (isInitialized && hackmdUri != null) {
-    content = (
-      <AnyEditor
-        ref={hackmdEditorRef}
-        hackmdUri={hackmdUri}
-        pageIdOnHackmd={pageIdOnHackmd}
-        initializationMarkdown={isResume() ? null : revision.body}
-        onChange={hackmdEditorChangeHandler}
-        onSaveWithShortcut={(document) => {
-          onSaveWithShortcut(document);
-        }}
-        onPenpalErrorOccured={penpalErrorOccuredHandler}
-      >
-      </AnyEditor>
-    );
-  }
-  else {
-    content = renderPreInitContent();
-  }
-
-
-  return (
-    <div className="position-relative">
-
-      {content}
-
-      { hasError && (
-        <div className="hackmd-error position-absolute d-flex flex-column justify-content-center align-items-center">
-          <div className="bg-box p-5 text-center">
-            <h2 className="text-warning"><span className="material-symbols-outlined">error</span> {t('hackmd.integration_failed')}</h2>
-            <h4>{errorMessage}</h4>
-            <p className="card custom-card text-danger">
-              {errorReason}
-            </p>
-            {/* eslint-disable-next-line react/no-danger */}
-            <p dangerouslySetInnerHTML={{ __html: t('hackmd.check_configuration') }} />
-          </div>
-        </div>
-      ) }
-
-    </div>
-  );
-
-};

+ 0 - 115
apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx

@@ -1,115 +0,0 @@
-import React from 'react';
-
-import connectToChild from 'penpal/lib/connectToChild';
-import PropTypes from 'prop-types';
-
-import loggerFactory from '~/utils/logger';
-
-
-const DEBUG_PENPAL = false;
-
-const logger = loggerFactory('growi:HackmdEditor');
-
-export default class HackmdEditor extends React.PureComponent {
-
-  constructor(props) {
-    super(props);
-
-    this.hackmd = null;
-
-    this.initHackmdWithPenpal = this.initHackmdWithPenpal.bind(this);
-
-    this.notifyBodyChangesHandler = this.notifyBodyChangesHandler.bind(this);
-    this.saveWithShortcutHandler = this.saveWithShortcutHandler.bind(this);
-  }
-
-  componentDidMount() {
-    // append iframe with penpal
-    this.initHackmdWithPenpal();
-  }
-
-  async initHackmdWithPenpal() {
-    const shouldInit = document.getElementById('iframe-hackmd') != null;
-    if (shouldInit) {
-      return;
-    }
-
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const _this = this; // for in methods scope
-    const iframe = document.createElement('iframe');
-    iframe.src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
-    iframe.id = 'iframe-hackmd';
-    this.iframeContainer.appendChild(iframe);
-
-    const connection = connectToChild({
-      iframe,
-      methods: { // expose methods to HackMD
-        notifyBodyChanges(document) {
-          _this.notifyBodyChangesHandler(document);
-        },
-        saveWithShortcut(document) {
-          _this.saveWithShortcutHandler(document);
-        },
-      },
-      timeout: 15000,
-      debug: DEBUG_PENPAL,
-    });
-
-    try {
-      const child = await connection.promise;
-      this.hackmd = child;
-      if (this.props.initializationMarkdown != null) {
-        child.setValueOnInit(this.props.initializationMarkdown);
-      }
-    }
-    catch (err) {
-      logger.error(err);
-
-      if (this.props.onPenpalErrorOccured != null) {
-        this.props.onPenpalErrorOccured(err);
-      }
-    }
-  }
-
-  /**
-   * return markdown document of HackMD
-   * @return {Promise<string>}
-   */
-  getValue() {
-    return this.hackmd.getValue();
-  }
-
-  setValue(newValue) {
-    this.hackmd.setValue(newValue);
-  }
-
-  notifyBodyChangesHandler(body) {
-    // dispatch onChange() when there is difference from 'initializationMarkdown' props
-    if (this.props.onChange != null && body !== this.props.initializationMarkdown) {
-      this.props.onChange(body);
-    }
-  }
-
-  saveWithShortcutHandler(document) {
-    if (this.props.onSaveWithShortcut != null) {
-      this.props.onSaveWithShortcut(document);
-    }
-  }
-
-  render() {
-    return (
-      // will be rendered in componentDidMount
-      <div id="iframe-hackmd-container" ref={(c) => { this.iframeContainer = c }}></div>
-    );
-  }
-
-}
-
-HackmdEditor.propTypes = {
-  hackmdUri: PropTypes.string.isRequired,
-  pageIdOnHackmd: PropTypes.string.isRequired,
-  initializationMarkdown: PropTypes.string,
-  onChange: PropTypes.func,
-  onSaveWithShortcut: PropTypes.func,
-  onPenpalErrorOccured: PropTypes.func,
-};

+ 0 - 101
apps/app/_obsolete/src/components/UncontrolledCodeMirror.tsx

@@ -1,101 +0,0 @@
-import React, {
-  useCallback, useRef, MutableRefObject,
-} from 'react';
-
-import codemirror, { commands, Editor } from 'codemirror';
-import { type ICodeMirror, UnControlled as CodeMirror } from 'react-codemirror2';
-
-declare global {
-  // eslint-disable-next-line vars-on-top, no-var
-  var CodeMirror: ICodeMirror;
-}
-
-// set save handler
-// CommandActions in @types/codemirror does not include 'save' but actualy exists
-// https://codemirror.net/5/doc/manual.html#commands
-(commands as any).save = (instance) => {
-  if (instance.codeMirrorEditor != null) {
-    instance.codeMirrorEditor.dispatchSave();
-  }
-};
-
-window.CodeMirror = codemirror;
-require('codemirror/addon/display/placeholder');
-require('~/client/util/codemirror/gfm-growi.mode');
-
-export interface UncontrolledCodeMirrorProps extends ICodeMirror {
-  value: string;
-  isGfmMode?: boolean;
-  lineNumbers?: boolean;
-  onSave?: () => Promise<void>;
-  onCtrlEnter?: (event: Event) => void;
-  onPasteFiles?: (editor: any, event: Event) => void;
-  onScrollCursorIntoView?: (editor: any, event: Event) => void;
-}
-
-export const UncontrolledCodeMirror = React.forwardRef<CodeMirror|null, UncontrolledCodeMirrorProps>((props, forwardedRef): JSX.Element => {
-
-  const {
-    value, lineNumbers, options,
-    onPasteFiles, onScrollCursorIntoView,
-    ...rest
-  } = props;
-
-  const editorRef = useRef<Editor>();
-
-  const wrapperRef = useRef<CodeMirror|null>();
-
-  const editorDidMountHandler = useCallback((editor: Editor): void => {
-    editorRef.current = editor;
-    if (onPasteFiles != null) {
-      editor.on('paste', onPasteFiles);
-    }
-    if (onScrollCursorIntoView != null) {
-      editor.on('scrollCursorIntoView', onScrollCursorIntoView);
-    }
-  }, [onPasteFiles, onScrollCursorIntoView]);
-
-  const editorWillUnmountHandler = useCallback((): void => {
-    // workaround to fix editor duplicating by https://github.com/scniro/react-codemirror2/issues/284#issuecomment-1155928554
-    if (editorRef.current != null) {
-      (editorRef.current as any).display.wrapper.remove();
-    }
-    if (wrapperRef.current != null) {
-      (wrapperRef.current as any).hydrated = false;
-    }
-  }, []);
-
-  // default true
-  const isGfmMode = rest.isGfmMode ?? true;
-
-  return (
-    <CodeMirror
-      ref={(elem) => {
-        // register to wrapperRef
-        wrapperRef.current = elem;
-        // register to forwardedRef
-        if (forwardedRef != null) {
-          if (typeof forwardedRef === 'function') {
-            forwardedRef(elem);
-          }
-          else {
-            (forwardedRef as MutableRefObject<CodeMirror|null>).current = elem;
-          }
-        }
-      }}
-      value={value}
-      options={{
-        lineNumbers: lineNumbers ?? true,
-        mode: isGfmMode ? 'gfm-growi' : undefined,
-        tabSize: 4,
-        ...options,
-      }}
-      editorDidMount={editorDidMountHandler}
-      editorWillUnmount={editorWillUnmountHandler}
-      {...rest}
-    />
-  );
-
-});
-
-UncontrolledCodeMirror.displayName = 'UncontrolledCodeMirror';

+ 0 - 16
apps/app/_obsolete/src/interfaces/hackmd.ts

@@ -1,16 +0,0 @@
-
-export interface IResHackmdIntegrated {
-  ok: boolean,
-  error?: any,
-  pageIdOnHackmd?: string,
-  revisionIdHackmdSynced?: string,
-  hasDraftOnHackmd?: string
-}
-
-export interface IResHackmdDiscard {
-  ok: boolean,
-  error?: any,
-  pageIdOnHackmd?: string,
-  revisionIdHackmdSynced?: string,
-  hasDraftOnHackmd?: string
-}

+ 0 - 346
apps/app/_obsolete/src/server/routes/hackmd.js

@@ -1,346 +0,0 @@
-import * as hackmdFiles from '@growi/hackmd';
-
-import loggerFactory from '~/utils/logger';
-
-/* eslint-disable no-use-before-define */
-
-const logger = loggerFactory('growi:routes:hackmd');
-
-const axios = require('axios');
-const ejs = require('ejs');
-
-const ApiResponse = require('../util/apiResponse');
-
-/**
- * @swagger
- *
- *  components:
- *    schemas:
- *      Hackmd:
- *        description: Hackmd
- *        type: object
- *        properties:
- *          pageIdOnHackmd:
- *            type: string
- *            description: page ID on HackMD
- *            example: qLnodHLxT6C3hVEVczvbDQ
- *          revisionIdHackmdSynced:
- *            $ref: '#/components/schemas/Revision/properties/_id'
- *          hasDraftOnHackmd:
- *            type: boolean
- *            description: has draft on HackMD
- *            example: false
- */
-module.exports = function(crowi, app) {
-  const Page = crowi.models.Page;
-  const pageEvent = crowi.event('page');
-
-  /**
-   * GET /_hackmd/load-agent
-   *
-   * loadAgent action
-   * This should be access from HackMD and send agent script
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const loadAgent = function(req, res) {
-
-    const origin = crowi.appService.getSiteUrl();
-
-    // generate definitions to replace
-    const definitions = {
-      origin,
-    };
-
-    // inject origin to script
-    const script = ejs.render(hackmdFiles.agentJS, definitions);
-
-    res.set('Content-Type', 'application/javascript');
-    res.send(script);
-  };
-
-  /**
-   * GET /_hackmd/load-styles
-   *
-   * loadStyles action
-   * This should be access from HackMD and send script to insert styles
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const loadStyles = function(req, res) {
-
-    // generate definitions to replace
-    const definitions = {
-      styles: hackmdFiles.stylesCSS,
-    };
-    // inject styles to script
-    const script = ejs.render(hackmdFiles.stylesJS, definitions);
-
-    res.set('Content-Type', 'application/javascript');
-    res.send(script);
-  };
-
-  const validateForApi = async function(req, res, next) {
-    // validate process.env.HACKMD_URI
-    const hackmdUri = process.env.HACKMD_URI;
-    if (hackmdUri == null) {
-      return res.json(ApiResponse.error('HackMD for GROWI has not been setup'));
-    }
-    // validate pageId
-    const pageId = req.body.pageId;
-    if (pageId == null) {
-      return res.json(ApiResponse.error('pageId required'));
-    }
-    // validate page
-    const page = await Page.findOne({ _id: pageId });
-    if (page == null) {
-      return res.json(ApiResponse.error(`Page('${pageId}') does not exist`));
-    }
-
-    req.page = page;
-    next();
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.integrate:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: integrateHackmd
-   *        summary: /hackmd.integrate
-   *        description: Integrate hackmd
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to integrate HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    pageIdOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/pageIdOnHackmd'
-   *                    revisionIdHackmdSynced:
-   *                      $ref: '#/components/schemas/Hackmd/properties/revisionIdHackmdSynced'
-   *                    hasDraftOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/hasDraftOnHackmd'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.integrate
-   *
-   * Create page on HackMD and start to integrate
-   * @param {object} req
-   * @param {object} res
-   */
-  const integrate = async function(req, res) {
-    const hackmdUri = process.env.HACKMD_URI_FOR_SERVER || process.env.HACKMD_URI;
-    let page = req.page;
-
-    const hackmdPageUri = (page.pageIdOnHackmd != null)
-      ? `${hackmdUri}/${page.pageIdOnHackmd}`
-      : `${hackmdUri}/new`;
-
-    let hackmdResponse;
-    try {
-      // check if page is found or created in HackMD
-      hackmdResponse = await axios.get(hackmdPageUri, {
-        maxRedirects: 0,
-        // validate HTTP status is 200 or 302 or 404
-        validateStatus: (status) => {
-          return status === 200 || status === 302 || status === 404;
-        },
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error(err));
-    }
-
-    const { status, headers } = hackmdResponse;
-
-    // validate HackMD/CodiMD/HedgeDoc specific header
-    if (headers['codimd-version'] == null && headers['hackmd-version'] == null && headers['hedgedoc-version'] == null) {
-      const message = 'Connecting to a non-HackMD server.';
-      logger.error(message);
-      return res.json(ApiResponse.error(message));
-    }
-
-    try {
-      // when page is not found
-      if (status === 404) {
-        // reset registered data
-        page = await Page.registerHackmdPage(page, undefined);
-        // re-invoke
-        return integrate(req, res);
-      }
-
-      // when redirect
-      if (status === 302) {
-        // extract page id on HackMD
-        const pathnameOnHackmd = new URL(headers.location, hackmdUri).pathname; // e.g. '/NC7bSRraT1CQO1TO7wjCPw'
-        const pageIdOnHackmd = pathnameOnHackmd.substr(1); //                      strip the head '/'
-
-        page = await Page.registerHackmdPage(page, pageIdOnHackmd);
-      }
-      // when page is found
-      else {
-        page = await Page.syncRevisionToHackmd(page);
-      }
-
-      const data = {
-        pageIdOnHackmd: page.pageIdOnHackmd,
-        revisionIdHackmdSynced: page.revisionHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      return res.json(ApiResponse.success(data));
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('Integration with HackMD process failed'));
-    }
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.discard:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: discardHackmd
-   *        summary: /hackmd.discard
-   *        description: Discard hackmd
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to integrate HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    pageIdOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/pageIdOnHackmd'
-   *                    revisionIdHackmdSynced:
-   *                      $ref: '#/components/schemas/Hackmd/properties/revisionIdHackmdSynced'
-   *                    hasDraftOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/hasDraftOnHackmd'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.discard
-   *
-   * Create page on HackMD and start to integrate
-   * @param {object} req
-   * @param {object} res
-   */
-  const discard = async function(req, res) {
-    let page = req.page;
-
-    try {
-      page = await Page.syncRevisionToHackmd(page);
-
-      const data = {
-        pageIdOnHackmd: page.pageIdOnHackmd,
-        revisionIdHackmdSynced: page.revisionHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      return res.json(ApiResponse.success(data));
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('discard process failed'));
-    }
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.saveOnHackmd:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: saveOnHackmd
-   *        summary: /hackmd.saveOnHackmd
-   *        description: Receive when save operation triggered on HackMD
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to receive when save operation triggered on HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.saveOnHackmd
-   *
-   * receive when save operation triggered on HackMD
-   * !! This will be invoked many time from many people !!
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const saveOnHackmd = async function(req, res) {
-    const { page, user } = req;
-
-    try {
-      await Page.updateHasDraftOnHackmd(page, true);
-      pageEvent.emit('saveOnHackmd', page, user);
-      return res.json(ApiResponse.success());
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('saveOnHackmd process failed'));
-    }
-  };
-
-  return {
-    loadAgent,
-    loadStyles,
-    validateForApi,
-    integrate,
-    discard,
-    saveOnHackmd,
-  };
-};

+ 0 - 22
apps/app/_obsolete/src/stores/hackmd.ts

@@ -1,22 +0,0 @@
-import { SWRResponse } from 'swr';
-
-import { useStaticSWR } from './use-static-swr';
-
-type Nullable<T> = T | null;
-
-export const usePageIdOnHackmd = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('pageIdOnHackmd', initialData);
-};
-
-
-export const useHasDraftOnHackmd = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('hasDraftOnHackmd', initialData);
-};
-
-export const useRevisionIdHackmdSynced = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('revisionIdHackmdSynced', initialData);
-};
-
-export const useIsHackmdDraftUpdatingInRealtime = (initialData?: Nullable<boolean>): SWRResponse<Nullable<boolean>, Error> => {
-  return useStaticSWR<Nullable<boolean>, Error>('isHackmdDraftUpdatingInRealtime', initialData);
-};

+ 0 - 170
apps/app/_obsolete/src/styles/_override.scss

@@ -1,170 +0,0 @@
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-
-// * {
-//   outline: none !important;
-// }
-
-// .container,
-// .container-sm,
-// .container-md,
-// .container-lg,
-// .container-xl,
-// .container-fluid {
-//   @include media-breakpoint-down(xs) {
-//     padding-right: 10px;
-//     padding-left: 10px;
-//   }
-//   @include media-breakpoint-up(md) {
-//     padding-right: 30px;
-//     padding-left: 30px;
-//   }
-// }
-
-// h1 {
-//   font-size: 36px;
-//   line-height: 48px;
-// }
-
-// h2 {
-//   font-size: 24px;
-//   line-height: 36px;
-// }
-
-// h3 {
-//   font-size: 21px;
-//   line-height: 30px;
-// }
-
-// h4 {
-//   font-size: 18px;
-//   line-height: 22px;
-// }
-
-// h5 {
-//   font-size: 16px;
-//   line-height: 18px;
-// }
-
-// h6 {
-//   font-size: 12px;
-//   line-height: 18px;
-// }
-
-// // Navs
-// .nav-tabs {
-//   .nav-item {
-//     margin-right: 0.15rem;
-//     a.active {
-//       cursor: default;
-//     }
-//   }
-// }
-
-// // Custom Control
-// .form-check {
-//   .form-check-input,
-//   .form-check-input + .form-check-label {
-//     cursor: pointer;
-//   }
-// }
-
-// // card (substitute panel of bootstrap3)
-// .card {
-//   margin-bottom: 20px;
-// }
-
-// .card-header {
-//   font-weight: 700;
-//   text-transform: none;
-// }
-
-// .card-header:first-child {
-// }
-
-// .card.custom-card {
-//   min-height: 20px;
-//   padding: $card-spacer-y $card-spacer-x;
-// }
-
-// // Dropdowns
-// .dropdown-toggle {
-//   &.btn.disabled {
-//     pointer-events: auto;
-//     cursor: not-allowed;
-//   }
-
-//   // hide caret
-//   &.dropdown-toggle-no-caret::after {
-//     content: none;
-//   }
-// }
-
-// //Modals
-// .modal-open {
-//   width: 100%;
-//   padding-right: 0 !important;
-// }
-
-// .modal-content {
-//   box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
-// }
-
-// .modal-header {
-//   border-bottom: 1px solid #e5e5e5;
-// }
-
-// .modal-footer {
-//   border-top: 1px solid #e5e5e5;
-// }
-
-// // When fading in the modal, animate it to slide down
-// .modal.fade .modal-dialog {
-//   @include transition($modal-transition);
-//   transform: $modal-fade-transform;
-// }
-
-// .modal.show .modal-dialog {
-//   transform: $modal-show-transform;
-// }
-
-// // When trying to close, animate focus to scale
-// .modal.modal-static .modal-dialog {
-//   transform: $modal-scale-transform;
-// }
-
-// // col-form-label (substitute for control-label of bootstrap3)
-// .col-form-label {
-//   text-align: right;
-// }
-
-// // label
-// label {
-//   // add with-no-font-weight class in case you do not want to apply font-weight 700 to label
-//   :not(.with-no-font-weight) {
-//     font-weight: 700;
-//   }
-// }
-
-// // disabled button (reproduction from bootstrap3.)
-// // see https://cccabinet.jpn.org/bootstrap4/components/buttons#disabled-state
-// .btn.disabled,
-// .btn[disabled],
-// fieldset[disabled] .btn {
-//   cursor: not-allowed;
-// }
-
-// // progress bar
-// .progress {
-//   margin-bottom: 18px;
-//   overflow: hidden;
-// }
-
-// .text-break {
-//   word-break: break-word;
-//   overflow-wrap: break-word;
-// }
-
-// // prevent tooltip flickering (flashing) on hover
-// .tooltip {
-//   pointer-events: none;
-// }

+ 0 - 32
apps/app/_obsolete/src/styles/theme/_hsl-functions.scss

@@ -1,32 +0,0 @@
-@use 'bootstrap/scss/functions' as bs;
-
-@function contrast($color, $darken-degrees: 0%, $alpha-degrees: 100%) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  // @return hsl($color-hs, clamp(10%, calc((100% - $color-l - 51% ) * 1000), 95%));
-  @return hsla($color-hs, clamp(10%, calc((100% - $color-l - $darken-degrees - 51% ) * 1000), 95%), $alpha-degrees);
-}
-
-@function lighten($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsl($color-hs, calc($color-l + $degrees));
-}
-@function darken($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsl($color-hs, calc($color-l - $degrees));
-}
-@function alpha($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsla($color-hs,$color-l,$degrees);
-}

+ 0 - 108
apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss

@@ -1,108 +0,0 @@
-@use '../bootstrap/init' as *;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/hsl-button';
-@use './mixins/hsl-badge';
-@use './hsl-functions' as hsl;
-
-$hsl-colors: (
-  'primary': var(--primary),
-  'secondary': var(--secondary)
-);
-
-@each $color, $value in $hsl-colors {
-  .bg-#{$color} {
-    background-color: $value !important;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .border-#{$color} {
-    border-color: $value !important;
-  }
-}
-
-// TODO: hover-focus() dropped in bootstrap v5
-// https://redmine.weseek.co.jp/issues/128307
-// @each $color, $value in $hsl-colors {
-//   .text-#{$color} {
-//     color: $value !important;
-//     @if $emphasized-link-hover-darken-percentage != 0 {
-//       a {
-//         @include hover-focus() {
-//           color: hsl.darken($value, $emphasized-link-hover-darken-percentage) !important;
-//         }
-//       }
-//     }
-//   }
-// }
-
-@each $color, $value in $hsl-colors {
-  .btn-#{$color} {
-    @include hsl-button.button-variant($value, $value);
-    @include hsl-button.button-svg-icon-variant($value, $value);
-    box-shadow: none !important;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .btn-outline-#{$color} {
-    @include hsl-button.button-outline-variant($value, $value, hsl.alpha($value, 10%), $value);
-    @include mixins-buttons.button-outline-svg-icon-variant($value, $value);
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: $value;
-      background-color: hsl.alpha($value, 10%);
-      border-color: $value;
-    }
-
-    box-shadow: none !important;
-  }
-
-  // separate style: https://github.com/weseek/growi/pull/6804
-  .show > .btn-outline-#{$color}.dropdown-toggle {
-    color: $value;
-    background-color: hsl.alpha($value, 10%);
-    border-color: $value;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .alert-#{$color} {
-    $alert-color: rgba(white,90%);
-    $alert-bg-color: hsl.darken($value, calc($alert-bg-level + 0.95) * $theme-color-interval);
-    $alert-border-color: hsl.darken($value, $alert-border-level * $theme-color-interval);
-
-    color: $alert-color;
-    @include gradient-bg($alert-bg-color);
-    border-color: $alert-border-color;
-
-    hr {
-      border-top-color: hsl.darken($value, calc($alert-border-level + 5) * $theme-color-interval);
-    }
-
-    .alert-link {
-      color: hsl.contrast($value);
-    }
-  }
-
-  // Alert link
-  :root, .wiki {
-    .alert.alert-primary {
-      a,
-      a:hover {
-        color: hsl.darken($value, calc($alert-bg-level - 7.7) * $theme-color-interval);
-      }
-    }
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .bg-#{$color} {
-    @include hsl-badge.badge-variant($value);
-  }
-
-  a.bg-#{$color}  {
-    @include hsl-badge.badge-variant($value);
-  }
-}

+ 0 - 29
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss

@@ -1,29 +0,0 @@
-@use '../bootstrap/init' as *;
-
-//
-// Border
-//
-
-.border {
-  border: $border-width solid $border-color !important;
-}
-
-.border-top {
-  border-top: $border-width solid $border-color !important;
-}
-
-.border-end {
-  border-right: $border-width solid $border-color !important;
-}
-
-.border-bottom {
-  border-bottom: $border-width solid $border-color !important;
-}
-
-.border-start {
-  border-left: $border-width solid $border-color !important;
-}
-
-.border-info {
-  border-color: $info !important;
-}

+ 0 - 22
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss

@@ -1,22 +0,0 @@
-.btn-link {
-  color: var(--color-link);
-  svg {
-    fill: var(--color-link);
-  }
-
-  &:hover {
-    color: var(--color-link-hover);
-    svg {
-      fill: var(--color-link-hover);
-    }
-  }
-
-  &:disabled,
-  &.disabled {
-    color: var(--color-link-disabled, #{$gray-500});
-    svg {
-      fill: var(--color-link-disabled, #{$gray-500});
-    }
-  }
-}
-

+ 0 - 60
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss

@@ -1,60 +0,0 @@
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_reboot.scss
-//
-//
-
-// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
-
-// Body
-//
-// 1. Remove the margin in all browsers.
-// 2. As a best practice, apply a default `background-color`.
-// 3. Set an explicit initial text-align value so that we can later use
-//    the `inherit` value on things like `<th>` elements.
-
-html, body {
-  color: var(--color-global);
-  background-color: var(--bgcolor-global); // 2
-
-  svg {
-    fill: var(--color-global);
-  }
-}
-
-// Links
-
-a {
-  color: var(--color-link);
-  text-decoration: $link-decoration;
-  background-color: transparent; // Remove the gray background on active links in IE 10.
-
-  svg {
-    fill: var(--color-link);
-  }
-
-  &:hover {
-    color: var(--color-link-hover);
-    text-decoration: $link-hover-decoration;
-
-    svg {
-      fill: var(--color-link-hover);
-    }
-  }
-}
-
-// And undo these styles for placeholder links/named anchors (without href).
-// It would be more straightforward to just use a[href] in previous block, but that
-// causes specificity issues in many other styles that are too complex to fix.
-// See https://github.com/twbs/bootstrap/issues/19402
-
-// a:not([href]) {
-//   color: inherit;
-//   text-decoration: none;
-
-//   &:hover {
-//     color: inherit;
-//     text-decoration: none;
-//   }
-// }

+ 0 - 38
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss

@@ -1,38 +0,0 @@
-@use '../bootstrap/init' as *;
-
-.dropdown-menu {
-  color: $color-dropdown;
-  svg {
-    fill: $color-dropdown;
-  }
-
-  background-color: $bgcolor-dropdown;
-}
-
-.dropdown-item {
-  color: $color-dropdown-link;
-  svg {
-    fill: $color-dropdown-link;
-  }
-
-  // TODO: hover-focus() dropped in bootstrap v5
-  @include hover-focus() {
-    color: $color-dropdown-link;
-    svg {
-      fill: $color-dropdown-link-hover;
-    }
-
-    @include gradient-bg($bgcolor-dropdown-link-hover);
-  }
-
-  &:active,
-  &.active,
-  &:active:hover,
-  &.active:hover {
-    color: $color-dropdown-link-active;
-    background-color:  $bgcolor-dropdown-link-active;
-    svg {
-      fill: $color-dropdown-link-active;
-    }
-  }
-}

+ 0 - 52
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss

@@ -1,52 +0,0 @@
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_nav.scss
-//
-//
-
-.nav-link {
-  // Disabled state lightens text
-  &.disabled {
-    color: $nav-link-disabled-color;
-    svg {
-      fill: $nav-link-disabled-color;
-    }
-  }
-}
-
-//
-// Tabs
-//
-
-.nav-tabs {
-  border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
-
-  .nav-link {
-    border: $nav-tabs-border-width solid transparent;
-    @include border-top-radius($nav-tabs-border-radius);
-
-    // TODO: hover-focus() dropped in bootstrap v5
-    @include hover-focus() {
-      border-color: $nav-tabs-link-hover-border-color;
-    }
-
-    &.disabled {
-      color: $nav-link-disabled-color;
-      background-color: transparent;
-      border-color: transparent;
-    }
-  }
-
-  .nav-link.active,
-  .nav-item.show .nav-link {
-    color: $nav-tabs-link-active-color;
-    background-color: $nav-tabs-link-active-bg;
-    border-color: $nav-tabs-link-active-border-color;
-  }
-
-  .dropdown-menu {
-    // Remove the top rounded corners here since there is a hard edge above the menu
-    @include border-top-radius(0);
-  }
-}

+ 0 - 74
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss

@@ -1,74 +0,0 @@
-@use '../bootstrap/init' as *;
-
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_tables.scss
-//
-//
-
-.table {
-  color: $color-table;
-  background-color: $bgcolor-table; // Reset for nesting within parents with `background-color`.
-
-  th,
-  td {
-    border-top-color: $border-color-table;
-  }
-
-  thead th {
-    border-bottom-color: $border-color-table;
-  }
-
-  tbody + tbody {
-    border-top-color: $border-color-table;
-  }
-}
-
-.table-bordered {
-  border-color: $border-color-table;
-
-  th,
-  td {
-    border-color: $border-color-table;
-  }
-}
-
-.table-hover {
-  tbody tr {
-    &:hover {
-      color: $color-table-hover;
-      background-color: $bgcolor-table-hover;
-    }
-  }
-}
-
-.table-dark {
-  color: $table-dark-color;
-  background-color: $table-dark-bg;
-
-  th,
-  td,
-  thead th {
-    border-color: $table-dark-border-color;
-  }
-
-  &.table-bordered {
-    border: 0;
-  }
-
-  &.table-striped {
-    tbody tr:nth-of-type(#{$table-striped-order}) {
-      background-color: $table-dark-accent-bg;
-    }
-  }
-
-  &.table-hover {
-    tbody tr {
-      &:hover {
-        color: $table-dark-hover-color;
-        background-color: $table-dark-hover-bg;
-      }
-    }
-  }
-}

+ 0 - 3
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss

@@ -1,3 +0,0 @@
-.text-muted {
-  color: $text-muted !important;
-}

+ 0 - 103
apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss

@@ -1,103 +0,0 @@
-@use '../bootstrap/init' as *;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-
-@each $color, $value in $theme-colors {
-  @include bg-variant('.bg-#{$color}', $value);
-}
-
-@each $color, $value in $theme-colors {
-  .border-#{$color} {
-    border-color: $value;
-  }
-}
-
-@each $color, $value in $theme-colors {
-  @include text-emphasis-variant('.text-#{$color}', $value, true);
-}
-
-@each $color, $value in $theme-colors {
-  .btn-#{$color} {
-    @include button-variant($value, $value);
-    @include mixins-buttons.button-svg-icon-variant($value, $value);
-    box-shadow: none !important;
-  }
-}
-
-@each $color, $value in $theme-colors {
-  .btn-outline-#{$color} {
-    @include button-outline-variant($value, $color-hover: $value, $active-background: rgba($value, 0.1), $active-border: $value);
-    @include mixins-buttons.button-outline-svg-icon-variant($value, $color-hover: $value);
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: $value;
-      background-color: rgba($value, 0.1);
-      border-color: $value;
-    }
-
-    box-shadow: none !important;
-  }
-
-  // separate style: https://github.com/weseek/growi/pull/6804
-  .show > .btn-outline-#{$color}.dropdown-toggle {
-    color: $value;
-    background-color: rgba($value, 0.1);
-    border-color: $value;
-  }
-}
-
-@each $theme-color, $color in $theme-colors {
-  .form-check-#{$theme-color} {
-    .form-check-label::before {
-      border-color: $input-border-color;
-      transition: 0.3s ease-in-out;
-    }
-    .form-check-input:checked + .form-check-label::before {
-      background-color: $color;
-      border-color: $color;
-    }
-    .form-check-input:checked + .form-check-label::after {
-      color: var(--bgcolor-global);
-    }
-    .form-check-input:not(:disabled):active ~ .form-check-label::before {
-      color: var(--bgcolor-global);
-      background-color: $color;
-      border-color: $color;
-    }
-    .form-check-input:focus:not(:checked) ~ .form-check-label::before {
-      color: var(--bgcolor-global);
-      background-color: var(--bgcolor-global);
-      border-color: $input-focus-border-color;
-    }
-  }
-}
-
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-// theme-color-level() dropped in bootstrap v5
-// @each $color, $value in $theme-colors {
-//   .alert-#{$color} {
-//     @include alert-variant(
-//       theme-color-level($color, $alert-bg-level),
-//       theme-color-level($color, $alert-border-level),
-//       theme-color-level($color, $alert-color-level)
-//     );
-//   }
-//   // Alert link
-//   :root, .wiki {
-//     .alert.alert-#{$color} {
-//       a,
-//       a:hover {
-//         color: theme-color-level($color, $alert-color-level - 2);
-//       }
-//     }
-//   }
-// }
-
-@each $color, $value in $theme-colors {
-  .bg-#{$color} {
-    @include badge-variant($value);
-  }
-  a.bg-#{$color} {
-    @include badge-variant($value);
-  }
-}

+ 0 - 9
apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss

@@ -1,9 +0,0 @@
-@mixin count-badge($color, $bg-color, $min-width: initial) {
-  min-width: $min-width;
-  padding: 0.1rem 0.5rem;
-  font-family: var(--font-family-monospace);
-  font-size: 12px;
-  font-weight: 500;
-  color: $color;
-  background-color: $bg-color;
-}

+ 0 - 23
apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss

@@ -1,23 +0,0 @@
-@use '../../bootstrap/init' as bs;
-@use '../hsl-functions' as hsl;
-
-// @mixin badge-variant($bg) {
-@mixin badge-variant($bg) {
-  color: hsl.contrast($bg);
-  background-color: $bg;
-
-  @at-root a#{&} {
-    // TODO: hover-focus() dropped in bootstrap v5
-    // https://redmine.weseek.co.jp/issues/128307
-    // @include bs.hover-focus() {
-    //   color: hsl.contrast($bg);
-    //   background-color: hsl.darken($bg, 10%);
-    // }
-
-    &:focus,
-    &.focus {
-      outline: 0;
-      // box-shadow: 0 0 0 $badge-focus-width hsl.alpha($bg, 50%);
-    }
-  }
-}

+ 0 - 146
apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss

@@ -1,146 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-@use '../hsl-functions' as hsl;
-
-// @mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) {
-@mixin button-variant($background, $border, $hover-background-darken-degrees: 7.5%, $hover-border-darken-degrees: 10%, $active-background-darken-degrees: 10%, $active-border-darken-degrees: 12.5%) {
-  $hover-background: hsl.darken($background, $hover-background-darken-degrees);  // DO NOT ... twice
-  $hover-border: hsl.darken($border, $hover-border-darken-degrees);  // DO NOT ... twice
-
-  color: hsl.contrast($background);
-  @include bs.gradient-bg($background);
-  border-color: $border;
-  // @include box-shadow($btn-box-shadow);
-
-  &:hover {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($hover-background);
-    border-color: $hover-border;
-  }
-
-  &:focus,
-  &.focus {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($hover-background);
-    border-color: $hover-border;
-    // TODO: color-yiq() to color-contrast()
-    // https://redmine.weseek.co.jp/issues/128307
-    // @if $enable-shadows {
-    //   @include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
-    // } @else {
-    //   // Avoid using mixin so we can pass custom focus shadow properly
-    //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
-    // }
-  }
-
-  // // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($background);
-    border-color: $border;
-    // Remove CSS gradients if they're enabled
-    @if bs.$enable-gradients {
-      background-image: none;
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    color: hsl.contrast($background);
-    background-color: $hover-background;
-    border-color: $hover-border;
-  }
-  //   @if $enable-gradients {
-  //     background-image: none; // Remove the gradient for the pressed/active state
-  //   }
-  //   border-color: $active-border;
-
-  // TODO: color-yiq() to color-contrast()
-  // https://redmine.weseek.co.jp/issues/128307
-  //   &:focus {
-  //     // @if $enable-shadows and $btn-active-box-shadow != none {
-  //     //   @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
-  //     // } @else {
-  //     //   // Avoid using mixin so we can pass custom focus shadow properly
-  //     //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
-  //     // }
-  //   }
-  // }
-}
-
-// @mixin button-outline-variant($color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) {
-@mixin button-outline-variant($color, $color-hover: hsl.contrast($color), $active-background: $color, $active-border: $color) {
-  color: $color;
-  border-color: $color;
-
-  &:hover {
-    color: $color-hover;
-    background-color: $active-background;
-    border-color: $active-border;
-  }
-
-  // &:focus,
-  // &.focus {
-  //   box-shadow: 0 0 0 bs.$btn-focus-width hsl.alpha($color,50%);
-  // }
-
-  &.disabled,
-  &:disabled {
-    color: $color;
-    background-color: transparent;
-  }
-
-  // &:not(:disabled):not(.disabled):active,
-  // &:not(:disabled):not(.disabled).active,
-  // .show > &.dropdown-toggle {
-  //   color: hsl.contrast($active-background);
-  //   background-color: $active-background;
-  //   border-color: $active-border;
-
-  // &:focus {
-  //   @if $enable-shadows and $btn-active-box-shadow != none {
-  //     @include bs.box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width hsl.alpha($color,50%));
-  //   } @else {
-  //     // Avoid using mixin so we can pass custom focus shadow properly
-  //     box-shadow: 0 0 0 $btn-focus-width hsl.alpha($color,50%);
-  //   }
-  // }
-  // }
-}
-
-// @mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
-@mixin button-svg-icon-variant($background, $hover-background-darken-degrees: 7.5%, $active-background-darken-degrees: 10%) {
-  svg {
-    fill: hsl.contrast($background);
-  }
-
-  &:hover {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  &:focus,
-  &.focus {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-}

+ 0 - 72
apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss

@@ -1,72 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use '../../mixins';
-@use './count-badge';
-
-@mixin override-list-group-item($color, $bgcolor, $color-hover: $color, $bgcolor-hover: $bgcolor, $color-active: $color, $bgcolor-active: $bgcolor) {
-  .list-group {
-    .list-group-item {
-      color: $color;
-      background-color: $bgcolor;
-      border-color: $border-color-global;
-
-      &.list-group-item-action {
-        &:hover {
-          background-color: $bgcolor-hover;
-        }
-        &.active {
-          color: $color-active;
-          background-color: $bgcolor-active;
-        }
-      }
-    }
-  }
-}
-
-@mixin override-list-group-item-for-pagetree($color, $bgcolor-hover, $bgcolor-active, $btn-color, $btn-color-hover, $btn-bgcolor-hover, $btn-bgcolor-active) {
-  .grw-pagetree-is-over {
-    background: $bgcolor-hover;
-  }
-  .list-group-item {
-    color: $color;
-    background-color: transparent;
-    border-color: $border-color-global;
-
-    .grw-count-badge {
-      @include count-badge.count-badge($btn-color, $bgcolor-hover, 28px);
-    }
-
-    .btn.btn-page-item-control {
-      color: $btn-color;
-      background-color: transparent;
-      &:hover {
-        color: $btn-color-hover;
-        background-color: $btn-bgcolor-hover;
-      }
-      &:not(:disabled):not(.disabled):active,
-      &:not(:disabled):not(.disabled).active {
-        color: $btn-color-hover;
-        background-color: $btn-bgcolor-active;
-      }
-    }
-
-    &.grw-pagetree-current-page-item {
-      background: $bgcolor-hover;
-    }
-
-    &.list-group-item-action {
-      &:hover {
-        background-color: $bgcolor-hover;
-      }
-      &:active {
-        background-color: $bgcolor-active;
-      }
-    }
-    .grw-pagetree-title-anchor,
-    .grw-foldertree-title-anchor {
-      .grw-sidebar-text-muted {
-        // color: rgba(desaturate($color, 50%), 0.6);
-      }
-    }
-  }
-}

+ 0 - 22
apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss

@@ -1,22 +0,0 @@
-@mixin btn-page-editor-mode-manager($textColor, $borderColor, $bgColorHoverAndActive, $bgColor: white) {
-  color: $textColor;
-  background-color: $bgColor;
-  border-color: $borderColor;
-
-  &:not(:first-child) {
-    &::before {
-      border-left-color: $borderColor;
-    }
-  }
-
-  &:hover,
-  &:active,
-  &.active {
-    color: $textColor;
-    background-color: $bgColorHoverAndActive;
-    border-color: $borderColor;
-    &::after {
-      border-color: $bgColorHoverAndActive;
-    }
-  }
-}

+ 0 - 159
apps/app/bin/cdn/cdn-resources-downloader.ts

@@ -1,159 +0,0 @@
-import path from 'path';
-import { URL } from 'url';
-import urljoin from 'url-join';
-import { Transform } from 'stream';
-import replaceStream from 'replacestream';
-
-import { cdnLocalScriptRoot, cdnLocalStyleRoot, cdnLocalStyleWebRoot } from '^/config/cdn';
-import * as cdnManifests from '^/resource/cdn-manifests';
-
-import { CdnResource, CdnManifest } from '~/interfaces/cdn';
-import loggerFactory from '~/utils/logger';
-import { downloadTo } from '~/utils/download';
-
-const logger = loggerFactory('growi:service:CdnResourcesDownloader');
-
-export default class CdnResourcesDownloader {
-
-  async downloadAndWriteAll(): Promise<any> {
-    const cdnScriptResources: CdnResource[] = cdnManifests.js.map((manifest: CdnManifest) => {
-      return { manifest, outDir: cdnLocalScriptRoot };
-    });
-
-    const cdnStyleResources: CdnResource[] = cdnManifests.style.map((manifest) => {
-      return { manifest, outDir: cdnLocalStyleRoot };
-    });
-
-    const dlStylesOptions = {
-      replaceUrl: {
-        webroot: cdnLocalStyleWebRoot,
-      },
-    };
-
-    return Promise.all([
-      this.downloadScripts(cdnScriptResources),
-      this.downloadStyles(cdnStyleResources, dlStylesOptions),
-    ]);
-  }
-
-  /**
-   * Download script files from CDN
-   * @param cdnResources JavaScript resource data
-   * @param options
-   */
-  private async downloadScripts(cdnResources: CdnResource[], options?: any): Promise<any> {
-    logger.debug('Downloading scripts', cdnResources);
-
-    const opts = Object.assign({}, options);
-    const ext = opts.ext || 'js';
-
-    const promises = cdnResources.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing CdnResource '${manifest.name}'`);
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        `${manifest.name}.${ext}`,
-      );
-    });
-
-    return Promise.all(promises);
-  }
-
-  /**
-   * Download style sheet file from CDN
-   *  Assets in CSS is also downloaded
-   * @param cdnResources CSS resource data
-   * @param options
-   */
-  private async downloadStyles(cdnResources: CdnResource[], options?: any): Promise<any> {
-    logger.debug('Downloading styles', cdnResources);
-
-    const opts = Object.assign({}, options);
-    const ext = opts.ext || 'css';
-
-    // styles
-    const assetsResourcesStore: CdnResource[] = [];
-    const promisesForStyle = cdnResources.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing CdnResource '${manifest.name}'`);
-
-      let urlReplacer: Transform|null = null;
-
-      // generate replaceStream instance
-      if (opts.replaceUrl != null) {
-        urlReplacer = this.generateReplaceUrlInCssStream(cdnResource, assetsResourcesStore, opts.replaceUrl.webroot);
-      }
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        `${manifest.name}.${ext}`,
-        urlReplacer,
-      );
-    });
-
-    // wait until all styles are downloaded
-    await Promise.all(promisesForStyle);
-
-    logger.debug('Downloading assets', assetsResourcesStore);
-
-    // assets in css
-    const promisesForAssets = assetsResourcesStore.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing assts in css '${manifest.name}'`);
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        manifest.name,
-      );
-    });
-
-    return Promise.all(promisesForAssets);
-  }
-
-  /**
-   * Generate replaceStream instance to replace 'url(..)'
-   *
-   * e.g.
-   *  Before  : url(../images/logo.svg)
-   *  After   : url(/path/to/webroot/${cdnResources.name}/logo.svg)
-   *
-   * @param cdnResource CSS resource data
-   * @param assetsResourcesStore An array to store CdnResource that is detected by 'url()' in CSS
-   * @param webroot
-   */
-  private generateReplaceUrlInCssStream(cdnResource: CdnResource, assetsResourcesStore: CdnResource[], webroot: string): Transform {
-    return replaceStream(
-      /url\((?!['"]?data:)["']?(.+?)["']?\)/g, // https://regex101.com/r/Sds38A/3
-      (match, url) => {
-        // generate URL Object
-        const parsedUrl = url.startsWith('http')
-          ? new URL(url) // when url is fqcn
-          : new URL(url, cdnResource.manifest.url); // when url is relative
-        const basename = path.basename(parsedUrl.pathname);
-
-        logger.debug(`${cdnResource.manifest.name} has ${parsedUrl.toString()}`);
-
-        // add assets metadata to download later
-        const replacedCdnResource = {
-          manifest: {
-            name: basename,
-            url: parsedUrl.toString(),
-          },
-          outDir: path.join(cdnResource.outDir, cdnResource.manifest.name),
-        };
-        assetsResourcesStore.push(replacedCdnResource);
-
-        const replaceUrl = urljoin(webroot, cdnResource.manifest.name, basename);
-        return `url(${replaceUrl})`;
-      },
-    );
-  }
-
-}

+ 0 - 33
apps/app/bin/download-cdn-resources.ts

@@ -1,33 +0,0 @@
-/**
- * the tool for download CDN resources and save as file
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-import { envUtils } from '@growi/core/dist/utils';
-
-import loggerFactory from '../src/utils/logger';
-
-import CdnResourcesDownloader from './cdn/cdn-resources-downloader';
-
-const logger = loggerFactory('growi:bin:download-cdn-resources');
-
-// check env var
-const noCdn: boolean = envUtils.toBoolean(process.env.NO_CDN);
-if (!noCdn) {
-  logger.info('Using CDN. No resources are downloaded.');
-  // exit
-  process.exit(0);
-}
-
-logger.info('This is NO_CDN mode. Start to download resources.');
-
-
-const downloader = new CdnResourcesDownloader();
-
-downloader.downloadAndWriteAll()
-  .then(() => {
-    logger.info('Download is completed successfully');
-  })
-  .catch((err) => {
-    logger.error(err);
-  });

+ 0 - 1
apps/app/config/logger/config.dev.js

@@ -25,7 +25,6 @@ module.exports = {
   // 'growi:lib:importer': 'debug',
   // 'growi:routes:page': 'debug',
   'growi-plugin:*': 'debug',
-  // 'growi:InterceptorManager': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:g2g-transfer': 'debug',
   'growi:service:questionnaire': 'debug',

+ 11 - 4
apps/app/config/next-i18next.config.js

@@ -1,12 +1,14 @@
+const isDev = process.env.NODE_ENV === 'development';
+
 const path = require('path');
 
 const { AllLang, Lang } = require('@growi/core');
 const { isServer } = require('@growi/core/dist/utils');
-const I18nextChainedBackend = require('i18next-chained-backend').default;
-const I18NextHttpBackend = require('i18next-http-backend');
+const I18nextChainedBackend = isDev ? require('i18next-chained-backend').default : undefined;
+const I18NextHttpBackend = require('i18next-http-backend').default;
 const I18NextLocalStorageBackend = require('i18next-localstorage-backend').default;
 
-const isDev = process.env.NODE_ENV === 'development';
+const HMRPlugin = isDev ? require('i18next-hmr/plugin').HMRPlugin : undefined;
 
 module.exports = {
   defaultLang: Lang.en_US,
@@ -17,7 +19,12 @@ module.exports = {
   defaultNS: 'translation',
   localePath: path.resolve('./public/static/locales'),
   serializeConfig: false,
-  use: isServer() ? [] : [I18nextChainedBackend],
+  // eslint-disable-next-line no-nested-ternary
+  use: isDev
+    ? isServer()
+      ? [new HMRPlugin({ webpack: { server: true } })]
+      : [I18nextChainedBackend, new HMRPlugin({ webpack: { client: true } })]
+    : [],
   backend: {
     backends: isServer() ? [] : [I18NextLocalStorageBackend, I18NextHttpBackend],
     backendOptions: [

+ 1 - 0
apps/app/next-env.d.ts

@@ -1,5 +1,6 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
+/// <reference types="next/navigation-types/compat/navigation" />
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/basic-features/typescript for more information.

+ 1 - 1
apps/app/next.config.js

@@ -120,7 +120,7 @@ module.exports = async(phase, { defaultConfig }) => {
 
       // setup i18next-hmr
       if (!options.isServer && options.dev) {
-        const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+        const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
         config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
       }
 

+ 13 - 17
apps/app/package.json

@@ -55,8 +55,9 @@
     "@aws-skd/*": "fix version above 3.186.0 that is required by mongodb@4.16.0",
     "@keycloak/keycloak-admin-client": "19.0.0 or above exports only ESM.",
     "escape-string-regexp": "5.0.0 or above exports only ESM",
-    "string-width": "5.0.0 or above exports only ESM.",
-    "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker"
+    "next-themes": "0.3.0 causes a type error: https://github.com/pacocoursey/next-themes/issues/122",
+    "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker",
+    "string-width": "5.0.0 or above exports only ESM."
   },
   "dependencies": {
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
@@ -123,10 +124,10 @@
     "hast-util-select": "^5.0.5",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
-    "i18next": "^22.4.10",
-    "i18next-chained-backend": "^4.0.0",
-    "i18next-http-backend": "^2.0.0",
-    "i18next-localstorage-backend": "^4.0.0",
+    "i18next": "^23.10.1",
+    "i18next-chained-backend": "^4.6.2",
+    "i18next-http-backend": "^2.5.0",
+    "i18next-localstorage-backend": "^4.2.0",
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "ldapjs": "^3.0.2",
@@ -144,8 +145,8 @@
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "mustache": "^4.2.0",
-    "next": "^13.3.0",
-    "next-i18next": "^13.2.1",
+    "next": "^14.1.3",
+    "next-i18next": "^15.2.0",
     "next-superjson": "^0.0.4",
     "next-themes": "^0.2.1",
     "nocache": "^3.0.1",
@@ -171,7 +172,7 @@
     "react-dnd-html5-backend": "^14.1.0",
     "react-dom": "^18.2.0",
     "react-error-boundary": "^3.1.4",
-    "react-i18next": "^12.2.0",
+    "react-i18next": "^14.1.0",
     "react-image-crop": "^8.3.0",
     "react-markdown": "^8.0.7",
     "react-multiline-clamp": "^2.0.0",
@@ -225,8 +226,7 @@
     "@growi/presentation": "link:../../packages/presentation",
     "@growi/ui": "link:../../packages/ui",
     "@handsontable/react": "=2.1.0",
-    "@icon/themify-icons": "1.0.1-alpha.3",
-    "@next/bundle-analyzer": "^13.2.3",
+    "@next/bundle-analyzer": "^14.1.3",
     "@swc-node/jest": "^1.6.2",
     "@swc/jest": "^0.2.24",
     "@types/archiver": "^6.0.2",
@@ -247,15 +247,13 @@
     "diff2html": "^3.4.35",
     "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
-    "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-jest": "^26.5.3",
     "eslint-plugin-regex": "^1.8.0",
-    "font-awesome": "^4.7.0",
     "fslightbox-react": "^1.7.6",
     "handsontable": "=6.2.2",
     "happy-dom": "^13.2.0",
-    "i18next-hmr": "^1.11.0",
+    "i18next-hmr": "^3.0.4",
     "jest": "^29.5.0",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
@@ -275,13 +273,11 @@
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",
-    "simple-line-icons": "^2.5.5",
     "simple-load-script": "^1.0.2",
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.2.0",
     "source-map-loader": "^4.0.1",
     "swagger2openapi": "^7.0.8",
-    "tsc-alias": "^1.2.9",
-    "y-codemirror.next": "^0.3.2"
+    "tsc-alias": "^1.2.9"
   }
 }

+ 3 - 0
apps/app/public/images/icons/slack/slack-logo-background.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
+  <circle fill="white" cx="300" cy="300" r="300" />
+</svg>

+ 3 - 0
apps/app/public/images/icons/slack/slack-logo-dark-background.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
+  <circle fill="#370f38" cx="300" cy="300" r="300" />
+</svg>

+ 2 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -311,7 +311,7 @@
     }
   },
   "page_edit": {
-    "input_channels": "Input channels",
+    "input_channels": "Slack channel name...",
     "theme": "Theme",
     "keymap": "Keymap",
     "indent": "Indent",
@@ -438,6 +438,7 @@
     }
   },
   "modal_resolve_conflict": {
+    "conflicts_with_new_body_on_server_side": "Conflict with new body on server side. Please select or edit the page body to resolve the conflict.",
     "file_conflicting_with_newer_remote": "This file is conflicting with newer remote file",
     "resolve_conflict_message": "Please select page body",
     "resolve_conflict": "Resolve Conflict",

+ 1 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -471,6 +471,7 @@
     }
   },
   "modal_resolve_conflict": {
+    "conflicts_with_new_body_on_server_side": "サーバー側の新しい本文と衝突します。ページ本文を選択または編集して衝突を解消してください。",
     "file_conflicting_with_newer_remote": "サーバー側の新しいファイルと衝突します。",
     "resolve_conflict_message": "ページ本文を選んでください",
     "resolve_conflict": "衝突を解消",

+ 1 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -427,6 +427,7 @@
     }
   },
   "modal_resolve_conflict": {
+    "conflicts_with_new_body_on_server_side": "与服务器端的新正文文本冲突。 请选择或编辑页面正文以解决冲突",
     "file_conflicting_with_newer_remote": "此文件与较新的远程文件冲突",
     "resolve_conflict_message": "选择页面正文",
     "resolve_conflict": "解决冲突",

+ 0 - 221
apps/app/resource/cdn-manifests.js

@@ -1,221 +0,0 @@
-module.exports = {
-  js: [
-    {
-      name: 'basis',
-      // eslint-disable-next-line max-len
-      url: 'https://cdn.jsdelivr.net/combine/npm/jquery@3.4.0,npm/popper.js@1.15.0,npm/bootstrap@4.5.0/dist/js/bootstrap.min.js,npm/scrollpos-styler@0.7.1,npm/jquery-slimscroll@1.3.8/jquery.slimscroll.min.js',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight',
-      url: 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/highlight.min.js',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight-addons',
-      url: 'https://cdn.jsdelivr.net/combine/'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/dockerfile.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/go.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/gradle.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/json.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/less.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/plaintext.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/scss.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/typescript.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/swift.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/kotlin.min.js,'
-        + 'npm/highlightjs-line-numbers.js@2.6.0/dist/highlightjs-line-numbers.min.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'mathjax',
-      url: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'drawio-viewer',
-      url: 'https://jgraph.github.io/drawio/src/main/webapp/js/viewer.min.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-dialog',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/addon/dialog/dialog.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-vim',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/vim.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-emacs',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/emacs.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-sublime',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/sublime.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'redoc-standalone',
-      url: 'https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js',
-      args: {
-        integrity: '',
-      },
-    },
-  ],
-  style: [
-    {
-      name: 'lato',
-      url: 'https://fonts.googleapis.com/css?family=Lato:400,700',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'Press Start 2P',
-      url: 'https://fonts.googleapis.com/css?family=Press+Start+2P',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'font-awesome',
-      url: 'https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'themify-icons',
-      url: 'https://cdn.jsdelivr.net/npm/cd-themify-icons@0.0.1/index.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'simple-line-icons',
-      url: 'https://cdn.jsdelivr.net/npm/simple-line-icons@2.4.1/css/simple-line-icons.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'material-icons',
-      url: 'https://cdn.jsdelivr.net/npm/material-icons@0.3.1/iconfont/material-icons.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-
-    {
-      name: 'animate.css',
-      url: 'https://cdn.jsdelivr.net/npm/animate.css@3.7.2/animate.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight-theme-github',
-      url: 'https://cdn.jsdelivr.net/npm/highlight.js@9.13.0/styles/github.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-dialog',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/addon/dialog/dialog.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-eclipse',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/eclipse.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-elegant',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/elegant.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-neo',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/neo.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-mdn-like',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/mdn-like.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-material',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/material.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-dracula',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/dracula.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-monokai',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/monokai.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-twilight',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/twilight.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-  ],
-};

+ 0 - 253
apps/app/resource/locales/en_US/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

+ 169 - 0
apps/app/resource/locales/en_US/sandbox-bootstrap5.md

@@ -0,0 +1,169 @@
+# 1. Badges
+
+<span class="badge text-bg-primary">primary</span>  
+
+<span class="badge text-bg-secondary">secondary</span>  
+
+<span class="badge text-bg-success">success</span>  
+
+<span class="badge text-bg-danger">danger</span>  
+
+<span class="badge text-bg-warning">warning</span>  
+
+<span class="badge text-bg-info">info</span>  
+
+<span class="badge text-bg-light">light</span>  
+
+<span class="badge text-bg-dark">dark</span>  
+
+
+# 2. Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert.
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert.
+</div>
+
+<div class="alert alert-success" role="alert">
+  This is a success alert.
+</div>
+
+<div class="alert alert-danger" role="alert">
+  This is a danger alert.
+</div>
+
+<div class="alert alert-warning" role="alert">
+  This is a warning alert.
+</div>
+
+<div class="alert alert-info" role="alert">
+  This is a info alert.
+</div>
+
+<div class="alert alert-light" role="alert">
+  This is a light alert.
+</div>
+
+<div class="alert alert-dark" role="alert">
+  This is a dark alert.
+</div>
+
+
+# 3. Cards
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+
+# 4. Colors
+## Contextual colors
+<p class="text-primary">Look, I'm in a well!</p>
+<p class="text-warning">Look, I'm in a well!</p>
+<p class="text-danger">Look, I'm in a well!</p>
+
+## Contextual backgrounds
+<p class="text-danger bg-primary">Look, I'm in a well!</p>
+<p class="text-primary bg-warning">Look, I'm in a well!</p>
+<p class="text-warning bg-danger">Look, I'm in a well!</p>
+
+
+# 5. Collapse
+## Displaying content
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  Show content
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- Content you want to display
+  - Content you want to display
+      
+  </div>
+</div>
+
+## Hiding content
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  Hide content
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- Content you want to hide
+  - Content you want to hide
+
+  </div>
+</div>
+
+
+# Official docs
+- [Click here for Badges details](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [Click here for Alerts details](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [Click here for Cards details](https://getbootstrap.jp/docs/5.3/components/card/)
+- [Click here for Colors details](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [Click here for Collapse details](https://getbootstrap.jp/docs/5.3/components/collapse/)

+ 7 - 10
apps/app/resource/locales/en_US/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(former Draw.io)
+# :pencil: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -15,7 +15,7 @@ See [diagrams.net](https://diagrams.net)
 ```
 
 
-## AWS diagram
+## AWS configuration diagram
 
 ``` drawio
 3Zhdb5swFIZ/TS4XYRswuUzSr0mtVqmtejkZOASvgJHtfO3Xz+YjgdJqiaa1SbnBvD7G9vv4IJsRmeeba8nK9E7EkI2wE29G5GKEMfa9wNyssq0VhFyvVhaSx422Fx74b2hEp1GXPAbVC9RCZJqXfTESRQGR7mlMSrHuhyUi6/dasgUMhIeIZUP1mcc6rdUA071+A3yRtj0jf1LX5KwNbmaiUhaLdUcilyMyl0LoupRv5pBZ91pf6nZX79TuBiah0G80eFIgf4S/rCfYyVhowFRBI+xFIi9FYZthr3WvVaqYGxZy2+xRsugFpCndPN7dmtu0LJtuMxZBaswE2Te4HR7ezXA3cqW3ravGi9IW883CrpsxWyt3nIuQ24BZwrNsLjIhq2CSJOBHkdGVluIFOjUxnYSOY2pWIDU30G7tPO+F4pqLwsSEQmuRmwCmynp1JHwDZoizerS2HWzeNRR1JnENIgcttyakafANuQ3aZnG7Ph37vk8d6pAgoBO3rl131k3TIO0smVZjjZGLXUd7mKbQ2Ng+dvD+M+6n7xatUqDVgXTJ8XQVGZLFlJqEeYtsUl2fRRb7Y+QEJCCIBhQ5ExL0OBPHOReyqsre6VKnRjM+Vu4dxtg9nnEkFgXXYgh6ThFBV6cHmgRj10XUo9jByA1c90vk8/TeJvQ107Bm2wNpe8fTZiX/uWg6GRD3psSZeadH/C+p7RNvTAhxzaedUuoFwbkgf34w4i3Lw5gdSNw/nnhWvf9nsiyimtWBH/TCjPSzgCP/FXH3SwC/YJqFTMGBsOnxsONtwXIRh0PK1q/Z5PRymzgni3qwfW86X7FsCS113KcSLeXKWnNhd7hQxFN7nNlnk1GuuO2yqo+ZSqtg9BYXPwogTHYuQzw49Lzy2AxELGUEnc28OXgxuQA93AF2SEjIzB5j1X/7EdYNfJqcuU/uB/nUnpfP1ijvo4xC52SUNzTK/yij8DkZ5Q+Nov/HKPO4/2lT1XX+fZHLPw==
@@ -27,8 +27,7 @@ See [diagrams.net](https://diagrams.net)
 
 See [PlantUML](http://plantuml.com/).
 
-## Sequence diagram
-
+## Sequence Diagram
 ``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
@@ -63,7 +62,6 @@ deactivate A
 
 
 ## Class diagram
-
 ``` plantuml
 @startuml
 
@@ -155,7 +153,7 @@ State3 --> [*] : Aborted
 
 # :pencil: Mermaid
 
-## Pie chart diagram
+## Pie graph
 
 ```mermaid
 %%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
@@ -167,7 +165,7 @@ pie showData
     "Iron" :  5
 ```
 
-## Gantt diagram
+## Gantt chart
 
 ```mermaid
 gantt
@@ -181,7 +179,7 @@ gantt
     another task      : 24d
 ```
 
-## Gitgraph diagram
+## Git tree diagram
 
 ```mermaid
 %%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
@@ -202,14 +200,13 @@ gitGraph
   commit
 ```
 
-## Mindmap diagram
+## Mind map
 
 ```mermaid
 mindmap
   root((mindmap))
     Origins
       Long history
-      ::icon(fa fa-book)
       Popularisation
         British popular psychology author Tony Buzan
     Research

+ 110 - 391
apps/app/resource/locales/en_US/sandbox.md

@@ -1,439 +1,158 @@
-# :memo: Table of Contents
+# What is Sandbox?
+- In this page, you will find tips that help you to master GROWI 
+- Feel free to enrich the content of your pages with the references under this hierarchy
 
-Add `ToC` after some `#` signs.
-`Table of Contents` or `Table-of-Contents` is also OK.
 
-```
-# ToC
-```
-
-## ToC
-
-# :memo: Block Elements
+# :closed_book:Headings & Paragraphs
+- By inserting headings and paragraphs, you can make the text on the page easier to read
 
 ## Headers
-
-Add one `#` per level at the start of the line
+- Add `#` before the heading text to create a heading 
+    - Depending on the number of `#`, the typeface size of headings would be different shown in the View screen 
+    - Check the View screen on the right side to understand the effect of headings
+- The number of `#` will decide the hierarchy level and help you to organize the contents
 
 ```
-# Header 1
-## Header 2
-### Header 3
-#### Header 4
-##### Header 5
-###### Header 6
+# First-level heading
+## Second-level heading
+### Third-level heading
+#### Forth-level heading
+##### Fifth-level heading
+###### Sixth-level heading
 ```
 
-### Header 3
+## Break
+- Insert two half-width spaces at the end of the sentence you want to break
+    - You can also change this in the Setting to break the line without half-width spaces
+        - Change the line break setting in the `Markdown Settings` sector of the admin page
 
-#### Header 4
+#### Without line break
+Paragraph 1
+Paragraph 2
 
-##### Header 5
+#### With line break
+Paragraph 1  
+Paragraph 2
 
-###### Header 6
+## Block
+- Paragraphs can be created by inserting a blank table in the text
+- Passage can be broken into sentences and make them easier to read
 
-## Block paragraph
+#### Without paragraph
+Paragraph 1  
+Paragraph 2
 
-Paragraphs are created by inserting a newline character
-A paragraph can be created by pressing Enter at the end of the previous paragraph.
+#### With paragraph
+Paragraph 1  
 
-```
-paragraph1
-(Blank line)
-paragraph2
-```
+Paragraph 2
 
-paragraph1
 
-paragraph2
+# :green_book: Styling Text
+- Various styles can be applied to enrich the textual expression of a sentence
+    - These styles also can be easily applied by selecting the toolbar icon at the bottom of the Edit screen
 
-## Br new line
+## Italic
+- Enclose the text with an asterisk `*` or an underscore `_`.
 
-Add two spaces before break.
-***This behavior can be modified in the options menu.***
+#### Examples
+- This sentence indicates emphasis with *Italic*
+- This sentence indicates emphasis with _Italic_ 
 
-```
-foo
-bar(two spaces)
-baz
-```
+## Bold
+- Enclose the text with two asterisks `*` or two underscores `_`
 
-foo
-bar
-baz
+#### Example
+- This sentence indicates emphasis with **Bold** 
+- This sentence indicates emphasis with __Bold__
 
-## Blockquotes
-
-Add one `>` per level at the start of the line
-
-```
-> quote
-> quote
->> nested quotes
-```
-
-> quote
-> quote
->> nested quotes
+## Italic & Bold
+- Enclose the text with three asterisks `*` or three underscores `_`
 
-## Code
+#### Example
+- This sentence indicates emphasis with ***Italic & Bold***
+- This sentence indicates emphasis witH ___Italic & Bold___
 
-Wrap code with three back quotes or tildes.
 
-```
-print 'foo'
-```
+# :orange_book: Insert Lists
+## Bulleted List
+- Insert a bulleted list by starting a line with a hyphen `-`, a plus `+`, or an asterisk `*`
 
-### Syntax highlight and file name
+#### Example
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
 
-- corresponding [highlight.js Demo](https://highlightjs.org/static/demo/) of common category
+## Numbered List
+- `Number.` at the beginning of a line to insert a numbered list
+- Numbered list and bulleted list can also be combined for use
 
+#### Example
+1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+        - This sentence is present in the bulleted list 
+1. This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+## Task List
+- Insert an unchecked checkbox list by writing `[] `
+    - Check the checkbox by writing `[x]`
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
+#### Example
+- [ ] Task 1
+    - [x] Task 1-1
+    - [ ] Task 1-2
+- [x] Task 2
 
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
+# :blue_book: Others
+## Blockquotes
+- Use quoted expressions by putting `>` at the beginning of the paragraph
+    - Multiple quotations can be expressed by using a sequence of `>` characters
+- Lists and other elements can be used together within the blockquotes
 
-### Inline code
+#### Example
+> - Quotation
+> - Quotation
+>> Multiple quotations need to insert more `>`
 
-Words wrapped by `` `back quotes` `` will be formatted as inline code.
+## Code
+- It is possible to express the code by adding it in three `` ` ``
 
+#### Example
 ```
-This is `Inline Code`.
-```
-
-This is  `Inline Code`.
-
-## Pre-arranged text
+Add codes here  
+Line breaks and paragraphs can be reflected in the code
 
-Code blocks should be preceded by four spaces or one tab.
-
-```
-    class Foo
-        def foo
-            print 'foo'
-        end
-    end
+- List also can be used in code
+    - List also can be used in code
 ```
 
-    class Foo
-        def foo
-            print 'foo'
-        end
-    end
+## Inline Code
+- Enclose words in `` ` `` to make inline code
 
-## Horizontal Line
+#### Example
+Here is the `inline code` 
 
-Write three underscores `_`, or asterisks`*`.
+## Horizontal lines
+- Insert the horizontal line with three or more consecutive asterisks `*` or underscores `_`
 
-```
+#### Example
+Below is a horizontal line
 ***
-___
----
-```
 
-***
+Below is a horizontal line
 ___
----
-
-
-
-# :memo: Typography
-
-## Strong Text
-
-### Italic
-
-To italicize text, add one asterisk or underscores before and after a word or phrase.
-
-```
-This is *Italic* .
-This is _Italic_ .
-```
-
-This is *Italic* .
-This is _Italic_ .
-
-### Bold
-
-To make text bold, add two asterisks or underscores before and after a word or phrase.
-
-```
-This is **bold**.
-This is __bold__.
-```
-
-This is **bold**.
-This is __bold__.
-
-### Bold + Italic
-
-To bold and italicize text, add three asterisks or underscores before and after a word or phrase.
-
-```
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-```
-
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-
-# :memo: Images
-
-You can insert `<img>` tag using `![description](URL)`.
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-```
-
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-The size of the image can be set by using an HTML image tag
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-```
-
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
-
-## Markdown standard
-
-You can create links using `[Display text](URL)`.
-
-```
-[Google](https://www.google.co.jp/)
-```
-
-[Google](https://www.google.co.jp/)
-
-## Pukiwiki like linker
-
-This is the most flexible linker.
-Both the page description and link address can be displayed on the page.
-
-```
-[[./Bootstrap4]]
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-```
-
-[[./Bootstrap4]]  
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-
-# :memo: Lists
-
-## Ul Bulleted list
-
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items.
-Items can be nested using indentation.
-
-```
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-```
-
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-
-## Ol Numbered List
-
-To create an ordered list, add line items with numbers followed by periods.
-The numbers don’t have to be in numerical order, but the list should start with the number one.
-
-```
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-```
-
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-
-
-## Check list
-
-```
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-```
-
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-
-
-# :memo: Table
-
-## Markdown Standard
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
-```
-
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
-
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV with header
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## CSV
-
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-## CSV with header
-
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-
-# :memo: Footnote
-
-You can write a reference [^1] to a footnote.
-
-Long footnotes can be written as [^longnote].
-
-[^1]: A_reference_to_the_first_footnote.
-
-[^longnote]: An_example_of_writing_a_footnote_in_multiple_blocks.
-
-    Subsequent paragraphs are indented and belong to the previous footnote.
-
-
-# :memo: Emoji
-
-:smiley: :smile: :laughing: :innocent: :drooling_face:
-
-:family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
-
-:+1: :-1: :open_hands: :raised_hands: :point_right:
-
-:apple: :green_apple: :strawberry: :cake: :hamburger:
-
-:basketball: :football: :baseball: :volleyball: :8ball:
-
-:hearts: :broken_heart: :heartbeat: :heartpulse: :heart_decoration:
 
-:watch: :gear: :gem: :wrench: :email:
 
+# :ledger: More Applications
+- [Bootstrap5](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: More..
+- [Diagrams](/Sandbox/Diagrams)
 
-- Want to attach Bootstrap4 Tags?
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- Want to draw Diagrams?
-    - :arrow_right: [/Sandbox/Diagrams]
-- Want to write Math Formulas?
-    - :arrow_right: [/Sandbox/Math]
+- [Math](/Sandbox/Math)

+ 46 - 59
apps/app/resource/locales/en_US/welcome.md

@@ -1,64 +1,51 @@
-# :tada: Welcome to GROWI
+# :tada: Welcome to GROWI 
 
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
+GROWI is an internal wiki & knowledge base tool for corporations and individuals.  
+With GROWI, members can easily share and edit information in a company, university seminar, or circle.
 
-GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
-Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.
+Casually writing down the information you know and editing it together can **reduce tacit knowledge within the team**.  
+Let's increase the amount of information shared on a daily base!
 
-We can easily write what we know and edit it together, we can **simplify the tacit knowledge (knowledge which is hard to explain with words) in our team**.  
-Let's increase the information exchange everyday.
-
-### :beginner: How to create a page easily 
-
-- Start from "**Create**" button on the upper right, or the **Pencil Icon** on the lower right.
-    - The page title can be edited again later, don't worry about the title.
-        - On title input field, it's possible to create the page's hierarchy with half-width `/` (slash).
-        - (Example)Try entering `/category1/category2/page-title-we-want-to-create`.
-- We can create a bullet point by adding `-`  at the beginning of the line.
-- We can also copy and paste, drag and drop attachments such as images, PDF, Word/Excel/PowerPoint, etc.
-- Once we finished, press the "**Update**" button to publish the page.
-    - We can also save it by `Ctrl(⌘) + S`.
-
-For more information: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">
-    Tips
-  </div>
-  <div class="card-body">
-    <ul>
-      <li>Ctrl(⌘) + "/" to show quick help.</li>
-      <li>We can write HTML with <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a>.</li>
-    </ul>
-  </div>
+<div class="alert alert-primary" role="alert">
+※Feel free to edit and use this page as the top page of the wiki.
 </div>
 
-# :anchor: For administrator <small>〜After you construct the site〜</small>
-
-### :arrow_right: Do you will use a Wiki with more than one person?
-- :heavy_check_mark: Let's invite some members.
-    - [Add/invite new members to the Wiki](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
-### :arrow_right: Work with Slack to receive page and comment notifications.
-- :heavy_check_mark:  [Slack integration](https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#overview)
-### :arrow_right: Are you switching from another system?
-- :heavy_check_mark: It's possible to import data from other GROWI, esa.io, Qiita:Team.
-    -  [Import Data](https://docs.growi.org/en/admin-guide/management-cookbook/import.html)
-
-For more information: [Admin Guide](https://docs.growi.org/en/admin-guide/)
-
-
-# Content List Example
-
-We can display the content list using a table and `$lsx`.
-
-| All page list (First 15 pages)      | [/Sandbox] List of subordinate pages |
-| ----------------------------------- | ------------------------------------ |
-| $lsx(/,num=15)                      | $lsx(/Sandbox)                       |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
-
-We welcome newcomers joining our slack channel to help improve GROWI.
-In addition to discussing development, we are also happy to answer your questions when you join.
+# :beginner: What can you do with GROWI?
+## 1. Knowledge Management: Create pages to store information and knowledge
+- How to create and edit pages?
+    - You can create a new page from the "Pencil Icon" in the upper left corner of the screen
+    - You can edit a page you have already created by clicking "Edit" in the upper right corner of the screen
+- How to manage pages?
+    - GROWI manages pages in a **hierarchical** structure
+        - Example: ` /page A/page B/page C ` 
+    - Apart from Hierarchy, pages can also be managed with Tags
+
+## 2. Information Retrieval: Search information in various ways
+- Keyword search
+- Search using various sidebars
+    - Search by Page Tree
+    - Search by Latest Changes
+    - Search by Tag, and more...
+
+## 3. Information Sharing: Easy to share both internally and externally
+- You can send the URL and permalink of the page to your company members
+    - User Groups can be used to manage viewing privileges among members of the company
+- GROWI also allows pages to be viewed by users outside the company who do not have an account
+    - Let's share information with users outside your company using shared links!
+
+#### :bulb: Check [Sndbox](/Sandbox) to learn more on how to edit pages!
+
+
+# :wrench: For Administrators - Once GROWI is created
+
+### :arrow_right: Wanna use GROWI with multiple people?
+- :heavy_check_mark: Invite your members!
+    - [Add or invite new members to GROWI](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
+
+### :arrow_right: Not satisfied with the current look of GROWI?
+- :heavy_check_mark: No worry! Let's customize the theme of GROWI!
+    - [Customizing GROWI Themes](/admin/customize)
+
+### :arrow_right: GROWI security settings are not completed?
+- :heavy_check_mark: Come to update your GROWI security settings!
+    - [Update GROWI security settings](/admin/security)

+ 0 - 253
apps/app/resource/locales/ja_JP/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

+ 258 - 0
apps/app/resource/locales/ja_JP/sandbox-bootstrap5.md

@@ -0,0 +1,258 @@
+# Bootstrap について
+- GROWI では [Bootstrap](https://getbootstrap.jp/docs/5.3/getting-started/introduction/)(Bootstrap5)を活用して文章やテキストの装飾をすることが可能です
+- 以下にて紹介する代表的な Bootstrap をそのまま引用し活用していただくことが可能です
+
+# 1. バッジ(Badges)
+
+<span class="badge text-bg-primary">テキスト</span>  
+
+<span class="badge text-bg-secondary">テキスト</span>  
+
+<span class="badge text-bg-success">テキスト</span>  
+
+<span class="badge text-bg-danger">テキスト</span>  
+
+<span class="badge text-bg-warning">テキスト</span>  
+
+<span class="badge text-bg-info">テキスト</span>  
+
+<span class="badge text-bg-light">テキスト</span>  
+
+<span class="badge text-bg-dark">テキスト</span>  
+
+---
+
+#### 活用例
+
+- 入社してすぐにやることリスト
+    1. 自己紹介文を記載してください <span class="badge text-bg-danger">必須</span>  
+    2. 振込先口座情報を記載してください <span class="badge text-bg-danger">必須</span>  
+    3. SNS アカウントを記載してください <span class="badge text-bg-secondary">任意</span>  
+
+---
+
+
+
+
+# 2. アラート(Alerts)
+
+<div class="alert alert-primary" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-success" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-danger" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-warning" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-info" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-light" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-dark" role="alert">
+  テキストが入ります
+</div>
+
+---
+
+#### 活用例
+
+<div class="alert alert-danger" role="alert">
+  ※こちらの情報はチーム長以上の役職のメンバー以外は編集しないでください※
+</div>
+
+---
+
+
+
+
+# 3. カード(Cards)
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+---
+
+#### 活用例
+
+<div class="card text-bg-warning mb-3" style="max-width: 40rem;">
+  <div class="card-header">一口コラム</div>
+  <div class="card-body">
+    <h5 class="card-title">日本で最初のカレーライスのレシピとは?</h5>
+    <p class="card-text">日本で初めてカレーライスの調理法が紹介されたのは、1872年(明治5年)に出版された「西洋料理指南」という本でした。</p>
+    <p class="card-text">使用する食材として「ネギ・ショウガ・ニンニク・バター・エビ・タイ・鶏・小麦粉・カレー粉」などが挙げられています。</p>
+  </div>
+</div>
+
+---
+
+
+
+
+# 4. カラー(Colors)
+## テキストカラー
+<p class="text-primary">テキストはこちら</p>
+<p class="text-warning">テキストはこちら</p>
+<p class="text-danger">テキストはこちら</p>
+
+## 背景カラー
+<p class="bg-primary">テキストはこちら</p>
+<p class="bg-warning">テキストはこちら</p>
+<p class="bg-danger">テキストはこちら</p>
+
+## テキスト&背景カラー
+<p class="text-danger bg-primary">テキストはこちら</p>
+<p class="text-primary bg-warning">テキストはこちら</p>
+<p class="text-warning bg-danger">テキストはこちら</p>
+
+---
+
+#### 活用例
+
+- <p class="text-danger">プロジェクトにアサインされる場合はスタートアップを完了させておきましょう</p>
+- <p class="bg-warning">分からないことがあればまとめて質問しましょう</p>
+
+---
+
+
+
+
+# 5. コラプス(Collapse)
+- コラプスはコンテンツの 表示 / 非表示 の切り替えの際に活用できます
+
+## コンテンツの表示
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  コンテンツを表示する
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- 表示させたいコンテンツの内容が入ります
+  - 表示させたいコンテンツの内容が入ります
+      
+  </div>
+</div>
+
+
+## コンテンツの非表示
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  コンテンツを非表示にする
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- 非表示にさせたいコンテンツの内容が入ります
+  - 非表示にさせたいコンテンツの内容が入ります
+
+  </div>
+</div>
+
+
+#### 活用例
+<a class="btn btn-warning text-white" data-bs-toggle="collapse" href="#collapse-3">
+  最終順位を確認する!
+</a>
+
+<div class="collapse" id="collapse-3">
+  <div class="card card-body">
+
+##### 優勝者は **Bさん** です!!
+
+| 対象者 | 点数 | 順位 |
+| ------ | ---- | ---- |
+| Aさん  | 80pt | 2位  |
+| Bさん  | 95pt | 1位  |
+| Cさん  | 70pt | 3位  |
+      
+  </div>
+</div>
+
+
+
+
+
+
+# 公式ドキュメント
+- [バッジの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [アラートの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [カード詳細はこちら](https://getbootstrap.jp/docs/5.3/components/card/)
+- [カラーの詳細はこちら](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [コラプスの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/collapse/)

Diff do ficheiro suprimidas por serem muito extensas
+ 12 - 3
apps/app/resource/locales/ja_JP/sandbox-diagrams.md


+ 4 - 4
apps/app/resource/locales/ja_JP/sandbox-math.md

@@ -1,10 +1,10 @@
-# :pencil: Math
-
-See [KaTeX](https://katex.org/).
+# Math について
+- GROWI では  [MathJax](https://www.mathjax.org/) を活用して文章内に数式を挿入することが可能です
+- 以下にて紹介する代表的な MathJax の記法をそのまま引用し活用することが可能です
 
 ## Inline Formula
 
-When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are
+When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are  
   $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
 
 ## The Lorenz Equations

+ 192 - 329
apps/app/resource/locales/ja_JP/sandbox.md

@@ -1,19 +1,16 @@
-# :memo: 目次
+# Sandbox(サンドボックス)とは
+- この階層下では、GROWI をより便利に活用するための活用術や活用ヒントを掲載しています
+- この階層下のページ内容を組織内で自由に書き換えて GROWI の理解度を深めるために活用しましょう!
 
-いくつかの `#` 記号に続けて `ToC` という文字列を記述します。  
-`Table of Contents` または `Table-of-Contents` でも構いません。
 
-```
-# ToC
-```
-
-## ToC
+# :memo:見出しや段落
+- 見出しや段落を挿入することで、ページ内の文章にメリハリがつき読みやすい文章を作成することが可能です
 
-# :memo: Block Elements
-
-## Headers 見出し
-
-先頭に`#`をレベルの数だけ記述します。
+## 見出し(Headers)
+- 行頭に `#` をレベルの数だけ記述することで見出しを作成することが可能です
+    - 各見出しに応じて View 画面に表示される際のデザインも異なります
+    - 各見出しに応じて View 画面右側に表示される目次が生成されます
+- このページ内にもたくさんの見出しが活用されており、`#` の数に応じて内容をグルーピングすることで可能です
 
 ```
 # 見出し1
@@ -24,382 +21,253 @@
 ###### 見出し6
 ```
 
-### 見出し3
-
-#### 見出し4
-
-##### 見出し5
-
-###### 見出し6
+## 改行(Br)
+- 改行したい文章の行末に半角スペースを2つ挿入することで改行をすることができます
+    - こちらの挙動は、設定画面から半角スペースなしで改行が反映されるように設定を変更することが可能です
+        - 「マークダウン設定_Line Break設定(/admin/markdown)」から変更が可能です
 
-## Block 段落
+#### 改行がない場合
+文章 1 の内容が入ります
+文章 2 の内容が入ります
 
-空白行を挟むことで段落となります。
+#### 改行がある場合
+文章 1 の内容が入ります  
+文章 2 の内容が入ります
 
-```
-段落1
-(空行)
-段落2
-```
+## 段落(Block)
+- 文章内で空白表を挿入することで段落を作成することが可能です
+- 段落を作成することで文章の節目を作成し読みやすい文章を作成することができます
 
-段落1
+#### 段落がない場合
+文章 1 の内容が入ります  
+文章 2 の内容が入ります
 
-段落2
+#### 段落がある場合
+文章 1 の内容が入ります  
 
-## Br 改行
+文章 2 の内容が入ります
 
-改行の前に半角スペース``を2つ記述します。
-***この挙動は、オプションで変更可能です***
 
-```
-hoge
-fuga(スペース2つ)
-piyo
-```
+# :memo:文字の強調
+- 各種記述方法を適用させることで文内の文字の表現を豊かにすることが可能です
+    - これらの表現は Edit 画面下部のツールバーから該当のアイコンを選択することで簡単に適用させることも可能です
 
-hoge
-fuga
-piyo
+## 斜体(Italic)
+- アスタリスク `*` もしくはアンダースコア `_` 1つで該当の文字列を囲みます
 
-## Blockquotes 引用
+#### 活用例
+- この文章は *斜体が適用* されます  
+- この文章は _斜体が適用_ されます
 
-先頭に`>`を記述します。ネストは`>`を多重に記述します。
+## 太字(Bold)
+- アスタリスク `*` もしくはアンダースコア `_` 2つで該当の文字列を囲みます
 
-```
-> 引用
-> 引用
->> 多重引用
-```
+#### 活用例
+- この文章は **強調が適用** されます  
+- この文章は __強調が適用__ されます
 
-> 引用
-> 引用
->> 多重引用
+## 斜体 & 太字(Italic & Bold)
+- アスタリスク `*` もしくはアンダースコア `_` 3つで該当の文字列を囲みます
 
-## Code コード
+#### 活用例
+- この文章は ***斜体 & 太字が適用*** されます  
+- この文章は ___斜体 & 太字が適用___ されます
 
-`` `バッククオート` `` 3つ、あるいはチルダ`~`3つで囲みます。
 
-```
-print 'hoge'
-```
+# :memo:リストの挿入
+## 箇条書きリスト
+- ハイフン `-`、プラス `+`、アスタリスク `*` を行頭に記述することで、箇条書きのリストを挿入することでができます
+    - タブを活用することで前の行のリストに紐づくリストを挿入することも可能です
 
-### シンタックスハイライトとファイル名
-
-- [highlight.js Demo](https://highlightjs.org/static/demo/) の common カテゴリ内の言語に対応しています
+#### 活用例
+- この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています
+        - この文章は箇条書きリストで表現しています
+        - この文章は箇条書きリストで表現しています
+- この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています
 
+## 番号付きリスト
+- `番号.` を行頭に記述することで、番号付きのリストを挿入することができます
+    - タブを活用することで前の行のリストに紐づくリストを挿入することも可能です
+- 番号付きリストと箇条書きリストを組み合わせて活用することも可能です
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
-
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
-
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
-
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
+#### 活用例
+1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+        - この文章は箇条書きリストで表現しています 
+1. この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています  
 
-### インラインコード
-
-`` `バッククオート` `` で単語を囲むとインラインコードになります。
-
-```
-これは `インラインコード`です。
-```
-
-これは `インラインコード`です。
-
-## pre 整形済みテキスト
-
-半角スペース4個もしくはタブで、コードブロックをpre表示できます
-
-```
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-```
-
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-
-## Hr 水平線
-
-アンダースコア`_` 、アスタリスク`*`を3つ以上連続して記述します。
-
-```
-***
-___
----
-```
-
-***
-___
----
-
-
-
-# :memo: Typography
-
-## 強調
-
-### em
-
-アスタリスク`*`もしくはアンダースコア`_`1個で文字列を囲みます。
+## タスクリスト
+- `[] ` を記述することでリストに対して未チェックのチェックボックスを挿入することができます
+    - `[x] ` を記述することでチェック済みのチェックボックスを挿入することができます
 
-```
-これは *イタリック* です
-これは _イタリック_ です
-```
+#### 活用例
+- [ ] タスク 1
+    - [x] タスク 1-1
+    - [ ] タスク 1-2
+- [x] タスク2
 
-これは *イタリック* です
-これは _イタリック_ です
 
-### strong
+# :memo:表の挿入
+## Markdown 標準
+- Markdown で記載できる標準的な形式の表です
 
-アスタリスク`*`もしくはアンダースコア`_`2個で文字列を囲みます。
+#### 活用例
+| 左揃え               |               右揃え |        中央揃え        |
+| :------------------- | -------------------: | :--------------------: |
+| この列は             |             この列は |        この列は        |
+| 左揃えで表示されます | 右揃えで表示されます | 中央揃えで表示されます |
 
+## TSV
+#### 活用例
+``` tsv
+10:00	集合
+10:20	移動
 ```
-これは **ボールド** です
-これは __ボールド__ です
-```
-
-これは **ボールド** です
-これは __ボールド__ です
 
-### em + strong
-
-アスタリスク`*`もしくはアンダースコア`_`3個で文字列を囲みます。
-
-```
-これは ***イタリック&ボールド*** です
-これは ___イタリック&ボールド___ です
+## TSV(ヘッダー付き)
+#### 活用例
+``` tsv-h
+時間	行動
+10:00	集合
+10:20	移動
 ```
 
-これは ***イタリック&ボールド*** です
-これは ___イタリック&ボールド___ です
-
-# :memo: Images
-
-`![Alt文字列](URL)` で`<img>`タグを挿入できます。
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
+## CSV
+#### 活用例
+``` csv
+11:00,MTG
+12:00,昼食
 ```
 
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-画像の大きさなどの指定をする場合はimgタグを使用します。
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
+## CSV(ヘッダー付き)
+#### 活用例
+``` csv-h
+時間,行動
+11:00,MTG
+12:00,昼食
 ```
 
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
 
+# :memo:リンクの挿入
 ## Markdown 標準
+- Markdown で記載できる標準的な形式のリンクです
+- `[表示されるテキスト](リンク先のURL)`でリンクに変換されます
 
-`[表示テキスト](URL)`でリンクに変換されます。
-
-```
-[Google](https://www.google.co.jp/)
-```
-
+#### 活用例
 [Google](https://www.google.co.jp/)
 
 ## Pukiwiki like linker
+- もっとも柔軟なリンクの形式です
+- 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます
 
-最も柔軟な Linker です。
-記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
+#### 活用例
+Bootstrap によるページの装飾方法の記述方法は [[こちらをご確認ください>./Bootstrap5]]
 
-```
-[[./Bootstrap4]]
-Bootstrap4のExampleは[[こちら>./Bootstrap4]]
-```
 
-[[./Bootstrap4]]  
-Bootstrap4のExampleは[[こちら>./Bootstrap4]]
+# :memo:画像の挿入
+## 画像(Images)の挿入
+- `![Alt文字列](URL)` で`<img>`タグを挿入できます
 
-# :memo: Lists
+#### 活用例
+![Minion](https://octodex.github.com/images/minion.png)
 
-## Ul 箇条書きリスト
+## 画像のサイズ指定
+- 画像の大きさなどを指定する場合はimgタグを使用します
 
-ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。
-ネストはタブで表現します。
+#### 活用例
+<img src="https://octodex.github.com/images/dojocat.jpg" width="500px">
 
-```
-- リスト1
-    - リスト1_1
-        - リスト1_1_1
-        - リスト1_1_2
-    - リスト1_2
-- リスト2
-- リスト3
-```
 
-- リスト1
-    - リスト1_1
-        - リスト1_1_1
-        - リスト1_1_2
-    - リスト1_2
-- リスト2
-- リスト3
+# :memo:コンテンツやページの表示
+## 目次(ToC)
+- いくつかの `#` 記号に続けて `ToC` を記述することでページ内に目次を生成することができます
+    - `ToC` は `Table of Contents` または `Table-of-Contents` でも適用されます
+- 生成される目次は、ページ内で `ToC` を記述した以降の部分の目次となります
 
-## Ol 番号付きリスト
+#### 活用例
+##### ToC
 
-`番号.`を先頭に記述します。ネストはタブで表現します。
-番号は自動的に採番されるため、すべての行を1.と記述するのがお勧めです。
+## 配下ページの表示(lsx)
+- ページ内に `$lsx()` を記述することで配下に作成されているページを表示することができます
+- 各種オプションを指定することで表示される配下ページを操作することができます
+    - lsx の詳細は [GROWI 公式ドキュメント](https://docs.growi.org/ja/guide/features/lsx.html) をご確認ください
 
-```
-1. 番号付きリスト1
-    1. 番号付きリスト1-1
-    1. 番号付きリスト1-2
-1. 番号付きリスト2
-1. 番号付きリスト3
-```
+#### 活用例
+$lsx()
 
-1. 番号付きリスト1
-    1. 番号付きリスト1-1
-    1. 番号付きリスト1-2
-1. 番号付きリスト2
-1. 番号付きリスト3
+# :memo:その他の基本的な表現
+## 引用(Blockquotes)
+- 行頭に `>` を記述することで引用表現をすることが可能です
+    - 多重引用の際は `>` を複数個連続で記述することで表現が可能です
+- 引用内でリストなどの要素を併用することも可能です
 
+#### 活用例
+> - 引用する文章が入ります
+> - 引用する文章が入ります
+>> 多重引用したい文章の場合は複数個の挿入が必要です
 
-## タスクリスト
+## コード(Code)
+- `` ` `` 3つで囲むことでコードの表現をすることが可能です
 
+#### 活用例
 ```
-- [ ] タスク 1
-    - [x] タスク 1.1
-    - [ ] タスク 1.2
-- [x] タスク2
-```
-
-- [ ] タスク 1
-    - [x] タスク 1.1
-    - [ ] タスク 1.2
-- [x] タスク2
-
-
-# :memo: Table
+コードが入ります  
+改行や段落をコード内で反映させることが可能です
 
-## Markdown 標準
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
+- リストもコード内での表現が可能です
+    - リストもコード内での表現が可能です
 ```
 
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
+## インラインコード
+- `` ` `` で単語を囲むとインラインコードになります
 
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
+#### 活用例
+こちらは `インラインコード` です
 
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV (ヘッダー付き)
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
+## シンタックスハイライトとファイル名
+- [highlight.js Demo](https://highlightjs.org/static/demo/) の common カテゴリ内の言語に対応しています
 
-## CSV
+#### 活用例 
+```javascript:mersenne-twister.js
+function MersenneTwister(seed) {
+  if (arguments.length == 0) {
+    seed = new Date().getTime();
+  }
 
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
+  this._mt = new Array(624);
+  this.setSeed(seed);
+}
 ```
-~~~
 
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
+## pre 整形済みテキスト
+- 半角スペース4個もしくはタブで、コードブロックを pre 表示できます
 
-## CSV (ヘッダー付き)
+#### 活用例
+    class Hoge
+        def hoge
+            print 'hoge'
+        end
+    end
 
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
+## 水平線(Hr)
+- アスタリスク `*` もしくはアンダースコア `_` を3つ以上連続して記述することで水平線を挿入できます
 
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
+#### 活用例
+以下に水平線が挿入されます
+***
 
+以下に水平線が挿入されます
+___
 
-# :memo: Footnote
+## 脚注(Footnote)
+- 脚注 `[^1]` と脚注への参照 `[^1]:` を作成することができます
 
+#### 活用例
 脚注への参照[^1]を書くことができます。
 
 長い脚注は[^longnote]のように書くことができます。
@@ -410,9 +278,7 @@ Content Cell,Content Cell
 
     後続の段落はインデントされて、前の脚注に属します。
 
-
-# :memo: Emoji
-
+## 絵文字(Emoji)
 :smiley: :smile: :laughing: :innocent: :drooling_face:
 
 :family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
@@ -428,12 +294,9 @@ Content Cell,Content Cell
 :watch: :gear: :gem: :wrench: :email:
 
 
+# :memo:さらに応用的な表現
+- [ページの装飾方法(Bootstrap5)](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: 更に…
+- [図形の表現方法(Diagrams)](/Sandbox/Diagrams)
 
-- Bootstrap4 のタグを使う
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- 図表を書く
-    - :arrow_right: [/Sandbox/Diagrams]
-- 数式を書く
-    - :arrow_right: [/Sandbox/Math]
+- [数式の表現方法(Math)](/Sandbox/Math)

+ 44 - 53
apps/app/resource/locales/ja_JP/welcome.md

@@ -1,60 +1,51 @@
 # :tada: GROWI へようこそ
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
-GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
+GROWI は法人・個人向けの wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。
 
 知っている情報をカジュアルに書き出しみんなで編集することで、**チーム内での暗黙知を減らす**ことができます。  
-当たり前に共有される情報を日々増やしていきましょう。
-
-### :beginner: 簡単なページの作り方
-
-- 右上の "**作成**"ボタンまたは右下の**鉛筆アイコン**のボタンからページを書き始めることができます
-    - ページタイトルは後から変更できますので、適当に入力しても大丈夫です
-        - タイトル入力欄では、半角の `/` (スラッシュ) でページ階層を作れます
-        - (例)`/カテゴリ1/カテゴリ2/作りたいページタイトル` のように入力してみてください
-- `- ` を行頭につけると、この文章のような箇条書きを書くことができます
-- 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
-- 書けたら "**更新**" ボタンを押してページを公開しましょう
-    - `Ctrl(⌘) + S` でも保存できます
-
-さらに詳しくはこちら: [ページを作成する](https://docs.growi.org/ja/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">Tips</div>
-  <div class="card-body"><ul>
-    <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
-    <li>HTML/CSS の記述には、<a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a> を利用できます</li>
-  </ul></div>
-</div>
-
-
-# :anchor: 管理者の方へ <small>〜Wikiを作ったら〜</small>
-
-### :arrow_right: 複数人でWikiを使いますか?
-- :heavy_check_mark: メンバーを招待しましょう
-    - [Wikiに新しいメンバーを追加・招待する](https://docs.growi.org/ja/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
-### :arrow_right: Slackと連携してページやコメントの通知を受け取りましょう
-- :heavy_check_mark:  [Slack連携](https://docs.growi.org/ja/admin-guide/management-cookbook/slack-integration/#%E6%A6%82%E8%A6%81)
-### :arrow_right: 他のシステムからの乗り換えですか?
-- :heavy_check_mark: 他の GROWI、esa. io、Qiita:Team のデータをインポートすることが出来ます
-    -  [データのインポート](https://docs.growi.org/ja/admin-guide/management-cookbook/import.html)
-
-さらに詳しくはこちら: [管理者ガイド](https://docs.growi.org/ja/admin-guide/)
-
+当たり前に共有される情報を日々増やしていきましょう!
 
-# コンテンツリストアップ例
-
-テーブルと `$lsx` を使ってコンテンツリストを表示できます。
-
-| 全てのページリスト (First 15 pages) | [/Sandbox] 配下ページ一覧 |
-| ----------------------------------- | ------------------------- |
-| $lsx(/,num=15)                      | $lsx(/Sandbox)            |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
+<div class="alert alert-primary" role="alert">
+※こちらのページは wiki の TOP ページとして自由に編集してご活用ください
+</div>
 
-GROWI をより良いものにするために、是非 Slack に参加してください。  
-開発に関する議論を行っている他、導入時の質問等も受け付けています。
+# :beginner: GROWI でできること
+## 1. **【情報の蓄積】** ページを作成し情報やナレッジの蓄積ができます
+- ページの作成と編集方法 
+    - 画面左上の「鉛筆アイコン」から新規でページを作成することが可能です
+    - 作成済みのページは画面右上の「Edit」からページを編集することが可能です
+- ページの管理方法
+    - GROWI ではページを「階層構造」で管理します
+        - ` /ページa/ページb/ページc ` のような構造
+    - 「階層構造」とは別に「タグ」でもページを管理することが可能です
+
+## 2. **【情報の検索】** 情報やナレッジは様々な方法で検索できます
+- キーワード検索
+- 各種サイドバーを活用した検索
+    - 「ページツリー」からの検索
+    - 「最新の変更」からの検索
+    - 「タグ」からの検索 など…
+
+## 3. **【情報の共有】** 情報やナレッジは社内外を問わず簡単に共有可能です
+- 社内のメンバーに対してはページの URL やパーマリンクを送ることで共有が可能です
+    - 社内のメンバー内でも「ユーザーグループ」を活用することで閲覧権限の管理をすることが可能です
+- アカウントを保有していない社外のユーザーのページ閲覧を可能にすることも可能です
+    - 「共有リンク」を活用し社外のユーザーに情報を共有しましょう
+
+#### :bulb:ページの編集方法が分からないときは [Snadbox](/Sandbox) を確認してみましょう!
+
+
+# :wrench:管理者の方へ ~ GROWI を作成したら~
+
+### :arrow_right: 複数人で GROWI を使いますか?
+- :heavy_check_mark: メンバーを招待しましょう!
+    - [GROWI に新しいメンバーを追加・招待する](https://docs.growi.org/ja/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
+
+### :arrow_right: GROWI の見た目はこのままで満足ですか?
+- :heavy_check_mark: GROWI の見た目をカスタイマイズしましょう!
+    - [GROWI のテーマをカスタイマイズする](/admin/customize)
+
+### :arrow_right: GROWI のセキュリティ設定は完了していますか?
+- :heavy_check_mark: GROWI のセキュリティ設定を更新しましょう!
+    - [GROWI のセキュリティ設定を更新する](/admin/security)

+ 0 - 253
apps/app/resource/locales/zh_CN/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

+ 169 - 0
apps/app/resource/locales/zh_CN/sandbox-bootstrap5.md

@@ -0,0 +1,169 @@
+# 1. Badges
+
+<span class="badge text-bg-primary">primary</span>  
+
+<span class="badge text-bg-secondary">secondary</span>  
+
+<span class="badge text-bg-success">success</span>  
+
+<span class="badge text-bg-danger">danger</span>  
+
+<span class="badge text-bg-warning">warning</span>  
+
+<span class="badge text-bg-info">info</span>  
+
+<span class="badge text-bg-light">light</span>  
+
+<span class="badge text-bg-dark">dark</span>  
+
+
+# 2. Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert.
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert.
+</div>
+
+<div class="alert alert-success" role="alert">
+  This is a success alert.
+</div>
+
+<div class="alert alert-danger" role="alert">
+  This is a danger alert.
+</div>
+
+<div class="alert alert-warning" role="alert">
+  This is a warning alert.
+</div>
+
+<div class="alert alert-info" role="alert">
+  This is a info alert.
+</div>
+
+<div class="alert alert-light" role="alert">
+  This is a light alert.
+</div>
+
+<div class="alert alert-dark" role="alert">
+  This is a dark alert.
+</div>
+
+
+# 3. Cards
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+
+# 4. Colors
+## 背景颜色
+<p class="text-primary">Look, I'm in a well!</p>
+<p class="text-warning">Look, I'm in a well!</p>
+<p class="text-danger">Look, I'm in a well!</p>
+
+## 背景情况
+<p class="text-danger bg-primary">Look, I'm in a well!</p>
+<p class="text-primary bg-warning">Look, I'm in a well!</p>
+<p class="text-warning bg-danger">Look, I'm in a well!</p>
+
+
+# 5. Collapse
+## 显示内容
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  Show content
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- Content you want to display
+  - Content you want to display
+      
+  </div>
+</div>
+
+## 隐藏内容
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  Hide content
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- Content you want to hide
+  - Content you want to hide
+
+  </div>
+</div>
+
+
+# 官方文件
+- [点击此处了解徽章详情](https://getbootstrap.com/docs/5.3/components/badge/)
+- [单击此处了解警报详情](https://getbootstrap.com/docs/5.3/components/alerts/)
+- [点击此处了解贺卡详情](https://getbootstrap.com/docs/5.3/components/card/)
+- [点击此处了解颜色详情](https://getbootstrap.com/docs/5.3/utilities/colors/)
+- [点击此处查看折叠详情](https://getbootstrap.com/docs/5.3/components/collapse/)

+ 7 - 10
apps/app/resource/locales/zh_CN/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(former Draw.io)
+# :pencil: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -15,7 +15,7 @@ See [diagrams.net](https://diagrams.net)
 ```
 
 
-## AWS diagram
+## AWS configuration diagram
 
 ``` drawio
 3Zhdb5swFIZ/TS4XYRswuUzSr0mtVqmtejkZOASvgJHtfO3Xz+YjgdJqiaa1SbnBvD7G9vv4IJsRmeeba8nK9E7EkI2wE29G5GKEMfa9wNyssq0VhFyvVhaSx422Fx74b2hEp1GXPAbVC9RCZJqXfTESRQGR7mlMSrHuhyUi6/dasgUMhIeIZUP1mcc6rdUA071+A3yRtj0jf1LX5KwNbmaiUhaLdUcilyMyl0LoupRv5pBZ91pf6nZX79TuBiah0G80eFIgf4S/rCfYyVhowFRBI+xFIi9FYZthr3WvVaqYGxZy2+xRsugFpCndPN7dmtu0LJtuMxZBaswE2Te4HR7ezXA3cqW3ravGi9IW883CrpsxWyt3nIuQ24BZwrNsLjIhq2CSJOBHkdGVluIFOjUxnYSOY2pWIDU30G7tPO+F4pqLwsSEQmuRmwCmynp1JHwDZoizerS2HWzeNRR1JnENIgcttyakafANuQ3aZnG7Ph37vk8d6pAgoBO3rl131k3TIO0smVZjjZGLXUd7mKbQ2Ng+dvD+M+6n7xatUqDVgXTJ8XQVGZLFlJqEeYtsUl2fRRb7Y+QEJCCIBhQ5ExL0OBPHOReyqsre6VKnRjM+Vu4dxtg9nnEkFgXXYgh6ThFBV6cHmgRj10XUo9jByA1c90vk8/TeJvQ107Bm2wNpe8fTZiX/uWg6GRD3psSZeadH/C+p7RNvTAhxzaedUuoFwbkgf34w4i3Lw5gdSNw/nnhWvf9nsiyimtWBH/TCjPSzgCP/FXH3SwC/YJqFTMGBsOnxsONtwXIRh0PK1q/Z5PRymzgni3qwfW86X7FsCS113KcSLeXKWnNhd7hQxFN7nNlnk1GuuO2yqo+ZSqtg9BYXPwogTHYuQzw49Lzy2AxELGUEnc28OXgxuQA93AF2SEjIzB5j1X/7EdYNfJqcuU/uB/nUnpfP1ijvo4xC52SUNzTK/yij8DkZ5Q+Nov/HKPO4/2lT1XX+fZHLPw==
@@ -27,8 +27,7 @@ See [diagrams.net](https://diagrams.net)
 
 See [PlantUML](http://plantuml.com/).
 
-## Sequence diagram
-
+## Sequence Diagram
 ``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
@@ -63,7 +62,6 @@ deactivate A
 
 
 ## Class diagram
-
 ``` plantuml
 @startuml
 
@@ -155,7 +153,7 @@ State3 --> [*] : Aborted
 
 # :pencil: Mermaid
 
-## Pie chart diagram
+## Pie graph
 
 ```mermaid
 %%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
@@ -167,7 +165,7 @@ pie showData
     "Iron" :  5
 ```
 
-## Gantt diagram
+## Gantt chart
 
 ```mermaid
 gantt
@@ -181,7 +179,7 @@ gantt
     another task      : 24d
 ```
 
-## Gitgraph diagram
+## Git tree diagram
 
 ```mermaid
 %%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
@@ -202,14 +200,13 @@ gitGraph
   commit
 ```
 
-## Mindmap diagram
+## Mind map
 
 ```mermaid
 mindmap
   root((mindmap))
     Origins
       Long history
-      ::icon(fa fa-book)
       Popularisation
         British popular psychology author Tony Buzan
     Research

+ 110 - 389
apps/app/resource/locales/zh_CN/sandbox.md

@@ -1,439 +1,160 @@
-# :memo: Table of Contents
+# 什么是沙盒?
+- 在本页中,您可以找到帮助您掌握 GROWI 的技巧。
+- 您可以在此层级下的参考资料中丰富您的网页内容
 
-Add `ToC` after some `#` signs.
-`Table of Contents` or `Table-of-Contents` is also OK.
 
-```
-# ToC
-```
-
-## ToC
-
-# :memo: Block Elements
-
-## Headers
+# :closed_book:标题和段落
+- 通过插入标题和段落,可以使页面上的文字更易于阅读
 
-Add one `#` per level at the start of the line
+## 标题
+- 在标题文字前添加 `#` 以创建标题 
+    - 在 "视图 "屏幕中,标题的字体大小会因 "#"的数量而异 
+    - 查看右侧的 "视图 "屏幕,了解标题的效果
+- `#`的数量将决定层次结构的级别,并帮助您组织内容
 
 ```
-# Header 1
-## Header 2
-### Header 3
-#### Header 4
-##### Header 5
-###### Header 6
+# 一级标题
+## 二级标题
+### 三级标题
+#### 第四级标题
+##### 第五级标题
+###### 第六级标题
 ```
 
-### Header 3
+### 断句
+- 在要换行的句子末尾插入两个半宽空格
+    - 您也可以在 "设置 "中进行更改,使换行不使用半宽空格
+        - 在管理页面的 "Markdown 设置 "部分更改换行设置
 
-#### Header 4
+#### 无换行
+段落 1
+第 2 段
 
-##### Header 5
+#### 有换行符
+段落 1  
+第 2 段
 
-###### Header 6
+## 段落
+- 在文本中插入空白表格即可创建段落
+- 可将段落分成若干句子,使其更易于阅读
 
-## Block paragraph
+#### 无段落
+段落 1  
+第 2 段
 
-Pararaphs are created by inserting a newline character
-A paragraph can be created by pressing Enter at the end of the previous paragraph.
+#### 有段落
+第 1 段  
 
-```
-paragraph1
-(Blank line)
-paragraph2
-```
+第 2 段
 
-paragraph1
 
-paragraph2
+# :green_book: 文本样式
+- 可以使用各种样式来丰富句子的文字表达方式
+    - 选择 "编辑 "屏幕底部的工具栏图标,也可以轻松应用这些样式
 
-## Br new line
+##斜体
+- 用星号`*`或下划线`_`括住文本。
 
-Add two spaces before break.
-***This behaviour can be modified in the options menu.***
+#### 示例
+- 这句话用*斜体*表示强调
+- 这句话用 _Italic_ 表示强调 
 
-```
-hoge
-fuga(two spaces)
-piyo
-```
+## 粗体
+- 用两个星号`*`或两个下划线`_`括住文本。
 
-hoge
-fuga
-piyo
+#### 示例
+- 这句话用 ** 粗体** 表示强调 
+- 这句话用__粗体__表示强调
 
-## Blockquotes
+## 斜体和粗体
+- 用三个星号`*`或三个下划线`_`括起来
 
-Add one `>` per level at the start of the line
+#### 示例
+- 本句用***斜体和粗体***表示强调
+- 本句用____斜体和粗体____表示强调
 
-```
-> quote
-> quote
->> nested quotes
-```
 
-> quote
-> quote
->> nested quotes
+# :orange_book: 插入列表
+## 缩略图列表
+- 用连字符 `-`、加号 `+` 或星号 `*` 开头一行,插入一个项目符号列表
 
-## Code
+#### 示例
+- 这句话出现在项目符号列表中
+    - 这句话出现在项目符号列表中
+        - 这句话出现在项目符号列表中
+        - 这句话出现在项目符号列表中
+- 此句出现在项目符号列表中
+    - 此句子出现在项目符号列表中
 
-Wrap code with three back quotes or tildes.
+## 编号列表
+- 在行首添加 `Number.` 以插入编号列表
+- 编号列表和项目符号列表也可合并使用
 
-```
-print 'hoge'
-```
+#### 示例
+1. 编号列表中有这样一句话
+    1. 编号列表中包含这句话
+    1. 该句子出现在编号表中
+    1. 此句出现在编号列表中
+        - 此句出现在项目符号列表中 
+1. 此句出现在项目符号列表中
+    - 此句出现在项目符号列表中
 
-### Syntax highlight and file name
+##任务列表
+- 通过书写 `[] ` 插入未选中复选框列表
+    - 通过书写 `[x]` 选中复选框
 
-- corresponding [highlight.js Demo](https://highlightjs.org/static/demo/) of common category
+#### 示例
+- [ ] 任务 1
+    - [x] 任务 1-1
+    - [ ] 任务 1-2
+- [x] 任务 2
 
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+# :blue_book: 其他
+### 引号
+- 在段落开头加上`>`,使用引号表达式
+    - 使用`>`字符序列可表达多个引号
+- 列表和其他元素可在方括号内一起使用
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
+#### 示例
+> - 引号
+> - 引号
+>> 多个引号需要插入更多的 `>` 字符
 
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+## 代码
+- 可以通过将代码添加到三个 `` `` `` 中来表达代码
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
+#### 示例
 ```
+在此处添加代码  
+代码中可以体现换行和段落
 
-### Inline code
-
-Words wrapped by `` `back quotes` `` will be formatted as inline code.
-
-```
-This is `Inline Code`.
+- 代码中也可使用列表
+    - 也可在代码中使用列表
 ```
 
-This is  `Inline Code`.
+## 内联代码
 
-## Pre-arranged text
 
-Code blocks should be preceded by four spaces or one tab.
 
-```
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-```
 
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
+#### 示例
+以下是内联代码 
 
-## Horizontal Line
+## 水平线
+- 用三个或三个以上连续的星号`*`或下划线`_`插入水平线
 
-Write three underscores `_`, or asterisks`*`.
-
-```
+#### 示例
+以下是水平线
 ***
-___
----
-```
 
-***
+下面是水平线
 ___
----
-
-
-
-# :memo: Typography
-
-## Strong Text
-
-### Italic
-
-To italicize text, add One asterisk or underscores before and after a word or phrase.
-
-```
-This is *Italic* .
-This is _Italic_ .
-```
-
-This is *Italic* .
-This is _Italic_ .
-
-### Bold
-
-To bold text, add two asterisks or underscores before and after a word or phrase.
-
-```
-This is **bold**.
-This is __bold__.
-```
-
-This is **bold**.
-This is __bold__.
-
-### Bold + Italic
-
-To bold and italicize text, add three asterisks or underscores before and after a word or phrase.
-
-```
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-```
-
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-
-# :memo: Images
-
-You can insert `<img>` tag using `![description](URL)`.
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-```
-
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-The size of the image can be set by using an HTML image tag
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-```
-
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
-
-## Markdown standard
-
-You can create links using `[Display text](URL)`.
-
-```
-[Google](https://www.google.co.jp/)
-```
-
-[Google](https://www.google.co.jp/)
-
-## Pukiwiki like linker
-
-This is the most flexible linker.
-Both the page description and link address can be displayed on the page.
-
-```
-[[./Bootstrap4]]
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-```
-
-[[./Bootstrap4]]  
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-
-# :memo: Lists
-
-## Ul Bulleted list
-
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items. 
-Items can be nested using indentation.
-
-```
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-```
-
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-
-## Ol Numbered List
-
-To create an ordered list, add line items with numbers followed by periods. 
-The numbers don’t have to be in numerical order, but the list should start with the number one.
-
-```
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-```
-
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-
-
-## Check list
-
-```
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-```
-
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-
-
-# :memo: Table
-
-## Markdown Standard
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
-```
-
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
-
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV with header
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## CSV
-
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-## CSV with header
-
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-
-# :memo: Footnote
-
-You can write a reference [^1] to a footnote.
-
-Long footnotes can be written as [^longnote].
-
-[^1]: A_reference_to_the_first_footnote.
-
-[^longnote]: An_example_of_writing_a_footnote_in_multiple_blocks.
-
-    Subsequent paragraphs are indented and belong to the previous footnote.
-
-
-# :memo: Emoji
-
-:smiley: :smile: :laughing: :innocent: :drooling_face:
-
-:family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
-
-:+1: :-1: :open_hands: :raised_hands: :point_right:
-
-:apple: :green_apple: :strawberry: :cake: :hamburger:
-
-:basketball: :football: :baseball: :volleyball: :8ball:
-
-:hearts: :broken_heart: :heartbeat: :heartpulse: :heart_decoration:
 
-:watch: :gear: :gem: :wrench: :email:
 
+# :ledger: 更多应用
+- [Bootstrap5](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: More..
+- [Diagrams](/Sandbox/Diagrams)
 
-- Try to attach Bootstrap4 Tags?
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- Try to draw Diagrams?
-    - :arrow_right: [/Sandbox/Diagrams]
-- Try to write Math Formulas?
-    - :arrow_right: [/Sandbox/Math]
+- [Math](/Sandbox/Math)

+ 46 - 59
apps/app/resource/locales/zh_CN/welcome.md

@@ -1,64 +1,51 @@
-# :tada: 欢迎来到GROWI
+# :tada: 欢迎访问 GROWI
 
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
+GROWI 是面向企业和个人的维基知识库工具。  
+知识信息可以在公司、大学实验室和俱乐部中轻松共享,任何人都可以编辑创建的页面。  
 
-GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
-公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。
+随手写下你所知道的信息,然后一起编辑,可以减少团队中的隐性知识。  
+理所当然地增加每天共享的信息量!
 
-我们可以很容易地写下我们知道的东西,并一起编辑,我们可以**简化我们团队中的隐性知识(难以用语言解释的知识)**。 
-让我们每天增加信息交流。
-
-### :beginner: 如何轻松制作一个页面 
-
-- 从右上方的 "**创建**"按钮,或右下方的**铅笔图标开始。
-    - 页面标题以后可以再编辑,不用担心标题的问题。
-        - 在标题输入栏,可以用半宽的`/`(斜线)创建页面的层次。
-        - 例子)尝试输入`/category1/category2/page-title-we-want-to-create`。
-- 我们可以通过在行首添加`-`来创建一个要点。
-- 我们还可以复制和粘贴,拖放附件,如图片、PDF、Word/Excel/PowerPoint等。
-- 一旦我们完成了,按 "**更新**"按钮来发布页面。
-    - 我们也可以通过`Ctrl(⌘) + S`来保存。
-
-了解更多信息: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">
-    Tips
-  </div>
-  <div class="card-body">
-    <ul>
-      <li>Ctrl(⌘) + "/" 显示快速帮助。</li>
-      <li>你可以用 <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4编写HTML</a>.</li>
-    </ul>
-  </div>
+<div class="alert alert-primary" role="alert">
+※此页面可自由编辑,并作为维基的首页使用
 </div>
 
-# :anchor: 对于管理员来说 <small>〜如果你创建了一个Wiki〜</small>
-
-### :arrow_right: 你会和多个人一起使用Wiki吗?
-- :heavy_check_mark: 让我们邀请一些成员。
-    - [Add/invite new members to the Wiki](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
-### :arrow_right: 与Slack合作,接收页面和评论通知。
-- :heavy_check_mark:  [Slack integration](https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#overview)
-### :arrow_right: 你是否从另一个系统转换?
-- :heavy_check_mark: 可以从其他GROWI, esa.io, Qiita:Team导入数据。
-    -  [Import Data](https://docs.growi.org/en/admin-guide/management-cookbook/import.html)
-
-了解更多信息: [Admin Guide](https://docs.growi.org/en/admin-guide/)
-
-
-# 内容列表示例
-
-你可以用一个表格和`$lsx`来显示内容列表。
-
-| 所有页面列表(前15页)      | [/Sandbox] 下级页面列表 |
-| ---------------------------| ------------------------|
-| $lsx(/,num=15)             | $lsx(/Sandbox)          |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
-
-我们欢迎新人加入我们的slack频道,帮助改善Growi。
-除了讨论发展问题,我们也很乐意在你加入时回答你的问题。
+# :beginner: 使用 GROWI 可以做什么
+## 1. 可以创建页面来存储信息和知识。
+- 如何创建和编辑页面
+    - 可以通过屏幕左上角的 "铅笔图标 "创建新页面。
+    - 点击屏幕右上角的 "编辑 "可编辑已创建的页面。
+- 如何管理页面
+    - GROWI 采用 "分层结构 "管理页面。
+        - ` /page a/page b/page c ` 结构类似。
+    - 除了 "层次结构",还可以通过 "标签 "来管理页面。
+
+## 2. 检索信息和知识的方式有以下几种。
+- 关键词搜索
+- 使用各种侧边栏进行搜索。
+    - 按 "页面树 "搜索。
+    - 按 "最新更改 "搜索。
+    - 按 "标签 "搜索 例如...
+
+## 3. 信息和知识可以很容易地在内部和外部共享。
+- 内部成员可通过发送页面的 URL 或 URL 链接获得通知。
+    - 使用 "用户组 "可以管理公司成员内部的查看权限。
+- 还可以允许未持有账户的外部用户查看页面。
+    - 使用 "共享链接 "与公司以外的用户共享信息。
+
+#### :bulb:如果不确定如何编辑页面,请选中 [Sandbox](/Sandbox)!
+
+
+# :wrench:针对管理员 - GROWI 创建后
+
+### :arrow_right: 您是否与多人一起使用 GROWI?
+- :heavy_check_mark: 邀请您的会员
+    - [添加和邀请新成员加入 GROWI](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
+
+### :arrow_right: 您对 GROWI 现在的样子满意吗?
+- :heavy_check_mark: 定制 GROWI 的外观和感觉!
+    - [定制 GROWI 主题](/admin/customize)
+
+### :arrow_right: GROWI 安全配置是否已完成?
+- :heavy_check_mark: 更新您的 GROWI 安全设置!
+    - [更新 GROWI 安全设置](/admin/security)

+ 0 - 5
apps/app/src/client/services/page-operation.ts

@@ -96,11 +96,6 @@ export const createPage = async(params: IApiv3PageCreateParams): Promise<IApiv3P
   return res.data;
 };
 
-export const updatePage = async(params: IApiv3PageUpdateParams): Promise<IApiv3PageUpdateResponse> => {
-  const res = await apiv3Put<IApiv3PageUpdateResponse>('/page', params);
-  return res.data;
-};
-
 export type UpdateStateAfterSaveOption = {
   supressEditingMarkdownMutation: boolean,
 }

+ 47 - 11
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -5,14 +5,14 @@ import type EventEmitter from 'events';
 import { Origin } from '@growi/core';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
+import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
 import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
 import { useShareLinkId } from '~/stores/context';
-import { useDrawioModal } from '~/stores/modal';
+import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
+import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
-import { updatePage } from '../page-operation';
-
 
 const logger = loggerFactory('growi:cli:side-effects:useDrawioModalLauncherForView');
 
@@ -34,31 +34,67 @@ export const useDrawioModalLauncherForView = (opts?: {
 
   const { open: openDrawioModal } = useDrawioModal();
 
-  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
+  const { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModal();
+
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
+
+  // eslint-disable-next-line max-len
+  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
     if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
     }
 
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
-
     try {
-      const currentRevisionId = currentPage.revision._id;
-      await updatePage({
+      await _updatePage({
         pageId: currentPage._id,
-        revisionId: currentRevisionId,
+        revisionId,
         body: newMarkdown,
         origin: Origin.View,
       });
 
+      closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
     catch (error) {
+      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
+      if (remoteRevidsionData != null) {
+        onConflict(remoteRevidsionData, newMarkdown);
+      }
+
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, shareLinkId]);
+  }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
+
+  // eslint-disable-next-line max-len
+  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    return async(newMarkdown: string) => {
+      await updatePage(revisionId, newMarkdown, onConflict);
+    };
+  }, [updatePage]);
+
+  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+    setRemoteLatestPageData(remoteRevidsionData);
+
+    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
+    if (resolveConflictHandler == null) {
+      return;
+    }
+
+    openConflictDiffModal(newMarkdown, resolveConflictHandler);
+  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+
+  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
+    if (currentPage == null || currentPage.revision == null) {
+      return;
+    }
+
+    const currentRevisionId = currentPage.revision._id;
+    const currentMarkdown = currentPage.revision.body;
+    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
 
+    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+  }, [currentPage, onConflictHandler, updatePage]);
 
   // set handler to open DrawioModal
   useEffect(() => {

+ 47 - 11
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -5,14 +5,14 @@ import type EventEmitter from 'events';
 import { Origin } from '@growi/core';
 
 import type MarkdownTable from '~/client/models/MarkdownTable';
+import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import { useShareLinkId } from '~/stores/context';
-import { useHandsontableModal } from '~/stores/modal';
+import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
+import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
-import { updatePage } from '../page-operation';
-
 
 const logger = loggerFactory('growi:cli:side-effects:useHandsontableModalLauncherForView');
 
@@ -34,31 +34,67 @@ export const useHandsontableModalLauncherForView = (opts?: {
 
   const { open: openHandsontableModal } = useHandsontableModal();
 
-  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
+  const { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModal();
+
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
+
+  // eslint-disable-next-line max-len
+  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
     if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
     }
 
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
-
     try {
-      const currentRevisionId = currentPage.revision._id;
-      await updatePage({
+      await _updatePage({
         pageId: currentPage._id,
-        revisionId: currentRevisionId,
+        revisionId,
         body: newMarkdown,
         origin: Origin.View,
       });
 
+      closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
     catch (error) {
+      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
+      if (remoteRevidsionData != null) {
+        onConflict?.(remoteRevidsionData, newMarkdown);
+      }
+
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, shareLinkId]);
+  }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
+
+  // eslint-disable-next-line max-len
+  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    return async(newMarkdown: string) => {
+      await updatePage(revisionId, newMarkdown, onConflict);
+    };
+  }, [updatePage]);
+
+  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+    setRemoteLatestPageData(remoteRevidsionData);
+
+    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
+    if (resolveConflictHandler == null) {
+      return;
+    }
+
+    openConflictDiffModal(newMarkdown, resolveConflictHandler);
+  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+
+  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
+    if (currentPage == null || currentPage.revision == null) {
+      return;
+    }
+
+    const currentRevisionId = currentPage.revision._id;
+    const currentMarkdown = currentPage.revision.body;
+    const newMarkdown = replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
 
+    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+  }, [currentPage, onConflictHandler, updatePage]);
 
   // set handler to open HandsonTableModal
   useEffect(() => {

+ 30 - 9
apps/app/src/client/services/side-effects/page-updated.ts

@@ -3,17 +3,24 @@ import { useCallback, useEffect } from 'react';
 import { useGlobalSocket } from '@growi/core/dist/swr';
 
 import { SocketEventName } from '~/interfaces/websocket';
-import { useCurrentPageId } from '~/stores/page';
+import { usePageStatusAlert } from '~/stores/alert';
+import { useSWRxCurrentPage, useSWRMUTxCurrentPage } from '~/stores/page';
 import { useSetRemoteLatestPageData, type RemoteRevisionData } from '~/stores/remote-latest-page';
+import { useEditorMode, EditorMode } from '~/stores/ui';
+
 
 export const usePageUpdatedEffect = (): void => {
 
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
   const { data: socket } = useGlobalSocket();
-  const { data: currentPageId } = useCurrentPageId();
+  const { data: editorMode } = useEditorMode();
+  const { data: currentPage } = useSWRxCurrentPage();
+  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { open: openPageStatusAlert, close: closePageStatusAlert } = usePageStatusAlert();
 
-  const setLatestRemotePageData = useCallback((data) => {
+  const remotePageDataUpdateHandler = useCallback((data) => {
+    // Set remote page data
     const { s2cMessagePageUpdated } = data;
 
     const remoteData: RemoteRevisionData = {
@@ -23,22 +30,36 @@ export const usePageUpdatedEffect = (): void => {
       remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
     };
 
-    if (currentPageId != null && currentPageId === s2cMessagePageUpdated.pageId) {
+    if (currentPage?._id != null && currentPage._id === s2cMessagePageUpdated.pageId) {
       setRemoteLatestPageData(remoteData);
-    }
 
-  }, [currentPageId, setRemoteLatestPageData]);
+      // Open PageStatusAlert
+      const currentRevisionId = currentPage?.revision?._id;
+      const remoteRevisionId = s2cMessagePageUpdated.revisionId;
+      const isRevisionOutdated = (currentRevisionId != null || remoteRevisionId != null) && currentRevisionId !== remoteRevisionId;
+
+      // !!CAUTION!! Timing of calling openPageStatusAlert may clash with components/PageEditor/conflict.tsx
+      if (isRevisionOutdated && editorMode === EditorMode.View) {
+        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: mutateCurrentPage });
+      }
+
+      // Clear cache
+      if (!isRevisionOutdated) {
+        closePageStatusAlert();
+      }
+    }
+  }, [currentPage?._id, currentPage?.revision?._id, editorMode, mutateCurrentPage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
 
   // listen socket for someone updating this page
   useEffect(() => {
 
     if (socket == null) { return }
 
-    socket.on(SocketEventName.PageUpdated, setLatestRemotePageData);
+    socket.on(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
 
     return () => {
-      socket.off(SocketEventName.PageUpdated, setLatestRemotePageData);
+      socket.off(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
     };
 
-  }, [setLatestRemotePageData, socket]);
+  }, [remotePageDataUpdateHandler, socket]);
 };

+ 22 - 0
apps/app/src/client/services/update-page/conflict.tsx

@@ -0,0 +1,22 @@
+import type { ErrorV3 } from '@growi/core/dist/models';
+
+import { PageUpdateErrorCode } from '~/interfaces/apiv3';
+import { type RemoteRevisionData } from '~/stores/remote-latest-page';
+
+export const extractRemoteRevisionDataFromErrorObj = (errors: Array<ErrorV3>): RemoteRevisionData | undefined => {
+  for (const error of errors) {
+    if (error.code === PageUpdateErrorCode.CONFLICT) {
+
+      const latestRevision = error.args.returnLatestRevision;
+
+      const remoteRevidsionData = {
+        remoteRevisionId: latestRevision.revisionId,
+        remoteRevisionBody: latestRevision.revisionBody,
+        remoteRevisionLastUpdateUser: latestRevision.user,
+        remoteRevisionLastUpdatedAt: latestRevision.createdAt,
+      };
+
+      return remoteRevidsionData;
+    }
+  }
+};

+ 9 - 0
apps/app/src/client/services/update-page/index.ts

@@ -0,0 +1,9 @@
+import { apiv3Put } from '~/client/util/apiv3-client';
+import type { IApiv3PageUpdateParams, IApiv3PageUpdateResponse } from '~/interfaces/apiv3';
+
+export * from './conflict';
+
+export const updatePage = async(params: IApiv3PageUpdateParams): Promise<IApiv3PageUpdateResponse> => {
+  const res = await apiv3Put<IApiv3PageUpdateResponse>('/page', params);
+  return res.data;
+};

+ 2 - 1
apps/app/src/components/Admin/App/QuestionnaireSettings.tsx

@@ -2,6 +2,7 @@ import {
   useState, useCallback, useEffect,
 } from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
@@ -65,7 +66,7 @@ const QuestionnaireSettings = (): JSX.Element => {
 
       {isLoading && (
         <div className="text-muted text-center mb-5">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       )}
 

+ 3 - 2
apps/app/src/components/Admin/AuditLog/AuditLogDisableMode.tsx

@@ -1,4 +1,5 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'react-i18next';
 
@@ -6,7 +7,7 @@ export const AuditLogDisableMode: FC = () => {
   const { t } = useTranslation('admin');
 
   return (
-    <div id="content-main" className="content-main container-lg">
+    <div className="ccontainer-lg">
       <div className="container">
         <div className="row justify-content-md-center">
           <div className="col-md-6 mt-5">

+ 1 - 1
apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx

@@ -55,7 +55,7 @@ export const AuditLogSettings: FC = () => {
       </p>
       <p className="mt-1">
         <button type="button" className="btn btn-link p-0" aria-expanded="false" onClick={() => setIsExpandActionList(!isExpandActionList)}>
-          <span className={`material-symbols-outlined me-1 ${isExpandActionList ? 'fa-rotate-90' : ''}`}>navigate_next</span>
+          <span className={`material-symbols-outlined me-1 ${isExpandActionList ? 'rotate-90' : ''}`}>navigate_next</span>
           { t('admin:audit_log_management.action_list') }
         </button>
       </p>

+ 6 - 6
apps/app/src/components/Admin/AuditLogManagement.tsx

@@ -1,13 +1,13 @@
-import React, {
-  FC, useState, useCallback, useRef,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback, useRef } from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 
-import { IClearable } from '~/client/interfaces/clearable';
+import type { IClearable } from '~/client/interfaces/clearable';
 import { toastError } from '~/client/util/toastr';
-import { SupportedActionType } from '~/interfaces/activity';
+import type { SupportedActionType } from '~/interfaces/activity';
 import { useSWRxActivity } from '~/stores/activity';
 import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores/context';
 
@@ -213,7 +213,7 @@ export const AuditLogManagement: FC = () => {
           { isLoading
             ? (
               <div className="text-muted text-center mb-5">
-                <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+                <LoadingSpinner className="me-1 fs-3" />
               </div>
             )
             : (

+ 0 - 6
apps/app/src/components/Admin/Customize/CustomizeCssSetting.tsx

@@ -49,12 +49,6 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
               defaultValue={adminCustomizeContainer.state.currentCustomizeCss || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
             />
-            {/* disabled in v6.0.0 temporarily -- 2022.12.19 Yuki Takei
-            <p className="form-text text-muted text-end">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_settings.ctrl_space')}
-            </p>
-            */}
           </div>
 
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />

+ 3 - 2
apps/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx

@@ -2,6 +2,7 @@ import React, {
   useCallback, useEffect, useState,
 } from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 
 import { toastSuccess, toastError } from '~/client/util/toastr';
@@ -44,8 +45,8 @@ const CustomizeLayoutSetting = (): JSX.Element => {
 
   if (isContainerFluid == null) {
     return (
-      <div className="text-muted text-center">
-        <i className="fa fa-2x fa-spinner fa-pulse"></i>
+      <div className="text-muted text-center fs-3">
+        <LoadingSpinner />
       </div>
     );
   }

+ 0 - 6
apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx

@@ -53,12 +53,6 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
               defaultValue={adminCustomizeContainer.state.currentCustomizeNoscript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeNoscript(e.target.value) }}
             />
-            {/* disabled in v6.0.0 temporarily -- 2022.12.19 Yuki Takei
-            <span className="form-text text-muted text-end">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
-              {t('admin:customize_settings.ctrl_space')}
-            </span>
-            */}
           </div>
 
           <a

+ 0 - 6
apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -50,12 +50,6 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
               defaultValue={adminCustomizeContainer.state.currentCustomizeScript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
             />
-            {/* disabled in v6.0.0 temporarily -- 2022.12.19 Yuki Takei
-            <span className="form-text text-muted text-end">
-              <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_settings.ctrl_space')}
-            </span>
-            */}
           </div>
 
           <a

+ 3 - 1
apps/app/src/components/Admin/ElasticsearchManagement/ReconnectControls.tsx

@@ -1,7 +1,9 @@
 import React from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 
+
 type Props = {
   isEnabled?: boolean,
   isProcessing?: boolean,
@@ -21,7 +23,7 @@ const ReconnectControls = (props: Props): JSX.Element => {
         onClick={() => { props.onReconnectingRequested() }}
         disabled={!isEnabled}
       >
-        { isProcessing && <i className="fa fa-spinner fa-pulse me-2"></i> }
+        { isProcessing && <LoadingSpinner className="me-2" /> }
         { t('full_text_search_management.reconnect_button') }
       </button>
 

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff