Taichi Masuyama пре 4 година
родитељ
комит
e5975674df

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

@@ -1,5 +1,4 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
-import { pagePathUtils } from '@growi/core';
 
 
 import { IPage } from '../../../interfaces/page';
 import { IPage } from '../../../interfaces/page';
 import { ItemNode } from './ItemNode';
 import { ItemNode } from './ItemNode';
@@ -8,15 +7,11 @@ import { useSWRxPageAncestorsChildren } from '../../../stores/page-listing';
 import { useTargetAndAncestors } from '../../../stores/context';
 import { useTargetAndAncestors } from '../../../stores/context';
 import { HasObjectId } from '../../../interfaces/has-object-id';
 import { HasObjectId } from '../../../interfaces/has-object-id';
 
 
-const { isTopPage } = pagePathUtils;
 
 
 /*
 /*
  * Utility to generate initial node
  * Utility to generate initial node
  */
  */
 const generateInitialNodeBeforeResponse = (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');
-
   const nodes = targetAndAncestors.map((page): ItemNode => {
   const nodes = targetAndAncestors.map((page): ItemNode => {
     return new ItemNode(page, []);
     return new ItemNode(page, []);
   });
   });
@@ -33,9 +28,6 @@ const generateInitialNodeBeforeResponse = (targetAndAncestors: Partial<IPage>[])
 const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Partial<IPage & HasObjectId>[]>, rootNode: ItemNode): ItemNode => {
 const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Partial<IPage & HasObjectId>[]>, rootNode: ItemNode): ItemNode => {
   const paths = Object.keys(ancestorsChildren);
   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;
   let currentNode = rootNode;
   paths.reverse().forEach((path) => {
   paths.reverse().forEach((path) => {
     const childPages = ancestorsChildren[path];
     const childPages = ancestorsChildren[path];
@@ -58,7 +50,7 @@ const ItemsTree: FC = () => {
   // TODO: get from static SWR
   // TODO: get from static SWR
   const path = '/Sandbox';
   const path = '/Sandbox';
 
 
-  const { data: targetAndAncestors, error } = useTargetAndAncestors();
+  const { data, error } = useTargetAndAncestors();
 
 
   const { data: ancestorsChildrenData, error: error2 } = useSWRxPageAncestorsChildren(path);
   const { data: ancestorsChildrenData, error: error2 } = useSWRxPageAncestorsChildren(path);
 
 
@@ -66,10 +58,12 @@ const ItemsTree: FC = () => {
     return null;
     return null;
   }
   }
 
 
-  if (targetAndAncestors == null) {
+  if (data == null) {
     return null;
     return null;
   }
   }
 
 
+  const { targetAndAncestors, rootPage } = data;
+
   let initialNode: ItemNode;
   let initialNode: ItemNode;
 
 
   /*
   /*
@@ -85,7 +79,6 @@ const ItemsTree: FC = () => {
   else {
   else {
     const { ancestorsChildren } = ancestorsChildrenData;
     const { ancestorsChildren } = ancestorsChildrenData;
 
 
-    const rootPage = targetAndAncestors[targetAndAncestors.length - 1];
     const rootNode = new ItemNode(rootPage);
     const rootNode = new ItemNode(rootPage);
 
 
     initialNode = generateInitialNodeAfterResponse(ancestorsChildren, rootNode);
     initialNode = generateInitialNodeAfterResponse(ancestorsChildren, rootNode);

+ 6 - 1
packages/app/src/interfaces/page-listing-results.ts

@@ -6,8 +6,13 @@ export interface AncestorsChildrenResult {
   ancestorsChildren: Record<ParentPath, Partial<IPageForItem>[]>
   ancestorsChildren: Record<ParentPath, Partial<IPageForItem>[]>
 }
 }
 
 
-export type TargetAndAncestors = Partial<IPageForItem>[];
 
 
 export interface ChildrenResult {
 export interface ChildrenResult {
   children: Partial<IPageForItem>[]
   children: Partial<IPageForItem>[]
 }
 }
+
+
+export interface TargetAndAncestors {
+  targetAndAncestors: Partial<IPageForItem>[]
+  rootPage: Partial<IPageForItem>,
+}

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

@@ -33,11 +33,15 @@ const STATUS_DELETED = 'deleted';
 
 
 export interface PageDocument extends IPage, Document {}
 export interface PageDocument extends IPage, Document {}
 
 
+type TargetAndAncestorsResult = {
+  targetAndAncestors: PageDocument[]
+  rootPage: PageDocument
+}
 export interface PageModel extends Model<PageDocument> {
 export interface PageModel extends Model<PageDocument> {
   createEmptyPagesByPaths(paths: string[]): Promise<void>
   createEmptyPagesByPaths(paths: string[]): Promise<void>
   getParentIdAndFillAncestors(path: string): Promise<string | null>
   getParentIdAndFillAncestors(path: string): Promise<string | null>
   findByPathAndViewer(path: string | null, user, userGroups?, useFindOne?): Promise<PageDocument[]>
   findByPathAndViewer(path: string | null, user, userGroups?, useFindOne?): Promise<PageDocument[]>
-  findTargetAndAncestorsByPathOrId(pathOrId: string): Promise<PageDocument[]>
+  findTargetAndAncestorsByPathOrId(pathOrId: string): Promise<TargetAndAncestorsResult>
   findChildrenByParentPathOrIdAndViewer(parentPathOrId: string, user, userGroups?): Promise<PageDocument[]>
   findChildrenByParentPathOrIdAndViewer(parentPathOrId: string, user, userGroups?): Promise<PageDocument[]>
   findAncestorsChildrenByPathAndViewer(path: string, user, userGroups?): Promise<Record<string, PageDocument[]>>
   findAncestorsChildrenByPathAndViewer(path: string, user, userGroups?): Promise<Record<string, PageDocument[]>>
 }
 }
@@ -236,7 +240,7 @@ schema.statics.findByPathAndViewer = async function(
  * Find all ancestor pages by path. When duplicate pages found, it uses the oldest page as a result
  * Find all ancestor pages by path. When duplicate pages found, it uses the oldest page as a result
  * The result will include the target as well
  * The result will include the target as well
  */
  */
-schema.statics.findTargetAndAncestorsByPathOrId = async function(pathOrId: string): Promise<PageDocument[]> {
+schema.statics.findTargetAndAncestorsByPathOrId = async function(pathOrId: string): Promise<TargetAndAncestorsResult> {
   let path;
   let path;
   if (!hasSlash(pathOrId)) {
   if (!hasSlash(pathOrId)) {
     const _id = pathOrId;
     const _id = pathOrId;
@@ -254,7 +258,7 @@ schema.statics.findTargetAndAncestorsByPathOrId = async function(pathOrId: strin
 
 
   // Do not populate
   // Do not populate
   const queryBuilder = new PageQueryBuilder(this.find());
   const queryBuilder = new PageQueryBuilder(this.find());
-  const _ancestors: PageDocument[] = await queryBuilder
+  const _targetAndAncestors: PageDocument[] = await queryBuilder
     .addConditionToListByPathsArray(ancestorPaths)
     .addConditionToListByPathsArray(ancestorPaths)
     .addConditionToMinimizeDataForRendering()
     .addConditionToMinimizeDataForRendering()
     .addConditionToSortAncestorPages()
     .addConditionToSortAncestorPages()
@@ -264,10 +268,11 @@ schema.statics.findTargetAndAncestorsByPathOrId = async function(pathOrId: strin
 
 
   // no same path pages
   // no same path pages
   const ancestorsMap = new Map<string, PageDocument>();
   const ancestorsMap = new Map<string, PageDocument>();
-  _ancestors.forEach(page => ancestorsMap.set(page.path, page));
-  const ancestors = Array.from(ancestorsMap.values());
+  _targetAndAncestors.forEach(page => ancestorsMap.set(page.path, page));
+  const targetAndAncestors = Array.from(ancestorsMap.values());
+  const rootPage = targetAndAncestors[targetAndAncestors.length - 1];
 
 
-  return ancestors;
+  return { targetAndAncestors, rootPage };
 };
 };
 
 
 /*
 /*

+ 1 - 1
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -48,7 +48,7 @@ export default (crowi: Crowi): Router => {
     const Page: PageModel = crowi.model('Page');
     const Page: PageModel = crowi.model('Page');
 
 
     try {
     try {
-      const ancestorsChildren: Record<string, PageDocument[]> = await Page.findAncestorsChildrenByPathAndViewer(path as string, req.user);
+      const ancestorsChildren = await Page.findAncestorsChildrenByPathAndViewer(path as string, req.user);
       return res.apiv3({ ancestorsChildren });
       return res.apiv3({ ancestorsChildren });
     }
     }
     catch (err) {
     catch (err) {

+ 2 - 2
packages/app/src/server/routes/page.js

@@ -265,13 +265,13 @@ module.exports = function(crowi, app) {
   }
   }
 
 
   async function addRenderVarsForPageTree(renderVars, path) {
   async function addRenderVarsForPageTree(renderVars, path) {
-    const targetAndAncestors = await Page.findTargetAndAncestorsByPathOrId(path);
+    const { targetAndAncestors, rootPage } = await Page.findTargetAndAncestorsByPathOrId(path);
 
 
     if (targetAndAncestors.length === 0 && !isTopPage(path)) {
     if (targetAndAncestors.length === 0 && !isTopPage(path)) {
       throw new Error('Ancestors must have at least one page.');
       throw new Error('Ancestors must have at least one page.');
     }
     }
 
 
-    renderVars.targetAndAncestors = targetAndAncestors;
+    renderVars.targetAndAncestors = { targetAndAncestors, rootPage };
   }
   }
 
 
   function replacePlaceholdersOfTemplate(template, req) {
   function replacePlaceholdersOfTemplate(template, req) {

+ 1 - 1
packages/app/src/stores/context.tsx

@@ -4,5 +4,5 @@ import { useStaticSWR } from './use-static-swr';
 import { TargetAndAncestors } from '../interfaces/page-listing-results';
 import { TargetAndAncestors } from '../interfaces/page-listing-results';
 
 
 export const useTargetAndAncestors = (initialData?: TargetAndAncestors): SWRResponse<TargetAndAncestors, Error> => {
 export const useTargetAndAncestors = (initialData?: TargetAndAncestors): SWRResponse<TargetAndAncestors, Error> => {
-  return useStaticSWR<TargetAndAncestors, Error>('swr-targetAndAncestors', initialData);
+  return useStaticSWR<TargetAndAncestors, Error>('targetAndAncestors', initialData || null);
 };
 };

+ 1 - 0
packages/app/src/stores/page-listing.tsx

@@ -14,5 +14,6 @@ export const useSWRxPageAncestorsChildren = (
         ancestorsChildren: response.data.ancestorsChildren,
         ancestorsChildren: response.data.ancestorsChildren,
       };
       };
     }),
     }),
+    { revalidateOnFocus: false },
   );
   );
 };
 };

+ 19 - 20
packages/app/src/stores/use-static-swr.tsx

@@ -1,27 +1,26 @@
-import useSWR, {
-  Key, SWRResponse, mutate, useSWRConfig,
+import {
+  Key, SWRConfiguration, SWRResponse, mutate,
 } from 'swr';
 } from 'swr';
+import useSWRImmutable from 'swr/immutable';
 import { Fetcher } from 'swr/dist/types';
 import { Fetcher } from 'swr/dist/types';
 
 
 
 
-export const useStaticSWR = <Data, Error>(
-  key: Key,
-  initialData?: Data | Fetcher<Data>,
-  updateData?: Data | Fetcher<Data>,
-): SWRResponse<Data, Error> => {
-  const { cache } = useSWRConfig();
+export function useStaticSWR<Data, Error>(key: Key): SWRResponse<Data, Error>;
+export function useStaticSWR<Data, Error>(key: Key, data: Data | Fetcher<Data> | null): SWRResponse<Data, Error>;
+export function useStaticSWR<Data, Error>(key: Key, data: Data | Fetcher<Data> | null,
+  configuration: SWRConfiguration<Data, Error> | undefined): SWRResponse<Data, Error>;
 
 
-  if (updateData == null) {
-    if (cache.get(key) == null && initialData != null) {
-      mutate(key, initialData, false);
-    }
-  }
-  else {
-    mutate(key, updateData);
+export function useStaticSWR<Data, Error>(
+    ...args: readonly [Key]
+    | readonly [Key, Data | Fetcher<Data> | null]
+    | readonly [Key, Data | Fetcher<Data> | null, SWRConfiguration<Data, Error> | undefined]
+): SWRResponse<Data, Error> {
+  const [key, fetcher, configuration] = args;
+
+  const fetcherFixed = fetcher || configuration?.fetcher;
+  if (fetcherFixed != null) {
+    mutate(key, fetcherFixed);
   }
   }
 
 
-  return useSWR(key, null, {
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-  });
-};
+  return useSWRImmutable(key, null, configuration);
+}

+ 0 - 2
packages/app/src/utils/swr-utils.ts

@@ -2,7 +2,5 @@ import { SWRConfiguration } from 'swr';
 
 
 
 
 export const swrGlobalConfiguration: SWRConfiguration = {
 export const swrGlobalConfiguration: SWRConfiguration = {
-  fetcher: undefined,
-  revalidateOnFocus: false,
   errorRetryCount: 1,
   errorRetryCount: 1,
 };
 };