Yuki Takei 6 месяцев назад
Родитель
Сommit
85ac5a9367
24 измененных файлов с 190 добавлено и 200 удалено
  1. 5 6
      apps/app/src/client/components/PageComment/CommentEditor.tsx
  2. 2 3
      apps/app/src/client/components/PageEditor/HandsontableModal.tsx
  3. 3 2
      apps/app/src/client/components/PageEditor/LinkEditModal.tsx
  4. 3 4
      apps/app/src/client/components/PageEditor/PageEditor.tsx
  5. 4 3
      apps/app/src/client/components/TemplateModal/TemplateModal.tsx
  6. 1 0
      packages/editor/package.json
  7. 2 2
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  8. 2 2
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/EmojiButton.tsx
  9. 2 2
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/LinkEditButton.tsx
  10. 2 2
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TableButton.tsx
  11. 2 2
      packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TemplateButton.tsx
  12. 0 40
      packages/editor/src/client/stores/use-drawio.ts
  13. 0 45
      packages/editor/src/client/stores/use-handsontable.ts
  14. 0 30
      packages/editor/src/client/stores/use-link-edit-modal.ts
  15. 0 27
      packages/editor/src/client/stores/use-resolved-theme.ts
  16. 0 30
      packages/editor/src/client/stores/use-template-modal.ts
  17. 1 0
      packages/editor/src/index.ts
  18. 5 0
      packages/editor/src/states/index.ts
  19. 28 0
      packages/editor/src/states/modal/drawio-for-editor.ts
  20. 30 0
      packages/editor/src/states/modal/handsontable.ts
  21. 36 0
      packages/editor/src/states/modal/link-edit.ts
  22. 33 0
      packages/editor/src/states/modal/template.ts
  23. 26 0
      packages/editor/src/states/ui/resolved-theme.ts
  24. 3 0
      pnpm-lock.yaml

+ 5 - 6
apps/app/src/client/components/PageComment/CommentEditor.tsx

@@ -4,10 +4,9 @@ import React, {
   useMemo,
 } from 'react';
 
-import { GlobalCodeMirrorEditorKey } from '@growi/editor';
+import { GlobalCodeMirrorEditorKey, useResolvedThemeActions } from '@growi/editor';
 import { CodeMirrorEditorComment } from '@growi/editor/dist/client/components/CodeMirrorEditorComment';
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
-import { useResolvedThemeForEditor } from '@growi/editor/dist/client/stores/use-resolved-theme';
 import { UserPicture } from '@growi/ui/dist/components';
 import { useAtomValue } from 'jotai';
 import { useTranslation } from 'next-i18next';
@@ -87,9 +86,9 @@ export const CommentEditor = (props: CommentEditorProps): JSX.Element => {
   const isSlackConfigured = useAtomValue(isSlackConfiguredAtom);
   const { data: editorSettings } = useEditorSettings();
   const { markDirty, markClean } = useCommentEditorsDirtyMap();
-  const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
+  const { mutateResolvedTheme } = useResolvedThemeActions();
   const { resolvedTheme } = useNextThemes();
-  mutateResolvedTheme({ themeData: resolvedTheme });
+  mutateResolvedTheme(resolvedTheme);
 
   const editorKey = useMemo(() => {
     if (replyTo != null) {
@@ -317,9 +316,9 @@ export const CommentEditorPre = (props: CommentEditorProps): JSX.Element => {
   const { onCommented, onCanceled, ...rest } = props;
 
   const currentUser = useCurrentUser();
-  const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
+  const { mutateResolvedTheme } = useResolvedThemeActions();
   const { resolvedTheme } = useNextThemes();
-  mutateResolvedTheme({ themeData: resolvedTheme });
+  mutateResolvedTheme(resolvedTheme);
 
   const [isReadyToUse, setIsReadyToUse] = useState(false);
 

+ 2 - 3
apps/app/src/client/components/PageEditor/HandsontableModal.tsx

@@ -2,8 +2,7 @@ import React, {
   useState, useCallback, useMemo, type JSX,
 } from 'react';
 
-import { MarkdownTable } from '@growi/editor';
-import { useHandsontableModalForEditor } from '@growi/editor/dist/client/stores/use-handsontable';
+import { MarkdownTable, useHandsontableModalForEditorStatus } from '@growi/editor';
 import { HotTable } from '@handsontable/react';
 import type Handsontable from 'handsontable';
 import { useTranslation } from 'next-i18next';
@@ -37,7 +36,7 @@ export const HandsontableModalSubstance = (): JSX.Element => {
   const { t } = useTranslation('commons');
   const handsontableModalData = useHandsontableModalStatus();
   const { close: closeHandsontableModal } = useHandsontableModalActions();
-  const { data: handsontableModalForEditorData } = useHandsontableModalForEditor();
+  const handsontableModalForEditorData = useHandsontableModalForEditorStatus();
 
   const isOpened = handsontableModalData?.isOpened ?? false;
   const isOpendInEditor = handsontableModalForEditorData?.isOpened ?? false;

+ 3 - 2
apps/app/src/client/components/PageEditor/LinkEditModal.tsx

@@ -5,7 +5,7 @@ import React, {
 import path from 'path';
 
 import { Linker } from '@growi/editor';
-import { useLinkEditModal } from '@growi/editor/dist/client/stores/use-link-edit-modal';
+import { useLinkEditModalStatus, useLinkEditModalActions } from '@growi/editor/dist/states/modal/link-edit';
 import { useTranslation } from 'next-i18next';
 import {
   Modal,
@@ -36,7 +36,8 @@ export const LinkEditModal = (): JSX.Element => {
   const { t } = useTranslation();
   const currentPath = useCurrentPagePath();
   const { data: rendererOptions } = usePreviewOptions();
-  const { data: linkEditModalStatus, close } = useLinkEditModal();
+  const linkEditModalStatus = useLinkEditModalStatus();
+  const { close } = useLinkEditModalActions();
 
   const [isUseRelativePath, setIsUseRelativePath] = useState<boolean>(false);
   const [isUsePermanentLink, setIsUsePermanentLink] = useState<boolean>(false);

+ 3 - 4
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -9,10 +9,9 @@ import nodePath from 'path';
 import { Origin } from '@growi/core';
 import type { IPageHasId } from '@growi/core/dist/interfaces';
 import { pathUtils } from '@growi/core/dist/utils';
-import { GlobalCodeMirrorEditorKey } from '@growi/editor';
+import { GlobalCodeMirrorEditorKey, useResolvedThemeActions } from '@growi/editor';
 import { CodeMirrorEditorMain } from '@growi/editor/dist/client/components/CodeMirrorEditorMain';
 import { useCodeMirrorEditorIsolated } from '@growi/editor/dist/client/stores/codemirror-editor';
-import { useResolvedThemeForEditor } from '@growi/editor/dist/client/stores/use-resolved-theme';
 import { useRect } from '@growi/ui/dist/utils';
 import detectIndent from 'detect-indent';
 import { useAtomValue } from 'jotai';
@@ -126,7 +125,7 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
 
   const { data: rendererOptions } = usePreviewOptions();
 
-  const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
+  const { mutateResolvedTheme } = useResolvedThemeActions();
 
   const shouldExpandContent = useShouldExpandContent(currentPage);
 
@@ -136,7 +135,7 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   useConflictEffect();
 
   const { resolvedTheme } = useNextThemes();
-  mutateResolvedTheme({ themeData: resolvedTheme });
+  mutateResolvedTheme(resolvedTheme);
 
   const currentRevisionId = currentPage?.revision?._id;
   const isRevisionIdRequiredForPageUpdate = currentPage?.revision?.origin === undefined;

+ 4 - 3
apps/app/src/client/components/TemplateModal/TemplateModal.tsx

@@ -5,7 +5,7 @@ import React, {
 import assert from 'assert';
 
 import type { Lang } from '@growi/core';
-import { useTemplateModal, type TemplateModalStatus } from '@growi/editor/dist/client/stores/use-template-modal';
+import { useTemplateModalStatus, useTemplateModalActions, type TemplateModalState } from '@growi/editor';
 import {
   extractSupportedLocales, getLocalizedTemplate, type TemplateSummary,
 } from '@growi/pluginkit/dist/v4';
@@ -111,7 +111,7 @@ const TemplateDropdownItem: React.FC<TemplateSummaryItemProps> = ({
 };
 
 type TemplateModalSubstanceProps = {
-  templateModalStatus: TemplateModalStatus,
+  templateModalStatus: TemplateModalState,
   close: () => void,
 }
 
@@ -319,7 +319,8 @@ const TemplateModalSubstance = (props: TemplateModalSubstanceProps): JSX.Element
 
 
 export const TemplateModal = (): JSX.Element => {
-  const { data: templateModalStatus, close } = useTemplateModal();
+  const templateModalStatus = useTemplateModalStatus();
+  const { close } = useTemplateModalActions();
 
   if (templateModalStatus == null) {
     return <></>;

+ 1 - 0
packages/editor/package.json

@@ -18,6 +18,7 @@
     "lint": "npm-run-all -p lint:*"
   },
   "dependencies": {
+    "jotai": "^2.12.3",
     "lib0": "^0.2.94",
     "markdown-table": "^3.0.3",
     "react": "^18.2.0",

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

@@ -1,6 +1,6 @@
 import { useCallback, type JSX } from 'react';
 
-import { useDrawioModalForEditor } from '../../../stores/use-drawio';
+import { useDrawioModalForEditorActions } from '../../../../states/modal/drawio-for-editor';
 
 type Props = {
   editorKey: string,
@@ -8,7 +8,7 @@ type Props = {
 
 export const DiagramButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
-  const { open: openDrawioModal } = useDrawioModalForEditor();
+  const { open: openDrawioModal } = useDrawioModalForEditorActions();
   const onClickDiagramButton = useCallback(() => {
     openDrawioModal(editorKey);
   }, [editorKey, openDrawioModal]);

+ 2 - 2
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/EmojiButton.tsx

@@ -7,8 +7,8 @@ import emojiData from '@emoji-mart/data';
 import Picker from '@emoji-mart/react';
 import { Modal } from 'reactstrap';
 
+import { useResolvedTheme } from '../../../../states/ui/resolved-theme';
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
-import { useResolvedThemeForEditor } from '../../../stores/use-resolved-theme';
 
 type Props = {
   editorKey: string,
@@ -20,7 +20,7 @@ export const EmojiButton = (props: Props): JSX.Element => {
   const [isOpen, setIsOpen] = useState(false);
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
-  const { data: resolvedTheme } = useResolvedThemeForEditor();
+  const resolvedTheme = useResolvedTheme();
   const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
 
   const selectEmoji = useCallback((emoji: { shortcodes: string }): void => {

+ 2 - 2
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/LinkEditButton.tsx

@@ -3,9 +3,9 @@ import { useCallback, type JSX } from 'react';
 import { DropdownItem } from 'reactstrap';
 
 import type { GlobalCodeMirrorEditorKey } from '../../../../consts';
+import { useLinkEditModalActions } from '../../../../states/modal/link-edit';
 import { getMarkdownLink, replaceFocusedMarkdownLinkWithEditor } from '../../../services-internal';
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
-import { useLinkEditModal } from '../../../stores/use-link-edit-modal';
 
 
 type Props = {
@@ -14,7 +14,7 @@ type Props = {
 
 export const LinkEditButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
-  const { open: openLinkEditModal } = useLinkEditModal();
+  const { open: openLinkEditModal } = useLinkEditModalActions();
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
 
   const onClickOpenLinkEditModal = useCallback(() => {

+ 2 - 2
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TableButton.tsx

@@ -1,7 +1,7 @@
 import { useCallback, type JSX } from 'react';
 
+import { useHandsontableModalForEditorActions } from '../../../../states/modal/handsontable';
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
-import { useHandsontableModalForEditor } from '../../../stores/use-handsontable';
 
 
 type Props = {
@@ -11,7 +11,7 @@ type Props = {
 export const TableButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
-  const { open: openTableModal } = useHandsontableModalForEditor();
+  const { open: openTableModal } = useHandsontableModalForEditorActions();
   const editor = codeMirrorEditor?.view;
   const onClickTableButton = useCallback(() => {
     openTableModal(editor);

+ 2 - 2
packages/editor/src/client/components-internal/CodeMirrorEditor/Toolbar/TemplateButton.tsx

@@ -1,7 +1,7 @@
 import { useCallback, type JSX } from 'react';
 
+import { useTemplateModalActions } from '../../../../states/modal/template';
 import { useCodeMirrorEditorIsolated } from '../../../stores/codemirror-editor';
-import { useTemplateModal } from '../../../stores/use-template-modal';
 
 type Props = {
   editorKey: string,
@@ -10,7 +10,7 @@ type Props = {
 export const TemplateButton = (props: Props): JSX.Element => {
   const { editorKey } = props;
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
-  const { open: openTemplateModal } = useTemplateModal();
+  const { open: openTemplateModal } = useTemplateModalActions();
 
   const onClickTempleteButton = useCallback(() => {
     const editor = codeMirrorEditor?.view;

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

@@ -1,40 +0,0 @@
-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,
-  };
-};

+ 0 - 45
packages/editor/src/client/stores/use-handsontable.ts

@@ -1,45 +0,0 @@
-import { useCallback } from 'react';
-
-import type { EditorView } from '@codemirror/view';
-import { useSWRStatic } from '@growi/core/dist/swr';
-import type { SWRResponse } from 'swr';
-
-type HandsontableModalStatus = {
-  isOpened: boolean,
-  editor?: EditorView,
-}
-
-type HandsontableModalStatusUtils = {
-  open(
-    editor?: EditorView,
-  ): void
-  close(): void
-}
-
-export const useHandsontableModalForEditor = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
-  const initialData: HandsontableModalStatus = {
-    isOpened: false,
-    editor: undefined,
-  };
-
-  const swrResponse = useSWRStatic<HandsontableModalStatus, Error>('handsontableModalStatus', status, { fallbackData: initialData });
-
-  const { mutate } = swrResponse;
-
-  const open = useCallback((editor?: EditorView): void => {
-    mutate({
-      isOpened: true, editor,
-    });
-  }, [mutate]);
-  const close = useCallback((): void => {
-    mutate({
-      isOpened: false, editor: undefined,
-    });
-  }, [mutate]);
-
-  return {
-    ...swrResponse,
-    open,
-    close,
-  };
-};

+ 0 - 30
packages/editor/src/client/stores/use-link-edit-modal.ts

@@ -1,30 +0,0 @@
-import { useSWRStatic } from '@growi/core/dist/swr';
-import type { SWRResponse } from 'swr';
-
-import type { Linker } from '../../models';
-
-type LinkEditModalStatus = {
-  isOpened: boolean,
-  defaultMarkdownLink?: Linker,
-  onSave?: (linkText: string) => void
-}
-
-type LinkEditModalUtils = {
-  open(defaultMarkdownLink: Linker, onSave: (linkText: string) => void): void,
-  close(): void,
-}
-
-export const useLinkEditModal = (): SWRResponse<LinkEditModalStatus, Error> & LinkEditModalUtils => {
-
-  const initialStatus: LinkEditModalStatus = { isOpened: false };
-  const swrResponse = useSWRStatic<LinkEditModalStatus, Error>('linkEditModal', undefined, { fallbackData: initialStatus });
-
-  return Object.assign(swrResponse, {
-    open: (defaultMarkdownLink: Linker, onSave: (linkText: string) => void) => {
-      swrResponse.mutate({ isOpened: true, defaultMarkdownLink, onSave });
-    },
-    close: () => {
-      swrResponse.mutate({ isOpened: false });
-    },
-  });
-};

+ 0 - 27
packages/editor/src/client/stores/use-resolved-theme.ts

@@ -1,27 +0,0 @@
-import { useCallback } from 'react';
-
-import type { ColorScheme } from '@growi/core';
-import { useSWRStatic } from '@growi/core/dist/swr';
-import type { SWRResponse } from 'swr';
-import { mutate } from 'swr';
-
-type ResolvedThemeStatus = {
-  themeData: ColorScheme,
-}
-
-type ResolvedThemeUtils = {
-  mutateResolvedThemeForEditor(resolvedTheme: ColorScheme): void
-}
-
-export const useResolvedThemeForEditor = (): SWRResponse<ResolvedThemeStatus, Error> & ResolvedThemeUtils => {
-  const swrResponse = useSWRStatic<ResolvedThemeStatus, Error>('resolvedTheme');
-
-  const mutateResolvedThemeForEditor = useCallback((resolvedTheme: ColorScheme) => {
-    mutate('resolvedTheme', { themeData: resolvedTheme });
-  }, []);
-
-  return {
-    ...swrResponse,
-    mutateResolvedThemeForEditor,
-  };
-};

+ 0 - 30
packages/editor/src/client/stores/use-template-modal.ts

@@ -1,30 +0,0 @@
-import { useSWRStatic } from '@growi/core/dist/swr';
-import type { SWRResponse } from 'swr';
-
-type TemplateSelectedCallback = (templateText: string) => void;
-type TemplateModalOptions = {
-  onSubmit?: TemplateSelectedCallback,
-}
-export type TemplateModalStatus = TemplateModalOptions & {
-  isOpened: boolean,
-}
-
-type TemplateModalUtils = {
-  open(opts: TemplateModalOptions): void,
-  close(): void,
-}
-
-export const useTemplateModal = (): SWRResponse<TemplateModalStatus, Error> & TemplateModalUtils => {
-
-  const initialStatus: TemplateModalStatus = { isOpened: false };
-  const swrResponse = useSWRStatic<TemplateModalStatus, Error>('templateModal', undefined, { fallbackData: initialStatus });
-
-  return Object.assign(swrResponse, {
-    open: (opts: TemplateModalOptions) => {
-      swrResponse.mutate({ isOpened: true, onSubmit: opts.onSubmit });
-    },
-    close: () => {
-      swrResponse.mutate({ isOpened: false });
-    },
-  });
-};

+ 1 - 0
packages/editor/src/index.ts

@@ -2,3 +2,4 @@
 export * from './consts';
 export * from './interfaces';
 export * from './models';
+export * from './states';

+ 5 - 0
packages/editor/src/states/index.ts

@@ -0,0 +1,5 @@
+export * from './modal/link-edit';
+export * from './modal/template';
+export * from './modal/drawio-for-editor';
+export * from './modal/handsontable';
+export * from './ui/resolved-theme';

+ 28 - 0
packages/editor/src/states/modal/drawio-for-editor.ts

@@ -0,0 +1,28 @@
+import { atom, useAtomValue, useSetAtom } from 'jotai';
+
+type DrawioModalForEditorState = {
+  isOpened: boolean;
+  editorKey?: string;
+};
+
+const drawioModalForEditorAtom = atom<DrawioModalForEditorState>({
+  isOpened: false,
+  editorKey: undefined,
+});
+
+export const useDrawioModalForEditorStatus = () => {
+  return useAtomValue(drawioModalForEditorAtom);
+};
+
+export const useDrawioModalForEditorActions = () => {
+  const setModalState = useSetAtom(drawioModalForEditorAtom);
+
+  return {
+    open: (editorKey: string) => {
+      setModalState({ isOpened: true, editorKey });
+    },
+    close: () => {
+      setModalState({ isOpened: false, editorKey: undefined });
+    },
+  };
+};

+ 30 - 0
packages/editor/src/states/modal/handsontable.ts

@@ -0,0 +1,30 @@
+import type { EditorView } from '@codemirror/view';
+import { atom, useAtomValue, useSetAtom } from 'jotai';
+
+
+type HandsontableModalState = {
+  isOpened: boolean;
+  editor?: EditorView;
+};
+
+const handsontableModalAtom = atom<HandsontableModalState>({
+  isOpened: false,
+  editor: undefined,
+});
+
+export const useHandsontableModalForEditorStatus = () => {
+  return useAtomValue(handsontableModalAtom);
+};
+
+export const useHandsontableModalForEditorActions = () => {
+  const setModalState = useSetAtom(handsontableModalAtom);
+
+  return {
+    open: (editor?: EditorView) => {
+      setModalState({ isOpened: true, editor });
+    },
+    close: () => {
+      setModalState({ isOpened: false, editor: undefined });
+    },
+  };
+};

+ 36 - 0
packages/editor/src/states/modal/link-edit.ts

@@ -0,0 +1,36 @@
+import { atom, useAtomValue, useSetAtom } from 'jotai';
+
+import type { Linker } from '../../models';
+
+type LinkEditModalState = {
+  isOpened: boolean;
+  defaultMarkdownLink?: Linker;
+  onSave?: (linkText: string) => void;
+};
+
+const linkEditModalAtom = atom<LinkEditModalState>({
+  isOpened: false,
+  defaultMarkdownLink: undefined,
+  onSave: undefined,
+});
+
+export const useLinkEditModalStatus = () => {
+  return useAtomValue(linkEditModalAtom);
+};
+
+export const useLinkEditModalActions = () => {
+  const setModalState = useSetAtom(linkEditModalAtom);
+
+  return {
+    open: (defaultMarkdownLink: Linker, onSave: (linkText: string) => void) => {
+      setModalState({ isOpened: true, defaultMarkdownLink, onSave });
+    },
+    close: () => {
+      setModalState({
+        isOpened: false,
+        defaultMarkdownLink: undefined,
+        onSave: undefined,
+      });
+    },
+  };
+};

+ 33 - 0
packages/editor/src/states/modal/template.ts

@@ -0,0 +1,33 @@
+import { atom, useAtomValue, useSetAtom } from 'jotai';
+
+type TemplateSelectedCallback = (templateText: string) => void;
+
+type TemplateModalOptions = {
+  onSubmit?: TemplateSelectedCallback;
+};
+
+export type TemplateModalState = TemplateModalOptions & {
+  isOpened: boolean;
+};
+
+const templateModalAtom = atom<TemplateModalState>({
+  isOpened: false,
+  onSubmit: undefined,
+});
+
+export const useTemplateModalStatus = () => {
+  return useAtomValue(templateModalAtom);
+};
+
+export const useTemplateModalActions = () => {
+  const setModalState = useSetAtom(templateModalAtom);
+
+  return {
+    open: (opts: TemplateModalOptions) => {
+      setModalState({ isOpened: true, onSubmit: opts.onSubmit });
+    },
+    close: () => {
+      setModalState({ isOpened: false, onSubmit: undefined });
+    },
+  };
+};

+ 26 - 0
packages/editor/src/states/ui/resolved-theme.ts

@@ -0,0 +1,26 @@
+import type { ColorScheme } from '@growi/core';
+import { atom, useAtom, useSetAtom } from 'jotai';
+
+
+type ResolvedThemeState = {
+  themeData?: ColorScheme;
+};
+
+const resolvedThemeAtom = atom<ResolvedThemeState>({
+  themeData: undefined,
+});
+
+export const useResolvedTheme = () => {
+  const [state] = useAtom(resolvedThemeAtom);
+  return state;
+};
+
+export const useResolvedThemeActions = () => {
+  const setState = useSetAtom(resolvedThemeAtom);
+
+  return {
+    mutateResolvedTheme: (resolvedTheme: ColorScheme) => {
+      setState({ themeData: resolvedTheme });
+    },
+  };
+};

+ 3 - 0
pnpm-lock.yaml

@@ -1278,6 +1278,9 @@ importers:
 
   packages/editor:
     dependencies:
+      jotai:
+        specifier: ^2.12.3
+        version: 2.12.3(@types/react@18.3.3)(react@18.2.0)
       lib0:
         specifier: ^0.2.94
         version: 0.2.94