editor-mode.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { isServer } from '@growi/core/dist/utils';
  2. import { atom, useAtom } from 'jotai';
  3. import { useCallback } from 'react';
  4. import { useIsEditable, usePageNotFound } from '~/states/page';
  5. import { EditorMode, EditorModeHash, type UseEditorModeReturn } from './types';
  6. import { determineEditorModeByHash } from './utils';
  7. // Base atom for editor mode
  8. const editorModeBaseAtom = atom<EditorMode | null>(null);
  9. // Derived atom with initialization logic
  10. export const editorModeAtom = atom(
  11. (get) => {
  12. const baseMode = get(editorModeBaseAtom);
  13. // If already initialized, return the current mode
  14. if (baseMode !== null) {
  15. return baseMode;
  16. }
  17. // Initialize from hash on first access
  18. return determineEditorModeByHash();
  19. },
  20. (_get, set, newMode: EditorMode) => {
  21. // Update URL hash when mode changes (client-side only)
  22. if (!isServer()) {
  23. const { pathname, search } = window.location;
  24. const hash =
  25. newMode === EditorMode.Editor
  26. ? EditorModeHash.Edit
  27. : EditorModeHash.View;
  28. window.history.replaceState(null, '', `${pathname}${search}${hash}`);
  29. }
  30. set(editorModeBaseAtom, newMode);
  31. },
  32. );
  33. export const useEditorMode = (): UseEditorModeReturn => {
  34. const isEditable = useIsEditable();
  35. const isNotFound = usePageNotFound();
  36. const [editorMode, setEditorModeRaw] = useAtom(editorModeAtom);
  37. // Check if editor mode should be prevented
  38. const preventModeEditor =
  39. !isEditable || isNotFound === undefined || isNotFound === true;
  40. // Ensure View mode when editing is not allowed
  41. const finalMode = preventModeEditor ? EditorMode.View : editorMode;
  42. // Custom setter that respects permissions and updates hash
  43. const setEditorMode = useCallback(
  44. (newMode: EditorMode) => {
  45. if (preventModeEditor && newMode === EditorMode.Editor) {
  46. // If editing is not allowed, do nothing
  47. return;
  48. }
  49. setEditorModeRaw(newMode);
  50. },
  51. [preventModeEditor, setEditorModeRaw],
  52. );
  53. const getClassNamesByEditorMode = useCallback(() => {
  54. const classNames: string[] = [];
  55. if (finalMode === EditorMode.Editor) {
  56. classNames.push('editing', 'builtin-editor');
  57. }
  58. return classNames;
  59. }, [finalMode]);
  60. return {
  61. editorMode: finalMode,
  62. setEditorMode,
  63. getClassNamesByEditorMode,
  64. };
  65. };
  66. /**
  67. * Internal atoms for derived atom usage (special naming convention)
  68. * These atoms are exposed only for creating derived atoms in other modules
  69. */
  70. export const _atomsForDerivedAbilities = {
  71. editorModeAtom,
  72. } as const;