Yuki Takei 2 лет назад
Родитель
Сommit
28bdf50f7c

+ 1 - 1
apps/app/src/client/services/page-operation.ts

@@ -124,7 +124,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     await mutateCurrentPageId(pageId);
     await mutateCurrentPageId(pageId);
     const updatedPage = await mutateCurrentPage();
     const updatedPage = await mutateCurrentPage();
 
 
-    if (updatedPage == null) { return }
+    if (updatedPage == null || updatedPage.revision == null) { return }
 
 
     // supress to mutate only when updated from built-in editor
     // supress to mutate only when updated from built-in editor
     // and see: https://github.com/weseek/growi/pull/7118
     // and see: https://github.com/weseek/growi/pull/7118

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

@@ -34,7 +34,7 @@ export const useDrawioModalLauncherForView = (opts?: {
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openDrawioModal } = useDrawioModal();
 
 
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 

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

@@ -33,7 +33,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
   const { open: openHandsontableModal } = useHandsontableModal();
   const { open: openHandsontableModal } = useHandsontableModal();
 
 
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 
@@ -64,6 +64,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
     }
     }
 
 
     const handler = (bol: number, eol: number) => {
     const handler = (bol: number, eol: number) => {
+      if (currentPage.revision == null) return;
+
       const markdown = currentPage.revision.body;
       const markdown = currentPage.revision.body;
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));

+ 2 - 2
apps/app/src/client/util/bookmark-utils.ts

@@ -1,6 +1,6 @@
 import type { IRevision, Ref } from '@growi/core';
 import type { IRevision, Ref } from '@growi/core';
 
 
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
 
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 
 
@@ -31,7 +31,7 @@ export const deleteBookmarkFolder = async(bookmarkFolderId: string): Promise<voi
 };
 };
 
 
 // Rename page from bookmark item control
 // Rename page from bookmark item control
-export const renamePage = async(pageId: string, revisionId: Ref<IRevision>, newPagePath: string): Promise<void> => {
+export const renamePage = async(pageId: string, revisionId: Ref<IRevision> | undefined, newPagePath: string): Promise<void> => {
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
 };
 };
 
 

+ 1 - 1
apps/app/src/components/ContentLinkButtons.tsx

@@ -40,7 +40,7 @@ RecentlyCreatedLinkButton.displayName = 'RecentlyCreatedLinkButton';
 
 
 
 
 export type ContentLinkButtonsProps = {
 export type ContentLinkButtonsProps = {
-  author?: IUserHasId,
+  author: IUserHasId | null,
 }
 }
 
 
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {

+ 2 - 2
apps/app/src/components/Page/PageView.tsx

@@ -68,7 +68,7 @@ export const PageView = (props: Props): JSX.Element => {
   const { data: viewOptions } = useViewOptions();
   const { data: viewOptions } = useViewOptions();
 
 
   const page = pageBySWR ?? initialPage;
   const page = pageBySWR ?? initialPage;
-  const isNotFound = isNotFoundMeta || page?.revision == null;
+  const isNotFound = isNotFoundMeta || page == null;
   const isUsersHomepagePath = isUsersHomepage(pagePath);
   const isUsersHomepagePath = isUsersHomepage(pagePath);
 
 
   const shouldExpandContent = useShouldExpandContent(page);
   const shouldExpandContent = useShouldExpandContent(page);
@@ -124,7 +124,7 @@ export const PageView = (props: Props): JSX.Element => {
     : null;
     : null;
 
 
   const Contents = () => {
   const Contents = () => {
-    if (isNotFound) {
+    if (isNotFound || page?.revision == null) {
       return <NotFoundPage path={pagePath} />;
       return <NotFoundPage path={pagePath} />;
     }
     }
 
 

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/PageAttachment.tsx

@@ -33,7 +33,7 @@ const PageAttachment = (): JSX.Element => {
   const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
   const { data: dataAttachments, remove } = useSWRxAttachments(pageId, pageNumber);
   const { open: openDeleteAttachmentModal } = useDeleteAttachmentModal();
   const { open: openDeleteAttachmentModal } = useDeleteAttachmentModal();
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: currentPage } = useSWRxCurrentPage();
-  const markdown = currentPage?.revision.body;
+  const markdown = currentPage?.revision?.body;
 
 
   // Custom hooks
   // Custom hooks
   const inUseAttachmentsMap: { [id: string]: boolean } | undefined = useMemo(() => {
   const inUseAttachmentsMap: { [id: string]: boolean } | undefined = useMemo(() => {

+ 4 - 0
apps/app/src/components/PageContentFooter.tsx

@@ -21,6 +21,10 @@ export const PageContentFooter = (props: PageContentFooterProps): JSX.Element =>
     creator, lastUpdateUser, createdAt, updatedAt,
     creator, lastUpdateUser, createdAt, updatedAt,
   } = page;
   } = page;
 
 
+  if (page.isEmpty) {
+    return <></>;
+  }
+
   return (
   return (
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
     <div className={`${styles['page-content-footer']} page-content-footer py-4 d-edit-none d-print-none}`}>
       <div className="container-lg grw-container-convertible">
       <div className="container-lg grw-container-convertible">

+ 5 - 5
apps/app/src/components/PageControls/PageControls.tsx

@@ -107,7 +107,7 @@ type CommonProps = {
 type PageControlsSubstanceProps = CommonProps & {
 type PageControlsSubstanceProps = CommonProps & {
   pageId: string,
   pageId: string,
   shareLinkId?: string | null,
   shareLinkId?: string | null,
-  revisionId: string | null,
+  revisionId?: string | null,
   path?: string | null,
   path?: string | null,
   pageInfo: IPageInfoForOperation,
   pageInfo: IPageInfoForOperation,
   expandContentWidth?: boolean,
   expandContentWidth?: boolean,
@@ -178,7 +178,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     const page: IPageToRenameWithMeta = {
     const page: IPageToRenameWithMeta = {
       data: {
       data: {
         _id: pageId,
         _id: pageId,
-        revision: revisionId,
+        revision: revisionId ?? null,
         path,
         path,
       },
       },
       meta: pageInfo,
       meta: pageInfo,
@@ -195,7 +195,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     const pageToDelete: IPageToDeleteWithMeta = {
     const pageToDelete: IPageToDeleteWithMeta = {
       data: {
       data: {
         _id: pageId,
         _id: pageId,
-        revision: revisionId,
+        revision: revisionId ?? null,
         path,
         path,
       },
       },
       meta: pageInfo,
       meta: pageInfo,
@@ -311,7 +311,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
 type PageControlsProps = CommonProps & {
 type PageControlsProps = CommonProps & {
   pageId: string,
   pageId: string,
   shareLinkId?: string | null,
   shareLinkId?: string | null,
-  revisionId?: string,
+  revisionId?: string | null,
   path?: string | null,
   path?: string | null,
   expandContentWidth?: boolean,
   expandContentWidth?: boolean,
 };
 };
@@ -346,7 +346,7 @@ export const PageControls = memo((props: PageControlsProps): JSX.Element => {
       {...props}
       {...props}
       pageInfo={pageInfo}
       pageInfo={pageInfo}
       pageId={pageId}
       pageId={pageId}
-      revisionId={revisionId ?? null}
+      revisionId={revisionId}
       path={path}
       path={path}
       onClickEditTagsButton={onClickEditTagsButton}
       onClickEditTagsButton={onClickEditTagsButton}
       onClickDuplicateMenuItem={onClickDuplicateMenuItem}
       onClickDuplicateMenuItem={onClickDuplicateMenuItem}

+ 1 - 1
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -347,7 +347,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     mutateIsConflict(false);
     mutateIsConflict(false);
 
 
     // set resolved markdown in editing markdown
     // set resolved markdown in editing markdown
-    const markdown = pageData?.revision.body ?? '';
+    const markdown = pageData?.revision?.body ?? '';
     mutateEditingMarkdown(markdown);
     mutateEditingMarkdown(markdown);
 
 
   }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);
   }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);

+ 1 - 1
apps/app/src/components/PageEditor/page-path-rename-utils.ts

@@ -40,7 +40,7 @@ export const usePagePathRenameHandler = (
     try {
     try {
       await apiv3Put('/pages/rename', {
       await apiv3Put('/pages/rename', {
         pageId: currentPage._id,
         pageId: currentPage._id,
-        revisionId: currentPage.revision._id,
+        revisionId: currentPage.revision?._id,
         newPagePath,
         newPagePath,
       });
       });
 
 

+ 1 - 1
apps/app/src/components/PagePresentationModal.tsx

@@ -60,7 +60,7 @@ const PagePresentationModal = (): JSX.Element => {
     return <></>;
     return <></>;
   }
   }
 
 
-  const markdown = currentPage?.revision.body;
+  const markdown = currentPage?.revision?.body;
 
 
   return (
   return (
     <Modal
     <Modal

+ 8 - 5
apps/app/src/components/PageSideContents/PageSideContents.tsx

@@ -1,6 +1,7 @@
 import React, { Suspense, useCallback } from 'react';
 import React, { Suspense, useCallback } from 'react';
 
 
-import { getIdForRef, type IPageHasId, type IPageInfoForOperation } from '@growi/core';
+import type { IPagePopulatedToShowRevision } from '@growi/core';
+import { getIdForRef, type IPageInfoForOperation } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
@@ -70,7 +71,7 @@ const Tags = (props: TagsProps): JSX.Element => {
 
 
 
 
 export type PageSideContentsProps = {
 export type PageSideContentsProps = {
-  page: IPageHasId,
+  page: IPagePopulatedToShowRevision,
   isSharedUser?: boolean,
   isSharedUser?: boolean,
 }
 }
 
 
@@ -91,9 +92,11 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
   return (
   return (
     <>
     <>
       {/* Tags */}
       {/* Tags */}
-      <Suspense fallback={<PageTagsSkeleton />}>
-        <Tags pageId={page._id} revisionId={getIdForRef(page.revision)} />
-      </Suspense>
+      { page.revision != null && (
+        <Suspense fallback={<PageTagsSkeleton />}>
+          <Tags pageId={page._id} revisionId={page.revision._id} />
+        </Suspense>
+      ) }
 
 
       <div className={`${styles['grw-page-accessories-controls']} d-flex flex-column gap-2`}>
       <div className={`${styles['grw-page-accessories-controls']} d-flex flex-column gap-2`}>
         {/* Page list */}
         {/* Page list */}

+ 1 - 1
apps/app/src/components/PageStatusAlert.tsx

@@ -38,7 +38,7 @@ export const PageStatusAlert = (): JSX.Element => {
 
 
   const refreshPage = useCallback(async() => {
   const refreshPage = useCallback(async() => {
     const updatedPageData = await mutatePageData();
     const updatedPageData = await mutatePageData();
-    mutateEditingMarkdown(updatedPageData?.revision.body);
+    mutateEditingMarkdown(updatedPageData?.revision?.body);
   }, [mutateEditingMarkdown, mutatePageData]);
   }, [mutateEditingMarkdown, mutatePageData]);
 
 
   const onClickResolveConflict = useCallback(() => {
   const onClickResolveConflict = useCallback(() => {

+ 1 - 1
apps/app/src/components/PageTimeline.tsx

@@ -30,7 +30,7 @@ const TimelineCard = ({ page }: TimelineCardProps): JSX.Element => {
         </Link>
         </Link>
       </div>
       </div>
       <div className="card-body">
       <div className="card-body">
-        { rendererOptions != null && (
+        { rendererOptions != null && page.revision != null && (
           <RevisionLoader
           <RevisionLoader
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}

+ 15 - 17
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -119,7 +119,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const page = pageWithMeta?.data;
+  const page = pageWithMeta.data;
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
@@ -183,7 +183,10 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       return <></>;
       return <></>;
     }
     }
 
 
-    const revisionId = getIdForRef(page.revision);
+    const revisionId = page.revision != null ? getIdForRef(page.revision) : null;
+    const additionalMenuItemRenderer = revisionId != null
+      ? props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />
+      : undefined;
 
 
     return (
     return (
       <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
       <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
@@ -194,7 +197,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           expandContentWidth={shouldExpandContent}
           expandContentWidth={shouldExpandContent}
           showPageControlDropdown={showPageControlDropdown}
           showPageControlDropdown={showPageControlDropdown}
           forceHideMenuItems={forceHideMenuItems}
           forceHideMenuItems={forceHideMenuItems}
-          additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
+          additionalMenuItemRenderer={additionalMenuItemRenderer}
           onClickDuplicateMenuItem={duplicateItemClickedHandler}
           onClickDuplicateMenuItem={duplicateItemClickedHandler}
           onClickRenameMenuItem={renameItemClickedHandler}
           onClickRenameMenuItem={renameItemClickedHandler}
           onClickDeleteMenuItem={deleteItemClickedHandler}
           onClickDeleteMenuItem={deleteItemClickedHandler}
@@ -205,8 +208,6 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   }, [page, shouldExpandContent, showPageControlDropdown, forceHideMenuItems,
   }, [page, shouldExpandContent, showPageControlDropdown, forceHideMenuItems,
       duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, switchContentWidthHandler]);
       duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, switchContentWidthHandler]);
 
 
-  const isRenderable = page != null && rendererOptions != null;
-
   const fluidLayoutClass = shouldExpandContent ? _fluidLayoutClass : '';
   const fluidLayoutClass = shouldExpandContent ? _fluidLayoutClass : '';
 
 
   return (
   return (
@@ -217,25 +218,23 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     >
     >
       <RightComponent />
       <RightComponent />
 
 
-      { isRenderable && (
-        <div className="container-lg grw-container-convertible pt-2 pb-2">
-          <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3" />
-        </div>
-      ) }
+      <div className="container-lg grw-container-convertible pt-2 pb-2">
+        <PagePathNav pageId={page._id} pagePath={page.path} formerLinkClassName="small" latterLinkClassName="fs-3" />
+      </div>
 
 
       <div
       <div
         id="search-result-content-body-container"
         id="search-result-content-body-container"
         ref={scrollElementRef}
         ref={scrollElementRef}
         className="search-result-content-body-container container-lg grw-container-convertible overflow-y-scroll"
         className="search-result-content-body-container container-lg grw-container-convertible overflow-y-scroll"
       >
       >
-        { isRenderable && (
+        { page.revision != null && rendererOptions != null && (
           <RevisionLoader
           <RevisionLoader
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}
             revisionId={page.revision}
             revisionId={page.revision}
           />
           />
         )}
         )}
-        { isRenderable && (
+        { page.revision != null && (
           <PageComment
           <PageComment
             rendererOptions={rendererOptions}
             rendererOptions={rendererOptions}
             pageId={page._id}
             pageId={page._id}
@@ -245,11 +244,10 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
             isReadOnly
             isReadOnly
           />
           />
         )}
         )}
-        { isRenderable && (
-          <PageContentFooter
-            page={page}
-          />
-        )}
+
+        <PageContentFooter
+          page={page}
+        />
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 1 - 1
apps/app/src/components/ShareLinkPageView.tsx

@@ -67,7 +67,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
 
 
 
   const Contents = () => {
   const Contents = () => {
-    if (isNotFound) {
+    if (isNotFound || page.revision == null) {
       return <></>;
       return <></>;
     }
     }
 
 

+ 1 - 1
apps/app/src/components/Sidebar/Custom/CustomSidebarSubstance.tsx

@@ -19,7 +19,7 @@ export const CustomSidebarSubstance = (): JSX.Element => {
 
 
   if (rendererOptions == null) return <></>;
   if (rendererOptions == null) return <></>;
 
 
-  const markdown = page?.revision.body;
+  const markdown = page?.revision?.body;
 
 
   return (
   return (
     <div className={`py-3 grw-custom-sidebar-content ${styles['grw-custom-sidebar-content']}`}>
     <div className={`py-3 grw-custom-sidebar-content ${styles['grw-custom-sidebar-content']}`}>

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

@@ -260,7 +260,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
     if (currentPageId != null && !props.isNotFound) {
     if (currentPageId != null && !props.isNotFound) {
       const mutatePageData = async() => {
       const mutatePageData = async() => {
         const pageData = await mutateCurrentPage();
         const pageData = await mutateCurrentPage();
-        mutateEditingMarkdown(pageData?.revision.body);
+        mutateEditingMarkdown(pageData?.revision?.body);
       };
       };
 
 
       // If skipSSR is true, use the API to retrieve page data.
       // If skipSSR is true, use the API to retrieve page data.

+ 1 - 0
apps/app/src/server/models/page.ts

@@ -1045,6 +1045,7 @@ schema.methods.calculateAndUpdateLatestRevisionBodyLength = async function(this:
   // eslint-disable-next-line rulesdir/no-populate
   // eslint-disable-next-line rulesdir/no-populate
   const populatedPageDocument = await this.populate<PageDocument>('revision', 'body');
   const populatedPageDocument = await this.populate<PageDocument>('revision', 'body');
 
 
+  assert(populatedPageDocument.revision != null);
   assert(isPopulated(populatedPageDocument.revision));
   assert(isPopulated(populatedPageDocument.revision));
 
 
   this.latestRevisionBodyLength = populatedPageDocument.revision.body.length;
   this.latestRevisionBodyLength = populatedPageDocument.revision.body.length;

+ 2 - 2
packages/core/src/interfaces/page.ts

@@ -18,7 +18,7 @@ export type IGrantedGroup = {
 export type IPage = {
 export type IPage = {
   path: string,
   path: string,
   status: string,
   status: string,
-  revision: Ref<IRevision>,
+  revision?: Ref<IRevision>,
   tags: Ref<ITag>[],
   tags: Ref<ITag>[],
   creator: any,
   creator: any,
   createdAt: Date,
   createdAt: Date,
@@ -50,7 +50,7 @@ export type IPagePopulatedToShowRevision = Omit<IPageHasId, 'lastUpdateUser'|'cr
   creator: IUserHasId | null,
   creator: IUserHasId | null,
   deleteUser: IUserHasId,
   deleteUser: IUserHasId,
   grantedGroups: { type: GroupType, item: IUserGroupHasId }[],
   grantedGroups: { type: GroupType, item: IUserGroupHasId }[],
-  revision: IRevisionHasId,
+  revision?: IRevisionHasId,
   author: IUserHasId,
   author: IUserHasId,
 }
 }