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

refactor editor state management

Yuki Takei 8 месяцев назад
Родитель
Сommit
a6067fd532

+ 1 - 1
apps/app/src/services/layout/use-editor-mode-class-name.ts

@@ -1,4 +1,4 @@
-import { useEditorMode } from '~/stores-universal/ui';
+import { useEditorMode } from '~/states/ui/editor';
 
 export const useEditorModeClassName = (): string => {
   const { getClassNamesByEditorMode } = useEditorMode();

+ 0 - 13
apps/app/src/states/ui/editor.ts

@@ -1,13 +0,0 @@
-import { atom, useAtom } from 'jotai';
-
-import { EditorMode } from '~/stores-universal/ui';
-
-import type { UseAtom } from './helper';
-
-export const editorModeAtom = atom<EditorMode>(EditorMode.View);
-
-// TODO: migrate from SWR version
-// see: ~/stores-universal/ui
-export const useEditorMode = (): UseAtom<typeof editorModeAtom> => {
-  return useAtom(editorModeAtom);
-};

+ 33 - 0
apps/app/src/states/ui/editor/atoms.ts

@@ -0,0 +1,33 @@
+import { isServer } from '@growi/core/dist/utils';
+import { atom } from 'jotai';
+
+import { EditorMode, EditorModeHash } from './types';
+import { determineEditorModeByHash } from './utils';
+
+// Base atom for editor mode
+export const editorModeBaseAtom = atom<EditorMode | null>(null);
+
+// Derived atom with initialization logic
+export const editorModeAtom = atom(
+  (get) => {
+    const baseMode = get(editorModeBaseAtom);
+
+    // If already initialized, return the current mode
+    if (baseMode !== null) {
+      return baseMode;
+    }
+
+    // Initialize from hash on first access
+    return determineEditorModeByHash();
+  },
+  (get, set, newMode: EditorMode) => {
+    // Update URL hash when mode changes (client-side only)
+    if (!isServer()) {
+      const { pathname, search } = window.location;
+      const hash = newMode === EditorMode.Editor ? EditorModeHash.Edit : EditorModeHash.View;
+      window.history.replaceState(null, '', `${pathname}${search}${hash}`);
+    }
+
+    set(editorModeBaseAtom, newMode);
+  },
+);

+ 44 - 0
apps/app/src/states/ui/editor/hooks.ts

@@ -0,0 +1,44 @@
+import { useCallback } from 'react';
+
+import { useAtom } from 'jotai';
+
+import { useIsEditable } from '~/states/context';
+import { usePageNotFound } from '~/states/page';
+
+import { editorModeAtom } from './atoms';
+import { EditorMode, type UseEditorModeReturn } from './types';
+
+export const useEditorMode = (): UseEditorModeReturn => {
+  const [isEditable] = useIsEditable();
+  const [isNotFound] = usePageNotFound();
+  const [editorMode, setEditorModeRaw] = useAtom(editorModeAtom);
+
+  // Check if editor mode should be prevented
+  const preventModeEditor = !isEditable || isNotFound === undefined || isNotFound === true;
+
+  // Ensure View mode when editing is not allowed
+  const finalMode = preventModeEditor ? EditorMode.View : editorMode;
+
+  // Custom setter that respects permissions and updates hash
+  const setEditorMode = useCallback((newMode: EditorMode) => {
+    if (preventModeEditor && newMode === EditorMode.Editor) {
+      // If editing is not allowed, do nothing
+      return;
+    }
+    setEditorModeRaw(newMode);
+  }, [preventModeEditor, setEditorModeRaw]);
+
+  const getClassNamesByEditorMode = useCallback(() => {
+    const classNames: string[] = [];
+    if (finalMode === EditorMode.Editor) {
+      classNames.push('editing', 'builtin-editor');
+    }
+    return classNames;
+  }, [finalMode]);
+
+  return {
+    editorMode: finalMode,
+    setEditorMode,
+    getClassNamesByEditorMode,
+  };
+};

+ 7 - 0
apps/app/src/states/ui/editor/index.ts

@@ -0,0 +1,7 @@
+// Export only the essential public API
+export { EditorMode } from './types';
+export type { EditorMode as EditorModeType } from './types';
+export { useEditorMode } from './hooks';
+
+// Export utility functions that might be needed elsewhere
+export { determineEditorModeByHash } from './utils';

+ 17 - 0
apps/app/src/states/ui/editor/types.ts

@@ -0,0 +1,17 @@
+export const EditorMode = {
+  View: 'view',
+  Editor: 'editor',
+} as const;
+export type EditorMode = typeof EditorMode[keyof typeof EditorMode];
+
+export const EditorModeHash = {
+  View: '',
+  Edit: '#edit',
+} as const;
+export type EditorModeHash = typeof EditorModeHash[keyof typeof EditorModeHash];
+
+export type UseEditorModeReturn = {
+  editorMode: EditorMode;
+  setEditorMode: (mode: EditorMode) => void;
+  getClassNamesByEditorMode: () => string[];
+}

+ 18 - 0
apps/app/src/states/ui/editor/utils.ts

@@ -0,0 +1,18 @@
+import { isServer } from '@growi/core/dist/utils';
+
+import { EditorMode, EditorModeHash } from './types';
+
+export const determineEditorModeByHash = (): EditorMode => {
+  if (isServer()) {
+    return EditorMode.View;
+  }
+
+  const { hash } = window.location;
+
+  switch (hash) {
+    case EditorModeHash.Edit:
+      return EditorMode.Editor;
+    default:
+      return EditorMode.View;
+  }
+};