Taichi Masuyama 4 лет назад
Родитель
Сommit
18e54b74e2
1 измененных файлов с 61 добавлено и 20 удалено
  1. 61 20
      packages/app/src/server/service/page-operation.ts

+ 61 - 20
packages/app/src/server/service/page-operation.ts

@@ -1,7 +1,9 @@
-import { pagePathUtils } from '@growi/core';
+import { pagePathUtils, pathUtils } from '@growi/core';
+import escapeStringRegexp from 'escape-string-regexp';
 
-import PageOperation, { PageActionType } from '~/server/models/page-operation';
+import PageOperation from '~/server/models/page-operation';
 
+const { addTrailingSlash } = pathUtils;
 const { isTrashPage } = pagePathUtils;
 
 class PageOperationService {
@@ -16,38 +18,77 @@ class PageOperationService {
   }
 
   /**
-   * Check if the operation is operatable by comparing paths with all PageOperation documents
+   * Check if the operation is operatable by comparing paths with all Main PageOperation documents
    * @param fromPath The path to operate from
    * @param toPath The path to operate to
    * @param actionType The action type of the operation
    * @returns Promise<boolean>
    */
-  async canOperate(fromPath: string, toPath: string, actionType: PageActionType): Promise<boolean> {
-    // 1. Block when toPath is already reserved by other toPath
-    // 2. Block when toPath is already reserved by fromPath of "Delete" or "DeleteCompletely"
-    if (toPath != null) {
-      const isPageOpsExist = await PageOperation.exists({ toPath });
-      const isToPathTrash = isTrashPage(toPath);
-      const isForbidden1 = isPageOpsExist && !isToPathTrash;
-
-      if (isForbidden1) {
-        return false;
+  async canOperate(isRecursively: boolean, fromPathToOp?: string, toPathToOp?: string): Promise<boolean> {
+    const mainOps = await PageOperation.findMainOps();
+    const toPaths = mainOps.map(op => op.toPath).filter((p): p is string => p != null);
+
+    if (isRecursively) {
+
+      if (fromPathToOp != null && !isTrashPage(fromPathToOp)) {
+        const flag = toPaths.some(p => this.isEitherOfPathAreaOverlap(p, fromPathToOp));
+        if (flag) return false;
+      }
+
+      if (toPathToOp != null && !isTrashPage(toPathToOp)) {
+        const flag = toPaths.some(p => this.isEitherOfPathAreaOverlap(p, toPathToOp));
+        if (flag) return false;
       }
 
-      const pageOps = await PageOperation.findMainOps({ actionType: { $in: [PageActionType.Delete, PageActionType.DeleteCompletely] } });
-      const fromPathsToBlock = pageOps.map(po => po.fromPath);
-      const isForbidden2 = fromPathsToBlock.includes(toPath);
+    }
+    else {
+
+      if (fromPathToOp != null && !isTrashPage(fromPathToOp)) {
+        const flag = toPaths.some(p => this.isPathAreaOverlap(p, fromPathToOp));
+        if (flag) return false;
+      }
 
-      if (isForbidden2) {
-        return false;
+      if (toPathToOp != null && !isTrashPage(toPathToOp)) {
+        const flag = toPaths.some(p => this.isPathAreaOverlap(p, toPathToOp));
+        if (flag) return false;
       }
+
     }
 
-    if (fromPath != null) {
+    return true;
+  }
+
+  private isEitherOfPathAreaOverlap(path1: string, path2: string): boolean {
+    if (path1 === path2) {
       return true;
     }
 
-    return true;
+    const path1WithSlash = addTrailingSlash(path1);
+    const path2WithSlash = addTrailingSlash(path2);
+
+    const path1Area = new RegExp(`^${escapeStringRegexp(path1WithSlash)}`);
+    const path2Area = new RegExp(`^${escapeStringRegexp(path2WithSlash)}`);
+
+    if (path1Area.test(path2) || path2Area.test(path1)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  private isPathAreaOverlap(pathToTest: string, pathToBeTested: string): boolean {
+    if (pathToTest === pathToBeTested) {
+      return true;
+    }
+
+    const pathWithSlash = addTrailingSlash(pathToTest);
+
+    const pathAreaToTest = new RegExp(`^${escapeStringRegexp(pathWithSlash)}`);
+    if (pathAreaToTest.test(pathToBeTested)) {
+      return true;
+    }
+
+    return false;
   }
 
 }