Pārlūkot izejas kodu

WIP: migrate to jotai

Yuki Takei 7 mēneši atpakaļ
vecāks
revīzija
357066e23c

BIN
.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl


+ 19 - 20
apps/app/src/pages/[[...path]].page.tsx

@@ -14,29 +14,28 @@ import type {
 } from 'next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
-import { useRouter } from 'next/router';
 import superjson from 'superjson';
 
 import { BasicLayout } from '~/components/Layout/BasicLayout';
 import { PageView } from '~/components/PageView/PageView';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { useEditorModeClassName } from '~/services/layout/use-editor-mode-class-name';
+import {
+  useIsSharedUser,
+  useIsSearchPage,
+} from '~/states/context';
 import {
   useCurrentPageData, useCurrentPageId, useCurrentPagePath, usePageNotFound,
 } from '~/states/page';
 import { useHydratePageAtoms } from '~/states/page/hydrate';
+import { useRedirectFrom } from '~/states/page/redirect';
 import {
   useDisableLinkSharing,
   useRendererConfig,
 } from '~/states/server-configurations';
 import { useHydrateServerConfigurationAtoms } from '~/states/server-configurations/hydrate';
 import { useHydrateSidebarAtoms } from '~/states/sidebar/hydrate';
-import {
-  useIsSharedUser,
-  useIsSearchPage,
-} from '~/stores-universal/context';
-import { useEditingMarkdown } from '~/stores/editor';
-import { useRedirectFrom } from '~/stores/page-redirect';
+import { useEditingMarkdown } from '~/states/ui/editor';
 import { useSetupGlobalSocket, useSetupGlobalSocketForPage } from '~/stores/websocket';
 import { useSWRMUTxCurrentPageYjsData } from '~/stores/yjs';
 
@@ -120,22 +119,12 @@ const isInitialProps = (props: Props): props is (InitialProps & SameRouteEachPro
 };
 
 const Page: NextPageWithLayout<Props> = (props: Props) => {
-  const router = useRouter();
-
-  // Monitor router changes - cleaned up empty handler
-  useEffect(() => {
-    // router.events handlers removed as they were only for debugging
-  }, [router.events, props.currentPathname]);
 
   // register global EventEmitter
   if (isClient() && window.globalEmitter == null) {
     window.globalEmitter = new EventEmitter();
   }
 
-  useRedirectFrom(props.redirectFrom ?? null);
-  useIsSharedUser(false); // this page can't be routed for '/share'
-  useIsSearchPage(false);
-
   // Initialize Jotai atoms with initial data - must be called unconditionally
   const pageData = isInitialProps(props) ? props.pageWithMeta?.data : undefined;
   useHydratePageAtoms(pageData);
@@ -146,10 +135,13 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   const [isNotFound] = usePageNotFound();
   const [rendererConfig] = useRendererConfig();
   const [disableLinkSharing] = useDisableLinkSharing();
+  const [, setRedirectFrom] = useRedirectFrom();
+  const [, setIsSharedUser] = useIsSharedUser();
+  const [, setIsSearchPage] = useIsSearchPage();
+  const [, setEditingMarkdown] = useEditingMarkdown();
 
   const { trigger: mutateCurrentPageYjsDataFromApi } = useSWRMUTxCurrentPageYjsData();
 
-  const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
 
   useSetupGlobalSocket();
   useSetupGlobalSocketForPage(pageId);
@@ -161,6 +153,13 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // If initial props and skipSSR, fetch page data on client-side
   useInitialCSRFetch(isInitialProps(props) && props.skipSSR);
 
+  // Initialize atom values
+  useEffect(() => {
+    setRedirectFrom(props.redirectFrom ?? null);
+    setIsSharedUser(false); // this page can't be routed for '/share'
+    setIsSearchPage(false);
+  }, [props.redirectFrom, setRedirectFrom, setIsSharedUser, setIsSearchPage]);
+
   // Optimized effects with minimal dependencies
   useEffect(() => {
     // Load YJS data only when revision changes and page exists
@@ -172,9 +171,9 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useEffect(() => {
     // Initialize editing markdown only when page path changes
     if (currentPagePath) {
-      mutateEditingMarkdown(currentPage?.revision?.body);
+      setEditingMarkdown(currentPage?.revision?.body || '');
     }
-  }, [currentPagePath, currentPage?.revision?.body, mutateEditingMarkdown]);
+  }, [currentPagePath, currentPage?.revision?.body, setEditingMarkdown]);
 
   // If the data on the page changes without router.push, pageWithMeta remains old because getServerSideProps() is not executed
   // So preferentially take page data from useSWRxCurrentPage

+ 29 - 3
apps/app/src/states/context.ts

@@ -1,7 +1,7 @@
 import { atom, useAtom } from 'jotai';
 
 import { currentUserAtom, growiCloudUriAtom } from './global';
-import type { UseAtom } from './ui/helper';
+import type { UseAtom } from './helper';
 
 /**
  * Atom for checking if current path is identical
@@ -71,6 +71,24 @@ export const useIsAdmin = (): UseAtom<typeof isAdminAtom> => {
   return useAtom(isAdminAtom);
 };
 
+/**
+ * Atom for checking if current user is a shared user
+ */
+const isSharedUserAtom = atom<boolean>(false);
+
+export const useIsSharedUser = (): UseAtom<typeof isSharedUserAtom> => {
+  return useAtom(isSharedUserAtom);
+};
+
+/**
+ * Atom for checking if current page is a search page
+ */
+const isSearchPageAtom = atom<boolean | null>(null);
+
+export const useIsSearchPage = (): UseAtom<typeof isSearchPageAtom> => {
+  return useAtom(isSearchPageAtom);
+};
+
 /**
  * Computed atom for GROWI documentation URL
  * Depends on growiCloudUri atom
@@ -86,7 +104,9 @@ const growiDocumentationUrlAtom = atom((get) => {
   return 'https://docs.growi.org';
 });
 
-export const useGrowiDocumentationUrl = (): UseAtom<typeof growiDocumentationUrlAtom> => {
+export const useGrowiDocumentationUrl = (): UseAtom<
+  typeof growiDocumentationUrlAtom
+> => {
   return useAtom(growiDocumentationUrlAtom);
 };
 
@@ -101,7 +121,13 @@ const isEditableAtom = atom((get) => {
   const isNotCreatable = get(isNotCreatableAtom);
   const isIdenticalPath = get(isIdenticalPathAtom);
 
-  return (!isForbidden && !isIdenticalPath && !isNotCreatable && !isGuestUser && !isReadOnlyUser);
+  return (
+    !isForbidden &&
+    !isIdenticalPath &&
+    !isNotCreatable &&
+    !isGuestUser &&
+    !isReadOnlyUser
+  );
 });
 
 export const useIsEditable = (): UseAtom<typeof isEditableAtom> => {

+ 1 - 1
apps/app/src/states/global/global.ts

@@ -1,7 +1,7 @@
 import type { ColorScheme, IUserHasId } from '@growi/core';
 import { atom, useAtom } from 'jotai';
 
-import type { UseAtom } from '../ui/helper';
+import type { UseAtom } from '../helper';
 
 // CSRF Token atom (no persistence needed as it's server-provided)
 export const csrfTokenAtom = atom<string>('');

+ 0 - 0
apps/app/src/states/ui/helper.ts → apps/app/src/states/helper.ts


+ 1 - 1
apps/app/src/states/page/hooks.ts

@@ -2,7 +2,7 @@ import { pagePathUtils } from '@growi/core/dist/utils';
 import { useAtom } from 'jotai';
 
 import { useCurrentPathname } from '../global';
-import type { UseAtom } from '../ui/helper';
+import type { UseAtom } from '../helper';
 
 import {
   currentPageIdAtom,

+ 11 - 20
apps/app/src/states/page/index.ts

@@ -7,31 +7,22 @@
 
 // Core page state hooks
 export {
-  useCurrentPageId,
   useCurrentPageData,
+  useCurrentPageId,
   useCurrentPagePath,
-  usePageNotFound,
-  usePageNotCreatable,
+  useIsRevisionOutdated,
+  useIsTrashPage,
   useLatestRevision,
-  useTemplateTags,
-  useTemplateBody,
+  usePageNotCreatable,
+  usePageNotFound,
+  useRemoteRevisionBody,
   // Remote revision hooks (replacements for stores/remote-latest-page.ts)
   useRemoteRevisionId,
-  useRemoteRevisionBody,
-  useRemoteRevisionLastUpdateUser,
   useRemoteRevisionLastUpdatedAt,
-  useIsTrashPage,
-  useIsRevisionOutdated,
+  useRemoteRevisionLastUpdateUser,
+  useTemplateBody,
+  useTemplateTags,
 } from './hooks';
-
+export { useCurrentPageLoading } from './use-current-page-loading';
 // Data fetching hooks
-export {
-  useFetchCurrentPage,
-} from './use-fetch-current-page';
-
-export {
-  useCurrentPageLoading,
-} from './use-current-page-loading';
-
-// Re-export types that external consumers might need
-export type { UseAtom } from '../ui/helper';
+export { useFetchCurrentPage } from './use-fetch-current-page';

+ 12 - 0
apps/app/src/states/page/redirect.ts

@@ -0,0 +1,12 @@
+import { atom, useAtom } from 'jotai';
+
+import type { UseAtom } from '../helper';
+
+/**
+ * Atom for redirect from path
+ */
+const redirectFromAtom = atom<string | null>(null);
+
+export const useRedirectFrom = (): UseAtom<typeof redirectFromAtom> => {
+  return useAtom(redirectFromAtom);
+};

+ 1 - 1
apps/app/src/states/server-configurations/server-configurations.ts

@@ -2,7 +2,7 @@ import { atom, useAtom } from 'jotai';
 
 import type { RendererConfig } from '~/interfaces/services/renderer';
 
-import type { UseAtom } from '../ui/helper';
+import type { UseAtom } from '../helper';
 
 /**
  * Atom for AI feature enabled status

+ 1 - 1
apps/app/src/states/ui/device.ts

@@ -5,7 +5,7 @@ import { Breakpoint } from '@growi/ui/dist/interfaces';
 import { addBreakpointListener, cleanupBreakpointListener } from '@growi/ui/dist/utils';
 import { atom, useAtom } from 'jotai';
 
-import type { UseAtom } from './helper';
+import type { UseAtom } from '../helper';
 
 
 // Device state atoms

+ 9 - 1
apps/app/src/states/ui/editor/atoms.ts

@@ -24,10 +24,18 @@ export const editorModeAtom = atom(
     // 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;
+      const hash =
+        newMode === EditorMode.Editor
+          ? EditorModeHash.Edit
+          : EditorModeHash.View;
       window.history.replaceState(null, '', `${pathname}${search}${hash}`);
     }
 
     set(editorModeBaseAtom, newMode);
   },
 );
+
+/**
+ * Atom for editing markdown content
+ */
+export const editingMarkdownAtom = atom<string>('');

+ 18 - 11
apps/app/src/states/ui/editor/hooks.ts

@@ -1,11 +1,11 @@
-import { useCallback } from 'react';
-
 import { useAtom } from 'jotai';
+import { useCallback } from 'react';
 
 import { useIsEditable } from '~/states/context';
+import type { UseAtom } from '~/states/helper';
 import { usePageNotFound } from '~/states/page';
 
-import { editorModeAtom } from './atoms';
+import { editingMarkdownAtom, editorModeAtom } from './atoms';
 import { EditorMode, type UseEditorModeReturn } from './types';
 
 export const useEditorMode = (): UseEditorModeReturn => {
@@ -14,19 +14,23 @@ export const useEditorMode = (): UseEditorModeReturn => {
   const [editorMode, setEditorModeRaw] = useAtom(editorModeAtom);
 
   // Check if editor mode should be prevented
-  const preventModeEditor = !isEditable || isNotFound === undefined || isNotFound === true;
+  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 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[] = [];
@@ -42,3 +46,6 @@ export const useEditorMode = (): UseEditorModeReturn => {
     getClassNamesByEditorMode,
   };
 };
+
+export const useEditingMarkdown = (): UseAtom<typeof editingMarkdownAtom> =>
+  useAtom(editingMarkdownAtom);

+ 2 - 1
apps/app/src/states/ui/editor/index.ts

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

+ 1 - 1
apps/app/src/states/ui/sidebar.ts

@@ -6,7 +6,7 @@ import { EditorMode } from '~/states/ui/editor';
 import { editorModeAtom } from '~/states/ui/editor/atoms'; // import the atom directly
 
 import { isDeviceLargerThanXlAtom } from './device';
-import type { UseAtom } from './helper';
+import type { UseAtom } from '../helper';
 
 
 const isDrawerOpenedAtom = atom(false);