Taichi Masuyama 4 лет назад
Родитель
Сommit
8324d84ef6

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

@@ -6,13 +6,14 @@ import { ItemNode } from './ItemNode';
 import Item from './Item';
 import { useSWRxPageAncestorsChildren } from '../../../stores/page-listing';
 import { useTargetAndAncestors } from '../../../stores/context';
+import { HasObjectId } from '../../../interfaces/has-object-id';
 
 const { isTopPage } = pagePathUtils;
 
 /*
- * Utility to generate initial node and return
+ * Utility to generate initial node
  */
-const generateInitialNode = (targetAndAncestors: Partial<IPage>[]): ItemNode => {
+const generateInitialNodeBeforeResponse = (targetAndAncestors: Partial<IPage>[]): ItemNode => {
   const rootPage = targetAndAncestors[targetAndAncestors.length - 1]; // the last item is the root
   if (!isTopPage(rootPage?.path as string)) throw new Error('/ not exist in ancestors');
 
@@ -29,12 +30,33 @@ const generateInitialNode = (targetAndAncestors: Partial<IPage>[]): ItemNode =>
   return rootNode;
 };
 
+const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Partial<IPage & HasObjectId>[]>, rootNode: ItemNode): ItemNode => {
+  const paths = Object.keys(ancestorsChildren);
+
+  const rootPath = paths[paths.length - 1]; // the last item is the root
+  if (!isTopPage(rootPath)) throw new Error('rootPath must be "/"');
+
+  let currentNode = rootNode;
+  paths.reverse().forEach((path) => {
+    const childPages = ancestorsChildren[path];
+    currentNode.children = ItemNode.generateNodesFromPages(childPages);
+
+    const nextNode = currentNode.children.filter((node) => {
+      return paths.includes(node.page.path);
+    })[0];
+    currentNode = nextNode;
+  });
+
+  return rootNode;
+};
+
+
 /*
  * ItemsTree
  */
 const ItemsTree: FC = () => {
-  // TODO: get from props
-  const path = '/';
+  // TODO: get from static SWR
+  const path = '/Sandbox/Mathematics';
 
   const { data: targetAndAncestors, error } = useTargetAndAncestors();
 
@@ -48,35 +70,25 @@ const ItemsTree: FC = () => {
     return null;
   }
 
-  const initialNode = generateInitialNode(targetAndAncestors);
+  let initialNode: ItemNode;
+
+  /*
+   * Before swr response comes back
+   */
+  if (ancestorsChildrenData == null) {
+    initialNode = generateInitialNodeBeforeResponse(targetAndAncestors);
+  }
 
   /*
    * When swr request finishes
    */
-  if (ancestorsChildrenData != null) {
-    // increment initialNode
+  else {
     const { ancestorsChildren } = ancestorsChildrenData;
 
-    // flatten ancestors
-    let ancestors: ItemNode[] = [];
-
-    if (initialNode.children.length === 0) { // when showing top page
-      ancestors = [initialNode];
-    }
-    else {
-      let currentNode = initialNode;
-      while (currentNode.hasChildren() && currentNode?.children?.[0] != null) {
-        ancestors.push(currentNode);
-        const child = currentNode.children[0];
-        currentNode = child;
-      }
-    }
-
-    // update children
-    ancestors.forEach((node) => {
-      const childPages = ancestorsChildren[node.page.path as string];
-      node.children = ItemNode.generateNodesFromPages(childPages);
-    });
+    const rootPage = targetAndAncestors[targetAndAncestors.length - 1];
+    const rootNode = new ItemNode(rootPage);
+
+    initialNode = generateInitialNodeAfterResponse(ancestorsChildren, rootNode);
   }
 
   const isOpen = true;
@@ -87,9 +99,5 @@ const ItemsTree: FC = () => {
   );
 };
 
-/*
- * ItemsTree wrapper
- */
-
 
 export default ItemsTree;

+ 11 - 12
packages/app/src/server/models/page.ts

@@ -84,20 +84,15 @@ schema.plugin(uniqueValidator);
  * Methods
  */
 const collectAncestorPaths = (path: string, ancestorPaths: string[] = []): string[] => {
-  if (isTopPage(path)) return ['/'];
+  if (isTopPage(path)) {
+    return ancestorPaths;
+  }
 
   const parentPath = nodePath.dirname(path);
   ancestorPaths.push(parentPath);
   return collectAncestorPaths(parentPath, ancestorPaths);
 };
 
-const collectAncestorPathsExcludingTop = (path: string, ancestorPaths: string[] = []): string[] => {
-  if (isTopPage(path)) return ancestorPaths;
-
-  const parentPath = nodePath.dirname(path);
-  ancestorPaths.push(parentPath);
-  return collectAncestorPaths(parentPath, ancestorPaths);
-};
 
 const hasSlash = (str: string): boolean => {
   return str.includes('/');
@@ -170,7 +165,7 @@ schema.statics.getParentIdAndFillAncestors = async function(path: string): Promi
     return parent._id;
   }
 
-  const ancestorPaths = collectAncestorPathsExcludingTop(path); // paths of parents need to be created
+  const ancestorPaths = collectAncestorPaths(path); // paths of parents need to be created
 
   // just create ancestors with empty pages
   await this.createEmptyPagesByPaths(ancestorPaths);
@@ -256,7 +251,7 @@ schema.statics.findTargetAndAncestorsByPathOrId = async function(pathOrId: strin
     path = pathOrId;
   }
 
-  const ancestorPaths = collectAncestorPathsExcludingTop(path);
+  const ancestorPaths = collectAncestorPaths(path);
   ancestorPaths.push(path); // include target
 
   // Do not populate
@@ -297,13 +292,17 @@ schema.statics.findChildrenByParentPathOrIdAndViewer = async function(parentPath
 };
 
 schema.statics.findAncestorsChildrenByPathAndViewer = async function(path: string, user, userGroups = null): Promise<Record<string, PageDocument[]>> {
-  const ancestorPaths = collectAncestorPaths(path);
+  const ancestorPaths = isTopPage(path) ? ['/'] : collectAncestorPaths(path);
   const regexps = ancestorPaths.map(path => new RegExp(generateChildrenRegExp(path))); // cannot use re2
 
   // get pages at once
   const queryBuilder = new PageQueryBuilder(this.find({ path: { $in: regexps } }));
   await addViewerCondition(queryBuilder, user, userGroups);
-  const _pages = await queryBuilder.query.lean().exec();
+  const _pages = await queryBuilder
+    .addConditionToMinimizeDataForRendering()
+    .query
+    .lean()
+    .exec();
   const pages = _pages.map((page: PageDocument & {isTarget?: boolean}) => {
     if (page.path === path) {
       page.isTarget = true;