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

Merge branch 'master' into feat/delete-permission-config

Haku Mizuki 4 лет назад
Родитель
Сommit
7446556d7d

+ 1 - 1
packages/app/resource/locales/en_US/translation.json

@@ -419,7 +419,7 @@
     },
     "help": {
       "redirect": "Redirect to new page if someone accesses under this path",
-      "metadata": "Remains last update user and updated date as is",
+      "metadata": "Last update user and updated date will remain the same",
       "recursive": "Move/Rename children of under this path recursively"
     }
   },

+ 2 - 2
packages/app/resource/locales/ja_JP/translation.json

@@ -413,12 +413,12 @@
       "Rename this page only": "このページのみを移動/名前変更",
       "Force rename all child pages": "全ての配下のページを移動/名前変更する",
       "Other options": "その他のオプション",
-      "Do not update metadata": "不更新元数据",
+      "Do not update metadata": "メタデータを更新しない",
       "Redirect": "リダイレクトする"
     },
     "help": {
       "redirect": "アクセスされた際に自動的に新しいページにジャンプします",
-      "metadata": "Remains last update user and updated date as is",
+      "metadata": "最終更新ユーザー、最終更新日を更新せず維持します",
       "recursive": "配下のページも移動/名前変更します"
     }
   },

+ 2 - 2
packages/app/resource/locales/zh_CN/translation.json

@@ -392,12 +392,12 @@
       "Rename this page only": "仅重命名此页面",
       "Force rename all child pages": "强制重命名所有子页面 ",
       "Other options": "其他选项",
-      "Update metadata": "更新元数据",
+      "Do not update metadata": "不更新元数据",
       "Redirect": "重定向"
 		},
 		"help": {
       "redirect": "Redirect to new page if someone accesses <code>%s</code>",
-      "metadata": "Update last update user and updated date",
+      "metadata": "Remains last update user and updated date as is",
       "recursive": "Move/Rename children of under <code>%s</code> recursively"
 		}
 	},

+ 3 - 2
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -40,6 +40,7 @@ type CommonProps = {
   onClickRevertMenuItem?: (pageId: string) => Promise<void> | void,
 
   additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
+  isInstantRename?: boolean,
 }
 
 
@@ -55,7 +56,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
     pageId, isLoading,
     pageInfo, isEnableActions, forceHideMenuItems,
     onClickBookmarkMenuItem, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem, onClickRevertMenuItem,
-    additionalMenuItemRenderer: AdditionalMenuItems,
+    additionalMenuItemRenderer: AdditionalMenuItems, isInstantRename,
   } = props;
 
 
@@ -151,7 +152,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
         { !forceHideMenuItems?.includes(MenuItemType.RENAME) && isEnableActions && pageInfo.isMovable && (
           <DropdownItem onClick={renameItemClickedHandler}>
             <i className="icon-fw  icon-action-redo"></i>
-            {t('Move/Rename')}
+            {t(isInstantRename ? 'Rename' : 'Move/Rename')}
           </DropdownItem>
         ) }
 

+ 8 - 10
packages/app/src/components/Sidebar/PageTree.tsx

@@ -27,8 +27,8 @@ const PageTree: FC = memo(() => {
         <div className="grw-sidebar-content-header p-3">
           <h3 className="mb-0">{t('Page Tree')}</h3>
         </div>
-        <div className="mt-5 mx-2 text-center">
-          <h3 className="text-gray">Page Tree now loading...</h3>
+        <div className="text-muted text-center mt-3">
+          <i className="fa fa-lg fa-spinner fa-pulse mr-1"></i>
         </div>
       </>
     );
@@ -65,14 +65,12 @@ const PageTree: FC = memo(() => {
         <h3 className="mb-0">{t('Page Tree')}</h3>
       </div>
 
-      <div className="grw-sidebar-content-body">
-        <ItemsTree
-          isEnableActions={!isGuestUser}
-          targetPath={path}
-          targetPathOrId={targetPathOrId}
-          targetAndAncestorsData={targetAndAncestorsData}
-        />
-      </div>
+      <ItemsTree
+        isEnableActions={!isGuestUser}
+        targetPath={path}
+        targetPathOrId={targetPathOrId}
+        targetAndAncestorsData={targetAndAncestorsData}
+      />
 
       {!isGuestUser && migrationStatus?.migratablePagesCount != null && migrationStatus.migratablePagesCount !== 0 && (
         <div className="grw-pagetree-footer border-top p-3 w-100">

+ 10 - 7
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -454,19 +454,22 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
+            isInstantRename
           >
             {/* pass the color property to reactstrap dropdownToggle props. https://6-4-0--reactstrap.netlify.app/components/dropdowns/  */}
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
               <i className="icon-options fa fa-rotate-90 p-1"></i>
             </DropdownToggle>
           </PageItemControl>
-          <button
-            type="button"
-            className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
-            onClick={onClickPlusButton}
-          >
-            <i className="icon-plus d-block p-0" />
-          </button>
+          {!pagePathUtils.isUsersTopPage(page.path ?? '') && (
+            <button
+              type="button"
+              className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
+              onClick={onClickPlusButton}
+            >
+              <i className="icon-plus d-block p-0" />
+            </button>
+          )}
         </div>
       </li>
 

+ 1 - 1
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -184,7 +184,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
 
   // ***************************  Scroll on init ***************************
   const scrollOnInit = useCallback(() => {
-    const scrollTargetElement = document.getElementById('grw-pagetree-is-target');
+    const scrollTargetElement = document.getElementById('grw-pagetree-current-page-item');
 
     if (sidebarScrollerRef?.current == null || scrollTargetElement == null) {
       return;

+ 1 - 1
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -165,7 +165,7 @@ const RecentChanges = (): JSX.Element => {
           </div>
         </div>
       </div>
-      <div className="grw-sidebar-content-body grw-recent-changes p-3">
+      <div className="grw-recent-changes p-3">
         <ul className="list-group list-group-flush">
           {(pages || []).map(page => (isRecentChangesSidebarSmall
             ? <SmallPageItem key={page._id} page={page} />

+ 25 - 2
packages/app/src/server/models/page.ts

@@ -1053,9 +1053,13 @@ export default (crowi: Crowi): any => {
       throw Error('Crowi is not set up');
     }
 
-    const isPageMigrated = pageData.parent != null;
+    const isExRestricted = pageData.grant === GRANT_RESTRICTED;
+    const isChildrenExist = pageData?.descendantCount > 0;
+    const exParent = pageData.parent;
+
+    const isPageOnTree = pageData.parent != null || isTopPage(pageData.path);
     const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
-    if (!isV5Compatible || !isPageMigrated) {
+    if (!isExRestricted && (!isV5Compatible || !isPageOnTree)) {
       // v4 compatible process
       return this.updatePageV4(pageData, body, previousBody, user, options);
     }
@@ -1069,6 +1073,16 @@ export default (crowi: Crowi): any => {
     const newPageData = pageData;
 
     if (grant === GRANT_RESTRICTED) {
+
+      if (isPageOnTree && isChildrenExist) {
+        // Update children's parent with new parent
+        const newParentForChildren = await this.createEmptyPage(pageData.path, pageData.parent, pageData.descendantCount);
+        await this.updateMany(
+          { parent: pageData._id },
+          { parent: newParentForChildren._id },
+        );
+      }
+
       newPageData.parent = null;
     }
     else {
@@ -1088,6 +1102,11 @@ export default (crowi: Crowi): any => {
       if (!isGrantNormalized) {
         throw Error('The selected grant or grantedGroup is not assignable to this page.');
       }
+
+      if (isExRestricted) {
+        const newParent = await this.getParentAndFillAncestors(newPageData.path, user);
+        newPageData.parent = newParent._id;
+      }
     }
 
     newPageData.applyScope(user, grant, grantUserGroupId);
@@ -1104,6 +1123,10 @@ export default (crowi: Crowi): any => {
 
     pageEvent.emit('update', savedPage, user);
 
+    if (isPageOnTree && !isChildrenExist) {
+      await this.removeLeafEmptyPagesRecursively(exParent);
+    }
+
     return savedPage;
   };
 

+ 1 - 0
packages/core/src/utils/page-path-utils.ts

@@ -100,6 +100,7 @@ const restrictedPatternsToCreate: Array<RegExp> = [
   /(\/\.\.)\/?/, // see: https://github.com/weseek/growi/issues/3582
   /^\/(_search|_private-legacy-pages)(\/.*|$)/,
   /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments|tags|share)(\/.*|$)/,
+  /^\/user\/[^/]+$/, // see: https://regex101.com/r/utVQct/1
 ];
 export const isCreatablePage = (path: string): boolean => {
   return !restrictedPatternsToCreate.some(pattern => path.match(pattern));