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

Merge branch 'master' into imprv/87816-show-presentation

kaori 4 лет назад
Родитель
Сommit
4b27bf99cf

+ 20 - 2
packages/app/src/components/PageList/PageListItemL.tsx

@@ -2,10 +2,11 @@ import React, { memo, useCallback } from 'react';
 
 
 import Clamp from 'react-multiline-clamp';
 import Clamp from 'react-multiline-clamp';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
+import urljoin from 'url-join';
 
 
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
-import { useIsDeviceSmallerThanLg } from '~/stores/ui';
+import { useIsDeviceSmallerThanLg, usePageRenameModalStatus, usePageDuplicateModalStatus } from '~/stores/ui';
 import {
 import {
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
   IPageInfoAll, IPageWithMeta, isIPageInfoForEntity, isIPageInfoForListing,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
@@ -34,6 +35,8 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
   } = props;
   } = props;
 
 
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
   const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
+  const { open: openDuplicateModal } = usePageDuplicateModalStatus();
+  const { open: openRenameModal } = usePageRenameModalStatus();
 
 
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
@@ -56,6 +59,16 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
     }
     }
   }, [isDeviceSmallerThanLg, onClickItem, pageData._id]);
   }, [isDeviceSmallerThanLg, onClickItem, pageData._id]);
 
 
+  const duplicateMenuItemClickHandler = useCallback(() => {
+    const { _id: pageId, path } = pageData;
+    openDuplicateModal(pageId, path);
+  }, [openDuplicateModal, pageData]);
+
+  const renameMenuItemClickHandler = useCallback(() => {
+    const { _id: pageId, revision: revisionId, path } = pageData;
+    openRenameModal(pageId, revisionId as string, path);
+  }, [openRenameModal, pageData]);
+
   const styleListGroupItem = (!isDeviceSmallerThanLg && onClickCheckbox != null) ? 'list-group-item-action' : '';
   const styleListGroupItem = (!isDeviceSmallerThanLg && onClickCheckbox != null) ? 'list-group-item-action' : '';
   // background color of list item changes when class "active" exists under 'list-group-item'
   // background color of list item changes when class "active" exists under 'list-group-item'
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
   const styleActive = !isDeviceSmallerThanLg && isSelected ? 'active' : '';
@@ -99,7 +112,10 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
               {/* page title */}
               {/* page title */}
               <Clamp lines={1}>
               <Clamp lines={1}>
                 <span className="h5 mb-0">
                 <span className="h5 mb-0">
-                  <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} />
+                  {/* Use permanent links to care for pages with the same name (Cannot use page path url) */}
+                  <span className="grw-page-path-hierarchical-link text-break">
+                    <a className="page-segment" href={encodeURI(urljoin('/', pageData._id))}>{linkedPagePathLatter.pathName}</a>
+                  </span>
                 </span>
                 </span>
               </Clamp>
               </Clamp>
 
 
@@ -116,7 +132,9 @@ export const PageListItemL = memo((props: Props): JSX.Element => {
                   pageId={pageData._id}
                   pageId={pageData._id}
                   pageInfo={pageMeta}
                   pageInfo={pageMeta}
                   onClickDeleteMenuItem={props.onClickDeleteButton}
                   onClickDeleteMenuItem={props.onClickDeleteButton}
+                  onClickRenameMenuItem={renameMenuItemClickHandler}
                   isEnableActions={isEnableActions}
                   isEnableActions={isEnableActions}
+                  onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
                 />
                 />
               </div>
               </div>
             </div>
             </div>

+ 6 - 0
packages/app/src/server/service/page-grant.ts

@@ -10,6 +10,8 @@ import { isIncludesObjectId, excludeTestIdsFromTargetIds } from '~/server/util/c
 const { addTrailingSlash } = pathUtils;
 const { addTrailingSlash } = pathUtils;
 const { isTopPage } = pagePathUtils;
 const { isTopPage } = pagePathUtils;
 
 
+const LIMIT_FOR_MULTIPLE_PAGE_OP = 20;
+
 type ObjectIdLike = mongoose.Types.ObjectId | string;
 type ObjectIdLike = mongoose.Types.ObjectId | string;
 
 
 type ComparableTarget = {
 type ComparableTarget = {
@@ -343,6 +345,10 @@ class PageGrantService {
   }
   }
 
 
   async separateNormalizedAndNonNormalizedPages(pageIds: ObjectIdLike[]): Promise<[(PageDocument & { _id: any })[], (PageDocument & { _id: any })[]]> {
   async separateNormalizedAndNonNormalizedPages(pageIds: ObjectIdLike[]): Promise<[(PageDocument & { _id: any })[], (PageDocument & { _id: any })[]]> {
+    if (pageIds.length > LIMIT_FOR_MULTIPLE_PAGE_OP) {
+      throw Error(`The maximum number of pageIds allowed is ${LIMIT_FOR_MULTIPLE_PAGE_OP}.`);
+    }
+
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
     const { PageQueryBuilder } = Page;
     const { PageQueryBuilder } = Page;
     const shouldCheckDescendants = true;
     const shouldCheckDescendants = true;

+ 8 - 1
packages/app/src/server/service/page.ts

@@ -1864,7 +1864,14 @@ class PageService {
       return;
       return;
     }
     }
 
 
-    const [normalizedIds, notNormalizedPaths] = await this.crowi.pageGrantService.separateNormalizedAndNonNormalizedPages(pageIds);
+    let normalizedIds;
+    let notNormalizedPaths;
+    try {
+      [normalizedIds, notNormalizedPaths] = await this.crowi.pageGrantService.separateNormalizedAndNonNormalizedPages(pageIds);
+    }
+    catch (err) {
+      throw err;
+    }
 
 
     if (normalizedIds.length === 0) {
     if (normalizedIds.length === 0) {
       // socket.emit('normalizeParentRecursivelyByPageIds', { error: err.message }); TODO: use socket to tell user
       // socket.emit('normalizeParentRecursivelyByPageIds', { error: err.message }); TODO: use socket to tell user