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

Merge branch 'master' into support/142908-comment-style

satof3 2 лет назад
Родитель
Сommit
cbf001449e
65 измененных файлов с 311 добавлено и 3713 удалено
  1. 4 5
      .github/workflows/release-rc-scheduled.yml
  2. 0 23
      .github/workflows/release-rc.yml
  3. 1 8
      .github/workflows/release-slackbot-proxy.yml
  4. 2 27
      .github/workflows/release.yml
  5. 0 1
      .github/workflows/reusable-app-build-image.yml
  6. 0 1
      apps/app/.eslintignore
  7. 0 1191
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  8. 0 126
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss
  9. 0 65
      apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts
  10. 0 344
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  11. 0 174
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  12. 0 672
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  13. 0 536
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  14. 0 22
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  15. 1 6
      apps/app/docker/codebuild/buildspec.yml
  16. 1 1
      apps/app/package.json
  17. 10 0
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.module.scss
  18. 7 9
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  19. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx
  20. 17 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss
  21. 5 5
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  22. 3 2
      apps/app/src/components/DeleteBookmarkFolderModal.tsx
  23. 1 1
      apps/app/src/components/DescendantsPageListModal.tsx
  24. 8 0
      apps/app/src/components/ExpandOrContractButton.module.scss
  25. 9 2
      apps/app/src/components/ExpandOrContractButton.tsx
  26. 1 1
      apps/app/src/components/PageAccessoriesModal/PageAccessoriesModal.tsx
  27. 1 1
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  28. 1 1
      apps/app/src/components/PageControls/LikeButtons.tsx
  29. 1 2
      apps/app/src/components/PageControls/SeenUserInfo.tsx
  30. 3 2
      apps/app/src/components/PageControls/SubscribeButton.tsx
  31. 11 11
      apps/app/src/components/PageEditor/HandsontableModal.tsx
  32. 3 3
      apps/app/src/components/PageEditor/MarkdownTableDataImportForm.tsx
  33. 3 3
      apps/app/src/components/PageHeader/PagePathHeader.module.scss
  34. 2 2
      apps/app/src/components/PageHeader/PagePathHeader.tsx
  35. 14 12
      apps/app/src/components/PageHistory/PageRevisionTable.tsx
  36. 2 21
      apps/app/src/components/PageHistory/RevisionDiff.module.scss
  37. 48 26
      apps/app/src/components/PageHistory/RevisionDiff.tsx
  38. 0 5
      apps/app/src/components/PageStatusAlert.module.scss
  39. 8 0
      apps/app/src/components/RevisionComparer/RevisionComparer.module.scss
  40. 26 26
      apps/app/src/components/RevisionComparer/RevisionComparer.tsx
  41. 1 1
      apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx
  42. 3 3
      apps/app/src/components/Sidebar/RecentChanges/RecentChangesSubstance.tsx
  43. 6 2
      apps/app/src/components/Sidebar/Sidebar.tsx
  44. 9 0
      apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss
  45. 18 10
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.module.scss
  46. 2 2
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx
  47. 4 6
      apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.module.scss
  48. 8 7
      apps/app/src/features/comment/server/models/comment.ts
  49. 1 1
      apps/app/src/server/routes/comment.js
  50. 6 4
      apps/app/src/server/service/passport.ts
  51. 17 14
      apps/app/src/stores/modal.tsx
  52. 5 2
      apps/app/src/stores/page.tsx
  53. 0 8
      apps/app/src/styles/_mixins.scss
  54. 19 0
      apps/app/src/styles/_override-handsontable.scss
  55. 3 0
      apps/app/src/styles/atoms/_tooltip.scss
  56. 0 60
      apps/app/src/styles/atoms/mixins/_buttons.scss
  57. 1 0
      apps/app/src/styles/style-app.scss
  58. 3 0
      apps/app/src/styles/vendor.scss
  59. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/LinkEditButton.tsx
  60. 0 60
      packages/preset-themes/src/styles/atoms/mixins/_buttons.scss
  61. 0 5
      packages/preset-themes/src/styles/atoms/mixins/_code.scss
  62. 1 1
      packages/preset-themes/src/styles/christmas.scss
  63. 0 32
      packages/preset-themes/src/styles/theme/_hsl-functions.scss
  64. 0 147
      packages/preset-themes/src/styles/theme/mixins/_hsl-button.scss
  65. 9 9
      yarn.lock

+ 4 - 5
.github/workflows-archived/release-rc-v7.yml → .github/workflows/release-rc-scheduled.yml

@@ -1,10 +1,9 @@
-name: Release Docker Images for RC (for dev/7.0.x)
+name: Release Docker Images for RC (for master)
 
 on:
-  push:
-    branches:
-      - dev/7.0.x
-
+  schedule:
+    # Weekdays at 24:00hrs (JST) Executed
+    - cron: '0 15 * * 1-5'
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}

+ 0 - 23
.github/workflows/release-rc.yml

@@ -18,7 +18,6 @@ jobs:
 
     outputs:
       TAGS: ${{ steps.meta.outputs.tags }}
-      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
@@ -37,16 +36,6 @@ jobs:
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
 
-    - name: Docker meta for ghcr.io
-      uses: docker/metadata-action@v4
-      id: meta-ghcr
-      with:
-        images: ghcr.io/weseek/growi
-        sep-tags: ','
-        tags: |
-          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
-          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
-
 
   build-image-rc:
     uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
@@ -69,15 +58,3 @@ jobs:
     secrets:
       DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 
-  publish-image-rc-ghcr:
-    needs: [determine-tags, build-image-rc]
-
-    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
-    with:
-      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
-      registry: ghcr.io
-      image-name: weseek/growi
-      tag-temporary: latest-rc
-    secrets:
-      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-

+ 1 - 8
.github/workflows/release-slackbot-proxy.yml

@@ -26,7 +26,7 @@ jobs:
       id: meta
       uses: docker/metadata-action@v4
       with:
-        images: weseek/growi-slackbot-proxy,ghcr.io/weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
+        images: weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
         tags: |
           type=raw,value=latest
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
@@ -35,13 +35,6 @@ jobs:
       run: |
         echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
 
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v2
-      with:
-        registry: ghcr.io
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-
     - name: Authenticate to Google Cloud for GROWI.cloud
       uses: google-github-actions/auth@v1
       with:

+ 2 - 27
.github/workflows/release.yml

@@ -82,7 +82,6 @@ jobs:
 
     outputs:
       TAGS: ${{ steps.meta.outputs.tags }}
-      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
@@ -103,18 +102,6 @@ jobs:
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
 
-    - name: Docker meta for ghcr.io
-      uses: docker/metadata-action@v4
-      id: meta-ghcr
-      with:
-        images: ghcr.io/weseek/growi
-        sep-tags: ','
-        tags: |
-          type=raw,value=latest
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
-
 
   build-image:
     needs: create-github-release
@@ -140,21 +127,9 @@ jobs:
     secrets:
       DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 
-  publish-image-ghcr:
-    needs: [determine-tags, build-image]
-
-    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
-    with:
-      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
-      registry: ghcr.io
-      image-name: weseek/growi
-      tag-temporary: latest
-    secrets:
-      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-
 
   post-publish:
-    needs: [create-github-release, publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image]
     runs-on: ubuntu-latest
 
     steps:
@@ -179,7 +154,7 @@ jobs:
 
 
   create-pr-for-next-rc:
-    needs: [create-github-release, publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image]
     runs-on: ubuntu-latest
 
     steps:

+ 0 - 1
.github/workflows/reusable-app-build-image.yml

@@ -52,5 +52,4 @@ jobs:
         CODEBUILD__environmentTypeOverride: ${{ (matrix.platform == 'amd64' && 'LINUX_CONTAINER') || 'ARM_CONTAINER' }}
         CODEBUILD__environmentVariablesOverride: '[
           { "name": "IMAGE_TAG", "type": "PLAINTEXT", "value": "docker.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" },
-          { "name": "IMAGE_TAG_GHCR", "type": "PLAINTEXT", "value": "ghcr.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" }
         ]'

+ 0 - 1
apps/app/.eslintignore

@@ -1,4 +1,3 @@
-/_obsolete/**
 /dist/**
 /transpiled/**
 /public/**

+ 0 - 1191
apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -1,1191 +0,0 @@
-import React, { useCallback, memo } from 'react';
-
-import { commands } from 'codemirror';
-import * as loadCssSync from 'load-css-file';
-import PropTypes from 'prop-types';
-import { Button } from 'reactstrap';
-import * as loadScript from 'simple-load-script';
-import { throttle, debounce } from 'throttle-debounce';
-import urljoin from 'url-join';
-
-import InterceptorManager from '~/services/interceptor-manager';
-import {
-  useHandsontableModal, useDrawioModal, useTemplateModal, useLinkEditModal,
-} from '~/stores/modal';
-import loggerFactory from '~/utils/logger';
-
-import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
-
-import AbstractEditor from './AbstractEditor';
-import CommentMentionHelper from './CommentMentionHelper';
-import EditorIcon from './EditorIcon';
-import EmojiPicker from './EmojiPicker';
-import EmojiPickerHelper from './EmojiPickerHelper';
-import GridEditModal from './GridEditModal';
-// TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-// import geu from './GridEditorUtil';
-import mdu from './MarkdownDrawioUtil';
-import markdownLinkUtil from './MarkdownLinkUtil';
-import markdownListUtil from './MarkdownListUtil';
-import MarkdownTableInterceptor from './MarkdownTableInterceptor';
-import mtu from './MarkdownTableUtil';
-import pasteHelper from './PasteHelper';
-import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
-import SimpleCheatsheet from './SimpleCheatsheet';
-
-import styles from './CodeMirrorEditor.module.scss';
-
-require('codemirror/addon/hint/show-hint.css'); // Import from CodeMirrorEditor.module.scss not working
-require('codemirror/addon/display/placeholder');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/matchtags');
-require('codemirror/addon/edit/closetag');
-require('codemirror/addon/edit/continuelist');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/search/searchcursor');
-require('codemirror/addon/search/match-highlighter');
-require('codemirror/addon/selection/active-line');
-require('codemirror/addon/scroll/annotatescrollbar');
-require('codemirror/addon/scroll/scrollpastend');
-require('codemirror/addon/fold/foldcode');
-require('codemirror/addon/fold/foldgutter');
-require('codemirror/addon/fold/markdown-fold');
-require('codemirror/addon/fold/brace-fold');
-require('codemirror/addon/display/placeholder');
-require('~/client/util/codemirror/autorefresh.ext');
-require('~/client/util/codemirror/drawio-fold.ext');
-require('~/client/util/codemirror/gfm-growi.mode');
-// import modes to highlight
-require('codemirror/mode/clike/clike');
-require('codemirror/mode/css/css');
-require('codemirror/mode/django/django');
-require('codemirror/mode/erlang/erlang');
-require('codemirror/mode/gfm/gfm');
-require('codemirror/mode/go/go');
-require('codemirror/mode/javascript/javascript');
-require('codemirror/mode/jsx/jsx');
-require('codemirror/mode/mathematica/mathematica');
-require('codemirror/mode/nginx/nginx');
-require('codemirror/mode/perl/perl');
-require('codemirror/mode/php/php');
-require('codemirror/mode/python/python');
-require('codemirror/mode/r/r');
-require('codemirror/mode/ruby/ruby');
-require('codemirror/mode/rust/rust');
-require('codemirror/mode/sass/sass');
-require('codemirror/mode/shell/shell');
-require('codemirror/mode/sql/sql');
-require('codemirror/mode/stex/stex');
-require('codemirror/mode/stylus/stylus');
-require('codemirror/mode/swift/swift');
-require('codemirror/mode/toml/toml');
-require('codemirror/mode/vb/vb');
-require('codemirror/mode/vue/vue');
-require('codemirror/mode/xml/xml');
-require('codemirror/mode/yaml/yaml');
-
-
-const MARKDOWN_TABLE_ACTIVATED_CLASS = 'markdown-table-activated';
-const MARKDOWN_LINK_ACTIVATED_CLASS = 'markdown-link-activated';
-
-class CodeMirrorEditor extends AbstractEditor {
-
-  constructor(props) {
-    super(props);
-    this.logger = loggerFactory('growi:PageEditor:CodeMirrorEditor');
-
-    this.state = {
-      isGfmMode: this.props.isGfmMode,
-      isLoadingKeymap: false,
-      isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value?.length === 0,
-      isCheatsheetModalShown: false,
-      additionalClassSet: new Set(),
-      isEmojiPickerShown: false,
-      emojiSearchText: '',
-      startPosWithEmojiPickerModeTurnedOn: null,
-      isEmojiPickerMode: false,
-      isTemplateModalOpened: false,
-    };
-
-    this.cm = React.createRef();
-    this.gridEditModal = React.createRef();
-    this.linkEditModal = React.createRef();
-    this.drawioModal = React.createRef();
-
-    this.init();
-
-    this.getCodeMirror = this.getCodeMirror.bind(this);
-
-    this.getBol = this.getBol.bind(this);
-    this.getEol = this.getEol.bind(this);
-
-    this.loadTheme = this.loadTheme.bind(this);
-    this.loadKeymapMode = this.loadKeymapMode.bind(this);
-    this.setKeymapMode = this.setKeymapMode.bind(this);
-    this.handleEnterKey = this.handleEnterKey.bind(this);
-    this.handleCtrlEnterKey = this.handleCtrlEnterKey.bind(this);
-
-    this.scrollCursorIntoViewHandler = this.scrollCursorIntoViewHandler.bind(this);
-    this.scrollCursorIntoViewHandlerThrottled = throttle(500, this.scrollCursorIntoViewHandler);
-    this.pasteHandler = this.pasteHandler.bind(this);
-    this.cursorHandler = this.cursorHandler.bind(this);
-    this.cursorHandlerDebounced = debounce(200, throttle(500, this.cursorHandler));
-    this.changeHandler = this.changeHandler.bind(this);
-    this.turnOnEmojiPickerMode = this.turnOnEmojiPickerMode.bind(this);
-    this.turnOffEmojiPickerMode = this.turnOffEmojiPickerMode.bind(this);
-    this.windowClickHandler = this.windowClickHandler.bind(this);
-    this.keyDownHandler = this.keyDownHandler.bind(this);
-    this.keyDownHandlerForEmojiPicker = this.keyDownHandlerForEmojiPicker.bind(this);
-    this.keyDownHandlerForEmojiPickerThrottled = throttle(400, this.keyDownHandlerForEmojiPicker);
-    this.showEmojiPicker = this.showEmojiPicker.bind(this);
-    this.keyPressHandlerForEmojiPicker = this.keyPressHandlerForEmojiPicker.bind(this);
-    this.keyPressHandlerForEmojiPickerThrottled = debounce(50, throttle(200, this.keyPressHandlerForEmojiPicker));
-    this.keyPressHandler = this.keyPressHandler.bind(this);
-
-    this.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
-
-    this.renderLoadingKeymapOverlay = this.renderLoadingKeymapOverlay.bind(this);
-    this.renderCheatsheetModalButton = this.renderCheatsheetModalButton.bind(this);
-
-    this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
-    // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-    // this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
-
-    this.foldDrawioSection = this.foldDrawioSection.bind(this);
-    this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
-    this.clickTableIconHandler = this.clickTableIconHandler.bind(this);
-
-    this.showTemplateModal = this.showTemplateModal.bind(this);
-    this.showLinkEditModal = this.showLinkEditModal.bind(this);
-
-  }
-
-  init() {
-    this.cmCdnRoot = 'https://cdn.jsdelivr.net/npm/codemirror@5.42.0';
-    this.cmNoCdnScriptRoot = '/static/js/cdn';
-    this.cmNoCdnStyleRoot = '/static/styles/cdn';
-    this.interceptorManager = new InterceptorManager();
-    this.interceptorManager.addInterceptors([
-      new PreventMarkdownListInterceptor(),
-      new MarkdownTableInterceptor(),
-    ]);
-
-    this.loadedThemeSet = new Set(['eclipse', 'elegant']); // themes imported in _vendor.scss
-    this.loadedKeymapSet = new Set();
-  }
-
-  componentDidMount() {
-    // ensure to be able to resolve 'this' to use 'codemirror.commands.save'
-    this.getCodeMirror().codeMirrorEditor = this;
-
-    // mark clean
-    this.getCodeMirror().getDoc().markClean();
-
-    // fold drawio section
-    this.foldDrawioSection();
-
-    // initialize commentMentionHelper if comment editor is opened
-    if (this.props.isComment) {
-      this.commentMentionHelper = new CommentMentionHelper(this.getCodeMirror());
-    }
-    this.emojiPickerHelper = new EmojiPickerHelper(this.getCodeMirror());
-
-    // HACKME: Find a better way to handle onClick for Editor
-    document.addEventListener('click', this.windowClickHandler);
-  }
-
-  componentWillUnmount() {
-    // HACKME: Find a better way to handle onClick for Editor
-    document.removeEventListener('click', this.windowClickHandler);
-  }
-
-  componentWillReceiveProps(nextProps) {
-    this.initializeEditorSettings(nextProps.editorSettings);
-
-    // fold drawio section
-    this.foldDrawioSection();
-  }
-
-  initializeEditorSettings(editorSettings) {
-    if (editorSettings == null) {
-      return;
-    }
-
-    // load theme
-    const theme = editorSettings.theme;
-    if (theme != null) {
-      this.loadTheme(theme);
-    }
-
-    // set keymap
-    const keymapMode = editorSettings.keymapMode;
-    if (keymapMode != null) {
-      this.setKeymapMode(keymapMode);
-    }
-  }
-
-  getCodeMirror() {
-    return this.cm.current?.editor;
-  }
-
-  /**
-   * @inheritDoc
-   */
-  forceToFocus() {
-    // use setInterval with reluctance -- 2018.01.11 Yuki Takei
-    const intervalId = setInterval(() => {
-      const editor = this.getCodeMirror();
-      editor.focus();
-      if (editor.hasFocus()) {
-        clearInterval(intervalId);
-        // refresh
-        editor.refresh();
-      }
-    }, 100);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setValue(newValue) {
-    this.getCodeMirror().getDoc().setValue(newValue);
-
-    // mark clean
-    this.getCodeMirror().getDoc().markClean();
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setGfmMode(bool) {
-    // update state
-    this.setState({
-      isGfmMode: bool,
-    });
-
-    this.updateCheatsheetStates(bool, null);
-
-    // update CodeMirror option
-    const mode = bool ? 'gfm' : undefined;
-    this.getCodeMirror().setOption('mode', mode);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setCaretLine(line) {
-    if (Number.isNaN(line)) {
-      return;
-    }
-
-    const editor = this.getCodeMirror();
-    const linePosition = Math.max(0, line - 1);
-
-    editor.setCursor({ line: linePosition }); // leave 'ch' field as null/undefined to indicate the end of line
-
-    setTimeout(() => {
-      this.setScrollTopByLine(linePosition);
-    }, 100);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setScrollTopByLine(line) {
-    if (Number.isNaN(line)) {
-      return;
-    }
-
-    const editor = this.getCodeMirror();
-    // get top position of the line
-    const top = editor.charCoords({ line: line - 1, ch: 0 }, 'local').top;
-    editor.scrollTo(null, top);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return editor.getDoc().getRange(this.getBol(), curPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrToEol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return editor.getDoc().getRange(curPos, this.getEol());
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBolToSelectedUpperPos() {
-    const editor = this.getCodeMirror();
-    const pos = this.selectUpperPos(editor.getCursor('from'), editor.getCursor('to'));
-    return editor.getDoc().getRange(this.getBol(), pos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceBolToCurrentPos(text) {
-    const editor = this.getCodeMirror();
-    const pos = this.selectLowerPos(editor.getCursor('from'), editor.getCursor('to'));
-    editor.getDoc().replaceRange(text, this.getBol(), pos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceLine(text) {
-    const editor = this.getCodeMirror();
-    editor.getDoc().replaceRange(text, this.getBol(), this.getEol());
-  }
-
-  /**
-   * @inheritDoc
-   */
-  insertText(text) {
-    const editor = this.getCodeMirror();
-    editor.getDoc().replaceSelection(text);
-  }
-
-  /**
-   * return the postion of the BOL(beginning of line)
-   */
-  getBol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return { line: curPos.line, ch: 0 };
-  }
-
-  /**
-   * return the postion of the EOL(end of line)
-   */
-  getEol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    const lineLength = editor.getDoc().getLine(curPos.line).length;
-    return { line: curPos.line, ch: lineLength };
-  }
-
-  /**
-   * select the upper position of pos1 and pos2
-   * @param {{line: number, ch: number}} pos1
-   * @param {{line: number, ch: number}} pos2
-   */
-  selectUpperPos(pos1, pos2) {
-    // if both is in same line
-    if (pos1.line === pos2.line) {
-      return (pos1.ch < pos2.ch) ? pos1 : pos2;
-    }
-    return (pos1.line < pos2.line) ? pos1 : pos2;
-  }
-
-  /**
-   * select the lower position of pos1 and pos2
-   * @param {{line: number, ch: number}} pos1
-   * @param {{line: number, ch: number}} pos2
-   */
-  selectLowerPos(pos1, pos2) {
-    // if both is in same line
-    if (pos1.line === pos2.line) {
-      return (pos1.ch < pos2.ch) ? pos2 : pos1;
-    }
-    return (pos1.line < pos2.line) ? pos2 : pos1;
-  }
-
-  loadCss(source) {
-    return new Promise((resolve) => {
-      loadCssSync(source);
-      resolve();
-    });
-  }
-
-  /**
-   * load Theme
-   * @see https://codemirror.net/doc/manual.html#config
-   *
-   * @param {string} theme
-   */
-  loadTheme(theme) {
-    if (!this.loadedThemeSet.has(theme)) {
-      const url = this.props.noCdn
-        ? urljoin(this.cmNoCdnStyleRoot, `codemirror-theme-${theme}.css`)
-        : urljoin(this.cmCdnRoot, `theme/${theme}.min.css`);
-
-      this.loadCss(url);
-
-      // update Set
-      this.loadedThemeSet.add(theme);
-    }
-  }
-
-  /**
-   * load assets for Key Maps
-   * @param {*} keymapMode 'default' or 'vim' or 'emacs' or 'sublime'
-   */
-  loadKeymapMode(keymapMode) {
-    const loadCss = this.loadCss;
-    const scriptList = [];
-    const cssList = [];
-
-    // add dependencies
-    if (this.loadedKeymapSet.size === 0) {
-      const dialogScriptUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnScriptRoot, 'codemirror-dialog.js')
-        : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.js');
-      const dialogStyleUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnStyleRoot, 'codemirror-dialog.css')
-        : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.css');
-
-      scriptList.push(loadScript(dialogScriptUrl));
-      cssList.push(loadCss(dialogStyleUrl));
-    }
-    // load keymap
-    if (!this.loadedKeymapSet.has(keymapMode)) {
-      const keymapScriptUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnScriptRoot, `codemirror-keymap-${keymapMode}.js`)
-        : urljoin(this.cmCdnRoot, `keymap/${keymapMode}.min.js`);
-      scriptList.push(loadScript(keymapScriptUrl));
-      // update Set
-      this.loadedKeymapSet.add(keymapMode);
-    }
-
-    // set loading state
-    this.setState({ isLoadingKeymap: true });
-
-    return Promise.all(scriptList.concat(cssList))
-      .then(() => {
-        this.setState({ isLoadingKeymap: false });
-      });
-  }
-
-  /**
-   * set Key Maps
-   * @see https://codemirror.net/doc/manual.html#keymaps
-   *
-   * @param {string} keymapMode 'default' or 'vim' or 'emacs' or 'sublime'
-   */
-  setKeymapMode(keymapMode) {
-    if (!keymapMode.match(/^(vim|emacs|sublime)$/)) {
-      // reset
-      this.getCodeMirror().setOption('keyMap', 'default');
-      return;
-    }
-
-    this.loadKeymapMode(keymapMode)
-      .then(() => {
-        let errorCount = 0;
-        const timer = setInterval(() => {
-          if (errorCount > 10) { // cancel over 3000ms
-            this.logger.error(`Timeout to load keyMap '${keymapMode}'`);
-            clearInterval(timer);
-          }
-
-          try {
-            this.getCodeMirror().setOption('keyMap', keymapMode);
-            clearInterval(timer);
-          }
-          catch (e) {
-            this.logger.info(`keyMap '${keymapMode}' has not been initialized. retry..`);
-
-            // continue if error occured
-            errorCount++;
-          }
-        }, 300);
-      });
-  }
-
-  /**
-   * handle ENTER key
-   */
-  handleEnterKey() {
-    if (!this.state.isGfmMode) {
-      commands.newlineAndIndent(this.getCodeMirror());
-      return;
-    }
-
-    const context = {
-      handlers: [], // list of handlers which process enter key
-      editor: this,
-      autoFormatMarkdownTable: this.props.editorSettings.autoFormatMarkdownTable,
-    };
-
-    const interceptorManager = this.interceptorManager;
-    interceptorManager.process('preHandleEnter', context)
-      .then(() => {
-        if (context.handlers.length === 0) {
-          markdownListUtil.newlineAndIndentContinueMarkdownList(this);
-        }
-      });
-  }
-
-  /**
-   * handle Ctrl+ENTER key
-   */
-  handleCtrlEnterKey() {
-    if (this.props.onCtrlEnter != null) {
-      this.props.onCtrlEnter();
-    }
-  }
-
-  scrollCursorIntoViewHandler(editor, event) {
-    if (this.props.onScrollCursorIntoView != null) {
-      const line = editor.getCursor().line;
-      this.props.onScrollCursorIntoView(line);
-    }
-  }
-
-  cursorHandler(editor, event) {
-    const { additionalClassSet } = this.state;
-    const hasCustomClass = additionalClassSet.has(MARKDOWN_TABLE_ACTIVATED_CLASS);
-    const hasLinkClass = additionalClassSet.has(MARKDOWN_LINK_ACTIVATED_CLASS);
-
-    const isInTable = mtu.isInTable(editor);
-    const isInLink = markdownLinkUtil.isInLink(editor);
-
-    if (!hasCustomClass && isInTable) {
-      additionalClassSet.add(MARKDOWN_TABLE_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (hasCustomClass && !isInTable) {
-      additionalClassSet.delete(MARKDOWN_TABLE_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (!hasLinkClass && isInLink) {
-      additionalClassSet.add(MARKDOWN_LINK_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (hasLinkClass && !isInLink) {
-      additionalClassSet.delete(MARKDOWN_LINK_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-  }
-
-  changeHandler(editor, data, value) {
-    if (this.props.onChange != null) {
-      const isClean = data.origin == null || editor.isClean() || value === this.props.value;
-      this.props.onChange(value, isClean);
-    }
-
-    this.updateCheatsheetStates(null, value);
-
-    // Show username hint on comment editor
-    if (this.props.isComment) {
-      this.commentMentionHelper.showUsernameHint();
-    }
-
-  }
-
-  turnOnEmojiPickerMode(pos) {
-    this.setState({
-      isEmojiPickerMode: true,
-      startPosWithEmojiPickerModeTurnedOn: pos,
-    });
-  }
-
-  turnOffEmojiPickerMode() {
-    this.setState({
-      isEmojiPickerMode: false,
-    });
-  }
-
-  showEmojiPicker(initialSearchingText) {
-    // show emoji picker with a stored word
-    this.setState({
-      isEmojiPickerShown: true,
-      emojiSearchText: initialSearchingText ?? '',
-    });
-
-    const resetStartPos = initialSearchingText == null;
-    if (resetStartPos) {
-      this.setState({ startPosWithEmojiPickerModeTurnedOn: null });
-    }
-
-    this.turnOffEmojiPickerMode();
-  }
-
-  keyPressHandlerForEmojiPicker(editor, event) {
-    const char = event.key;
-    const isEmojiPickerMode = this.state.isEmojiPickerMode;
-
-    // evaluate whether emoji picker mode to be turned on
-    if (!isEmojiPickerMode) {
-      const startPos = this.emojiPickerHelper.shouldModeTurnOn(char);
-      if (startPos == null) {
-        return;
-      }
-
-      this.turnOnEmojiPickerMode(startPos);
-      return;
-    }
-
-    // evaluate whether EmojiPicker to be opened
-    const startPos = this.state.startPosWithEmojiPickerModeTurnedOn;
-    if (this.emojiPickerHelper.shouldOpen(startPos)) {
-      const initialSearchingText = this.emojiPickerHelper.getInitialSearchingText(startPos);
-      this.showEmojiPicker(initialSearchingText);
-      return;
-    }
-
-    this.turnOffEmojiPickerMode();
-  }
-
-  keyPressHandler(editor, event) {
-    this.keyPressHandlerForEmojiPickerThrottled(editor, event);
-  }
-
-  keyDownHandlerForEmojiPicker(editor, event) {
-    const key = event.key;
-
-    if (!this.state.isEmojiPickerMode) {
-      return;
-    }
-
-    if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'BackSpace'].includes(key)) {
-      this.turnOffEmojiPickerMode();
-    }
-  }
-
-  keyDownHandler(editor, event) {
-    this.keyDownHandlerForEmojiPickerThrottled(editor, event);
-  }
-
-  windowClickHandler() {
-    this.turnOffEmojiPickerMode();
-  }
-
-  /**
-   * CodeMirror paste event handler
-   * see: https://codemirror.net/doc/manual.html#events
-   * @param {any} editor An editor instance of CodeMirror
-   * @param {any} event
-   */
-  pasteHandler(editor, 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);
-    }
-
-  }
-
-  /**
-   * update states which related to cheatsheet
-   * @param {boolean} isGfmModeTmp (use state.isGfmMode if null is set)
-   * @param {string} valueTmp (get value from codemirror if null is set)
-   */
-  updateCheatsheetStates(isGfmModeTmp, valueTmp) {
-    const isGfmMode = isGfmModeTmp || this.state.isGfmMode;
-    const value = valueTmp || this.getCodeMirror().getDoc().getValue();
-
-    // update isSimpleCheatsheetShown
-    const isSimpleCheatsheetShown = isGfmMode && value.length === 0;
-    this.setState({ isSimpleCheatsheetShown });
-  }
-
-  markdownHelpButtonClickedHandler() {
-    if (this.props.onMarkdownHelpButtonClicked != null) {
-      this.props.onMarkdownHelpButtonClicked();
-    }
-  }
-
-  renderLoadingKeymapOverlay() {
-    // centering
-    const style = {
-      top: 0,
-      right: 0,
-      bottom: 0,
-      left: 0,
-    };
-
-    return this.state.isLoadingKeymap
-      ? (
-        <div className="overlay overlay-loading-keymap">
-          <span style={style} className="overlay-content">
-            <div className="speeding-wheel d-inline-block"></div> Loading Keymap ...
-          </span>
-        </div>
-      )
-      : '';
-  }
-
-  renderCheatsheetModalButton() {
-    return (
-      <button type="button" className="btn-link gfm-cheatsheet-modal-link small" onClick={() => { this.markdownHelpButtonClickedHandler() }}>
-        <span className="material-symbols-outlined">help</span> Markdown
-      </button>
-    );
-  }
-
-  renderCheatsheetOverlay() {
-    const cheatsheetModalButton = this.renderCheatsheetModalButton();
-
-    return (
-      <div className="overlay overlay-gfm-cheatsheet mt-1 p-3">
-        { this.state.isSimpleCheatsheetShown
-          ? (
-            <div className="text-end">
-              {cheatsheetModalButton}
-              <div className="mb-2 d-none d-md-block">
-                <SimpleCheatsheet />
-              </div>
-            </div>
-          )
-          : (
-            <div className="me-4 mb-2">
-              {cheatsheetModalButton}
-            </div>
-          )
-        }
-      </div>
-    );
-  }
-
-  renderEmojiPicker() {
-    const { emojiSearchText } = this.state;
-    return this.state.isEmojiPickerShown
-      ? (
-        <div className="text-start">
-          <div className="mb-2 d-none d-md-block">
-            <EmojiPicker
-              onClose={() => this.setState({ isEmojiPickerShown: false })}
-              onSelected={emoji => this.emojiPickerHelper.addEmoji(emoji, this.state.startPosWithEmojiPickerModeTurnedOn)}
-              emojiSearchText={emojiSearchText}
-              emojiPickerHelper={this.emojiPickerHelper}
-              isOpen={this.state.isEmojiPickerShown}
-            />
-          </div>
-        </div>
-      )
-      : '';
-  }
-
-  /**
-   * return a function to replace a selected range with prefix + selection + suffix
-   *
-   * The cursor after replacing is inserted between the selection and the suffix.
-   */
-  createReplaceSelectionHandler(prefix, suffix) {
-    return () => {
-      const cm = this.getCodeMirror();
-      const selection = cm.getDoc().getSelection();
-      const curStartPos = cm.getCursor('from');
-      const curEndPos = cm.getCursor('to');
-
-      const curPosAfterReplacing = {};
-      curPosAfterReplacing.line = curEndPos.line;
-      if (curStartPos.line === curEndPos.line) {
-        curPosAfterReplacing.ch = curEndPos.ch + prefix.length;
-      }
-      else {
-        curPosAfterReplacing.ch = curEndPos.ch;
-      }
-
-      cm.getDoc().replaceSelection(prefix + selection + suffix);
-      cm.setCursor(curPosAfterReplacing);
-      cm.focus();
-    };
-  }
-
-  /**
-   * return a function to add prefix to selected each lines
-   *
-   * The cursor after editing is inserted between the end of the selection.
-   */
-  createAddPrefixToEachLinesHandler(prefix) {
-    return () => {
-      const cm = this.getCodeMirror();
-      const startLineNum = cm.getCursor('from').line;
-      const endLineNum = cm.getCursor('to').line;
-
-      const lines = [];
-      for (let i = startLineNum; i <= endLineNum; i++) {
-        lines.push(prefix + cm.getDoc().getLine(i));
-      }
-      const replacement = `${lines.join('\n')}\n`;
-      cm.getDoc().replaceRange(replacement, { line: startLineNum, ch: 0 }, { line: endLineNum + 1, ch: 0 });
-
-      cm.setCursor(endLineNum, cm.getDoc().getLine(endLineNum).length);
-      cm.focus();
-    };
-  }
-
-  /**
-   * make a selected line a header
-   *
-   * The cursor after editing is inserted between the end of the line.
-   */
-  makeHeaderHandler() {
-    const cm = this.getCodeMirror();
-    const lineNum = cm.getCursor('from').line;
-    const line = cm.getDoc().getLine(lineNum);
-    let prefix = '#';
-    if (!line.startsWith('#')) {
-      prefix += ' ';
-    }
-    cm.getDoc().replaceRange(prefix, { line: lineNum, ch: 0 }, { line: lineNum, ch: 0 });
-    cm.focus();
-  }
-
-  // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-  // showGridEditorHandler() {
-  //   this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
-  // }
-
-  showTemplateModal() {
-    const onSubmit = templateText => this.insertText(templateText);
-    this.props.onClickTemplateBtn({ onSubmit });
-  }
-
-  showLinkEditModal() {
-    const onSubmit = (linkText) => {
-      return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText);
-    };
-
-    const defaultMarkdownLink = markdownLinkUtil.getMarkdownLink(this.getCodeMirror());
-
-    this.props.onClickLinkEditBtn(defaultMarkdownLink, onSubmit);
-  }
-
-  // fold draw.io section (``` drawio ~ ```)
-  foldDrawioSection() {
-    const editor = this.getCodeMirror();
-    const lineNumbers = mdu.findAllDrawioSection(editor);
-    lineNumbers.forEach((lineNumber) => {
-      editor.foldCode({ line: lineNumber, ch: 0 }, { scanUp: false }, 'fold');
-    });
-  }
-
-  clickDrawioIconHandler() {
-    const drawioMxFile = mdu.getMarkdownDrawioMxfile(this.getCodeMirror());
-
-    this.props.onClickDrawioBtn(
-      drawioMxFile,
-      // onSave
-      (drawioMxFile) => {
-        mdu.replaceFocusedDrawioWithEditor(this.getCodeMirror(), drawioMxFile);
-        // Fold the section after the drawio section (```drawio) has been updated.
-        this.foldDrawioSection();
-      },
-    );
-  }
-
-  clickTableIconHandler() {
-    const markdownTable = mtu.getMarkdownTable(this.getCodeMirror());
-
-    this.props.onClickTableBtn(
-      markdownTable,
-      this.getCodeMirror(),
-      this.props.editorSettings.autoFormatMarkdownTable,
-    );
-  }
-
-  getNavbarItems() {
-    return [
-      <Button
-        key="nav-item-bold"
-        color={null}
-        size="sm"
-        title="Bold"
-        onClick={this.createReplaceSelectionHandler('**', '**')}
-      >
-        <EditorIcon icon="Bold" />
-      </Button>,
-      <Button
-        key="nav-item-italic"
-        color={null}
-        size="sm"
-        title="Italic"
-        onClick={this.createReplaceSelectionHandler('*', '*')}
-      >
-        <EditorIcon icon="Italic" />
-      </Button>,
-      <Button
-        key="nav-item-strikethrough"
-        color={null}
-        size="sm"
-        title="Strikethrough"
-        onClick={this.createReplaceSelectionHandler('~~', '~~')}
-      >
-        <EditorIcon icon="Strikethrough" />
-      </Button>,
-      <Button
-        key="nav-item-header"
-        color={null}
-        size="sm"
-        title="Heading"
-        onClick={this.makeHeaderHandler}
-      >
-        <EditorIcon icon="Heading" />
-      </Button>,
-      <Button
-        key="nav-item-code"
-        color={null}
-        size="sm"
-        title="Inline Code"
-        onClick={this.createReplaceSelectionHandler('`', '`')}
-      >
-        <EditorIcon icon="InlineCode" />
-      </Button>,
-      <Button
-        key="nav-item-quote"
-        color={null}
-        size="sm"
-        title="Quote"
-        onClick={this.createAddPrefixToEachLinesHandler('> ')}
-      >
-        <EditorIcon icon="Quote" />
-      </Button>,
-      <Button
-        key="nav-item-ul"
-        color={null}
-        size="sm"
-        title="List"
-        onClick={this.createAddPrefixToEachLinesHandler('- ')}
-      >
-        <EditorIcon icon="List" />
-      </Button>,
-      <Button
-        key="nav-item-ol"
-        color={null}
-        size="sm"
-        title="Numbered List"
-        onClick={this.createAddPrefixToEachLinesHandler('1. ')}
-      >
-        <EditorIcon icon="NumberedList" />
-      </Button>,
-      <Button
-        key="nav-item-checkbox"
-        color={null}
-        size="sm"
-        title="Check List"
-        onClick={this.createAddPrefixToEachLinesHandler('- [ ] ')}
-      >
-        <EditorIcon icon="CheckList" />
-      </Button>,
-      <Button
-        key="nav-item-attachment"
-        color={null}
-        size="sm"
-        title="Attachment"
-        onClick={this.props.onAddAttachmentButtonClicked}
-      >
-        <EditorIcon icon="Attachment" />
-      </Button>,
-      <Button
-        key="nav-item-link"
-        color={null}
-        size="sm"
-        title="Link"
-        onClick={this.showLinkEditModal}
-      >
-        <EditorIcon icon="Link" />
-      </Button>,
-      <Button
-        key="nav-item-image"
-        color={null}
-        size="sm"
-        title="Image"
-        onClick={this.createReplaceSelectionHandler('![', ']()')}
-      >
-        <EditorIcon icon="Image" />
-      </Button>,
-      // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-      // <Button
-      //   key="nav-item-grid"
-      //   color={null}
-      //   size="sm"
-      //   title="Grid"
-      //   onClick={this.showGridEditorHandler}
-      // >
-      //   <EditorIcon icon="Grid" />
-      // </Button>,
-      <Button
-        key="nav-item-table"
-        color={null}
-        size="sm"
-        title="Table"
-        onClick={this.clickTableIconHandler}
-      >
-        <EditorIcon icon="Table" />
-      </Button>,
-      <Button
-        key="nav-item-drawio"
-        color={null}
-        bssize="small"
-        title="draw.io"
-        onClick={this.clickDrawioIconHandler}
-      >
-        <EditorIcon icon="Drawio" />
-      </Button>,
-      <Button
-        key="nav-item-emoji"
-        color={null}
-        bssize="small"
-        title="Emoji"
-        onClick={() => this.showEmojiPicker()}
-      >
-        <EditorIcon icon="Emoji" />
-      </Button>,
-      <Button
-        key="nav-item-template"
-        color={null}
-        bssize="small"
-        title="Template"
-        onClick={() => this.showTemplateModal()}
-      >
-        <EditorIcon icon="Template" />
-      </Button>,
-    ];
-  }
-
-
-  render() {
-    const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
-    const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plain Text..';
-
-    const gutters = [];
-    if (this.props.lineNumbers != null) {
-      gutters.push('CodeMirror-linenumbers', 'CodeMirror-foldgutter');
-    }
-
-    return (
-      <div className={`grw-codemirror-editor ${styles['grw-codemirror-editor']}`}>
-
-        <UncontrolledCodeMirror
-          ref={this.cm}
-          className={additionalClasses}
-          placeholder="search"
-          value={this.props.value}
-          options={{
-            indentUnit: this.props.indentSize,
-            theme: this.props.editorSettings.theme ?? 'elegant',
-            styleActiveLine: this.props.editorSettings.styleActiveLine,
-            lineWrapping: true,
-            scrollPastEnd: true,
-            autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-            autoCloseTags: true,
-            placeholder,
-            matchBrackets: true,
-            emoji: true,
-            matchTags: { bothTags: true },
-            // folding
-            foldGutter: this.props.lineNumbers,
-            gutters,
-            // match-highlighter, matchesonscrollbar, annotatescrollbar options
-            highlightSelectionMatches: { annotateScrollbar: true },
-            // continuelist, indentlist
-            extraKeys: {
-              Enter: this.handleEnterKey,
-              'Ctrl-Enter': this.handleCtrlEnterKey,
-              'Cmd-Enter': this.handleCtrlEnterKey,
-              Tab: 'indentMore',
-              'Shift-Tab': 'indentLess',
-              'Ctrl-Q': (cm) => { cm.foldCode(cm.getCursor()) },
-            },
-          }}
-          onCursor={this.cursorHandlerDebounced}
-          onScroll={(editor, data) => {
-            if (this.props.onScroll != null) {
-            // add line data
-              const line = editor.lineAtHeight(data.top, 'local');
-              data.line = line;
-              this.props.onScroll(data);
-            }
-          }}
-          onChange={this.changeHandler}
-          onDragEnter={(editor, event) => {
-            if (this.props.onDragEnter != null) {
-              this.props.onDragEnter(event);
-            }
-          }}
-          onKeyPress={this.keyPressHandler}
-          onKeyDown={this.keyDownHandler}
-          onPasteFiles={this.pasteHandler}
-          onScrollCursorIntoView={this.scrollCursorIntoViewHandlerThrottled}
-        />
-
-        { this.renderLoadingKeymapOverlay() }
-
-        { this.renderCheatsheetOverlay() }
-        { this.renderEmojiPicker() }
-
-        {/*
-        // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-        <GridEditModal
-          ref={this.gridEditModal}
-          onSave={(grid) => { return geu.replaceGridWithHtmlWithEditor(this.getCodeMirror(), grid) }}
-        />
-         */}
-      </div>
-    );
-  }
-
-}
-
-CodeMirrorEditor.propTypes = Object.assign({
-  lineNumbers: PropTypes.bool,
-  editorSettings: PropTypes.object.isRequired,
-  onMarkdownHelpButtonClicked: PropTypes.func,
-  onAddAttachmentButtonClicked: PropTypes.func,
-}, AbstractEditor.propTypes);
-
-CodeMirrorEditor.defaultProps = {
-  lineNumbers: true,
-};
-
-const CodeMirrorEditorMemoized = memo(CodeMirrorEditor);
-
-
-const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
-  const { open: openDrawioModal } = useDrawioModal();
-  const { open: openHandsontableModal } = useHandsontableModal();
-  const { open: openTemplateModal } = useTemplateModal();
-  const { open: openLinkEditModal } = useLinkEditModal();
-
-  const openDrawioModalHandler = useCallback((drawioMxFile, onSave) => {
-    openDrawioModal(drawioMxFile, onSave);
-  }, [openDrawioModal]);
-
-  const openTableModalHandler = useCallback((markdownTable, editor, autoFormatMarkdownTable) => {
-    openHandsontableModal(markdownTable, editor, autoFormatMarkdownTable);
-  }, [openHandsontableModal]);
-
-  const openTemplateModalHandler = useCallback((onSubmit) => {
-    openTemplateModal(onSubmit);
-  }, [openTemplateModal]);
-
-  const openLinkEditModalHandler = useCallback((defaultMarkdownLink, onSubmit) => {
-    openLinkEditModal(defaultMarkdownLink, onSubmit);
-  }, [openLinkEditModal]);
-
-  return (
-    <CodeMirrorEditorMemoized
-      ref={ref}
-      onClickDrawioBtn={openDrawioModalHandler}
-      onClickTableBtn={openTableModalHandler}
-      onClickTemplateBtn={openTemplateModalHandler}
-      onClickLinkEditBtn={openLinkEditModalHandler}
-      {...props}
-    />
-  );
-});
-
-CodeMirrorEditorFc.displayName = 'CodeMirrorEditorFc';
-
-export default memo(CodeMirrorEditorFc);

+ 0 - 126
apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss

@@ -1,126 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-.grw-codemirror-editor :global {
-  @import '~codemirror/lib/codemirror';
-
-  // addons
-  @import '~codemirror/addon/fold/foldgutter';
-  @import '~codemirror/addon/lint/lint';
-
-  // themes
-  @import '~codemirror/theme/elegant';
-  @import '~codemirror/theme/eclipse';
-
-  .CodeMirror {
-    font-family: var(--font-family-monospace);
-    font-size: 15px;
-
-    pre.CodeMirror-line.grw-cm-header-line {
-      padding-top: 0.16em;
-      padding-bottom: 0.08em;
-      font-family: var(--font-family-monospace);
-
-      // '#'
-      .cm-formatting-header {
-        font-style: italic;
-        font-weight: bold;
-        opacity: 0.5;
-      }
-
-      .cm-header-1 {
-        font-size: 1.9em;
-      }
-      .cm-header-2 {
-        font-size: 1.6em;
-      }
-      .cm-header-3 {
-        font-size: 1.4em;
-      }
-      .cm-header-4 {
-        font-size: 1.35em;
-      }
-      .cm-header-5 {
-        font-size: 1.25em;
-      }
-      .cm-header-6 {
-        font-size: 1.2em;
-      }
-    }
-
-    .cm-matchhighlight {
-      color: bs.$gray-900 !important;
-      background-color: cyan;
-    }
-
-    .CodeMirror-selection-highlight-scrollbar {
-      background-color: darkcyan;
-    }
-
-    // overwrite .CodeMirror-placeholder
-    pre.CodeMirror-line-like.CodeMirror-placeholder {
-      color: bs.$text-muted;
-    }
-
-    // overwrite .CodeMirror-scroll
-    .CodeMirror-scroll {
-      box-sizing: border-box;
-    }
-  }
-
-  // patch to fix https://github.com/codemirror/CodeMirror/issues/4089
-  // see also https://github.com/codemirror/CodeMirror/commit/51a1e7da60a99e019f026a118dc7c98c2b1f9d62
-  .CodeMirror-wrap > div > textarea {
-    font-size: #{bs.$line-height-base}rem;
-  }
-
-  // overwrite .CodeMirror-hints
-  .CodeMirror-hints {
-    max-height: 30em !important;
-
-    // active line
-    .CodeMirror-hint-active.crowi-emoji-autocomplete {
-      .img-container {
-        padding-top: 0.3em;
-        padding-bottom: 0.3em;
-        font-size: 15px; // adjust to .wiki
-      }
-    }
-  }
-
-  // cheat sheat
-  .overlay.overlay-gfm-cheatsheet {
-    align-items: flex-end;
-    justify-content: flex-end;
-
-    pointer-events: none;
-
-    .card.gfm-cheatsheet {
-      box-shadow: unset;
-      opacity: 0.6;
-      .card-body {
-        min-width: 30em;
-        padding-bottom: 0;
-        font-family: var(--font-family-monospace);
-        color: bs.$text-muted;
-      }
-      ul > li {
-        list-style: none;
-      }
-    }
-
-    .gfm-cheatsheet-modal-link {
-      color: bs.$text-muted;
-      pointer-events: all;
-      cursor: pointer;
-      background-color: transparent;
-      border: none;
-
-      opacity: 0.6;
-
-      &:hover,
-      &:focus {
-        opacity: 1;
-      }
-    }
-  }
-}

+ 0 - 65
apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts

@@ -1,65 +0,0 @@
-import { Editor } from 'codemirror';
-import { i18n } from 'next-i18next';
-import { debounce } from 'throttle-debounce';
-
-import { apiv3Get } from '~/client/util/apiv3-client';
-
-type UsersListForHints = {
-  text: string
-  displayText: string
-}
-export default class CommentMentionHelper {
-
-  editor: Editor;
-
-  constructor(editor: Editor) {
-    this.editor = editor;
-  }
-
-  getUsenameHint = (): void => {
-    // Get word that contains `@` character at the begining
-    const currentPos = this.editor.getCursor();
-    const wordStart = this.editor.findWordAt(currentPos).anchor.ch - 1;
-    const wordEnd = this.editor.findWordAt(currentPos).head.ch;
-
-    const searchFrom = { line: currentPos.line, ch: wordStart };
-    const searchTo = { line: currentPos.line, ch: wordEnd };
-
-    const searchMention = this.editor.getRange(searchFrom, searchTo);
-    const isMentioning = searchMention.charAt(0) === '@';
-
-    // Return nothing if not mentioning
-    if (!isMentioning) {
-      return;
-    }
-
-    // Get username after `@` character and search username
-    const mention = searchMention.slice(1);
-    this.editor.showHint({
-      completeSingle: false,
-      hint: async() => {
-        if (mention.length > 0) {
-          const users = await this.getUsersList(mention);
-          return {
-            // Returns default value if i18n is null because it cannot do early return.
-            list: users.length > 0 ? users : [{ text: '', displayText: i18n != null ? i18n.t('page_comment.no_user_found') : 'No user found' }],
-            from: searchFrom,
-            to: searchTo,
-          };
-        }
-      },
-    });
-  };
-
-  getUsersList = async(q: string): Promise<UsersListForHints[]> => {
-    const limit = 20;
-    const { data } = await apiv3Get('/users/usernames', { q, limit });
-    return data.activeUser.usernames.map((username: string) => ({
-      text: `@${username} `,
-      displayText: username,
-    }));
-  };
-
-  showUsernameHint = debounce(800, () => this.getUsenameHint());
-
-}

+ 0 - 344
apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx

@@ -1,344 +0,0 @@
-import React, {
-  useState, useEffect, useRef, useMemo, useCallback,
-} from 'react';
-
-import type { IRevisionOnConflict } from '@growi/core';
-import { UserPicture } from '@growi/ui/dist/components';
-import CodeMirror from 'codemirror/lib/codemirror';
-import { format, parseISO } from 'date-fns';
-import { useTranslation } from 'next-i18next';
-import {
-  Modal, ModalHeader, ModalBody, ModalFooter,
-} from 'reactstrap';
-
-import { useSaveOrUpdate } from '~/client/services/page-operation';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { OptionsToSave } from '~/interfaces/page-operation';
-import { useCurrentPathname, useCurrentUser } from '~/stores/context';
-import { useCurrentPagePath, useSWRxCurrentPage, useCurrentPageId } from '~/stores/page';
-import {
-  useRemoteRevisionBody, useRemoteRevisionId, useRemoteRevisionLastUpdatedAt, useRemoteRevisionLastUpdateUser, useSetRemoteLatestPageData,
-} from '~/stores/remote-latest-page';
-
-import ExpandOrContractButton from '../ExpandOrContractButton';
-import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
-
-require('codemirror/lib/codemirror.css');
-require('codemirror/addon/merge/merge');
-require('codemirror/addon/merge/merge.css');
-const DMP = require('diff_match_patch');
-
-Object.keys(DMP).forEach((key) => { window[key] = DMP[key] });
-
-type ConflictDiffModalProps = {
-  isOpen?: boolean;
-  onClose?: (() => void);
-  markdownOnEdit: string;
-  optionsToSave: OptionsToSave | undefined;
-  afterResolvedHandler: () => void,
-};
-
-type ConflictDiffModalCoreProps = {
-  isOpen?: boolean;
-  onClose?: (() => void);
-  optionsToSave: OptionsToSave | undefined;
-  request: IRevisionOnConflictWithStringDate,
-  origin: IRevisionOnConflictWithStringDate,
-  latest: IRevisionOnConflictWithStringDate,
-  afterResolvedHandler: () => void,
-};
-
-type IRevisionOnConflictWithStringDate = Omit<IRevisionOnConflict, 'createdAt'> & {
-  createdAt: string
-}
-
-const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element => {
-  const {
-    onClose, request, origin, latest, optionsToSave, afterResolvedHandler,
-  } = props;
-
-  const { t } = useTranslation('');
-  const [resolvedRevision, setResolvedRevision] = useState<string>('');
-  const [isRevisionselected, setIsRevisionSelected] = useState<boolean>(false);
-  const [isModalExpanded, setIsModalExpanded] = useState<boolean>(false);
-  const [codeMirrorRef, setCodeMirrorRef] = useState<HTMLDivElement | null>(null);
-
-  const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
-  const { data: pageId } = useCurrentPageId();
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: currentPathname } = useCurrentPathname();
-
-  const saveOrUpdate = useSaveOrUpdate();
-
-  const uncontrolledRef = useRef<CodeMirror>(null);
-
-  useEffect(() => {
-    if (codeMirrorRef != null) {
-      CodeMirror.MergeView(codeMirrorRef, {
-        value: origin.revisionBody,
-        origLeft: request.revisionBody,
-        origRight: latest.revisionBody,
-        lineNumbers: true,
-        collapseIdentical: true,
-        showDifferences: true,
-        highlightDifferences: true,
-        connect: 'connect',
-        readOnly: true,
-        revertButtons: false,
-      });
-    }
-  }, [codeMirrorRef, origin.revisionBody, request.revisionBody, latest.revisionBody]);
-
-  const close = useCallback(() => {
-    if (onClose != null) {
-      onClose();
-    }
-  }, [onClose]);
-
-  const onResolveConflict = useCallback(async() => {
-    if (currentPathname == null) { return }
-    // disable button after clicked
-    setIsRevisionSelected(false);
-
-    const codeMirrorVal = uncontrolledRef.current?.editor.doc.getValue();
-
-    try {
-      const { page } = await saveOrUpdate(
-        codeMirrorVal,
-        { pageId, path: currentPagePath || currentPathname, revisionId: remoteRevisionId },
-        optionsToSave,
-      );
-      const remotePageData = {
-        remoteRevisionId: page.revision._id,
-        remoteRevisionBody: page.revision.body,
-        remoteRevisionLastUpdateUser: page.lastUpdateUser,
-        remoteRevisionLastUpdatedAt: page.updatedAt,
-        revisionIdHackmdSynced: page.revisionIdHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      setRemoteLatestPageData(remotePageData);
-      afterResolvedHandler();
-
-      close();
-
-      toastSuccess('Saved successfully');
-    }
-    catch (error) {
-      toastError(`Error occured: ${error.message}`);
-    }
-
-  }, [afterResolvedHandler, close, currentPagePath, currentPathname, optionsToSave, pageId, remoteRevisionId, saveOrUpdate, setRemoteLatestPageData]);
-
-  // TODO: No longer support custom close icon in bootstrap v5
-  // const resizeAndCloseButtons = useMemo(() => (
-  //   <div className="d-flex flex-nowrap">
-  //     <ExpandOrContractButton
-  //       isWindowExpanded={isModalExpanded}
-  //       expandWindow={() => setIsModalExpanded(true)}
-  //       contractWindow={() => setIsModalExpanded(false)}
-  //     />
-  //     <button type="button" className="close text-white" onClick={close} aria-label="Close">
-  //       <span aria-hidden="true">&times;</span>
-  //     </button>
-  //   </div>
-  // ), [isModalExpanded, close]);
-
-  const isOpen = props.isOpen ?? false;
-
-  return (
-    <Modal
-      isOpen={isOpen}
-      toggle={close}
-      backdrop="static"
-      className={`${isModalExpanded ? ' grw-modal-expanded' : ''}`}
-      size="xl"
-    >
-      {/* <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3" close={resizeAndCloseButtons}> */}
-      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3">
-        <span className="material-symbols-outlined">error</span>{t('modal_resolve_conflict.resolve_conflict')}
-      </ModalHeader>
-      <ModalBody className="mx-4 my-1">
-        { isOpen
-        && (
-          <div className="row">
-            <div className="col-12 text-center mt-2 mb-4">
-              <h2 className="fw-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={request.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {request.user.username}</p>
-                  <p className="my-0">{request.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.origin_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={origin.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {origin.user.username}</p>
-                  <p className="my-0">{origin.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={latest.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {latest.user.username}</p>
-                  <p className="my-0">{latest.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-12" ref={(el) => { setCodeMirrorRef(el) }}></div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(request.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'mine' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(origin.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(latest.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'theirs' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-12">
-              <div className="border border-dark">
-                <h3 className="fw-bold my-2 mx-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
-                <UncontrolledCodeMirror
-                  ref={uncontrolledRef}
-                  value={resolvedRevision}
-                  options={{
-                    placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
-                  }}
-                />
-              </div>
-            </div>
-          </div>
-        )}
-      </ModalBody>
-      <ModalFooter>
-        <button
-          type="button"
-          className="btn btn-outline-secondary"
-          onClick={onClose}
-        >
-          {t('Cancel')}
-        </button>
-        <button
-          type="button"
-          className="btn btn-primary ms-3"
-          onClick={onResolveConflict}
-          disabled={!isRevisionselected}
-        >
-          {t('modal_resolve_conflict.resolve_and_save')}
-        </button>
-      </ModalFooter>
-    </Modal>
-  );
-};
-
-
-export const ConflictDiffModal = (props: ConflictDiffModalProps): JSX.Element => {
-  const {
-    isOpen, onClose, optionsToSave, afterResolvedHandler,
-  } = props;
-  const { data: currentUser } = useCurrentUser();
-
-  // state for current page
-  const { data: currentPage } = useSWRxCurrentPage();
-
-  // state for latest page
-  const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { data: remoteRevisionBody } = useRemoteRevisionBody();
-  const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
-  const { data: remoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
-
-  const currentTime: Date = new Date();
-
-  const isRemotePageDataInappropriate = remoteRevisionId == null || remoteRevisionBody == null || remoteRevisionLastUpdateUser == null;
-
-  if (!isOpen || currentUser == null || currentPage == null || isRemotePageDataInappropriate) {
-    return <></>;
-  }
-
-  const currentPageCreatedAtFixed = typeof currentPage.updatedAt === 'string'
-    ? parseISO(currentPage.updatedAt)
-    : currentPage.updatedAt;
-
-  const request: IRevisionOnConflictWithStringDate = {
-    revisionId: '',
-    revisionBody: props.markdownOnEdit,
-    createdAt: format(currentTime, 'yyyy/MM/dd HH:mm:ss'),
-    user: currentUser,
-  };
-  const origin: IRevisionOnConflictWithStringDate = {
-    revisionId: currentPage?.revision._id,
-    revisionBody: currentPage?.revision.body,
-    createdAt: format(currentPageCreatedAtFixed, 'yyyy/MM/dd HH:mm:ss'),
-    user: currentPage?.lastUpdateUser,
-  };
-  const latest: IRevisionOnConflictWithStringDate = {
-    revisionId: remoteRevisionId,
-    revisionBody: remoteRevisionBody,
-    createdAt: format(new Date(remoteRevisionLastUpdatedAt || currentTime.toString()), 'yyyy/MM/dd HH:mm:ss'),
-    user: remoteRevisionLastUpdateUser,
-  };
-
-  const propsForCore = {
-    isOpen,
-    onClose,
-    optionsToSave,
-    request,
-    origin,
-    latest,
-    afterResolvedHandler,
-  };
-
-  return <ConflictDiffModalCore {...propsForCore} />;
-};

+ 0 - 174
apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -1,174 +0,0 @@
-import React, {
-  FC, useCallback, useRef,
-} from 'react';
-
-import { useTranslation } from 'next-i18next';
-import { useRipple } from 'react-use-ripple';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import { useUserUISettings } from '~/client/services/user-ui-settings';
-import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
-import { Themes, useNextThemes } from '~/stores/use-next-themes';
-
-import SidebarDockIcon from '../Icons/SidebarDockIcon';
-import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
-
-type AppearanceModeDropdownProps = {
-  isAuthenticated: boolean,
-}
-export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: AppearanceModeDropdownProps) => {
-
-  const { t } = useTranslation('commons');
-
-  const { isAuthenticated } = props;
-
-  const {
-    setTheme, resolvedTheme, useOsSettings, isDarkMode, isForcedByGrowiTheme,
-  } = useNextThemes();
-  const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
-  const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
-  const { scheduleToPut } = useUserUISettings();
-
-  // ripple
-  const buttonRef = useRef(null);
-  useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  const preferDrawerModeSwitchModifiedHandler = useCallback((preferDrawerMode: boolean, isEditMode: boolean) => {
-    if (isEditMode) {
-      mutatePreferDrawerModeOnEdit(preferDrawerMode);
-      scheduleToPut({ preferDrawerModeOnEditByUser: preferDrawerMode });
-    }
-    else {
-      updatePreferDrawerMode(preferDrawerMode);
-    }
-  }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
-
-  const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
-    if (isChecked) {
-      setTheme(Themes.SYSTEM);
-    }
-    else {
-      setTheme(resolvedTheme ?? Themes.LIGHT);
-    }
-  }, [resolvedTheme, setTheme]);
-
-  const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
-    setTheme(isDarkMode ? Themes.DARK : Themes.LIGHT);
-  }, [setTheme]);
-
-  /* eslint-disable react/prop-types */
-  const IconWithTooltip = ({
-    id, label, children, additionalClasses,
-  }) => (
-    <>
-      <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
-      <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
-    </>
-  );
-
-  const dropdownDivider = <div className="dropdown-divider"></div>;
-
-  const renderSidebarModeSwitch = useCallback((isEditMode: boolean) => {
-    return (
-      <>
-        <h6 className="dropdown-header">{t(isEditMode ? 'personal_dropdown.sidebar_mode_editor' : 'personal_dropdown.sidebar_mode')}</h6>
-        <form className="px-4">
-          <div className="justify-content-center">
-            <div className="col-auto mb-0 d-flex align-items-center">
-              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-drawer' : 'iwt-sidebar-drawer'} label="Drawer" additionalClasses="grw-sidebar-mode-icon">
-                <SidebarDrawerIcon />
-              </IconWithTooltip>
-              <div className="form-check form-switch form-check-secondary ms-2">
-                <input
-                  id={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}
-                  className="form-check-input"
-                  type="checkbox"
-                  checked={isEditMode ? !isPreferDrawerModeOnEdit : !isPreferDrawerMode}
-                  onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked, isEditMode)}
-                />
-                <label className="form-label form-check-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
-              </div>
-              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-dock' : 'iwt-sidebar-dock'} label="Dock" additionalClasses="grw-sidebar-mode-icon">
-                <SidebarDockIcon />
-              </IconWithTooltip>
-            </div>
-          </div>
-        </form>
-      </>
-    );
-  }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
-
-  return (
-    <div className="dropend">
-      {/* setting button */}
-      {/* remove .dropdown-toggle for hide caret */}
-      {/* See https://stackoverflow.com/a/44577512/13183572 */}
-      <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <span className="material-symbols-outlined">settings</span>
-      </button>
-
-      {/* dropdown */}
-      <div className="dropdown-menu">
-
-        {/* sidebar mode */}
-        {renderSidebarModeSwitch(false)}
-
-        {/* side bar mode on editor */}
-        {isAuthenticated && (
-          <>
-            {dropdownDivider}
-            {renderSidebarModeSwitch(true)}
-          </>
-        )}
-
-        {/* color mode */}
-        { !isForcedByGrowiTheme && (
-          <>
-            {dropdownDivider}
-            <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
-            <form className="px-4">
-              <div className="justify-content-center">
-                <div className="col-auto d-flex align-items-center">
-                  <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                  <span className="material-symbols-outlined">light_mode</span>
-                  </IconWithTooltip>
-                  <div className="form-check form-switch form-check-secondary ms-2">
-                    <input
-                      id="swUserPreference"
-                      className="form-check-input"
-                      type="checkbox"
-                      checked={isDarkMode}
-                      disabled={useOsSettings}
-                      onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
-                    />
-                    <label className="form-label form-check-label" htmlFor="swUserPreference"></label>
-                  </div>
-                  <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                  <span className="material-symbols-outlined">dark_mode</span>
-                  </IconWithTooltip>
-                </div>
-              </div>
-              <div>
-                <div className="col-auto">
-                  <div className="form-check">
-                    <input
-                      id="cbFollowOs"
-                      className="form-check-input"
-                      type="checkbox"
-                      checked={useOsSettings}
-                      onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
-                    />
-                    <label className="form-label form-check-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
-                  </div>
-                </div>
-              </div>
-            </form>
-          </>
-        ) }
-
-      </div>
-
-    </div>
-  );
-
-};

+ 0 - 672
apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss

@@ -1,672 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as *;
-
-@use '../variables' as var;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/count-badge';
-@use './mixins/hsl-button';
-@use './hsl-functions' as hsl;
-
-// determine optional variables
-:root[data-bs-theme='dark'] {
-  $color-list: var(--color-list,var(--color-global));
-  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
-  $color-list-hover: var(--color-list-hover,var(--color-global));
-  $color-list-active: var(--color-list-active,var(--color-reversal));
-  $bgcolor-list-hover: var(--bgcolor-list-hover,var(--bgcolor-global));
-  $bgcolor-list-active: var(--bgcolor-list-active,var(--primary));
-  $color-table: var(--color-table,white);
-  $bgcolor-table: var(--bgcolor-table,#343a40);
-  $border-color-table: var(--border-color-table,lighten(#343a40, 7.5%));
-  $color-table-hover: var(--color-table-hover,rgba(white, 0.075));
-  $bgcolor-table-hover: var(--bgcolor-table-hover,lighten(#343a40, 7.5%));
-  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
-  $color-tags: var(--color-tags,#949494);
-  $bgcolor-tags: var(--bgcolor-tags,var(--dark));
-  $border-color-global: var(--border-color-global,#{$gray-500});
-  $border-color-toc: var(--border-color-toc,#{$border-color-global});
-  $color-dropdown: var(--color-dropdown,var(--color-global));
-  $bgcolor-dropdown: var(--bgcolor-dropdown,var(--bgcolor-global));
-  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
-  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--light));
-  $bgcolor-dropdown-link-hover: var(--bgcolor-dropdown-link-hover,hsl.lighten(var(--bgcolor-global), 15%));
-  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--light));
-  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
-  $body-bg: var(--bgcolor-global);
-  $body-color: var(--color-global);
-
-  // override bootstrap variables
-  // $text-muted: $gray-550;
-  $table-dark-color: $color-table;
-  $table-dark-bg: $bgcolor-table;
-  $table-dark-border-color: $border-color-table;
-  $table-dark-hover-color: $color-table-hover;
-  $table-dark-hover-bg: $bgcolor-table-hover;
-  $border-color: $border-color-global;
-  $dropdown-color: $color-dropdown;
-  $dropdown-bg: $bgcolor-dropdown;
-  $dropdown-link-color: $color-dropdown-link;
-  $dropdown-link-hover-color: $color-dropdown-link-hover;
-  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
-  $dropdown-link-active-color: $color-dropdown-link-active;
-  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-  @import './mixins/list-group';
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-  // @import './reboot-bootstrap-text';
-  // @import './reboot-bootstrap-border-colors';
-  // @import './reboot-bootstrap-tables';
-  // @import './reboot-bootstrap-theme-colors';
-  // @import 'hsl-reboot-bootstrap-theme-colors';
-  // @import './reboot-bootstrap-dropdown';
-
-
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-
-  //   // List Group
-  //   @include override-list-group-item(
-  //     $color-list,
-  //     $bgcolor-sidebar-list-group,
-  //     $color-list-hover,
-  //     $bgcolor-list-hover,
-  //     $color-list-active,
-  //     $bgcolor-list-active
-  //   );
-  //   /*
-  //     * Form
-  //     */
-  //   input.form-control,
-  //   select.form-control,
-  //   select.form-select,
-  //   textarea.form-control {
-  //     color: var(--color-global);
-  //     background-color: hsl.darken(var(--bgcolor-global), 5%);
-  //     border-color: $border-color-global;
-  //     &:focus {
-  //       background-color: var(--bgcolor-global);
-  //     }
-  //     // FIXME: accent color
-  //     // border: 1px solid darken($border, 30%);
-  //   }
-
-  //   .form-control[disabled],
-  //   .form-control[readonly] {
-  //     color: hsl.lighten(var(--color-global),10%);
-  //     background-color: hsl.lighten(var(--bgcolor-global),5%);
-  //   }
-
-  // TODO: theme-color() dropped in bootstrap v5
-  // TODO: .input-group-prepend dropped in bootstrap v5
-  // https://redmine.weseek.co.jp/issues/128307
-  //   .input-group > .input-group-prepend > .input-group-text {
-  //     color: theme-color('light');
-  //     background-color: theme-color('secondary');
-  //     border: 1px solid theme-color('secondary');
-  //     border-right: none;
-  //     &.text-muted {
-  //       color: theme-color('light') !important;
-  //     }
-  //   }
-
-  //   .input-group input {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   label.form-check-label::before {
-  //     background-color: hsl.darken(var(--bgcolor-global),5%);
-  //   }
-
-  //   .rbt-input-multi .rbt-input-main {
-  //     color: black;
-  //   }
-  //   /*
-  //   * Table
-  //   */
-  //   .table {
-  //     @extend .table-dark !optional;
-  //     thead th {
-  //       vertical-align: bottom;
-  //       border-bottom: 2px solid #d6dadf;
-  //     }
-  //   }
-
-  //   /*
-  //   * Card
-  //   */
-  //   .card:not([class*='bg-']):not(.custom-card):not(.card-disabled) {
-  //     @extend .bg-dark;
-  //   }
-
-  //   .card.custom-card {
-  //     border-color: var(--secondary);
-  //   }
-
-  //   .card.card-disabled {
-  //     background-color: lighten($dark, 10%);
-  //     border-color: var(--secondary);
-  //   }
-
-  //   /*
-  //   * Pagination
-  //   */
-  //   ul.pagination {
-  //     li.page-item.disabled {
-  //       button.page-link {
-  //         color: $gray-400;
-  //       }
-  //     }
-  //     li.page-item.active {
-  //       button.page-link {
-  //         color: hsl.contrast(var(--primary));
-  //         background-color: var(--primary);
-  //         &:hover,
-  //         &:focus {
-  //           color: hsl.contrast(var(--primary));
-  //           background-color: var(--primary);
-  //         }
-  //       }
-  //     }
-  //     li.page-item {
-  //       button.page-link {
-  //         @extend .btn-dark;
-  //         color: var(--primary);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Login form
-  //   */
-  //   .nologin {
-  //     // background color
-  //     $color-gradient: #3c465c;
-  //     background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-  //       linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
-  //       linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
-  //       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
-
-  //     .nologin-header {
-  //       background-color: rgba(black, 0.5);
-
-  //       .logo {
-  //         background-color: rgba(white, 0);
-  //         fill: rgba(white, 0.5);
-  //       }
-
-  //       h1 {
-  //         color: rgba(white, 0.5);
-  //       }
-  //     }
-
-  //     .nologin-dialog {
-  //       background-color: rgba(black, 0.5);
-  //       .link-switch {
-  //         color: #7b9bd5;
-  //         &:hover {
-  //           color: lighten(#7b9bd5,10%);
-  //         }
-  //       }
-  //     }
-
-  //     .input-group {
-  //       .input-group-text {
-  //         color: darken(white, 30%);
-  //         background-color: rgba($gray-700, 0.7);
-  //       }
-
-  //       .form-control {
-  //         color: white;
-  //         background-color: rgba(#505050, 0.7);
-  //         box-shadow: unset;
-
-  //         &::placeholder {
-  //           color: darken(white, 30%);
-  //         }
-  //       }
-  //     }
-
-  //     .btn-fill {
-  //       .btn-label {
-  //         color: $gray-300;
-  //       }
-  //       .btn-label-text {
-  //         color: $gray-400;
-  //       }
-  //     }
-
-  //     .grw-external-auth-form {
-  //       border-color: gray !important;
-  //     }
-
-  //     .btn-external-auth-tab {
-  //       @extend .btn-dark;
-  //     }
-
-  //     // footer link text
-  //     .link-growi-org {
-  //       color: rgba(white, 0.4);
-
-  //       &:hover,
-  //       &.focus {
-  //         color: rgba(white, 0.7);
-
-  //         .growi {
-  //           color: darken(var.$growi-green, 5%);
-  //         }
-
-  //         .org {
-  //           color: darken(var.$growi-blue, 5%);
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI subnavigation
-  //   */
-  //   .grw-drawer-toggler {
-  //     @include button-variant($dark, $dark);
-  //     @include mixins-buttons.button-svg-icon-variant($dark, $dark);
-  //     color: $gray-400;
-  //     box-shadow: none !important;
-  //   }
-
-  //   /**
-  //    * GROWI PagePathHierarchicalLink
-  //    */
-  //   .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-  //     color: $gray-400;
-  //   }
-
-  //   /*
-  //   * GROWI page list
-  //   */
-  //   .page-list {
-  //     .page-list-ul {
-  //       > li {
-  //         > span.page-list-meta {
-  //           color: hsl.darken(var(--color-global),10%);
-  //         }
-  //       }
-  //     }
-
-  //     // List group
-  //     .list-group-item {
-  //       &.active {
-  //         background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
-  //       }
-  //       &.list-group-item-action:hover {
-  //         background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
-  //       }
-  //       .page-list-snippet {
-  //         color: hsl.darken(var(--color-global),10%);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI ToC
-  //   */
-  //   .revision-toc-content {
-  //     ::marker {
-  //       color: hsl.lighten(var(--color-global),30%);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI subnavigation
-  //   */
-  //   .grw-subnav {
-  //     background-color: var(--bgcolor-subnav);
-  //   }
-
-  //   .grw-subnav-fixed-container .grw-subnav {
-  //     background-color: hsl.alpha(var(--bgcolor-subnav),85%);
-  //   }
-
-  //   .grw-page-editor-mode-manager {
-  //     .btn-outline-primary {
-  //       &:hover {
-  //         color: var(--primary);
-  //         background-color: $gray-700;
-  //       }
-  //     }
-  //   }
-
-  //   // Search drop down
-  //   #search-typeahead-asynctypeahead {
-  //     background-color: var(--bgcolor-global);
-  //     .table {
-  //       background-color: transparent;
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Sidebar
-  //   */
-  //   .grw-sidebar {
-  //     --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
-  //     --gray-500-hs: 210,13%;
-  //     --gray-500-l: 61%;
-  //     // List
-  //     @include override-list-group-item(
-  //       $color-list,
-  //       $bgcolor-sidebar-list-group,
-  //       $color-list-hover,
-  //       $bgcolor-list-hover,
-  //       $color-list-active,
-  //       $bgcolor-list-active
-  //     );
-  //     // Pagetree
-  //     .grw-pagetree, .grw-foldertree {
-  //       @include override-list-group-item-for-pagetree(
-  //         var(--color-sidebar-context),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),8%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),15%),
-  //         hsl.darken(var(--color-sidebar-context),15%),
-  //         hsl.darken(var(--color-sidebar-context),10%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),18%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),24%)
-  //       );
-  //       .grw-pagetree-triangle-btn, .grw-foldertree-triangle-btn {
-  //         @include mixins-buttons.button-outline-svg-icon-variant(var(--secondary), $gray-200);
-  //       }
-  //       .btn-page-item-control {
-  //         @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
-  //         &:hover {
-  //           background-color: hsl.lighten(var(--bgcolor-sidebar-context),20%);
-  //         }
-  //         &:not(:disabled):not(.disabled):active,
-  //         &:not(:disabled):not(.disabled).active {
-  //           background-color: hsl.lighten(var(--bgcolor-sidebar-context),34%);
-  //         }
-  //         box-shadow: none !important;
-  //       }
-  //     }
-
-  //     // bookmarks
-  //     .grw-folder-tree-container {
-  //       .grw-drop-item-area , .grw-foldertree-item-container {
-  //         .grw-accept-drop-item {
-  //           border-color: hsl.lighten(var(--bgcolor-sidebar-context), 30%) !important;
-  //         }
-  //       }
-  //     }
-  //     .private-legacy-pages-link {
-  //       &:hover {
-  //         background: var(--bgcolor-list-hover);
-  //       }
-  //     }
-  //   }
-
-  //   .btn.btn-page-item-control {
-  //     @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
-  //     &:hover {
-  //       background-color: $gray-700;
-  //     }
-  //     &:not(:disabled):not(.disabled):active,
-  //     &:not(:disabled):not(.disabled).active {
-  //       color: $gray-200;
-  //       background-color: $gray-600;
-  //     }
-  //     box-shadow: none !important;
-  //   }
-
-  //   // Bookmark item on user page
-  //   .grw-user-page-list-m {
-  //     @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-  //     .grw-foldertree {
-  //       @include override-list-group-item-for-pagetree(
-  //         $body-color,
-  //         hsl.lighten($body-bg, 8%),
-  //         hsl.lighten($body-bg, 15%),
-  //         hsl.darken($body-color, 15%),
-  //         hsl.darken($body-color, 10%),
-  //         hsl.lighten($body-bg, 18%),
-  //         hsl.lighten($body-bg, 24%)
-  //       );
-  //       .grw-foldertree-triangle-btn {
-  //         @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
-  //       }
-  //     }
-  //     .grw-folder-tree-container {
-  //       .grw-drop-item-area , .grw-foldertree-item-container {
-  //         .grw-accept-drop-item {
-  //           border-color: hsl.lighten(var($body-bg), 30%) !important;
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   // Bookmark dropdown menu
-  //   .grw-bookmark-folder-dropdown  {
-  //     .grw-bookmark-folder-menu {
-  //       .form-control{
-  //         &:focus {
-  //           color: $body-color
-  //         }
-  //       }
-  //       .grw-bookmark-folder-menu-item  {
-  //         @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
-  //         .grw-bookmark-folder-menu-item-title {
-  //           color: $body-color
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * Popover
-  //   */
-  //   .popover {
-  //     background-color: var(--bgcolor-global);
-  //     border-color: var(--secondary);
-  //     .popover-header {
-  //       color: white;
-  //       background-color: var(--secondary);
-  //       border-color: var(--secondary);
-  //     }
-  //     .popover-body {
-  //       color: inherit;
-  //     }
-
-  // TODO: Check renamed .arrow to .popover-arrow
-  // see: https://getbootstrap.com/docs/5.2/migration/#popovers
-
-  //     &.bs-popover-top .arrow {
-  //       &::before {
-  //         border-top-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-top-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-bottom .arrow {
-  //       &::before {
-  //         border-bottom-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-bottom-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-right .arrow {
-  //       &::before {
-  //         border-right-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-right-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-left .arrow {
-  //       &::before {
-  //         border-left-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-left-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Grid Edit Modal
-  //   */
-  //   .grw-grid-edit-preview {
-  //     background: $gray-900;
-  //   }
-
-  //   /*
-  //   * Slack
-  //   */
-  //   .grw-slack-notification {
-  //     background-color: transparent;
-  //     $color-slack: #4b144c;
-
-  //     .form-control {
-  //       background: var(--bgcolor-global);
-  //     }
-
-  //     .form-check-label {
-  //       &::before {
-  //         background-color: var(--secondary);
-  //         border-color: transparent;
-  //       }
-  //       &::after {
-  //         background-color: darken($color-slack, 5%);
-  //         background-image: url(/images/icons/slack/slack-logo-dark-off.svg);
-  //       }
-  //     }
-
-  //     .form-check-input:checked ~ .form-check-label {
-  //       &::before {
-  //         background-color: lighten($color-slack, 10%);
-  //       }
-  //       &::after {
-  //         background-color: darken($color-slack, 5%);
-  //         background-image: url(/images/icons/slack/slack-logo-dark-on.svg);
-  //       }
-  //     }
-  //     .grw-slack-logo svg {
-  //       fill: #dd80de;
-  //     }
-
-  //     .grw-btn-slack {
-  //       background-color: black;
-  //       &:focus,
-  //       &:hover {
-  //         background-color: black;
-  //       }
-  //     }
-
-  //     .grw-btn-slack-triangle {
-  //       color: var(--secondary);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI HandsontableModal
-  //   */
-
-  //   .handsontable td {
-  //     color: black;
-  //   }
-
-  //   .grw-hot-modal-navbar {
-  //     background-color: var(--dark);
-  //   }
-
-  //   .wiki {
-  //     h1 {
-  //       border-color: hsl.lighten(var(--border-color-theme),10%);
-  //     }
-  //     h2 {
-  //       border-color: var(--border-color-theme);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI comment form
-  //   */
-  //   .comment-form {
-  //     #slack-mark-black {
-  //       display: none;
-  //     }
-  //   }
-
-  //   .page-comment-form .comment-form-main {
-  //     &:before {
-  //       border-right-color: var(--bgcolor-global);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI tags
-  //   */
-  //   .grw-tag-labels {
-  //     .grw-tag-label {
-  //       color: $color-tags;
-  //       background-color: $bgcolor-tags;
-  //     }
-  //   }
-
-  //   mark.rbt-highlight-text {
-  //     color: var(--color-global);
-  //   }
-
-  //   /*
-  //   * GROWI popular tags
-  //   */
-  //   .grw-popular-tag-labels {
-  //     .grw-tag-label {
-  //       color: $color-tags;
-  //       background-color: $bgcolor-tags;
-  //     }
-  //   }
-
-  //   /*
-  //   * admin settings
-  //   */
-  //   .admin-setting-header {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * grw-side-contents
-  //   */
-  //   .grw-side-contents-sticky-container {
-  //     .grw-count-badge {
-  //       @include count-badge.count-badge($gray-400, $gray-700);
-  //     }
-
-  //     .grw-border-vr {
-  //       border-color: $border-color-toc;
-  //     }
-
-  //     .revision-toc {
-  //       border-color: $border-color-toc;
-  //     }
-  //   }
-
-  //   /*
-  //   * drawio
-  //   */
-  //   .drawio-viewer {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * modal
-  //   */
-  //   .grw-modal-head {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * skeleton
-  //   */
-  //   .grw-skeleton {
-  //     background-color: hsl.lighten(var(--bgcolor-subnav),10%);
-  //   }
-}

+ 0 - 536
apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss

@@ -1,536 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as *;
-
-@use '../variables' as var;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/count-badge';
-@use './mixins/hsl-button';
-@use './hsl-functions' as hsl;
-
-// determine optional variables
-:root[data-bs-theme='light'] {
-  $color-list: var(--color-list,var(--color-global));
-  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
-  $color-list-hover: var(--color-list-hover,var(--color-global));
-  $bgcolor-list-hover: var(--bgcolor-list-hover, var(--bgcolor-global));
-  $bgcolor-list-active: var(--bgcolor-list-active, hsl.lighten(var(--primary),65%));
-  $color-list-active: var(--color-list-active,hsl(var(--primary-hs), clamp(10%, (100% - var(--primary-l)  - 65% - 51%) * 1000, 95%)));
-  $color-table: var(--color-table,var(--color-global));
-  $bgcolor-table: var(--bgcolor-table,null);
-  $border-color-table: var(--border-color-table,#{$gray-200});
-  $color-table-hover: var(--color-table-hover,var(--color-table));
-  $bgcolor-table-hover: var(--bgcolor-table-hover,rgba(black, 0.075));
-  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
-  $color-tags: var(--color-tags,var(--secondary));
-  $bgcolor-tags: var(--bgcolor-tags,#{$gray-200});
-  $border-color-global: var(--border-color-global,#{$gray-300});
-  $border-color-toc: var(--border-color-toc,#{$gray-300});
-  $color-dropdown: var(--color-dropdown,var(--color-global));
-  $bgcolor-dropdown: var(--color-dropdown,var(--bgcolor-global));
-  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
-  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--color-global));
-  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--color-reversal));
-  $bgcolor-dropdown-link-hover: hsl.darken(var(--bgcolor-global),15%);
-  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
-  $body-bg: var(--bgcolor-global);
-  $body-color: var(--color-global);
-
-  // override bootstrap variables
-  $text-muted: $gray-500;
-  $table-color: $color-table;
-  $table-bg: $bgcolor-table;
-  $table-border-color: $border-color-table;
-  $table-hover-color: $color-table-hover;
-  $table-hover-bg: $bgcolor-table-hover;
-  $border-color: $border-color-global;
-  $dropdown-color: $color-dropdown;
-  $dropdown-link-color: $color-dropdown-link;
-  $dropdown-link-hover-color: $color-dropdown-link-hover;
-  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
-  $dropdown-link-active-color: $color-dropdown-link-active;
-  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-  @import './mixins/list-group';
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-  // @import './reboot-bootstrap-text';
-  // @import './reboot-bootstrap-border-colors';
-  // @import './reboot-bootstrap-tables';
-  // @import './reboot-bootstrap-theme-colors';
-  // @import 'hsl-reboot-bootstrap-theme-colors';
-  // @import './reboot-bootstrap-dropdown';
-
-  // List Group
-  @include override-list-group-item(
-    $color-list,
-    $bgcolor-sidebar-list-group,
-    $color-list-hover,
-    $bgcolor-list-hover,
-    $color-list-active,
-    $bgcolor-list-active
-  );
-  /*
-  * Form
-  */
-  .form-control {
-    background-color: var(--bgcolor-global);
-  }
-
-  .form-control::placeholder {
-    color: hsl.darken(var(--bgcolor-global), 20%);
-  }
-
-  .form-control[disabled],
-  .form-control[readonly] {
-    color: hsl.lighten(var(--color-global),10%);
-    background-color: hsl.darken(var(--bgcolor-global),5%);
-  }
-
-  /*
-  * card
-  */
-  .card.card-disabled {
-    background-color: var(--bgcolor-global);
-    border-color: $gray-200;
-  }
-
-  /*
-  * GROWI Login form
-  */
-  .nologin {
-    // background color
-    $color-gradient: #3c465c;
-    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
-      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
-
-    .nologin-header {
-      background-color: rgba(white, 0.5);
-
-      .logo {
-        background-color: rgba(black, 0);
-        fill: rgba(black, 0.5);
-      }
-
-      h1 {
-        color: rgba(black, 0.5);
-      }
-    }
-
-    .nologin-dialog {
-      background-color: rgba(white, 0.5);
-      .link-switch {
-        color: #1939b8;
-        &:hover {
-          color: lighten(#1939b8,20%);
-        }
-      }
-    }
-
-    .dropdown-with-icon {
-      .dropdown-toggle {
-        color: white;
-        background-color: rgba($gray-600, 0.7);
-        box-shadow: unset;
-        &:focus {
-          color: white;
-          background-color: rgba($gray-600, 0.7);
-        }
-      }
-      i {
-        color: darken(white, 30%);
-        background-color: rgba($gray-700, 0.7);
-      }
-    }
-
-    .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba($gray-700, 0.7);
-      }
-
-      .form-control {
-        color: white;
-        background-color: rgba($gray-600, 0.7);
-        box-shadow: unset;
-
-        &::placeholder {
-          color: darken(white, 30%);
-        }
-      }
-    }
-
-    // footer link text
-    .link-growi-org {
-      color: rgba(black, 0.4);
-
-      &:hover,
-      &.focus {
-        color: black;
-
-        .growi {
-          color: darken(var.$growi-green, 20%);
-        }
-
-        .org {
-          color: darken(var.$growi-blue, 15%);
-        }
-      }
-    }
-  }
-
-  /*
-  * GROWI subnavigation
-  */
-  // .grw-subnav {
-  //   background-color: var(--bgcolor-subnav);
-  // }
-
-  .grw-subnav-fixed-container .grw-subnav {
-    background-color: hsl.alpha(var(--bgcolor-subnav),85%);
-  }
-
-  .grw-page-editor-mode-manager {
-    .btn-outline-primary {
-      &:hover {
-        color: var(--primary);
-        background-color: $gray-200;
-      }
-    }
-  }
-
-  .grw-drawer-toggler {
-    @include button-variant($light, $light);
-    @include mixins-buttons.button-svg-icon-variant($light, $light);
-    color: $gray-500;
-    box-shadow: none !important;
-  }
-
-  /**
-   * GROWI PagePathHierarchicalLink
-   */
-  // .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-  //   color: $gray-600;
-  // }
-  /*
-  * GROWI Sidebar
-  */
-  .grw-sidebar {
-    // List
-    @include override-list-group-item(
-      $color-list,
-      $bgcolor-sidebar-list-group,
-      $color-list-hover,
-      $bgcolor-list-hover,
-      $color-list-active,
-      $bgcolor-list-active
-    );
-    // sidebar-centent-bg
-    .grw-navigation-wrap {
-      // Drop a shadow on the light theme. The dark theme makes '$ bgcolor-sidebar-context' brighter than the body.
-      box-shadow: 0px 0px 3px rgba(black, 0.24);
-    }
-    // Pagetree
-    .grw-pagetree, .grw-foldertree {
-      @include override-list-group-item-for-pagetree(
-        var(--color-sidebar-context),
-        hsl.darken(var(--bgcolor-sidebar-context),5%),
-        hsl.darken(var(--bgcolor-sidebar-context),12%),
-        hsl.lighten(var(--color-sidebar-context),10%),
-        hsl.lighten(var(--color-sidebar-context),8%),
-        hsl.darken(var(--bgcolor-sidebar-context),15%),
-        hsl.darken(var(--bgcolor-sidebar-context),24%)
-      );
-
-      .grw-pagetree-triangle-btn, .grw-foldertree-triangle-btn {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, var(--primary));
-      }
-    }
-
-    // bookmark
-    .grw-folder-tree-container {
-      .grw-drop-item-area, .grw-foldertree-item-container  {
-        .grw-accept-drop-item {
-          border-color: hsl.darken(var(--bgcolor-sidebar-context), 30%) !important;
-        }
-      }
-    }
-
-    .private-legacy-pages-link {
-      &:hover {
-        background: $bgcolor-list-hover;
-      }
-    }
-  }
-
-  .btn.btn-page-item-control {
-    --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
-    --gray-500-hs: 210,13%;
-    --gray-500-l: 61%;
-    @include hsl-button.button-outline-variant(var(--gray-500), var(--primary), #{hsl.lighten(var(--primary), 52%)}, transparent);
-    &:hover {
-      background-color: hsl.lighten(var(--primary), 58%);
-    }
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: var(--primary);
-    }
-    box-shadow: none !important;
-  }
-
-
-  // Bookmark item on user page
-  .grw-user-page-list-m {
-    @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-    .grw-foldertree {
-      @include override-list-group-item-for-pagetree(
-        $body-color,
-        hsl.darken($body-bg, 5%),
-        hsl.darken($body-bg, 12%),
-        hsl.lighten($body-color, 10%),
-        hsl.lighten($body-color, 8%),
-        hsl.darken($body-bg, 15%),
-        hsl.darken($body-bg, 24%)
-      );
-      .grw-foldertree-triangle-btn {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
-      }
-    }
-    .grw-folder-tree-container {
-      .grw-drop-item-area, .grw-foldertree-item-container  {
-        .grw-accept-drop-item {
-          border-color: hsl.darken(var($body-bg), 30%) !important;
-        }
-      }
-    }
-  }
-
-  // Bookmark dropdown menu
-  .grw-bookmark-folder-dropdown  {
-    .grw-bookmark-folder-menu {
-      .form-control{
-        &:focus {
-          color: $body-color
-        }
-      }
-      .grw-bookmark-folder-menu-item {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
-        .grw-bookmark-folder-menu-item-title {
-          color: $body-color
-        }
-      }
-    }
-  }
-
-  /*
-  * GROWI page list
-  */
-  .page-list {
-    .page-list-ul {
-      > li {
-        > span.page-list-meta {
-          color: hsl.lighten(var(--color-global),10%);
-        }
-      }
-    }
-    // List group
-    .list-group-item {
-      &.active {
-        background-color: hsl.lighten(var(--primary),77%) !important;
-      }
-      &.list-group-item-action:hover {
-        background-color: hsl.lighten(var(--primary),72%) !important;
-      }
-      .page-list-snippet {
-        color: hsl.lighten(var(--color-global),10%);
-      }
-    }
-  }
-
-  /*
-  * GROWI ToC
-  */
-  .revision-toc-content {
-    ::marker {
-      color: hsl.darken(var(--bgcolor-global),20%);
-    }
-  }
-
-  /*
-  * GROWI Editor
-  */
-  .grw-editor-navbar-bottom {
-    background-color: $gray-100;
-
-    #slack-mark-white {
-      display: none;
-    }
-
-    .input-group-text {
-      margin-right: 1px;
-      color: var(--secondary);
-      border-color: var(--light);
-    }
-
-    .btn.btn-outline-secondary {
-      border-color: $border-color;
-    }
-  }
-
-  /*
-  * GROWI Link Edit Modal
-  */
-  .link-edit-modal {
-    span i {
-      color: $gray-400;
-    }
-  }
-
-  /*
-  * GROWI Grid Edit Modal
-  */
-
-  .grw-grid-edit-preview {
-    background: $gray-100;
-  }
-
-  /*
-  * Slack
-  */
-  .grw-slack-notification {
-    background-color: white;
-    $color-slack: #4b144c;
-
-    .form-control {
-      background: white;
-    }
-
-    .form-check-label {
-      &::before {
-        background-color: $gray-200;
-        border-color: transparent;
-      }
-      &::after {
-        background-color: white;
-        background-image: url(/images/icons/slack/slack-logo-off.svg);
-      }
-    }
-    .form-check-input:checked ~ .form-check-label {
-      &::before {
-        background-color: lighten($color-slack, 60%);
-      }
-      &::after {
-        background-image: url(/images/icons/slack/slack-logo-on.svg);
-      }
-    }
-    .grw-slack-logo svg {
-      fill: #af30b0;
-    }
-
-    .grw-btn-slack {
-      background-color: white;
-
-      &:hover,
-      &:focus {
-        background-color: white;
-      }
-    }
-
-    .grw-btn-slack-triangle {
-      color: var(--secondary);
-    }
-  }
-
-  /*
-  * GROWI HandsontableModal
-  */
-  .grw-hot-modal-navbar {
-    background-color: var(--light);
-  }
-
-  .wiki {
-    h1 {
-      border-color: var(--border-color-theme);
-    }
-    h2 {
-      border-color: var(--border-color-theme);
-    }
-  }
-
-  /*
-  * GROWI comment form
-  */
-  .comment-form {
-    #slack-mark-white {
-      display: none;
-    }
-  }
-
-  .page-comment-form .comment-form-main {
-    &:before {
-      border-right-color: var(--bgcolor-global);
-    }
-  }
-
-  /*
-  * GROWI tags
-  */
-  .grw-tag-labels {
-    .grw-tag-label {
-      color: $color-tags;
-      background-color: $bgcolor-tags;
-    }
-  }
-
-  /*
-  * GROWI popular tags
-  */
-  .grw-popular-tag-labels {
-    .grw-tag-label {
-      color: $color-tags;
-      background-color: $bgcolor-tags;
-    }
-  }
-
-  /*
-  * grw-side-contents
-  */
-  .grw-side-contents-sticky-container {
-    .grw-count-badge {
-      @include count-badge.count-badge($gray-600, $gray-200);
-    }
-
-    .grw-border-vr {
-      border-color: $border-color-toc;
-    }
-    .revision-toc {
-      border-color: $border-color-toc;
-    }
-  }
-
-  /*
-  * drawio
-  */
-  .drawio-viewer {
-    border-color: $border-color-global;
-  }
-
-  /*
-  * admin settings
-  */
-  .admin-setting-header {
-    border-color: $border-color;
-  }
-
-  /*
-  * modal
-  */
-  .grw-modal-head {
-    border-color: $border-color-global;
-  }
-
-  /*
-  * skeleton
-  */
-  .grw-skeleton {
-    background-color: hsl.darken(var(--bgcolor-subnav),10%);
-  }
-}

+ 0 - 22
apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss

@@ -1,22 +0,0 @@
-.toast-success {
-  background-color: var(--success) !important;
-}
-
-.toast-error {
-  background-color: var(--danger) !important;
-}
-
-.toast-info {
-  background-color: var(--info) !important;
-}
-
-.toast-warning {
-  background-color: var(--warning) !important;
-}
-
-:root {
-  --toastify-color-info: var(--info);
-  --toastify-color-success: var(--success);
-  --toastify-color-warning: var(--warning);
-  --toastify-color-error: var(--danger);
-}

+ 1 - 6
apps/app/docker/codebuild/buildspec.yml

@@ -4,10 +4,8 @@ env:
   variables:
     DOCKER_BUILDKIT: 1
     IMAGE_TAG: ''
-    IMAGE_TAG_GHCR: ''
   secrets-manager:
     DOCKER_REGISTRY_PASSWORD: growi/official-image-builder:DOCKER_REGISTRY_PASSWORD
-    DOCKER_REGISTRY_ON_GITHUB_PAT: growi/official-image-builder:DOCKER_REGISTRY_ON_GITHUB_PAT
 
 phases:
   pre_build:
@@ -19,17 +17,14 @@ phases:
       - git-lfs pull
       # login to docker.io
       - echo ${DOCKER_REGISTRY_PASSWORD} | docker login --username wsmoogle --password-stdin
-      # login to ghcr.io
-      - echo ${DOCKER_REGISTRY_ON_GITHUB_PAT} | docker login ghcr.io --username wsmoogle --password-stdin
   build:
     commands:
       - docker build -t ${IMAGE_TAG} -f ./apps/app/docker/Dockerfile .
-      - docker tag ${IMAGE_TAG} ${IMAGE_TAG_GHCR}
+      - docker tag ${IMAGE_TAG}
 
   post_build:
     commands:
       - docker push $IMAGE_TAG
-      - docker push $IMAGE_TAG_GHCR
 
 cache:
   paths:

+ 1 - 1
apps/app/package.json

@@ -243,7 +243,7 @@
     "babel-loader": "^8.2.5",
     "bootstrap": "^5.3.1",
     "connect-browser-sync": "^2.1.0",
-    "diff2html": "^3.4.35",
+    "diff2html": "^3.4.47",
     "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
     "eslint-plugin-cypress": "^2.12.1",

+ 10 - 0
apps/app/src/components/Bookmarks/BookmarkFolderMenu.module.scss

@@ -0,0 +1,10 @@
+.grw-bookmark-folder-menu  :global {
+  max-width: 65%;
+
+  .grw-bookmark-folder-menu-item-folder-first {
+    padding-left: 40px;
+  }
+  .grw-bookmark-folder-menu-item-folder-second {
+    padding-left: 60px;
+  }
+}

+ 7 - 9
apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -12,6 +12,7 @@ import { useSWRMUTxPageInfo } from '~/stores/page';
 
 import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
 
+import styles from './BookmarkFolderMenu.module.scss';
 
 type BookmarkFolderMenuProps = {
   isOpen: boolean,
@@ -116,7 +117,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
         <DropdownItem
           toggle={false}
           onClick={onUnbookmarkHandler}
-          className="grw-bookmark-folder-menu-item text-danger"
+          className="grw-bookmark-folder-menu-item text-danger text-truncate"
         >
           <span className="material-symbols-outlined">bookmark</span>{' '}
           <span className="mx-2">
@@ -129,7 +130,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
             <DropdownItem divider />
             <div key="root">
               <div
-                className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
+                className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action px-4"
                 tabIndex={0}
                 role="menuitem"
                 onClick={e => onMenuItemClickHandler(e, 'root')}
@@ -144,8 +145,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
             {bookmarkFolders?.map(folder => (
               <React.Fragment key={`bookmark-folders-${folder._id}`}>
                 <div
-                  className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
-                  style={{ paddingLeft: '40px' }}
+                  className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-first list-group-item list-group-item-action"
                   tabIndex={0}
                   role="menuitem"
                   onClick={e => onMenuItemClickHandler(e, folder._id)}
@@ -159,8 +159,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
                 {folder.children?.map(child => (
                   <div key={child._id}>
                     <div
-                      className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
-                      style={{ paddingLeft: '60px' }}
+                      className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-second list-group-item list-group-item-action"
                       tabIndex={0}
                       role="menuitem"
                       onClick={e => onMenuItemClickHandler(e, child._id)}
@@ -185,15 +184,14 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
     <UncontrolledDropdown
       isOpen={isOpen}
       onToggle={toggleHandler}
-      direction={isBookmarkFolderExists ? 'up' : 'down'}
-      className="grw-bookmark-folder-dropdown"
     >
       {children}
       <DropdownMenu
         end
         persist
         strategy="fixed"
-        className="grw-bookmark-folder-menu"
+        container="body"
+        className={`grw-bookmark-folder-menu ${styles['grw-bookmark-folder-menu']}`}
       >
         { renderBookmarkMenuItem() }
       </DropdownMenu>

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -19,7 +19,7 @@ export const BookmarkFolderMenuItem: React.FC<{
         onChange={e => e.stopPropagation()}
         onClick={e => e.stopPropagation()}
       />
-      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className="p-2 m-0 form-label">
+      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className="p-2 m-0 form-label text-truncate">
         {itemName}
       </label>
     </div>

+ 17 - 0
apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss

@@ -36,3 +36,20 @@
     @include btn-muted.colorize(bs.$orange);
   }
 }
+
+// == Colors
+.grw-former-link :global {
+  .separator {
+    opacity: 0.75;
+  }
+}
+
+.grw-former-link a {
+  --bs-link-opacity: 0.75;
+
+  &:global {
+    &:hover {
+      --bs-link-opacity: 1;
+    }
+  }
+}

+ 5 - 5
apps/app/src/components/Common/PagePathNav/PagePathNav.tsx

@@ -29,8 +29,8 @@ type Props = {
 
 const CopyDropdown = dynamic(() => import('../CopyDropdown').then(mod => mod.CopyDropdown), { ssr: false });
 
-const Separator = (): JSX.Element => {
-  return <span className={styles['grw-mx-02em']}>/</span>;
+const Separator = ({ className }: {className?: string}): JSX.Element => {
+  return <span className={`separator ${className ?? ''} ${styles['grw-mx-02em']}`}>/</span>;
 };
 
 export const PagePathNav: FC<Props> = (props: Props) => {
@@ -69,10 +69,10 @@ export const PagePathNav: FC<Props> = (props: Props) => {
     const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
     const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
     formerLink = (
-      <div className="fs-5">
+      <>
         <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />
         <Separator />
-      </div>
+      </>
     );
     latterLink = (
       <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} isInTrash={isInTrash} />
@@ -83,7 +83,7 @@ export const PagePathNav: FC<Props> = (props: Props) => {
 
   return (
     <div>
-      <span className={formerLinkClassName}>{formerLink}</span>
+      <span className={`${formerLinkClassName ?? ''} ${styles['grw-former-link']}`}>{formerLink}</span>
       <div className="d-flex align-items-center">
         <h1 className={`m-0 ${latterLinkClassName}`}>
           {latterLink}

+ 3 - 2
apps/app/src/components/DeleteBookmarkFolderModal.tsx

@@ -1,5 +1,6 @@
 
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 import {
@@ -46,7 +47,7 @@ const DeleteBookmarkFolderModal: FC = () => {
         {t('bookmark_folder.delete_modal.modal_header_label')}
       </ModalHeader>
       <ModalBody>
-        <div className="pb-1">
+        <div className="pb-1 text-break">
           <label className="form-label">{ t('bookmark_folder.delete_modal.modal_body_description') }:</label><br />
           <FolderIcon isOpen={false} /> {deleteBookmarkFolderModalData?.bookmarkFolder?.name}
         </div>

+ 1 - 1
apps/app/src/components/DescendantsPageListModal.tsx

@@ -75,7 +75,7 @@ export const DescendantsPageListModal = (): JSX.Element => {
         expandWindow={() => setIsWindowExpanded(true)}
         contractWindow={() => setIsWindowExpanded(false)}
       />
-      <button type="button" className="btn btn-close" onClick={close} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
     </span>
   ), [close, isWindowExpanded]);
 

+ 8 - 0
apps/app/src/components/ExpandOrContractButton.module.scss

@@ -0,0 +1,8 @@
+.btn-expand-or-contract {
+  padding: 3px;
+  opacity: .5;
+
+  &:hover {
+    opacity: .75;
+  }
+}

+ 9 - 2
apps/app/src/components/ExpandOrContractButton.tsx

@@ -1,12 +1,17 @@
 import type { FC } from 'react';
 import React from 'react';
 
+import styles from './ExpandOrContractButton.module.scss';
+
 type Props = {
   isWindowExpanded: boolean,
   contractWindow?: () => void,
   expandWindow?: () => void,
 };
 
+const moduleClass = styles['btn-expand-or-contract'] ?? '';
+
+
 const ExpandOrContractButton: FC<Props> = (props: Props) => {
   const { isWindowExpanded, contractWindow, expandWindow } = props;
 
@@ -25,10 +30,12 @@ const ExpandOrContractButton: FC<Props> = (props: Props) => {
   return (
     <button
       type="button"
-      className="btn material-symbols-outlined"
+      className={`btn ${moduleClass}`}
       onClick={isWindowExpanded ? clickContractButtonHandler : clickExpandButtonHandler}
     >
-      {isWindowExpanded ? 'close_fullscreen' : 'open_in_full'}
+      <span className="material-symbols-outlined fw-bold">
+        {isWindowExpanded ? 'close_fullscreen' : 'open_in_full'}
+      </span>
     </button>
   );
 };

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/PageAccessoriesModal.tsx

@@ -75,7 +75,7 @@ export const PageAccessoriesModal = (): JSX.Element => {
         expandWindow={() => setIsWindowExpanded(true)}
         contractWindow={() => setIsWindowExpanded(false)}
       />
-      <button type="button" className="btn btn-close" onClick={close} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
     </span>
   ), [close, isWindowExpanded]);
 

+ 1 - 1
apps/app/src/components/PageControls/BookmarkButtons.tsx

@@ -81,7 +81,7 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
           </span>
         </DropdownToggle>
       </BookmarkFolderMenu>
-      <UncontrolledTooltip placement="top" data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
+      <UncontrolledTooltip data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 

+ 1 - 1
apps/app/src/components/PageControls/LikeButtons.tsx

@@ -54,7 +54,7 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
         <span className={`material-symbols-outlined ${isLiked ? 'fill' : ''}`}>favorite</span>
       </button>
 
-      <UncontrolledTooltip data-testid="like-button-tooltip" placement="top" target="like-button" autohide={false} fade={false}>
+      <UncontrolledTooltip data-testid="like-button-tooltip" target="like-button" autohide={false} fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 

+ 1 - 2
apps/app/src/components/PageControls/SeenUserInfo.tsx

@@ -2,7 +2,6 @@ import type { FC } from 'react';
 import React, { useState } from 'react';
 
 import type { IUser } from '@growi/core';
-import { FootstampIcon } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
 
@@ -40,7 +39,7 @@ const SeenUserInfo: FC<Props> = (props: Props) => {
           </div>
         </PopoverBody>
       </Popover>
-      <UncontrolledTooltip data-testid="seen-user-info-tooltip" placement="top" target="btn-seen-user" fade={false}>
+      <UncontrolledTooltip data-testid="seen-user-info-tooltip" target="btn-seen-user" fade={false}>
         {t('tooltip.footprints')}
       </UncontrolledTooltip>
     </div>

+ 3 - 2
apps/app/src/components/PageControls/SubscribeButton.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useCallback } from 'react';
 
 import { SubscriptionStatusType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
@@ -41,7 +42,7 @@ const SubscribeButton: FC<Props> = (props: Props) => {
         </span>
       </button>
 
-      <UncontrolledTooltip data-testid="subscribe-button-tooltip" placement="top" target="subscribe-button" fade={false}>
+      <UncontrolledTooltip data-testid="subscribe-button-tooltip" target="subscribe-button" fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
     </>

+ 11 - 11
apps/app/src/components/PageEditor/HandsontableModal.tsx

@@ -442,7 +442,7 @@ export const HandsontableModal = (): JSX.Element => {
         contractWindow={contractWindow}
         expandWindow={expandWindow}
       />
-      <button type="button" className="btn btn-close" onClick={cancel} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={cancel} aria-label="Close"></button>
     </span>
   );
 
@@ -457,14 +457,14 @@ export const HandsontableModal = (): JSX.Element => {
       className={`handsontable-modal ${isWindowExpanded && 'grw-modal-expanded'}`}
       onOpened={handleModalOpen}
     >
-      <ModalHeader tag="h4" toggle={cancel} close={closeButton} className="bg-primary text-light">
+      <ModalHeader tag="h4" toggle={cancel} close={closeButton}>
         {t('handsontable_modal.title')}
       </ModalHeader>
       <ModalBody className="p-0 d-flex flex-column">
-        <div className="grw-hot-modal-navbar px-4 py-3 border-bottom">
+        <div className="grw-hot-modal-navbar p-3">
           <button
             type="button"
-            className="me-4 data-import-button btn btn-secondary"
+            className="me-4 data-import-button btn btn-outline-neutral-secondary"
             data-bs-toggle="collapse"
             data-bs-target="#collapseDataImport"
             aria-expanded={isDataImportAreaExpanded}
@@ -474,23 +474,23 @@ export const HandsontableModal = (): JSX.Element => {
             <span className="material-symbols-outlined">{isDataImportAreaExpanded ? 'expand_less' : 'expand_more'}</span>
           </button>
           <div role="group" className="btn-group">
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('l') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('l') }}>
               <span className="material-symbols-outlined">format_align_left</span>
             </button>
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('c') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('c') }}>
               <span className="material-symbols-outlined">format_align_center</span>
             </button>
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('r') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('r') }}>
               <span className="material-symbols-outlined">format_align_right</span>
             </button>
           </div>
           <Collapse isOpen={isDataImportAreaExpanded}>
-            <div className="mt-4">
+            <div className="py-3 border-bottom">
               <MarkdownTableDataImportForm onCancel={toggleDataImportArea} onImport={importData} />
             </div>
           </Collapse>
         </div>
-        <div ref={c => setHotTableContainer(c)} className="m-4 hot-table-container">
+        <div ref={c => setHotTableContainer(c)} className="hot-table-container px-3">
           <HotTable
             ref={c => setHotTable(c)}
             data={markdownTable.table}
@@ -507,9 +507,9 @@ export const HandsontableModal = (): JSX.Element => {
         </div>
       </ModalBody>
       <ModalFooter className="grw-modal-footer">
-        <button type="button" className="btn btn-danger" onClick={reset}>{t('commons:Reset')}</button>
+        <button type="button" className="btn btn-outline-danger" onClick={reset}>{t('commons:Reset')}</button>
         <div className="ms-auto">
-          <button type="button" className="me-2 btn btn-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
+          <button type="button" className="me-2 btn btn-outline-neutral-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
           <button type="button" className="btn btn-primary" onClick={save}>{t('handsontable_modal.done')}</button>
         </div>
       </ModalFooter>

+ 3 - 3
apps/app/src/components/PageEditor/MarkdownTableDataImportForm.tsx

@@ -66,7 +66,7 @@ export const MarkdownTableDataImportForm = (props: MarkdownTableDataImportFormPr
           <option value="html">HTML</option>
         </select>
       </div>
-      <div>
+      <div className="mt-2">
         <label htmlFor="data-import-form-type-textarea" className="form-label">{t('import_data')}</label>
         <textarea
           id="data-import-form-type-textarea"
@@ -88,8 +88,8 @@ export const MarkdownTableDataImportForm = (props: MarkdownTableDataImportFormPr
           />
         </div>
       </Collapse>
-      <div className="d-flex justify-content-end">
-        <Button color="secondary me-2" onClick={onCancel}>{t('cancel')}</Button>
+      <div className="mt-3 d-flex justify-content-end">
+        <Button color="outline-neutral-secondary me-2" onClick={onCancel}>{t('cancel')}</Button>
         <Button color="primary" onClick={importButtonHandler}>{t('import')}</Button>
       </div>
     </form>

+ 3 - 3
apps/app/src/components/PageHeader/PagePathHeader.module.scss

@@ -3,9 +3,9 @@
   input {
     min-width: 20px;
     min-height: unset;
-    padding-top: 0;
-    padding-bottom: 0;
-    line-height: 1em;
+    padding-top: 2px;
+    padding-bottom: 2px;
+    line-height: 1.2em;
   }
 
   .page-path-header-buttons {

+ 2 - 2
apps/app/src/components/PageHeader/PagePathHeader.tsx

@@ -1,7 +1,7 @@
 import {
-  useState, useEffect, useCallback, memo, useMemo,
+  useState, useCallback, memo,
 } from 'react';
-import type { CSSProperties, FC } from 'react';
+import type { FC } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';

+ 14 - 12
apps/app/src/components/PageHistory/PageRevisionTable.tsx

@@ -56,6 +56,7 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
 
   useEffect(() => {
     if (revisions != null) {
+      // when both source and target are specified
       if (sourceRevisionId != null && targetRevisionId != null) {
         const sourceRevision = revisions.filter(revision => revision._id === sourceRevisionId)[0];
         const targetRevision = revisions.filter(revision => revision._id === targetRevisionId)[0];
@@ -63,11 +64,10 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
         setTargetRevision(targetRevision);
       }
       else {
-        const latestRevision = revisions != null ? revisions[0] : null;
-        if (latestRevision != null) {
-          setSourceRevision(latestRevision);
-          setTargetRevision(latestRevision);
-        }
+        const latestRevision = revisions != null ? revisions[0] : undefined;
+        const previousRevision = revisions.length >= 2 ? revisions[1] : latestRevision;
+        setTargetRevision(latestRevision);
+        setSourceRevision(previousRevision);
       }
     }
   }, [revisions, sourceRevisionId, targetRevisionId]);
@@ -210,13 +210,15 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
       </table>
 
       {sourceRevision != null && targetRevision != null && (
-        <RevisionComparer
-          sourceRevision={sourceRevision}
-          targetRevision={targetRevision}
-          currentPageId={currentPageId}
-          currentPagePath={currentPagePath}
-          onClose={onClose}
-        />
+        <div className="mt-5">
+          <RevisionComparer
+            sourceRevision={sourceRevision}
+            targetRevision={targetRevision}
+            currentPageId={currentPageId}
+            currentPagePath={currentPagePath}
+            onClose={onClose}
+          />
+        </div>
       )
       }
     </>

+ 2 - 21
apps/app/src/components/PageHistory/RevisionDiff.module.scss

@@ -1,28 +1,9 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
 .revision-diff-container :global {
-  .comparison-header {
-    height: 34px;
-    background-color: #ffffff;
-    border: 1px solid bs.$gray-300;
-    .comparison-source-wrapper {
-      height: 26px;
-      margin-right: 1px;
-      border-right: 1px solid bs.$gray-300;
-      .comparison-source {
-        color: bs.$gray-500;
-      }
-    }
-    .comparison-target-wrapper {
-      height: 26px;
-      .comparison-target {
-        color: bs.$gray-500;
-      }
-    }
+  .link-created-at {
+    text-decoration-line: underline;
   }
 
   .revision-history-diff {
-    color: bs.$gray-900;
     table-layout: fixed;
 
     // revision-history

+ 48 - 26
apps/app/src/components/PageHistory/RevisionDiff.tsx

@@ -1,14 +1,17 @@
-import React from 'react';
+import { useMemo } from 'react';
 
 import type { IRevisionHasPageId } from '@growi/core';
 import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
 import { createPatch } from 'diff';
 import type { Diff2HtmlConfig } from 'diff2html';
 import { html } from 'diff2html';
+import { ColorSchemeType } from 'diff2html/lib/types';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import urljoin from 'url-join';
 
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
+
 import UserDate from '../User/UserDate';
 
 import styles from './RevisionDiff.module.scss';
@@ -31,6 +34,19 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
     currentRevision, previousRevision, revisionDiffOpened, currentPageId, currentPagePath, onClose,
   } = props;
 
+  const { theme } = useNextThemes();
+
+  const colorScheme: ColorSchemeType = useMemo(() => {
+    switch (theme) {
+      case Themes.DARK:
+        return ColorSchemeType.DARK;
+      case Themes.LIGHT:
+        return ColorSchemeType.LIGHT;
+      default:
+        return ColorSchemeType.AUTO;
+    }
+  }, [theme]);
+
   const previousText = (currentRevision._id === previousRevision._id) ? '' : previousRevision.body;
 
   const patch = createPatch(
@@ -42,6 +58,7 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
   const option: Diff2HtmlConfig = {
     outputFormat: 'side-by-side',
     drawFileList: false,
+    colorScheme,
   };
 
   const diffViewHTML = revisionDiffOpened ? html(patch, option) : '';
@@ -50,34 +67,39 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
 
   return (
     <div className={`${styles['revision-diff-container']}`}>
-      <div className="comparison-header">
-        <div className="container pt-1 pe-0">
-          <div className="row">
-            <div className="col comparison-source-wrapper pt-1 px-0">
-              <span className="comparison-source pe-3">{t('page_history.comparing_source')}</span><UserDate dateTime={previousRevision.createdAt} />
-              <Link
-                href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${previousRevision._id}`)}
-                className="ms-3"
-                onClick={onClose}
-                prefetch={false}
-              >
-                <span className="material-symbols-outlined">login</span>
-              </Link>
-            </div>
-            <div className="col comparison-target-wrapper pt-1">
-              <span className="comparison-target pe-3">{t('page_history.comparing_target')}</span><UserDate dateTime={currentRevision.createdAt} />
-              <Link
-                href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${currentRevision._id}`)}
-                className="ms-3"
-                onClick={onClose}
-                prefetch={false}
-              >
-                <span className="material-symbols-outlined">login</span>
-              </Link>
-            </div>
+      <div className="container">
+        <div className="row mt-2">
+          <div className="col px-0 py-2">
+            <span className="fw-bold">{t('page_history.comparing_source')}</span>
+            <Link
+              href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${previousRevision._id}`)}
+              className="small ms-2
+                link-created-at
+                link-secondary link-opacity-75 link-opacity-100-hover
+                link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
+              onClick={onClose}
+              prefetch={false}
+            >
+              <UserDate dateTime={previousRevision.createdAt} />
+            </Link>
+          </div>
+          <div className="col px-0 py-2">
+            <span className="fw-bold">{t('page_history.comparing_target')}</span>
+            <Link
+              href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${currentRevision._id}`)}
+              className="small ms-2
+                link-created-at
+                link-secondary link-opacity-75 link-opacity-100-hover
+                link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
+              onClick={onClose}
+              prefetch={false}
+            >
+              <UserDate dateTime={currentRevision.createdAt} />
+            </Link>
           </div>
         </div>
       </div>
+      {/* eslint-disable-next-line react/no-danger */}
       <div className="revision-history-diff pb-1" dangerouslySetInnerHTML={diffView} />
     </div>
   );

+ 0 - 5
apps/app/src/components/PageStatusAlert.module.scss

@@ -15,11 +15,6 @@
     }
     .grw-card-btn-container {
       text-align: center;
-
-      .btn {
-        // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-        // @include bs.button-size(bs.$btn-padding-y-lg, bs.$btn-padding-x-lg, bs.$btn-font-size-lg, bs.$btn-line-height-lg, bs.$btn-border-radius-lg);
-      }
     }
   }
 

+ 8 - 0
apps/app/src/components/RevisionComparer/RevisionComparer.module.scss

@@ -1,3 +1,7 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
 .revision-compare :global {
   .revision-compare-container {
     min-height: 100px;
@@ -13,6 +17,10 @@
   }
 
   .grw-copy-dropdown {
+    .btn.btn-copy {
+      @include btn-muted.colorize(bs.$gray-500);
+    }
+
     .dropdown-menu {
       min-width: 310px;
 

+ 26 - 26
apps/app/src/components/RevisionComparer/RevisionComparer.tsx

@@ -66,33 +66,33 @@ export const RevisionComparer = (props: RevisionComparerProps): JSX.Element => {
     <div className={`${styles['revision-compare']} revision-compare`}>
       <div className="d-flex">
         <h4 className="align-self-center">{ t('page_history.comparing_revisions') }</h4>
-        <Dropdown
-          className="grw-copy-dropdown align-self-center ms-auto"
-          isOpen={dropdownOpen}
-          toggle={() => toggleDropdown()}
-        >
-          <DropdownToggle
-            caret
-            className="d-block text-muted bg-transparent btn-copy border-0 py-0"
+
+        { !isNodiff && (
+          <Dropdown
+            className="grw-copy-dropdown align-self-center ms-auto"
+            isOpen={dropdownOpen}
+            toggle={() => toggleDropdown()}
           >
-            <span className="material-symbols-outlined">content_paste</span>
-          </DropdownToggle>
-          <DropdownMenu strategy="fixed" end>
-            {/* Page path URL */}
-            <CopyToClipboard text={generateURL(currentPagePath)}>
-              <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page URL', { ns: 'commons' })} contents={generateURL(currentPagePath)} />
-              </DropdownItem>
-            </CopyToClipboard>
-            {/* Permanent Link URL */}
-            <CopyToClipboard text={generateURL(currentPageId)}>
-              <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Permanent link', { ns: 'commons' })} contents={generateURL(currentPageId)} />
-              </DropdownItem>
-            </CopyToClipboard>
-            <DropdownItem divider className="my-0"></DropdownItem>
-          </DropdownMenu>
-        </Dropdown>
+            <DropdownToggle className="btn-copy">
+              <span className="material-symbols-outlined">content_paste</span>
+            </DropdownToggle>
+            <DropdownMenu strategy="fixed" end>
+              {/* Page path URL */}
+              <CopyToClipboard text={generateURL(currentPagePath)}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Page URL', { ns: 'commons' })} contents={generateURL(currentPagePath)} />
+                </DropdownItem>
+              </CopyToClipboard>
+              {/* Permanent Link URL */}
+              <CopyToClipboard text={generateURL(currentPageId)}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Permanent link', { ns: 'commons' })} contents={generateURL(currentPageId)} />
+                </DropdownItem>
+              </CopyToClipboard>
+              <DropdownItem divider className="my-0"></DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+        ) }
       </div>
 
       <div className={`revision-compare-container ${isNodiff ? 'nodiff' : ''}`}>

+ 1 - 1
apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx

@@ -50,7 +50,7 @@ export const PageTreeHeader = memo(({ isWipPageShown, onWipPageShownChange }: He
                 checked={isWipPageShown}
                 onChange={() => {}}
               />
-              <label className="form-label form-check-label text-muted" htmlFor="wipPageVisibility">
+              <label className="form-label form-check-label text-muted mb-0" htmlFor="wipPageVisibility">
                 {t('sidebar_header.show_wip_page')}
               </label>
             </div>

+ 3 - 3
apps/app/src/components/Sidebar/RecentChanges/RecentChangesSubstance.tsx

@@ -95,14 +95,14 @@ const PageItem = memo(({ page, isSmall, onClickTag }: PageItemProps): JSX.Elemen
   const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
   const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
   const FormerLink = () => (
-    <div className={`${formerLinkClass} ${isSmall ? 'text-truncate' : ''} small`}>
+    <div className={`${formerLinkClass} ${isSmall ? 'text-truncate small' : ''}`}>
       <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} />
     </div>
   );
 
   let locked;
   if (page.grant !== 1) {
-    locked = <span className="material-symbols-outlined ms-2">lock</span>;
+    locked = <span className="material-symbols-outlined ms-2 fs-6">lock</span>;
   }
 
   const isTagElementsRendered = !(isSmall || (page.tags.length === 0));
@@ -114,7 +114,7 @@ const PageItem = memo(({ page, isSmall, onClickTag }: PageItemProps): JSX.Elemen
         <UserPicture user={page.lastUpdateUser} size="md" noTooltip />
 
         <div className="flex-grow-1 ms-2">
-          <div className={`row ${isSmall ? 'gy-0' : 'gy-2'}`}>
+          <div className={`row ${isSmall ? 'gy-0' : 'gy-1'}`}>
 
             <div className="col-12">
               { !dPagePath.isRoot && <FormerLink /> }

+ 6 - 2
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -130,13 +130,17 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
     mutateCollapsedContentsOpened(false);
   }, [isCollapsedMode, mutateCollapsedContentsOpened]);
 
-  const openClass = `${isCollapsedContentsOpened ? 'open' : ''}`;
+  const closedClass = isCollapsedMode() && !isCollapsedContentsOpened ? 'd-none' : '';
+  const openedClass = isCollapsedMode() && isCollapsedContentsOpened ? 'open' : '';
   const collapsibleContentsWidth = isCollapsedMode() ? currentProductNavWidth : undefined;
 
   return (
     <div className={`flex-expand-horiz ${className}`} onMouseLeave={mouseLeaveHandler}>
       <Nav onPrimaryItemHover={primaryItemHoverHandler} />
-      <div className={`sidebar-contents-container flex-grow-1 overflow-y-auto overflow-x-hidden ${openClass}`} style={{ width: collapsibleContentsWidth }}>
+      <div
+        className={`sidebar-contents-container flex-grow-1 overflow-y-auto overflow-x-hidden ${closedClass} ${openedClass}`}
+        style={{ width: collapsibleContentsWidth }}
+      >
         {children}
       </div>
     </div>

+ 9 - 0
apps/app/src/components/Sidebar/SidebarHead/ToggleCollapseButton.module.scss

@@ -1,6 +1,7 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 @use '~/styles/variables' as var;
+@use '~/styles/mixins';
 
 @use '../button-styles';
 
@@ -23,6 +24,14 @@
   }
 }
 
+// Hide when editing
+@include mixins.editing() {
+  .btn-toggle-collapse {
+    visibility: hidden;
+  }
+}
+
+
 // == Colors
 .btn-toggle-collapse {
   &:global {

+ 18 - 10
apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.module.scss

@@ -1,5 +1,23 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+.btn-personal-dropdown :global {
+  img {
+    border: 2px solid var(--bs-border-color-translucent);
+  }
+}
+
+.personal-dropdown-header :global {
+  .item-text-email {
+    font-size: 10.5px;
+  }
+}
+
+.personal-dropdown-menu :global {
+  --bs-dropdown-font-size: 14px;
+}
+
+
+// == Colors
 @include bs.color-mode(light) {
   .personal-dropdown-header :global {
     color: var(--bs-gray-600);
@@ -21,13 +39,3 @@
     color: var(--bs-gray-500);
   }
 }
-
-.personal-dropdown-menu :global {
-  --bs-dropdown-font-size: 14px;
-}
-
-.personal-dropdown-header :global {
-  .item-text-email {
-    font-size: 10.5px;
-  }
-}

+ 2 - 2
apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx

@@ -45,7 +45,7 @@ export const PersonalDropdown = (): JSX.Element => {
         direction="end"
       >
         <DropdownToggle
-          className="btn btn-primary"
+          className={`btn btn-primary ${styles['btn-personal-dropdown']} opacity-100`}
           data-testid="personal-dropdown-button"
         >
           <UserPicture user={currentUser} noLink noTooltip />
@@ -56,7 +56,7 @@ export const PersonalDropdown = (): JSX.Element => {
           data-testid="personal-dropdown-menu"
           className={styles['personal-dropdown-menu']}
         >
-          <DropdownItem className={styles['personal-dropdown-header']}>
+          <DropdownItem className={styles['personal-dropdown-header']} header>
             <div className="mt-2 mb-3">
               <UserPicture user={currentUser} size="lg" noLink noTooltip />
             </div>

+ 4 - 6
apps/app/src/components/Sidebar/SidebarNav/SecondaryItems.module.scss

@@ -7,13 +7,11 @@
   .btn {
     @extend %btn-basis;
 
-    span {
-      opacity: 0.6;
+    opacity: 0.6;
 
-      &:hover,
-      &:focus {
-        opacity: 0.8;
-      }
+    &:hover,
+    &:focus {
+      opacity: 0.8;
     }
   }
 }

+ 8 - 7
apps/app/src/features/comment/server/models/comment.ts

@@ -1,9 +1,10 @@
 import type { IUser } from '@growi/core/dist/interfaces';
-import {
-  Types, Document, Model, Schema, Query,
+import type {
+  Types, Document, Model, Query,
 } from 'mongoose';
+import { Schema } from 'mongoose';
 
-import { IComment } from '~/interfaces/comment';
+import type { IComment } from '~/interfaces/comment';
 import { getOrCreateModel } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
@@ -111,10 +112,10 @@ commentSchema.statics.countCommentByPageId = async function(page) {
   return this.count({ page });
 };
 
-commentSchema.methods.removeWithReplies = async function(comment) {
-  await this.remove({
-    $or: (
-      [{ replyTo: this._id }, { _id: this._id }]),
+commentSchema.statics.removeWithReplies = async function(comment) {
+  await this.deleteMany({
+    $or:
+      [{ replyTo: comment._id }, { _id: comment._id }],
   });
 };
 

+ 1 - 1
apps/app/src/server/routes/comment.js

@@ -475,7 +475,7 @@ module.exports = function(crowi, app) {
         throw new Error('Current user is not operatable to this comment.');
       }
 
-      await comment.removeWithReplies(comment);
+      await Comment.removeWithReplies(comment);
       await Page.updateCommentCount(comment.page);
       commentEvent.emit(CommentEvent.DELETE, comment);
     }

+ 6 - 4
apps/app/src/server/service/passport.ts

@@ -1,4 +1,4 @@
-import { IncomingMessage } from 'http';
+import type { IncomingMessage } from 'http';
 
 import axiosRetry from 'axios-retry';
 import luceneQueryParser from 'lucene-query-parser';
@@ -9,14 +9,15 @@ import { Strategy as GitHubStrategy } from 'passport-github';
 import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
 import LdapStrategy from 'passport-ldapauth';
 import { Strategy as LocalStrategy } from 'passport-local';
-import { Profile, Strategy as SamlStrategy, VerifiedCallback } from 'passport-saml';
+import type { Profile, VerifiedCallback } from 'passport-saml';
+import { Strategy as SamlStrategy } from 'passport-saml';
 import urljoin from 'url-join';
 
 import loggerFactory from '~/utils/logger';
 
 import S2sMessage from '../models/vo/s2s-message';
 
-import { S2sMessageHandlable } from './s2s-messaging/handlable';
+import type { S2sMessageHandlable } from './s2s-messaging/handlable';
 
 const logger = loggerFactory('growi:service:PassportService');
 
@@ -738,7 +739,7 @@ class PassportService implements S2sMessageHandlable {
     return oidcIssuer;
   }
 
-  setupSamlStrategy() {
+  setupSamlStrategy(): void {
 
     this.resetSamlStrategy();
 
@@ -760,6 +761,7 @@ class PassportService implements S2sMessageHandlable {
             : configManager.getConfig('crowi', 'security:passport-saml:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
           issuer: configManager.getConfig('crowi', 'security:passport-saml:issuer'),
           cert: configManager.getConfig('crowi', 'security:passport-saml:cert'),
+          disableRequestedAuthnContext: true,
         },
         (profile: Profile, done: VerifiedCallback) => {
           if (profile) {

+ 17 - 14
apps/app/src/stores/modal.tsx

@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
 import type {
   IAttachmentHasId, IPageToDeleteWithMeta, IPageToRenameWithMeta, IUserGroupHasId,
 } from '@growi/core';
+import { useSWRStatic } from '@growi/core/dist/swr';
 import type { SWRResponse } from 'swr';
 
 
@@ -363,33 +364,35 @@ type PageAccessoriesModalUtils = {
 export const usePageAccessoriesModal = (): SWRResponse<PageAccessoriesModalStatus, Error> & PageAccessoriesModalUtils => {
 
   const initialStatus = { isOpened: false };
-  const swrResponse = useStaticSWR<PageAccessoriesModalStatus, Error>('pageAccessoriesModalStatus', undefined, { fallbackData: initialStatus });
+  const swrResponse = useSWRStatic<PageAccessoriesModalStatus, Error>('pageAccessoriesModalStatus', undefined, { fallbackData: initialStatus });
+
+  const { data, mutate } = swrResponse;
 
   return Object.assign(swrResponse, {
-    open: (activatedContents) => {
-      if (swrResponse.data == null) {
+    open: useCallback((activatedContents) => {
+      if (data == null) {
         return;
       }
-      swrResponse.mutate({
+      mutate({
         isOpened: true,
         activatedContents,
       });
-    },
-    close: () => {
-      if (swrResponse.data == null) {
+    }, [data, mutate]),
+    close: useCallback(() => {
+      if (data == null) {
         return;
       }
-      swrResponse.mutate({ isOpened: false });
-    },
-    selectContents: (activatedContents) => {
-      if (swrResponse.data == null) {
+      mutate({ isOpened: false });
+    }, [data, mutate]),
+    selectContents: useCallback((activatedContents) => {
+      if (data == null) {
         return;
       }
-      swrResponse.mutate({
-        isOpened: swrResponse.data.isOpened,
+      mutate({
+        isOpened: data.isOpened,
         activatedContents,
       });
-    },
+    }, [data, mutate]),
   });
 };
 

+ 5 - 2
apps/app/src/stores/page.tsx

@@ -214,12 +214,15 @@ export const useSWRMUTxPageInfo = (
     shareLinkId?: string | null,
 ): SWRMutationResponse<IPageInfo | IPageInfoForOperation> => {
 
+  // Cache remains from guest mode when logging in via the Login lead, so add 'isGuestUser' key
+  const { data: isGuestUser } = useIsGuestUser();
+
   // assign null if shareLinkId is undefined in order to identify SWR key only by pageId
   const fixedShareLinkId = shareLinkId ?? null;
 
   const key = useMemo(() => {
-    return pageId != null ? ['/page/info', pageId, fixedShareLinkId] : null;
-  }, [fixedShareLinkId, pageId]);
+    return pageId != null ? ['/page/info', pageId, fixedShareLinkId, isGuestUser] : null;
+  }, [fixedShareLinkId, isGuestUser, pageId]);
 
   return useSWRMutation(
     key,

+ 0 - 8
apps/app/src/styles/_mixins.scss

@@ -11,14 +11,6 @@
   height: $line-height;
   padding: (($line-height - $font-size)  / 2) 0;
 }
-/*
-.example {
-  @include grw-skeleton-text($font-size:$size, $line-height:$height);
-  max-width: 100%;
-}
-*/
-
-// values from './bootstrap/override'
 
 @mixin grw-skeleton-h3 {
   @include grw-skeleton-text(21px, 30px);

+ 19 - 0
apps/app/src/styles/_override-handsontable.scss

@@ -0,0 +1,19 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+// Table
+@include bs.color-mode(dark) {
+  .handsontable:not(.htMenu) .handsontable.ht_master {
+    td {
+      color: var(--bs-gray-800);
+    }
+  }
+}
+
+// Context Menu
+@include bs.color-mode(dark) {
+  .handsontable.htMenu .handsontable.ht_master {
+    td:not(.htDisabled) {
+      color: var(--bs-gray-800);
+    }
+  }
+}

+ 3 - 0
apps/app/src/styles/atoms/_tooltip.scss

@@ -0,0 +1,3 @@
+.tooltip {
+  pointer-events: none;
+}

+ 0 - 60
apps/app/src/styles/atoms/mixins/_buttons.scss

@@ -1,60 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
-  svg {
-    fill: color-contrast($background);
-  }
-
-  &:hover {
-    svg {
-      fill: color-contrast($hover-background);
-    }
-  }
-
-  &:focus,
-  &.focus {
-    svg {
-      fill: color-contrast($hover-background);
-    }
-  }
-
-  // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: color-contrast($background);
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: color-contrast($active-background);
-    }
-  }
-}
-
-@mixin button-outline-svg-icon-variant($value, $color-hover: $value) {
-  svg {
-    fill: $value;
-  }
-  &:hover {
-    svg {
-      fill: $color-hover;
-    }
-  }
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: $value;
-    }
-  }
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: $color-hover;
-    }
-  }
-}

+ 1 - 0
apps/app/src/styles/style-app.scss

@@ -7,6 +7,7 @@
 @import 'atoms/custom_control';
 @import 'atoms/code';
 @import 'atoms/tag';
+@import 'atoms/tooltip';
 
 // molecules
 @import 'molecules/toastr';

+ 3 - 0
apps/app/src/styles/vendor.scss

@@ -8,3 +8,6 @@
 // SimpleBar
 @import 'simplebar/dist/simplebar.min.css';
 @import './override-simplebar';
+
+// Handsontable
+@import './override-handsontable';

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/LinkEditButton.tsx

@@ -32,7 +32,7 @@ export const LinkEditButton = (props: Props): JSX.Element => {
   }, [codeMirrorEditor?.view, openLinkEditModal]);
 
   return (
-    <DropdownItem className="d-flex gap-1 align-items-center" onClick={onClickOpenLinkEditModal}>
+    <DropdownItem className="d-flex gap-2 align-items-center" onClick={onClickOpenLinkEditModal}>
       <span className="material-symbols-outlined fs-5">link</span>Link
     </DropdownItem>
   );

+ 0 - 60
packages/preset-themes/src/styles/atoms/mixins/_buttons.scss

@@ -1,60 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
-  svg {
-    fill: color-contrast($background);
-  }
-
-  &:hover {
-    svg {
-      fill: color-contrast($hover-background);
-    }
-  }
-
-  &:focus,
-  &.focus {
-    svg {
-      fill: color-contrast($hover-background);
-    }
-  }
-
-  // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: color-contrast($background);
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: color-contrast($active-background);
-    }
-  }
-}
-
-@mixin button-outline-svg-icon-variant($value, $color-hover: $value) {
-  svg {
-    fill: $value;
-  }
-  &:hover {
-    svg {
-      fill: $color-hover;
-    }
-  }
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: $value;
-    }
-  }
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: $color-hover;
-    }
-  }
-}

+ 0 - 5
packages/preset-themes/src/styles/atoms/mixins/_code.scss

@@ -1,5 +0,0 @@
-@mixin code-inline-color($color-inline-code,$bgcolor-inline-code, $bordercolor-inline-code) {
-  color: $color-inline-code;
-  background-color: $bgcolor-inline-code;
-  border-color: $bordercolor-inline-code;
-}

+ 1 - 1
packages/preset-themes/src/styles/christmas.scss

@@ -2,7 +2,7 @@
 
 @use './variables' as var;
 @use './theme/mixins/page-editor-mode-manager';
-@use './theme/hsl-functions' as hsl;
+// @use './theme/hsl-functions' as hsl;
 
 .growi:not(.login-page) {
   // add background-image

+ 0 - 32
packages/preset-themes/src/styles/theme/_hsl-functions.scss

@@ -1,32 +0,0 @@
-@use 'bootstrap/scss/functions' as bs;
-
-@function contrast($color, $darken-degrees: 0%) {
-  $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 hsl($color-hs, clamp(10%, calc((100% - $color-l - $darken-degrees - 51% ) * 1000), 95%));
-}
-
-@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 - 147
packages/preset-themes/src/styles/theme/mixins/_hsl-button.scss

@@ -1,147 +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);
-    }
-  }
-}

+ 9 - 9
yarn.lock

@@ -7424,15 +7424,15 @@ diff-sequences@^29.4.3:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
   integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
 
-diff2html@^3.4.35:
-  version "3.4.35"
-  resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-3.4.35.tgz#75b83c0a7edd8b9521db9bebf8c657cd57f52d3e"
-  integrity sha512-+pKs1BrA7l8DAwY33awHyznE3iuTIo58xmINmDBUwGsnou2KvBoSr6dAa6AvQAM7SH+nGtuOKNXmxumgbGp/Pw==
+diff2html@^3.4.47:
+  version "3.4.47"
+  resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-3.4.47.tgz#a9d33bb63815031981fac682eeb2a5d74a931109"
+  integrity sha512-2llDp8750FRUJl8n7apM0tlcqZYxbDHTw7qhzv/kGddByHRpn3Xg/sWHHIy34h492aGSpStEULydxqrITYpuoA==
   dependencies:
     diff "5.1.0"
     hogan.js "3.0.2"
   optionalDependencies:
-    highlight.js "11.6.0"
+    highlight.js "11.9.0"
 
 diff@5.1.0, diff@^5.0.0:
   version "5.1.0"
@@ -9657,10 +9657,10 @@ helmet@^4.6.0:
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
 
-highlight.js@11.6.0:
-  version "11.6.0"
-  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.6.0.tgz#a50e9da05763f1bb0c1322c8f4f755242cff3f5a"
-  integrity sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==
+highlight.js@11.9.0:
+  version "11.9.0"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
+  integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
 
 highlight.js@^10.4.1, highlight.js@^10.7.1, highlight.js@~10.7.0:
   version "10.7.3"