2
0
Эх сурвалжийг харах

Merge branch 'dev/7.0.x' into imprv/133905-replace-icons-material-symbols-outlined

Shun Miyazawa 2 жил өмнө
parent
commit
1bbe8e0b94
24 өөрчлөгдсөн 764 нэмэгдсэн , 486 устгасан
  1. 81 0
      .github/workflows/release-rc-v7.yml
  2. 2 2
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  3. 1 1
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  4. 15 9
      apps/app/src/client/services/use-on-template-button-clicked.ts
  5. 21 0
      apps/app/src/components/Page/markdown-drawio-util-for-view.ts
  6. 0 0
      apps/app/src/components/Page/markdown-table-util-for-view.ts
  7. 1 1
      apps/app/src/components/PageEditor/DrawioCommunicationHelper.ts
  8. 22 9
      apps/app/src/components/PageEditor/DrawioModal.tsx
  9. 0 179
      apps/app/src/components/PageEditor/MarkdownDrawioUtil.js
  10. 134 0
      apps/app/src/components/PageEditor/markdown-drawio-util-for-editor.ts
  11. 4 4
      apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx
  12. 7 7
      apps/app/src/components/Sidebar/PageCreateButton/hooks.tsx
  13. 5 0
      apps/app/src/pages/_private-legacy-pages.page.tsx
  14. 5 0
      apps/app/src/pages/_search.page.tsx
  15. 7 2
      apps/app/src/pages/me/[[...path]].page.tsx
  16. 6 0
      apps/app/src/pages/tags.page.tsx
  17. 5 1
      apps/app/src/pages/trash.page.tsx
  18. 15 2
      packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  19. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx
  20. 4 0
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts
  21. 50 0
      packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/fold-drawio.ts
  22. 40 0
      packages/editor/src/stores/use-drawio.ts
  23. 337 267
      packages/preset-themes/src/styles/fire-red.scss
  24. 1 1
      packages/preset-themes/vite.themes.config.ts

+ 81 - 0
.github/workflows/release-rc-v7.yml

@@ -0,0 +1,81 @@
+name: Release Docker Images for RC (for dev/7.0.x)
+
+on:
+  push:
+    branches:
+      - dev/7.0.x
+
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
+
+  determine-tags:
+    runs-on: ubuntu-latest
+
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
+
+    - name: Docker meta for docker.io
+      uses: docker/metadata-action@v4
+      id: meta
+      with:
+        images: docker.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          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 }}.{{sha}}
+
+
+  build-image-rc:
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image-rc:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    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 }}
+

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

@@ -5,7 +5,7 @@ import EventEmitter from 'events';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 import { useSaveOrUpdate } from '~/client/services/page-operation';
-import mdu from '~/components/PageEditor/MarkdownDrawioUtil';
+import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
 import type { OptionsToSave } from '~/interfaces/page-operation';
 import { useShareLinkId } from '~/stores/context';
 import { useDrawioModal } from '~/stores/modal';
@@ -41,7 +41,7 @@ export const useDrawioModalLauncherForView = (opts?: {
     }
 
     const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = mdu.replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
+    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
 
     const grantUserGroupIds = currentPage.grantedGroups.map((g) => {
       return {

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

@@ -4,7 +4,7 @@ import EventEmitter from 'events';
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
-import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/PageEditor/markdown-table-util-for-view';
+import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import type { OptionsToSave } from '~/interfaces/page-operation';
 import { useShareLinkId } from '~/stores/context';
 import { useHandsontableModal } from '~/stores/modal';

+ 15 - 9
apps/app/src/client/services/use-on-template-button-clicked.ts

@@ -1,5 +1,6 @@
 import { useCallback, useState } from 'react';
 
+import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { useRouter } from 'next/router';
 
 import { createPage, exist } from '~/client/services/page-operation';
@@ -7,6 +8,7 @@ import { LabelType } from '~/interfaces/template';
 
 export const useOnTemplateButtonClicked = (
     currentPagePath?: string,
+    isLoading?: boolean,
 ): {
   onClickHandler: (label: LabelType) => Promise<void>,
   isPageCreating: boolean
@@ -15,23 +17,27 @@ export const useOnTemplateButtonClicked = (
   const [isPageCreating, setIsPageCreating] = useState(false);
 
   const onClickHandler = useCallback(async(label: LabelType) => {
+    if (isLoading) return;
+
     try {
       setIsPageCreating(true);
 
-      const path = currentPagePath == null || currentPagePath === '/'
+      const targetPath = currentPagePath == null || currentPagePath === '/'
         ? `/${label}`
         : `${currentPagePath}/${label}`;
 
-      const params = {
-        isSlackEnabled: false,
-        slackChannels: '',
-        grant: 4,
-      // grant: currentPage?.grant || 1,
-      // grantUserGroupId: currentPage?.grantedGroup?._id,
-      };
+      const path = isCreatablePage(targetPath) ? targetPath : `/${label}`;
 
       const res = await exist(JSON.stringify([path]));
       if (!res.pages[path]) {
+        const params = {
+          isSlackEnabled: false,
+          slackChannels: '',
+          grant: 4,
+        // grant: currentPage?.grant || 1,
+        // grantUserGroupId: currentPage?.grantedGroup?._id,
+        };
+
         await createPage(path, '', params);
       }
 
@@ -43,7 +49,7 @@ export const useOnTemplateButtonClicked = (
     finally {
       setIsPageCreating(false);
     }
-  }, [currentPagePath, router]);
+  }, [currentPagePath, isLoading, router]);
 
   return { onClickHandler, isPageCreating };
 };

+ 21 - 0
apps/app/src/components/Page/markdown-drawio-util-for-view.ts

@@ -0,0 +1,21 @@
+/**
+ * return markdown where the drawioData specified by line number params is replaced to the drawioData specified by drawioData param
+ */
+export const replaceDrawioInMarkdown = (drawioData: string, markdown: string, beginLineNumber: number, endLineNumber: number): string => {
+  const splitMarkdown = markdown.split(/\r\n|\r|\n/);
+  const markdownBeforeDrawio = splitMarkdown.slice(0, beginLineNumber - 1);
+  const markdownAfterDrawio = splitMarkdown.slice(endLineNumber);
+
+  let newMarkdown = '';
+  if (markdownBeforeDrawio.length > 0) {
+    newMarkdown += `${markdownBeforeDrawio.join('\n')}\n`;
+  }
+  newMarkdown += '``` drawio\n';
+  newMarkdown += drawioData;
+  newMarkdown += '\n```';
+  if (markdownAfterDrawio.length > 0) {
+    newMarkdown += `\n${markdownAfterDrawio.join('\n')}`;
+  }
+
+  return newMarkdown;
+};

+ 0 - 0
apps/app/src/components/PageEditor/markdown-table-util-for-view.ts → apps/app/src/components/Page/markdown-table-util-for-view.ts


+ 1 - 1
apps/app/src/components/PageEditor/DrawioCommunicationHelper.ts

@@ -29,7 +29,7 @@ export class DrawioCommunicationHelper {
     this.callbackOpts = callbackOpts;
   }
 
-  onReceiveMessage(event: MessageEvent, drawioMxFile: string): void {
+  onReceiveMessage(event: MessageEvent, drawioMxFile: string | null): void {
 
     // check origin
     if (event.origin != null && this.drawioUri != null) {

+ 22 - 9
apps/app/src/components/PageEditor/DrawioModal.tsx

@@ -4,12 +4,15 @@ import React, {
   useMemo,
 } from 'react';
 
+import { useCodeMirrorEditorIsolated } from '@growi/editor';
+import { useDrawioModalForEditor } from '@growi/editor/src/stores/use-drawio';
 import {
   Modal,
   ModalBody,
 } from 'reactstrap';
 
 import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
+import { replaceFocusedDrawioWithEditor, getMarkdownDrawioMxfile } from '~/components/PageEditor/markdown-drawio-util-for-editor';
 import { useRendererConfig } from '~/stores/context';
 import { useDrawioModal } from '~/stores/modal';
 import { usePersonalSettings } from '~/stores/personal-settings';
@@ -47,6 +50,11 @@ export const DrawioModal = (): JSX.Element => {
   });
 
   const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
+  const { data: drawioModalDataInEditor, close: closeDrawioModalInEditor } = useDrawioModalForEditor();
+  const editorKey = drawioModalDataInEditor?.editorKey ?? null;
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
+  const editor = codeMirrorEditor?.view;
+  const isOpenedInEditor = (drawioModalDataInEditor?.isOpened ?? false) && (editor != null);
   const isOpened = drawioModalData?.isOpened ?? false;
 
   const drawioUriWithParams = useMemo(() => {
@@ -78,23 +86,28 @@ export const DrawioModal = (): JSX.Element => {
       return undefined;
     }
 
+    const save = editor != null ? (drawioMxFile: string) => {
+      replaceFocusedDrawioWithEditor(editor, drawioMxFile);
+    } : drawioModalData?.onSave;
+
     return new DrawioCommunicationHelper(
       rendererConfig.drawioUri,
       drawioConfig,
-      { onClose: closeDrawioModal, onSave: drawioModalData?.onSave },
+      { onClose: isOpened ? closeDrawioModal : closeDrawioModalInEditor, onSave: save },
     );
-  }, [closeDrawioModal, drawioModalData?.onSave, rendererConfig]);
+  }, [closeDrawioModal, closeDrawioModalInEditor, drawioModalData?.onSave, editor, isOpened, rendererConfig]);
 
   const receiveMessageHandler = useCallback((event: MessageEvent) => {
     if (drawioModalData == null) {
       return;
     }
 
-    drawioCommunicationHelper?.onReceiveMessage(event, drawioModalData.drawioMxFile);
-  }, [drawioCommunicationHelper, drawioModalData]);
+    const drawioMxFile = editor != null ? getMarkdownDrawioMxfile(editor) : drawioModalData.drawioMxFile;
+    drawioCommunicationHelper?.onReceiveMessage(event, drawioMxFile);
+  }, [drawioCommunicationHelper, drawioModalData, editor]);
 
   useEffect(() => {
-    if (isOpened) {
+    if (isOpened || isOpenedInEditor) {
       window.addEventListener('message', receiveMessageHandler);
     }
     else {
@@ -105,12 +118,12 @@ export const DrawioModal = (): JSX.Element => {
     return function() {
       window.removeEventListener('message', receiveMessageHandler);
     };
-  }, [isOpened, receiveMessageHandler]);
+  }, [isOpened, isOpenedInEditor, receiveMessageHandler]);
 
   return (
     <Modal
-      isOpen={isOpened}
-      toggle={() => closeDrawioModal()}
+      isOpen={isOpened || isOpenedInEditor}
+      toggle={() => (isOpened ? closeDrawioModal() : closeDrawioModalInEditor())}
       backdrop="static"
       className="drawio-modal grw-body-only-modal-expanded"
       size="xl"
@@ -126,7 +139,7 @@ export const DrawioModal = (): JSX.Element => {
         {/* iframe */}
         { drawioUriWithParams != null && (
           <div className="w-100 h-100 position-absolute d-flex">
-            { isOpened && (
+            { (isOpened || isOpenedInEditor) && (
               <iframe
                 src={drawioUriWithParams.href}
                 className="border-0 flex-grow-1"

+ 0 - 179
apps/app/src/components/PageEditor/MarkdownDrawioUtil.js

@@ -1,179 +0,0 @@
-/**
- * Utility for markdown drawio
- */
-class MarkdownDrawioUtil {
-
-  constructor() {
-    this.lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
-    this.lineEndPartOfDrawioRE = /^```$/;
-  }
-
-  /**
-   * return the postion of the BOD(beginning of drawio)
-   * (If the BOD is not found after the cursor or the EOD is found before the BOD, return null)
-   */
-  getBod(editor) {
-    const curPos = editor.getCursor();
-    const firstLine = editor.getDoc().firstLine();
-
-    if (this.lineBeginPartOfDrawioRE.test(editor.getDoc().getLine(curPos.line))) {
-      return { line: curPos.line, ch: 0 };
-    }
-
-    let line = curPos.line - 1;
-    let isFound = false;
-    for (; line >= firstLine; line--) {
-      const strLine = editor.getDoc().getLine(line);
-      if (this.lineBeginPartOfDrawioRE.test(strLine)) {
-        isFound = true;
-        break;
-      }
-
-      if (this.lineEndPartOfDrawioRE.test(strLine)) {
-        isFound = false;
-        break;
-      }
-    }
-
-    if (!isFound) {
-      return null;
-    }
-
-    const bodLine = Math.max(firstLine, line);
-    return { line: bodLine, ch: 0 };
-  }
-
-  /**
-   * return the postion of the EOD(end of drawio)
-   * (If the EOD is not found after the cursor or the BOD is found before the EOD, return null)
-   */
-  getEod(editor) {
-    const curPos = editor.getCursor();
-    const lastLine = editor.getDoc().lastLine();
-
-    if (this.lineEndPartOfDrawioRE.test(editor.getDoc().getLine(curPos.line))) {
-      return { line: curPos.line, ch: editor.getDoc().getLine(curPos.line).length };
-    }
-
-    let line = curPos.line + 1;
-    let isFound = false;
-    for (; line <= lastLine; line++) {
-      const strLine = editor.getDoc().getLine(line);
-      if (this.lineEndPartOfDrawioRE.test(strLine)) {
-        isFound = true;
-        break;
-      }
-
-      if (this.lineBeginPartOfDrawioRE.test(strLine)) {
-        isFound = false;
-        break;
-      }
-    }
-
-    if (!isFound) {
-      return null;
-    }
-
-    const eodLine = Math.min(line, lastLine);
-    const lineLength = editor.getDoc().getLine(eodLine).length;
-    return { line: eodLine, ch: lineLength };
-  }
-
-  /**
-   * return boolean value whether the cursor position is in a drawio
-   */
-  isInDrawioBlock(editor) {
-    const bod = this.getBod(editor);
-    const eod = this.getEod(editor);
-    if (bod === null || eod === null) {
-      return false;
-    }
-    return JSON.stringify(bod) !== JSON.stringify(eod);
-  }
-
-  /**
-   * return drawioData instance where the cursor is
-   * (If the cursor is not in a drawio block, return null)
-   */
-  getMarkdownDrawioMxfile(editor) {
-    if (this.isInDrawioBlock(editor)) {
-      const bod = this.getBod(editor);
-      const eod = this.getEod(editor);
-
-      // skip block begin sesion("``` drawio")
-      bod.line++;
-      // skip block end sesion("```")
-      eod.line--;
-      eod.ch = editor.getDoc().getLine(eod.line).length;
-
-      return editor.getDoc().getRange(bod, eod);
-    }
-    return null;
-  }
-
-  replaceFocusedDrawioWithEditor(editor, drawioData) {
-    const curPos = editor.getCursor();
-    const drawioBlock = ['``` drawio', drawioData.toString(), '```'].join('\n');
-    let beginPos;
-    let endPos;
-
-    if (this.isInDrawioBlock(editor)) {
-      beginPos = this.getBod(editor);
-      endPos = this.getEod(editor);
-    }
-    else {
-      beginPos = { line: curPos.line, ch: curPos.ch };
-      endPos = { line: curPos.line, ch: curPos.ch };
-    }
-
-    editor.getDoc().replaceRange(drawioBlock, beginPos, endPos);
-  }
-
-  /**
-   * return markdown where the drawioData specified by line number params is replaced to the drawioData specified by drawioData param
-   * @param {string} drawioData
-   * @param {string} markdown
-   * @param beginLineNumber
-   * @param endLineNumber
-   */
-  replaceDrawioInMarkdown(drawioData, markdown, beginLineNumber, endLineNumber) {
-    const splitMarkdown = markdown.split(/\r\n|\r|\n/);
-    const markdownBeforeDrawio = splitMarkdown.slice(0, beginLineNumber - 1);
-    const markdownAfterDrawio = splitMarkdown.slice(endLineNumber);
-
-    let newMarkdown = '';
-    if (markdownBeforeDrawio.length > 0) {
-      newMarkdown += `${markdownBeforeDrawio.join('\n')}\n`;
-    }
-    newMarkdown += '``` drawio\n';
-    newMarkdown += drawioData;
-    newMarkdown += '\n```';
-    if (markdownAfterDrawio.length > 0) {
-      newMarkdown += `\n${markdownAfterDrawio.join('\n')}`;
-    }
-
-    return newMarkdown;
-  }
-
-  /**
-   * return an array of the starting line numbers of the drawio sections found in markdown
-   */
-  findAllDrawioSection(editor) {
-    const lineNumbers = [];
-    // refs: https://github.com/codemirror/CodeMirror/blob/5.64.0/addon/fold/foldcode.js#L106-L111
-    for (let i = editor.firstLine(), e = editor.lastLine(); i <= e; i++) {
-      const line = editor.getLine(i);
-      const match = this.lineBeginPartOfDrawioRE.exec(line);
-      if (match) {
-        lineNumbers.push(i);
-      }
-    }
-    return lineNumbers;
-  }
-
-}
-
-// singleton pattern
-const instance = new MarkdownDrawioUtil();
-Object.freeze(instance);
-export default instance;

+ 134 - 0
apps/app/src/components/PageEditor/markdown-drawio-util-for-editor.ts

@@ -0,0 +1,134 @@
+import { EditorView } from '@codemirror/view';
+
+const lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
+const lineEndPartOfDrawioRE = /^```$/;
+const firstLineNum = 1;
+
+const curPos = (editor: EditorView) => {
+  return editor.state.selection.main.head;
+};
+
+const doc = (editor: EditorView) => {
+  return editor.state.doc;
+};
+
+const lastLineNum = (editor: EditorView) => {
+  return doc(editor).lines;
+};
+
+const getCursorLine = (editor: EditorView) => {
+  return doc(editor).lineAt(curPos(editor));
+};
+
+const getLine = (editor: EditorView, lineNum: number) => {
+  return doc(editor).line(lineNum);
+};
+
+/**
+ * return the postion of the BOD(beginning of drawio)
+ * (If the BOD is not found after the cursor or the EOD is found before the BOD, return null)
+ */
+const getBod = (editor: EditorView) => {
+  const strLine = getCursorLine(editor).text;
+  if (lineBeginPartOfDrawioRE.test(strLine)) {
+    // get the beginning of the line where the cursor is located
+    return getCursorLine(editor).from;
+  }
+
+  let line = getCursorLine(editor).number - 1;
+  let isFound = false;
+  for (; line >= firstLineNum; line--) {
+    const strLine = getLine(editor, line).text;
+    if (lineBeginPartOfDrawioRE.test(strLine)) {
+      isFound = true;
+      break;
+    }
+
+    if (lineEndPartOfDrawioRE.test(strLine)) {
+      isFound = false;
+      break;
+    }
+  }
+
+  if (!isFound) {
+    return null;
+  }
+
+  const botLine = Math.max(firstLineNum, line);
+  return getLine(editor, botLine).from;
+};
+
+/**
+ * return the postion of the EOD(end of drawio)
+ * (If the EOD is not found after the cursor or the BOD is found before the EOD, return null)
+ */
+const getEod = (editor: EditorView) => {
+  const lastLine = lastLineNum(editor);
+
+  const strLine = getCursorLine(editor).text;
+  if (lineEndPartOfDrawioRE.test(strLine)) {
+    // get the end of the line where the cursor is located
+    return getCursorLine(editor).to;
+  }
+
+  let line = getCursorLine(editor).number + 1;
+  let isFound = false;
+  for (; line <= lastLine; line++) {
+    const strLine = getLine(editor, line).text;
+    if (lineEndPartOfDrawioRE.test(strLine)) {
+      isFound = true;
+      break;
+    }
+
+    if (lineBeginPartOfDrawioRE.test(strLine)) {
+      isFound = false;
+      break;
+    }
+  }
+
+  if (!isFound) {
+    return null;
+  }
+
+  const eodLine = Math.min(line, lastLine);
+  return getLine(editor, eodLine).to;
+};
+
+/**
+ * return drawioData instance where the cursor is
+ * (If the cursor is not in a drawio block, return null)
+ */
+export const getMarkdownDrawioMxfile = (editor: EditorView): string | null => {
+  const bod = getBod(editor);
+  const eod = getEod(editor);
+  if (bod == null || eod == null || JSON.stringify(bod) === JSON.stringify(eod)) {
+    return null;
+  }
+
+  // skip block begin sesion("``` drawio")
+  const bodLineNum = doc(editor).lineAt(bod).number + 1;
+  const bodLine = getLine(editor, bodLineNum).from;
+  // skip block end sesion("```")
+  const eodLineNum = doc(editor).lineAt(eod).number - 1;
+  const eodLine = getLine(editor, eodLineNum).to;
+
+  return editor.state.sliceDoc(bodLine, eodLine);
+};
+
+export const replaceFocusedDrawioWithEditor = (editor: EditorView, drawioData: string): void => {
+  const drawioBlock = ['``` drawio', drawioData.toString(), '```'].join('\n');
+  let bod = getBod(editor);
+  let eod = getEod(editor);
+  if (bod == null || eod == null || JSON.stringify(bod) === JSON.stringify(eod)) {
+    bod = curPos(editor);
+    eod = curPos(editor);
+  }
+
+  editor.dispatch({
+    changes: {
+      from: bod,
+      to: eod,
+      insert: drawioBlock,
+    },
+  });
+};

+ 4 - 4
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -8,7 +8,7 @@ import { useOnTemplateButtonClicked } from '~/client/services/use-on-template-bu
 import { toastError } from '~/client/util/toastr';
 import { LabelType } from '~/interfaces/template';
 import { useCurrentUser } from '~/stores/context';
-import { useSWRxCurrentPage } from '~/stores/page';
+import { useCurrentPagePath } from '~/stores/page';
 
 import { CreateButton } from './CreateButton';
 import { DropendMenu } from './DropendMenu';
@@ -18,7 +18,7 @@ import { useOnNewButtonClicked, useOnTodaysButtonClicked } from './hooks';
 export const PageCreateButton = React.memo((): JSX.Element => {
   const { t } = useTranslation('commons');
 
-  const { data: currentPage, isLoading } = useSWRxCurrentPage();
+  const { data: currentPagePath, isLoading } = useCurrentPagePath();
   const { data: currentUser } = useCurrentUser();
 
   const [isHovered, setIsHovered] = useState(false);
@@ -27,9 +27,9 @@ export const PageCreateButton = React.memo((): JSX.Element => {
   const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
   const todaysPath = `${userHomepagePath}/${t('create_page_dropdown.todays.memo')}/${now}`;
 
-  const { onClickHandler: onClickNewButton, isPageCreating: isNewPageCreating } = useOnNewButtonClicked(isLoading, currentPage);
+  const { onClickHandler: onClickNewButton, isPageCreating: isNewPageCreating } = useOnNewButtonClicked(currentPagePath, isLoading);
   const { onClickHandler: onClickTodaysButton, isPageCreating: isTodaysPageCreating } = useOnTodaysButtonClicked(todaysPath, currentUser);
-  const { onClickHandler: onClickTemplateButton, isPageCreating: isTemplatePageCreating } = useOnTemplateButtonClicked(currentPage?.path);
+  const { onClickHandler: onClickTemplateButton, isPageCreating: isTemplatePageCreating } = useOnTemplateButtonClicked(currentPagePath, isLoading);
 
   const onClickTemplateButtonHandler = useCallback(async(label: LabelType) => {
     try {

+ 7 - 7
apps/app/src/components/Sidebar/PageCreateButton/hooks.tsx

@@ -1,14 +1,14 @@
 import { useCallback, useState } from 'react';
 
-import type { Nullable, IPagePopulatedToShowRevision, IUserHasId } from '@growi/core';
+import type { Nullable, IUserHasId } from '@growi/core';
 import { useRouter } from 'next/router';
 
 import { createPage, exist } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
 
 export const useOnNewButtonClicked = (
-    isLoading: boolean,
-    currentPage?: IPagePopulatedToShowRevision | null,
+    currentPagePath?: string,
+    isLoading?: boolean,
 ): {
   onClickHandler: () => Promise<void>,
   isPageCreating: boolean
@@ -22,9 +22,9 @@ export const useOnNewButtonClicked = (
     try {
       setIsPageCreating(true);
 
-      const parentPath = currentPage == null
+      const parentPath = currentPagePath == null
         ? '/'
-        : currentPage.path;
+        : currentPagePath;
 
       const params = {
         isSlackEnabled: false,
@@ -37,7 +37,7 @@ export const useOnNewButtonClicked = (
 
       const response = await createPage(parentPath, '', params);
 
-      router.push(`${response.page.id}#edit`);
+      router.push(`/${response.page.id}#edit`);
     }
     catch (err) {
       toastError(err);
@@ -45,7 +45,7 @@ export const useOnNewButtonClicked = (
     finally {
       setIsPageCreating(false);
     }
-  }, [currentPage, isLoading, router]);
+  }, [currentPagePath, isLoading, router]);
 
   return { onClickHandler, isPageCreating };
 };

+ 5 - 0
apps/app/src/pages/_private-legacy-pages.page.tsx

@@ -14,6 +14,7 @@ import {
   useCsrfToken, useCurrentUser, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useGrowiCloudUri, useIsEnabledMarp,
 } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
 
 import type { CommonProps } from './utils/commons';
 import {
@@ -46,6 +47,10 @@ const PrivateLegacyPage: NextPage<Props> = (props: Props) => {
 
   useCurrentUser(props.currentUser ?? null);
 
+  // clear the cache for the current page
+  const { mutate } = useSWRxCurrentPage();
+  mutate(undefined, { revalidate: false });
+
   // Search
   useIsSearchPage(true);
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);

+ 5 - 0
apps/app/src/pages/_search.page.tsx

@@ -14,6 +14,7 @@ import {
   useCsrfToken, useCurrentUser, useIsContainerFluid, useIsSearchPage, useIsSearchScopeChildrenAsDefault,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useRendererConfig, useShowPageLimitationL, useGrowiCloudUri,
 } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
 
 import { SearchPage } from '../components/SearchPage';
 
@@ -50,6 +51,10 @@ const SearchResultPage: NextPageWithLayout<Props> = (props: Props) => {
 
   useCurrentUser(props.currentUser ?? null);
 
+  // clear the cache for the current page
+  const { mutate } = useSWRxCurrentPage();
+  mutate(undefined, { revalidate: false });
+
   // Search
   useIsSearchPage(true);
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);

+ 7 - 2
apps/app/src/pages/me/[[...path]].page.tsx

@@ -18,6 +18,7 @@ import {
   useCsrfToken, useIsSearchScopeChildrenAsDefault,
   useRegistrationWhitelist, useShowPageLimitationXL, useRendererConfig, useIsEnabledMarp,
 } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import { NextPageWithLayout } from '../_app.page';
@@ -87,8 +88,6 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
 
   useIsSearchPage(false);
 
-  useCurrentUser(props.currentUser ?? null);
-
   useRegistrationWhitelist(props.registrationWhitelist);
 
   useShowPageLimitationXL(props.showPageLimitationXL);
@@ -97,6 +96,12 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
   useCsrfToken(props.csrfToken);
   useGrowiCloudUri(props.growiCloudUri);
 
+  useCurrentUser(props.currentUser ?? null);
+
+  // clear the cache for the current page
+  const { mutate } = useSWRxCurrentPage();
+  mutate(undefined, { revalidate: false });
+
   // init sidebar config with UserUISettings and sidebarConfig
   useInitSidebarConfig(props.sidebarConfig, props.userUISettings);
 

+ 6 - 0
apps/app/src/pages/tags.page.tsx

@@ -10,6 +10,7 @@ import Head from 'next/head';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IDataTagCount } from '~/interfaces/tag';
+import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxTagsList } from '~/stores/tag';
 
 import { BasicLayout } from '../components/Layout/BasicLayout';
@@ -44,6 +45,11 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
   const [offset, setOffset] = useState<number>(0);
 
   useCurrentUser(props.currentUser ?? null);
+
+  // clear the cache for the current page
+  const { mutate } = useSWRxCurrentPage();
+  mutate(undefined, { revalidate: false });
+
   const { data: tagDataList, error } = useSWRxTagsList(PAGING_LIMIT, offset);
   const { t } = useTranslation('');
   const setOffsetByPageNumber = useCallback((selectedPageNumber: number) => {

+ 5 - 1
apps/app/src/pages/trash.page.tsx

@@ -9,7 +9,7 @@ import Head from 'next/head';
 import { PagePathNavSticky } from '~/components/Common/PagePathNav';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
-import { useCurrentPageId } from '~/stores/page';
+import { useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
 
 import { BasicLayout } from '../components/Layout/BasicLayout';
 import {
@@ -41,6 +41,10 @@ type Props = CommonProps & {
 const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
   useCurrentUser(props.currentUser ?? null);
 
+  // clear the cache for the current page
+  const { mutate } = useSWRxCurrentPage();
+  mutate(undefined, { revalidate: false });
+
   useGrowiCloudUri(props.growiCloudUri);
 
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);

+ 15 - 2
packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -1,6 +1,19 @@
-export const DiagramButton = (): JSX.Element => {
+import { useCallback } from 'react';
+
+import { useDrawioModalForEditor } from '../../../stores/use-drawio';
+
+type Props = {
+  editorKey: string,
+}
+
+export const DiagramButton = (props: Props): JSX.Element => {
+  const { editorKey } = props;
+  const { open: openDrawioModal } = useDrawioModalForEditor();
+  const onClickDiagramButton = useCallback(() => {
+    openDrawioModal(editorKey);
+  }, [editorKey, openDrawioModal]);
   return (
-    <button type="button" className="btn btn-toolbar-button">
+    <button type="button" className="btn btn-toolbar-button" onClick={onClickDiagramButton}>
       <span className="material-symbols-outlined fs-5">lan</span>
     </button>
   );

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

@@ -28,7 +28,7 @@ export const Toolbar = memo((props: Props): JSX.Element => {
         editorKey={editorKey}
       />
       <TableButton editorKey={editorKey} />
-      <DiagramButton />
+      <DiagramButton editorKey={editorKey} />
       <TemplateButton editorKey={editorKey} />
     </div>
   );

+ 4 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -14,6 +14,7 @@ import { emojiAutocompletionSettings } from '../../extensions/emojiAutocompletio
 
 import { useAppendExtensions, type AppendExtensions } from './utils/append-extensions';
 import { useFocus, type Focus } from './utils/focus';
+import { FoldDrawio, useFoldDrawio } from './utils/fold-drawio';
 import { useGetDoc, type GetDoc } from './utils/get-doc';
 import { useInitDoc, type InitDoc } from './utils/init-doc';
 import { useInsertMarkdownElements, type InsertMarkdowElements } from './utils/insert-markdown-elements';
@@ -41,6 +42,7 @@ type UseCodeMirrorEditorUtils = {
   replaceText: ReplaceText,
   insertMarkdownElements: InsertMarkdowElements,
   insertPrefix: InsertPrefix,
+  foldDrawio: FoldDrawio,
 }
 export type UseCodeMirrorEditor = {
   state: EditorState | undefined;
@@ -95,6 +97,7 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
   const replaceText = useReplaceText(view);
   const insertMarkdownElements = useInsertMarkdownElements(view);
   const insertPrefix = useInsertPrefix(view);
+  const foldDrawio = useFoldDrawio(view);
 
   return {
     state,
@@ -108,5 +111,6 @@ export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor
     replaceText,
     insertMarkdownElements,
     insertPrefix,
+    foldDrawio,
   };
 };

+ 50 - 0
packages/editor/src/services/codemirror-editor/use-codemirror-editor/utils/fold-drawio.ts

@@ -0,0 +1,50 @@
+import { useEffect } from 'react';
+
+import { foldEffect } from '@codemirror/language';
+import { EditorView } from '@codemirror/view';
+
+export type FoldDrawio = void;
+
+const findAllDrawioSection = (view?: EditorView) => {
+  if (view == null) {
+    return;
+  }
+  const lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
+  const lineNumbers: number[] = [];
+  // repeat the process in each line from the top to the bottom in the editor
+  for (let i = 1, e = view.state.doc.lines; i <= e; i++) {
+    // get each line text
+    const lineTxt = view.state.doc.line(i).text;
+    const match = lineBeginPartOfDrawioRE.exec(lineTxt);
+    if (match) {
+      lineNumbers.push(i);
+    }
+  }
+  return lineNumbers;
+};
+
+const foldDrawioSection = (lineNumbers?: number[], view?: EditorView) => {
+  if (view == null || lineNumbers == null) {
+    return;
+  }
+  lineNumbers.forEach((lineNumber) => {
+    // get the end of the lines containing '''drawio
+    const from = view.state.doc.line(lineNumber).to;
+    // get the end of the lines containing '''
+    const to = view.state.doc.line(lineNumber + 2).to;
+    view?.dispatch({
+      effects: foldEffect.of({
+        from,
+        to,
+      }),
+    });
+  });
+};
+
+export const useFoldDrawio = (view?: EditorView): FoldDrawio => {
+  const lineNumbers = findAllDrawioSection(view);
+
+  useEffect(() => {
+    foldDrawioSection(lineNumbers, view);
+  }, [view, lineNumbers]);
+};

+ 40 - 0
packages/editor/src/stores/use-drawio.ts

@@ -0,0 +1,40 @@
+import { useCallback } from 'react';
+
+import { useSWRStatic } from '@growi/core/dist/swr';
+import type { SWRResponse } from 'swr';
+
+type DrawioModalStatus = {
+  isOpened: boolean,
+  editorKey: string | undefined,
+}
+
+type DrawioModalStatusUtils = {
+  open(
+    editorKey: string,
+  ): void,
+  close(): void,
+}
+
+export const useDrawioModalForEditor = (status?: DrawioModalStatus): SWRResponse<DrawioModalStatus, Error> & DrawioModalStatusUtils => {
+  const initialData: DrawioModalStatus = {
+    isOpened: false,
+    editorKey: undefined,
+  };
+  const swrResponse = useSWRStatic<DrawioModalStatus, Error>('drawioModalStatusForEditor', status, { fallbackData: initialData });
+
+  const { mutate } = swrResponse;
+
+  const open = useCallback((editorKey: string | undefined): void => {
+    mutate({ isOpened: true, editorKey });
+  }, [mutate]);
+
+  const close = useCallback((): void => {
+    mutate({ isOpened: false, editorKey: undefined });
+  }, [mutate]);
+
+  return {
+    ...swrResponse,
+    open,
+    close,
+  };
+};

+ 337 - 267
packages/preset-themes/src/styles/fire-red.scss

@@ -1,274 +1,344 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
+:root[data-bs-theme='light'] {
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
 
-@use './variables' as var;
-@use './theme/mixins/page-editor-mode-manager';
-@use './theme/hsl-functions' as hsl;
+  $primary: #EA5532;
+  $highlight: #C9BBBA;
 
-:root[data-bs-theme='light'] {
-  // Theme colors
-  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
-  --primary-hs: 11,81%;
-  --primary-l: 56%;
-  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
-  --secondary-hs: 208,7%;
-  --secondary-l: 46%;
-  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
-  --accentcolor-hs: 0,0%;
-  --accentcolor-l: 75%;
-
-  // Background colors
-  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
-  --bgcolor-global-hs: 0,0%;
-  --bgcolor-global-l: 100%;
-  --bgcolor-inline-code: #{bs.$gray-100}; //optional
-  --bgcolor-card: var(--accentcolor);
-  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),10%)};
-  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
-
-  // Font colors
-  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
-  --color-global-hs: 0,0%;
-  --color-global-l: 17%;
-  --color-reversal: #{bs.$gray-100};
-  --color-link: var(--primary);
-  --color-link-hs: var(--primary-hs);
-  --color-link-l: var(--primary-l);
-  --color-link-hover: #{hsl.lighten(var(--primary), 12%)};
-  --color-link-wiki: var(--primary);
-  --color-link-wiki-hs: var(--primary-hs);
-  --color-link-wiki-l: var(--primary-l);
-  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 12%)};
-  --color-link-nabvar: var(--color-reversal);
-  --color-inline-code: #c7254e; // optional
-  --color-search: var(--color-global);
-
-  // List Group colors
-  // --color-list: var(--color-global);
-  --bgcolor-list: transparent;
-  --color-list-hover: var(--color-global);
-  --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};
-  // --color-list-active: var(--color-reversal);
-  // --bgcolor-list-active: var(--primary);
-
-  // Navbar
-  --bgcolor-navbar: var(--color-global);
-  --bgcolor-navbar-hs: var(--color-global-hs);
-  --bgcolor-navbar-l: var(--color-global-l);
-  --bgcolor-search-top-dropdown: var(--primary);
-  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
-  --bgcolor-search-top-dropdown-l: var(--primary-l);
-  --border-image-navbar: linear-gradient(to right, var(--primary) 0%, #{hsl.darken(var(--primary), 5%)} 100%);
-
-  // Logo colors
-  --bgcolor-logo: var(--bgcolor-global);
-  --fillcolor-logo-mark: var(--bgcolor-global);
-
-  // Sidebar
-  --bgcolor-sidebar: var(--accentcolor);
-  --bgcolor-sidebar-hs: var(--accentcolor-hs);
-  --bgcolor-sidebar-l: var(--accentcolor-l);
-  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
-  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
-
-  // Sidebar resize button
-  --color-resize-button: #ffffff;
-  --bgcolor-resize-button: var(--primary);
-  --bgcolor-sidebar-context-hs: var(--primary-hs);
-  --bgcolor-sidebar-context-l: var(--primary-l);
-  --color-resize-button-hover: var(--color-reversal);
-  --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
-
-  // Sidebar contents
-  --color-sidebar-context: var(--color-global);
-  --color-sidebar-context-hs: var(--color-global-hs);
-  --color-sidebar-context-l: var(--color-global-l);
-  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
-  --bgcolor-sidebar-context-hs: 0,0%;
-  --bgcolor-sidebar-context-l: 93%;
-
-  // Sidebar list group
-  // --bgcolor-sidebar-list-group: #; // optional
-
-  // Subnavigation
-  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
-  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
-  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
-
-  // Icon colors
-  --color-editor-icons: var(--color-global);
-
-  // Border colors
-  --border-color-theme: var(--primary);
-  --bordercolor-inline-code: #ccc8c8; // optional
-
-  // admin theme box
-  --color-theme-color-box: var(--primary);
-
-  // Navs {
-  .nav-tabs {
-    border-bottom: var(--primary) 1px solid;
-    .nav-link {
-      &:hover {
-        border-color: #{hsl.lighten(var(--primary), 10%)};
-        border-bottom: none;
-      }
-      &.active {
-        background-color: transparent;
-      }
-    }
-  }
-
-  // Button
-  .btn-group.grw-page-editor-mode-manager {
-    .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.lighten(var(--primary), 20%)});
-    }
-  }
-
-  .grw-global-search .btn-secondary.dropdown-toggle {
-    color: white;
-  }
-
-  .btn-primary {
-    color:white !important;
-  }
+  @include generate-color-palette('primary', $primary, #120700, white);
+  @include generate-color-palette('highlight', $highlight, #120700, white);
+
+  $body-color:                mix(#C9BBBA, #120700, 40%);
+  $body-bg:                   white;
+
+  $body-secondary-color:      rgba($body-color, .75);
+  $body-secondary-bg:         $gray-200;
+
+  $body-tertiary-color:       rgba($body-color, .5);
+  $body-tertiary-bg:          $gray-100;
+
+  $border-color:              $gray-300;
+
+  $link-color:                $gray-800;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/apply-light';
+
+  --grw-wiki-link-color-rgb: var(--grw-primary-500-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-primary-600-rgb);
 }
 
 :root[data-bs-theme='dark'] {
-  // Theme colors
-  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
-  --primary-hs: 11,81%;
-  --primary-l: 56%;
-  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
-  --secondary-hs: 208,7%;
-  --secondary-l: 46%;
-  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
-  --accentcolor-hs: 0,0%;
-  --accentcolor-l: 13%;
-
-  // Background colors
-  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
-  --bgcolor-global-hs: 0,0%;
-  --bgcolor-global-l: 20%;
-  --bgcolor-inline-code: #{bs.$gray-100}; //optional
-  --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
-  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
-  --bgcolor-keyword-highlighted: #{darken(var.$grw-marker-red, 30%)};
-
-  // Font colors
-  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
-  --color-global-hs: 0,0%;
-  --color-global-l: 100%;
-  --color-reversal: #{bs.$gray-100};
-  --color-link: var(--primary);
-  --color-link-hs: var(--primary-hs);
-  --color-link-l: var(--primary-l);
-  --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
-  --color-link-wiki: var(--primary);
-  --color-link-wiki-hs: var(--primary-hs);
-  --color-link-wiki-l: var(--primary-l);
-  --color-link-wiki-hover: #{hsl.lighten(var(--color-link),12%)};
-  --color-link-nabvar: var(--color-reversal);
-  --color-inline-code: #c7254e; // optional
-  --color-search: #a7a7a7;
-
-  // List Group colors
-  // --color-list: var(--color-global);
-  --bgcolor-list: transparent;
-  --color-list-hover: var(--accentcolor);
-  // --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};// optional
-  // --color-list-active: white ; // optional
-  // --bgcolor-list-active: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
-
-  // Navbar
-  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
-  --bgcolor-navbar-hs: 0,0%;
-  --bgcolor-navbar-l: 17%;
-  --bgcolor-search-top-dropdown: var(--primary);
-  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
-  --bgcolor-search-top-dropdown-l: var(--primary-l);
-  --border-image-navbar: linear-gradient(to right, #ea5532 0%, #c9171e 100%);
-
-  // Logo colors
-  --bgcolor-logo: var(--color-global);
-  --fillcolor-logo-mark: var(--color-global);
-  // --fillcolor-logo-mark: #4e5a60;
-
-  // Sidebar
-  --bgcolor-sidebar: var(--accentcolor);
-  --bgcolor-sidebar-hs: var(--accentcolor-hs);
-  --bgcolor-sidebar-l: var(--accentcolor-l);
-  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
-  --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
-
-  // Sidebar resize button
-  --color-resize-button: var(--color-global);
-  --bgcolor-resize-button: var(--primary);
-  --bgcolor-resize-button-hs: var(--primary-hs);
-  --bgcolor-resize-button-l: var(--primary-l);
-  --color-resize-button-hover: var(--color-global);
-  --bgcolor-resize-button-hover: #{hsl.darken(var(--primary), 5%)};
-
-  // Sidebar contents
-  --color-sidebar-context: var(--color-global);
-  --color-sidebar-context-hs: var(--color-global-hs);
-  --color-sidebar-context-l: var(--color-global-l);
-  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
-  --bgcolor-sidebar-context-hs: 0,2%;
-  --bgcolor-sidebar-context-l: 25%;
-
-  // Sidebar list group
-  // --bgcolor-sidebar-list-group: #; // optional
-
-  // Subnavigation
-  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
-  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
-  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
-
-  // Icon colors
-  --color-editor-icons: var(--color-global);
-
-  // Border colors
-  --border-color-theme: var(--primary);
-  --bordercolor-inline-code: #4d4d4d; // optional
-
-  // admin theme box
-  --color-theme-color-box: var(--primary);
-
-  // Navs
-  .nav-tabs {
-    border-bottom: var(--primary) 1px solid;
-    .nav-link {
-      &:hover {
-        border-color: #{hsl.lighten(var(--primary), 10%)};
-        border-bottom: none;
-      }
-      &.active {
-        color: var(--primary);
-        background-color: transparent;
-        border-color: var(--primary);
-      }
-    }
-  }
-
-  // Table
-  .table {
-    color: white;
-  }
-
-  // Button
-  .btn-group.grw-page-editor-mode-manager {
-    .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.darken(var(--primary), 20%)});
-    }
-  }
-
-  .grw-global-search .btn-secondary.dropdown-toggle {
-    color: white;
-  }
-
-  .btn-primary {
-    color:white !important;
-  }
+  @import '@growi/core/scss/bootstrap/init-stage-1';
+  @import '@growi/core/scss/bootstrap/theming/variables';
+  @import '@growi/core/scss/bootstrap/theming/utils/color-palette';
 
+  $primary: #EA5532;
+  $highlight: #C9BBBA;
+
+  @include generate-color-palette('primary', $primary, #120700, white);
+  @include generate-color-palette('highlight', $highlight, #120700, white);
+
+  $body-color-dark:                   $gray-400;
+  $body-bg-dark:                      $gray-900;
+
+  $body-secondary-color-dark:         rgba($body-color-dark, .75);
+  $body-secondary-bg-dark:            $gray-800;
+
+  $body-tertiary-color-dark:          rgba($body-color-dark, .5);
+  $body-tertiary-bg-dark:             mix($gray-800, $gray-900, 50%);
+
+  $border-color-dark:                 $gray-700;
+
+  $link-color-dark:                   $gray-400;
+
+  @import 'bootstrap/scss/variables';
+  @import 'bootstrap/scss/variables-dark';
+
+  @import '@growi/core/scss/bootstrap/init-stage-2';
+
+  @import '@growi/core/scss/bootstrap/theming/apply-dark';
+
+  --grw-wiki-link-color-rgb: var(--grw-primary-500-rgb);
+  --grw-wiki-link-hover-color-rgb: var(--grw-primary-400-rgb);
 }
+
+// @use '@growi/core/scss/bootstrap/init' as bs;
+
+// @use './variables' as var;
+// @use './theme/mixins/page-editor-mode-manager';
+// @use './theme/hsl-functions' as hsl;
+
+// :root[data-bs-theme='light'] {
+//   // Theme colors
+//   --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+//   --primary-hs: 11,81%;
+//   --primary-l: 56%;
+//   --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+//   --secondary-hs: 208,7%;
+//   --secondary-l: 46%;
+//   --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+//   --accentcolor-hs: 0,0%;
+//   --accentcolor-l: 75%;
+
+//   // Background colors
+//   --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+//   --bgcolor-global-hs: 0,0%;
+//   --bgcolor-global-l: 100%;
+//   --bgcolor-inline-code: #{bs.$gray-100}; //optional
+//   --bgcolor-card: var(--accentcolor);
+//   --bgcolor-blinked-section: #{hsl.alpha(var(--primary),10%)};
+//   //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
+
+//   // Font colors
+//   --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+//   --color-global-hs: 0,0%;
+//   --color-global-l: 17%;
+//   --color-reversal: #{bs.$gray-100};
+//   --color-link: var(--primary);
+//   --color-link-hs: var(--primary-hs);
+//   --color-link-l: var(--primary-l);
+//   --color-link-hover: #{hsl.lighten(var(--primary), 12%)};
+//   --color-link-wiki: var(--primary);
+//   --color-link-wiki-hs: var(--primary-hs);
+//   --color-link-wiki-l: var(--primary-l);
+//   --color-link-wiki-hover: #{hsl.lighten(var(--primary), 12%)};
+//   --color-link-nabvar: var(--color-reversal);
+//   --color-inline-code: #c7254e; // optional
+//   --color-search: var(--color-global);
+
+//   // List Group colors
+//   // --color-list: var(--color-global);
+//   --bgcolor-list: transparent;
+//   --color-list-hover: var(--color-global);
+//   --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};
+//   // --color-list-active: var(--color-reversal);
+//   // --bgcolor-list-active: var(--primary);
+
+//   // Navbar
+//   --bgcolor-navbar: var(--color-global);
+//   --bgcolor-navbar-hs: var(--color-global-hs);
+//   --bgcolor-navbar-l: var(--color-global-l);
+//   --bgcolor-search-top-dropdown: var(--primary);
+//   --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+//   --bgcolor-search-top-dropdown-l: var(--primary-l);
+//   --border-image-navbar: linear-gradient(to right, var(--primary) 0%, #{hsl.darken(var(--primary), 5%)} 100%);
+
+//   // Logo colors
+//   --bgcolor-logo: var(--bgcolor-global);
+//   --fillcolor-logo-mark: var(--bgcolor-global);
+
+//   // Sidebar
+//   --bgcolor-sidebar: var(--accentcolor);
+//   --bgcolor-sidebar-hs: var(--accentcolor-hs);
+//   --bgcolor-sidebar-l: var(--accentcolor-l);
+//   // --bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
+//   --text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
+
+//   // Sidebar resize button
+//   --color-resize-button: #ffffff;
+//   --bgcolor-resize-button: var(--primary);
+//   --bgcolor-sidebar-context-hs: var(--primary-hs);
+//   --bgcolor-sidebar-context-l: var(--primary-l);
+//   --color-resize-button-hover: var(--color-reversal);
+//   --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
+
+//   // Sidebar contents
+//   --color-sidebar-context: var(--color-global);
+//   --color-sidebar-context-hs: var(--color-global-hs);
+//   --color-sidebar-context-l: var(--color-global-l);
+//   --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+//   --bgcolor-sidebar-context-hs: 0,0%;
+//   --bgcolor-sidebar-context-l: 93%;
+
+//   // Sidebar list group
+//   // --bgcolor-sidebar-list-group: #; // optional
+
+//   // Subnavigation
+//   --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+//   --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+//   --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
+
+//   // Icon colors
+//   --color-editor-icons: var(--color-global);
+
+//   // Border colors
+//   --border-color-theme: var(--primary);
+//   --bordercolor-inline-code: #ccc8c8; // optional
+
+//   // admin theme box
+//   --color-theme-color-box: var(--primary);
+
+//   // Navs {
+//   .nav-tabs {
+//     border-bottom: var(--primary) 1px solid;
+//     .nav-link {
+//       &:hover {
+//         border-color: #{hsl.lighten(var(--primary), 10%)};
+//         border-bottom: none;
+//       }
+//       &.active {
+//         background-color: transparent;
+//       }
+//     }
+//   }
+
+//   // Button
+//   .btn-group.grw-page-editor-mode-manager {
+//     .btn.btn-outline-primary {
+//       @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.lighten(var(--primary), 20%)});
+//     }
+//   }
+
+//   .grw-global-search .btn-secondary.dropdown-toggle {
+//     color: white;
+//   }
+
+//   .btn-primary {
+//     color:white !important;
+//   }
+// }
+
+// :root[data-bs-theme='dark'] {
+//   // Theme colors
+//   --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+//   --primary-hs: 11,81%;
+//   --primary-l: 56%;
+//   --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+//   --secondary-hs: 208,7%;
+//   --secondary-l: 46%;
+//   --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+//   --accentcolor-hs: 0,0%;
+//   --accentcolor-l: 13%;
+
+//   // Background colors
+//   --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+//   --bgcolor-global-hs: 0,0%;
+//   --bgcolor-global-l: 20%;
+//   --bgcolor-inline-code: #{bs.$gray-100}; //optional
+//   --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
+//   --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
+//   --bgcolor-keyword-highlighted: #{darken(var.$grw-marker-red, 30%)};
+
+//   // Font colors
+//   --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+//   --color-global-hs: 0,0%;
+//   --color-global-l: 100%;
+//   --color-reversal: #{bs.$gray-100};
+//   --color-link: var(--primary);
+//   --color-link-hs: var(--primary-hs);
+//   --color-link-l: var(--primary-l);
+//   --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
+//   --color-link-wiki: var(--primary);
+//   --color-link-wiki-hs: var(--primary-hs);
+//   --color-link-wiki-l: var(--primary-l);
+//   --color-link-wiki-hover: #{hsl.lighten(var(--color-link),12%)};
+//   --color-link-nabvar: var(--color-reversal);
+//   --color-inline-code: #c7254e; // optional
+//   --color-search: #a7a7a7;
+
+//   // List Group colors
+//   // --color-list: var(--color-global);
+//   --bgcolor-list: transparent;
+//   --color-list-hover: var(--accentcolor);
+//   // --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};// optional
+//   // --color-list-active: white ; // optional
+//   // --bgcolor-list-active: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
+
+//   // Navbar
+//   --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+//   --bgcolor-navbar-hs: 0,0%;
+//   --bgcolor-navbar-l: 17%;
+//   --bgcolor-search-top-dropdown: var(--primary);
+//   --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+//   --bgcolor-search-top-dropdown-l: var(--primary-l);
+//   --border-image-navbar: linear-gradient(to right, #ea5532 0%, #c9171e 100%);
+
+//   // Logo colors
+//   --bgcolor-logo: var(--color-global);
+//   --fillcolor-logo-mark: var(--color-global);
+//   // --fillcolor-logo-mark: #4e5a60;
+
+//   // Sidebar
+//   --bgcolor-sidebar: var(--accentcolor);
+//   --bgcolor-sidebar-hs: var(--accentcolor-hs);
+//   --bgcolor-sidebar-l: var(--accentcolor-l);
+//   // --bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
+//   --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
+
+//   // Sidebar resize button
+//   --color-resize-button: var(--color-global);
+//   --bgcolor-resize-button: var(--primary);
+//   --bgcolor-resize-button-hs: var(--primary-hs);
+//   --bgcolor-resize-button-l: var(--primary-l);
+//   --color-resize-button-hover: var(--color-global);
+//   --bgcolor-resize-button-hover: #{hsl.darken(var(--primary), 5%)};
+
+//   // Sidebar contents
+//   --color-sidebar-context: var(--color-global);
+//   --color-sidebar-context-hs: var(--color-global-hs);
+//   --color-sidebar-context-l: var(--color-global-l);
+//   --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+//   --bgcolor-sidebar-context-hs: 0,2%;
+//   --bgcolor-sidebar-context-l: 25%;
+
+//   // Sidebar list group
+//   // --bgcolor-sidebar-list-group: #; // optional
+
+//   // Subnavigation
+//   --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+//   --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+//   --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
+
+//   // Icon colors
+//   --color-editor-icons: var(--color-global);
+
+//   // Border colors
+//   --border-color-theme: var(--primary);
+//   --bordercolor-inline-code: #4d4d4d; // optional
+
+//   // admin theme box
+//   --color-theme-color-box: var(--primary);
+
+//   // Navs
+//   .nav-tabs {
+//     border-bottom: var(--primary) 1px solid;
+//     .nav-link {
+//       &:hover {
+//         border-color: #{hsl.lighten(var(--primary), 10%)};
+//         border-bottom: none;
+//       }
+//       &.active {
+//         color: var(--primary);
+//         background-color: transparent;
+//         border-color: var(--primary);
+//       }
+//     }
+//   }
+
+//   // Table
+//   .table {
+//     color: white;
+//   }
+
+//   // Button
+//   .btn-group.grw-page-editor-mode-manager {
+//     .btn.btn-outline-primary {
+//       @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.darken(var(--primary), 20%)});
+//     }
+//   }
+
+//   .grw-global-search .btn-secondary.dropdown-toggle {
+//     color: white;
+//   }
+
+//   .btn-primary {
+//     color:white !important;
+//   }
+
+// }

+ 1 - 1
packages/preset-themes/vite.themes.config.ts

@@ -14,7 +14,7 @@ export default defineConfig(({ mode }) => {
           // '/src/styles/blackboard.scss',
           // '/src/styles/christmas.scss',
           '/src/styles/default.scss',
-          // '/src/styles/fire-red.scss',
+          '/src/styles/fire-red.scss',
           // '/src/styles/future.scss',
           // '/src/styles/halloween.scss',
           // '/src/styles/hufflepuff.scss',