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

Merge pull request #7594 from weseek/feat/115447-119724-limit-directory-maximum-deps

feat: Maximum folder hierarchy of two levels
Ryoji Shimizu 2 лет назад
Родитель
Сommit
ab0088bc60

+ 21 - 9
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -152,11 +152,19 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
     }
   };
 
-  const isDroppable = (item: DragItemDataType, type: string | null| symbol): boolean => {
+  const isDropable = (item: DragItemDataType, type: string | null| symbol): boolean => {
     if (type === DRAG_ITEM_TYPE.FOLDER) {
       if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) {
         return false;
       }
+
+      // Maximum folder hierarchy of 2 levels
+      // If the drop source folder has child folders, the drop source folder cannot be moved because the drop source folder hierarchy is already 2.
+      // If the destination folder has a parent, the source folder cannot be moved because the destination folder hierarchy is already 2.
+      if (item.bookmarkFolder.children.length !== 0 || bookmarkFolder.parent != null) {
+        return false;
+      }
+
       return item.root !== root || item.level >= level;
     }
 
@@ -228,7 +236,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
         useDragMode={true}
         useDropMode={true}
         onDropItem={itemDropHandler}
-        isDropable={isDroppable}
+        isDropable={isDropable}
       >
         <li
           className={'list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center'}
@@ -277,13 +285,17 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
                 </DropdownToggle>
               </div>
             </BookmarkFolderItemControl>
-            <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>
+            {/* Maximum folder hierarchy of 2 levels */}
+            {!(bookmarkFolder.parent != null) && (
+              <button
+                id='create-bookmark-folder-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>
       </DragAndDropWrapper>

+ 14 - 21
apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -5,7 +5,7 @@ import React, {
 import { useTranslation } from 'next-i18next';
 import {
   DropdownItem,
-  DropdownMenu, DropdownToggle, UncontrolledDropdown, UncontrolledTooltip,
+  DropdownMenu, DropdownToggle, UncontrolledDropdown,
 } from 'reactstrap';
 
 import {
@@ -201,29 +201,22 @@ export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
         >
           <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>
         </DropdownToggle>
-        <DropdownToggle
-          color="transparent"
-          onClick={e => e.stopPropagation()}
-          onMouseEnter={onMouseEnterHandler}
-        >
-          {childrenExists
-            ? <TriangleIcon />
-            : (
-              <i className="icon-plus d-block pl-0" />
-            )}
-        </DropdownToggle>
+        {/* Maximum folder hierarchy of 2 levels */}
+        {item.parent == null && (
+          <DropdownToggle
+            color="transparent"
+            onClick={e => e.stopPropagation()}
+            onMouseEnter={onMouseEnterHandler}
+          >
+            {childrenExists
+              ? <TriangleIcon />
+              : <i className="icon-plus d-block pl-0" />
+            }
+          </DropdownToggle>
+        )}
         {renderBookmarkSubMenuItem()}
 
       </UncontrolledDropdown >
-      <UncontrolledTooltip
-        modifiers={{ preventOverflow: { boundariesElement: 'window' } }}
-        autohide={false}
-        placement="top"
-        target={`bookmark-delete-button-${item._id}`}
-        fade={false}
-      >
-        {t('bookmark_folder.delete')}
-      </UncontrolledTooltip>
     </>
   );
 };

+ 18 - 3
apps/app/src/server/models/bookmark-folder.ts

@@ -3,7 +3,6 @@ import monggoose, {
   Types, Document, Model, Schema,
 } from 'mongoose';
 
-
 import { IBookmarkFolder, BookmarkFolderItems, MyBookmarkList } from '~/interfaces/bookmark-info';
 import { IPageHasId } from '~/interfaces/page';
 
@@ -16,7 +15,6 @@ import { InvalidParentBookmarkFolderError } from './errors';
 const logger = loggerFactory('growi:models:bookmark-folder');
 const Bookmark = monggoose.model('Bookmark');
 
-
 export interface BookmarkFolderDocument extends Document {
   _id: Types.ObjectId
   name: string
@@ -39,7 +37,11 @@ export interface BookmarkFolderModel extends Model<BookmarkFolderDocument>{
 const bookmarkFolderSchema = new Schema<BookmarkFolderDocument, BookmarkFolderModel>({
   name: { type: String },
   owner: { type: Schema.Types.ObjectId, ref: 'User', index: true },
-  parent: { type: Schema.Types.ObjectId, ref: 'BookmarkFolder', required: false },
+  parent: {
+    type: Schema.Types.ObjectId,
+    ref: 'BookmarkFolder',
+    required: false,
+  },
   bookmarks: {
     type: [{
       type: Schema.Types.ObjectId, ref: 'Bookmark', required: false,
@@ -154,6 +156,19 @@ bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolde
   const parentFolder = parentId ? await this.findById(parentId) : null;
   updateFields.parent = parentFolder?._id ?? null;
 
+  // Maximum folder hierarchy of 2 levels
+  // If the destination folder (parentFolder) has a parent, the source folder cannot be moved because the destination folder hierarchy is already 2.
+  // If the drop source folder has child folders, the drop source folder cannot be moved because the drop source folder hierarchy is already 2.
+  if (parentId != null) {
+    if (parentFolder?.parent != null) {
+      throw new Error('Update bookmark folder failed');
+    }
+    const bookmarkFolder = await this.findById(bookmarkFolderId).populate('children');
+    if (bookmarkFolder?.children?.length !== 0) {
+      throw new Error('Update bookmark folder failed');
+    }
+  }
+
   const bookmarkFolder = await this.findByIdAndUpdate(bookmarkFolderId, { $set: updateFields }, { new: true });
   if (bookmarkFolder == null) {
     throw new Error('Update bookmark folder failed');

+ 7 - 2
apps/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -1,7 +1,6 @@
 import { ErrorV3 } from '@growi/core';
 import { body } from 'express-validator';
 
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { InvalidParentBookmarkFolderError } from '~/server/models/errors';
 import loggerFactory from '~/utils/logger';
@@ -17,7 +16,13 @@ const router = express.Router();
 const validator = {
   bookmarkFolder: [
     body('name').isString().withMessage('name must be a string'),
-    body('parent').isMongoId().optional({ nullable: true }),
+    body('parent').isMongoId().optional({ nullable: true })
+      .custom(async(parent: string) => {
+        const parentFolder = await BookmarkFolder.findById(parent);
+        if (parentFolder == null || parentFolder.parent != null) {
+          throw new Error('Maximum folder hierarchy of 2 levels');
+        }
+      }),
   ],
   bookmarkPage: [
     body('pageId').isMongoId().withMessage('Page ID must be a valid mongo ID'),