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

Merge branch 'feat/base-show-page-item-ctl-menu-on-empty-page' into feat/integrate-show-page-ctl-menu-on-empty-page

yohei0125 3 лет назад
Родитель
Сommit
bef57279b4

+ 6 - 4
packages/app/src/components/NotFoundPage.tsx

@@ -27,11 +27,13 @@ const NotFoundPage = (): JSX.Element => {
 
 
   // replace url in address bar with path when accessing empty page by permalink
   // replace url in address bar with path when accessing empty page by permalink
   useEffect(() => {
   useEffect(() => {
-    const isEmptyPage = pageId != null; // Todo: should be improved. https://redmine.weseek.co.jp/issues/98152
-    const isPathExist = path != null;
-    const isPathLink = notFoundTargetPathOrId?.includes('/');
+    if (path == null) {
+      return;
+    }
 
 
-    if (isEmptyPage && isPathExist && !isPathLink) {
+    const isEmptyPage = pageId != null; // Todo: should be improved. https://redmine.weseek.co.jp/issues/98152
+    const isPermalink = !notFoundTargetPathOrId?.includes('/');
+    if (isEmptyPage && isPermalink) {
       // The (as string) below is a workaround for the case. See the link. (Fixed in typescript version 4.4)
       // The (as string) below is a workaround for the case. See the link. (Fixed in typescript version 4.4)
       // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#control-flow-analysis-of-aliased-conditions-and-discriminants
       // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#control-flow-analysis-of-aliased-conditions-and-discriminants
       replaceURLHistory(path as string);
       replaceURLHistory(path as string);

+ 0 - 1
packages/app/src/components/PageContentFooter.tsx

@@ -5,7 +5,6 @@ import { IUser } from '../interfaces/user';
 
 
 import AuthorInfo from './Navbar/AuthorInfo';
 import AuthorInfo from './Navbar/AuthorInfo';
 
 
-
 type Props = {
 type Props = {
   createdAt: Date,
   createdAt: Date,
   updatedAt: Date,
   updatedAt: Date,

+ 6 - 1
packages/app/src/server/models/page.ts

@@ -915,7 +915,12 @@ export function generateGrantCondition(
 
 
 schema.statics.generateGrantCondition = generateGrantCondition;
 schema.statics.generateGrantCondition = generateGrantCondition;
 
 
-schema.statics.findNotEmptyClosestAncestor = async function(path: string): Promise<PageDocument> {
+// find ancestor page with isEmpty: false. If parameter path is '/', return undefined
+schema.statics.findNonEmptyClosestAncestor = async function(path: string): Promise<PageDocument | undefined> {
+  if (path === '/') {
+    return;
+  }
+
   const builderForAncestors = new PageQueryBuilder(this.find(), false); // empty page not included
   const builderForAncestors = new PageQueryBuilder(this.find(), false); // empty page not included
 
 
   const ancestors = await builderForAncestors
   const ancestors = await builderForAncestors

+ 2 - 2
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -129,10 +129,10 @@ export default (crowi: Crowi): Router => {
 
 
       const idToPageInfoMap: Record<string, IPageInfoAll> = {};
       const idToPageInfoMap: Record<string, IPageInfoAll> = {};
 
 
+      const isGuestUser = req.user == null;
       for (const page of pages) {
       for (const page of pages) {
         // construct isIPageInfoForListing
         // construct isIPageInfoForListing
-        // eslint-disable-next-line no-await-in-loop
-        const basicPageInfo = await pageService.constructBasicPageInfo(page, req.user);
+        const basicPageInfo = pageService.constructBasicPageInfo(page, isGuestUser);
 
 
         const pageInfo = (!isIPageInfoForEntity(basicPageInfo))
         const pageInfo = (!isIPageInfoForEntity(basicPageInfo))
           ? basicPageInfo
           ? basicPageInfo

+ 21 - 28
packages/app/src/server/service/page.ts

@@ -331,7 +331,8 @@ class PageService {
       };
       };
     }
     }
 
 
-    const pageInfo = await this.constructBasicPageInfo(page, user);
+    const isGuestUser = user == null;
+    const pageInfo = this.constructBasicPageInfo(page, isGuestUser);
 
 
     const Bookmark = this.crowi.model('Bookmark');
     const Bookmark = this.crowi.model('Bookmark');
     const bookmarkCount = await Bookmark.countByPageId(pageId);
     const bookmarkCount = await Bookmark.countByPageId(pageId);
@@ -340,7 +341,7 @@ class PageService {
       ...pageInfo,
       ...pageInfo,
       bookmarkCount,
       bookmarkCount,
     };
     };
-    const isGuestUser = user == null;
+
     if (isGuestUser) {
     if (isGuestUser) {
       return {
       return {
         data: page,
         data: page,
@@ -353,10 +354,22 @@ class PageService {
 
 
     const subscription = await Subscription.findByUserIdAndTargetId(user._id, pageId);
     const subscription = await Subscription.findByUserIdAndTargetId(user._id, pageId);
 
 
+    let creatorId = page.creator;
+    if (page.isEmpty) {
+      // Need non-empty ancestor page to get its creatorId because empty page does NOT have it.
+      // Use creatorId of ancestor page to determine whether the empty page is deletable
+      const notEmptyClosestAncestor = await Page.findNonEmptyClosestAncestor(page.path);
+      creatorId = notEmptyClosestAncestor.creator;
+    }
+    const isDeletable = this.canDelete(page.path, creatorId, user, false);
+    const isAbleToDeleteCompletely = this.canDeleteCompletely(page.path, creatorId, user, false); // use normal delete config
+
     return {
     return {
       data: page,
       data: page,
       meta: {
       meta: {
         ...metadataForGuest,
         ...metadataForGuest,
+        isDeletable,
+        isAbleToDeleteCompletely,
         isBookmarked,
         isBookmarked,
         isLiked,
         isLiked,
         subscriptionStatus: subscription?.status,
         subscriptionStatus: subscription?.status,
@@ -1422,7 +1435,7 @@ class PageService {
     }
     }
     else { // always recursive
     else { // always recursive
       deletedPage = page;
       deletedPage = page;
-      await this.deleteEmptyTarget(page);
+      await Page.deleteOne({ _id: page._id, isEmpty: true });
     }
     }
 
 
     // 1. Update descendantCount
     // 1. Update descendantCount
@@ -1489,12 +1502,6 @@ class PageService {
     return deletedPage;
     return deletedPage;
   }
   }
 
 
-  private async deleteEmptyTarget(page): Promise<void> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
-
-    await Page.deleteOne({ _id: page._id, isEmpty: true });
-  }
-
   async deleteRecursivelyMainOperation(page, user, pageOpId: ObjectIdLike): Promise<void> {
   async deleteRecursivelyMainOperation(page, user, pageOpId: ObjectIdLike): Promise<void> {
     await this.deleteDescendantsWithStream(page, user, false);
     await this.deleteDescendantsWithStream(page, user, false);
 
 
@@ -2169,27 +2176,16 @@ class PageService {
     });
     });
   }
   }
 
 
-  async constructBasicPageInfo(page: IPage, operator): Promise<IPageInfo | IPageInfoForEntity> {
-    const Page = mongoose.model('Page') as unknown as PageModel;
-
-    const isGuestUser = operator == null;
+  constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
     const isMovable = isGuestUser ? false : isMovablePage(page.path);
     const isMovable = isGuestUser ? false : isMovablePage(page.path);
 
 
     if (page.isEmpty) {
     if (page.isEmpty) {
-      // Need non-empty ancestor page to get its creator id because empty page does NOT have it.
-      // Use creator id of ancestor page to determine whether the empty page is deletable
-      const notEmptyClosestAncestor = await Page.findNotEmptyClosestAncestor(page.path);
-      const creatorId = notEmptyClosestAncestor.creator;
-
-      const isDeletable = this.canDelete(page.path, creatorId, operator, false);
-      const isAbleToDeleteCompletely = this.canDeleteCompletely(page.path, creatorId, operator, false); // use normal delete config
-
       return {
       return {
         isV5Compatible: true,
         isV5Compatible: true,
         isEmpty: true,
         isEmpty: true,
         isMovable,
         isMovable,
-        isDeletable,
-        isAbleToDeleteCompletely,
+        isDeletable: false,
+        isAbleToDeleteCompletely: false,
         isRevertible: false,
         isRevertible: false,
       };
       };
     }
     }
@@ -2197,9 +2193,6 @@ class PageService {
     const likers = page.liker.slice(0, 15) as Ref<IUserHasId>[];
     const likers = page.liker.slice(0, 15) as Ref<IUserHasId>[];
     const seenUsers = page.seenUsers.slice(0, 15) as Ref<IUserHasId>[];
     const seenUsers = page.seenUsers.slice(0, 15) as Ref<IUserHasId>[];
 
 
-    const isDeletable = this.canDelete(page.path, page.creator, operator, false);
-    const isAbleToDeleteCompletely = this.canDeleteCompletely(page.path, page.creator, operator, false); // use normal delete config
-
     return {
     return {
       isV5Compatible: isTopPage(page.path) || page.parent != null,
       isV5Compatible: isTopPage(page.path) || page.parent != null,
       isEmpty: false,
       isEmpty: false,
@@ -2208,8 +2201,8 @@ class PageService {
       seenUserIds: this.extractStringIds(seenUsers),
       seenUserIds: this.extractStringIds(seenUsers),
       sumOfSeenUsers: page.seenUsers.length,
       sumOfSeenUsers: page.seenUsers.length,
       isMovable,
       isMovable,
-      isDeletable,
-      isAbleToDeleteCompletely,
+      isDeletable: isMovable,
+      isAbleToDeleteCompletely: false,
       isRevertible: isTrashPage(page.path),
       isRevertible: isTrashPage(page.path),
     };
     };