page-node.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import * as url from 'url';
  2. import type { IPageHasId } from '@growi/core';
  3. import type { ParseRangeResult } from '@growi/core/dist/remark-plugins';
  4. import { removeTrailingSlash } from '@growi/core/dist/utils/path-utils';
  5. import type { PageNode } from '../../interfaces/page-node';
  6. import { getDepthOfPath } from '../../utils/depth-utils';
  7. function getParentPath(path: string) {
  8. return removeTrailingSlash(decodeURIComponent(url.resolve(path, './')));
  9. }
  10. /**
  11. * generate PageNode instances for target page and the ancestors
  12. *
  13. * @param {any} pathToNodeMap
  14. * @param {any} rootPagePath
  15. * @param {any} pagePath
  16. * @returns
  17. * @memberof Lsx
  18. */
  19. function generatePageNode(
  20. pathToNodeMap: Record<string, PageNode>,
  21. rootPagePath: string,
  22. pagePath: string,
  23. depthRange?: ParseRangeResult | null,
  24. ): PageNode | null {
  25. // exclude rootPagePath itself
  26. if (pagePath === rootPagePath) {
  27. return null;
  28. }
  29. const depthStartToProcess =
  30. getDepthOfPath(rootPagePath) + (depthRange?.start ?? 0); // at least 1
  31. const currentPageDepth = getDepthOfPath(pagePath);
  32. // return by the depth restriction
  33. // '/' will also return null because the depth is 0
  34. if (currentPageDepth < depthStartToProcess) {
  35. return null;
  36. }
  37. // return when already registered
  38. if (pathToNodeMap[pagePath] != null) {
  39. return pathToNodeMap[pagePath];
  40. }
  41. // generate node
  42. const node = { pagePath, children: [] };
  43. pathToNodeMap[pagePath] = node;
  44. /*
  45. * process recursively for ancestors
  46. */
  47. // get or create parent node
  48. const parentPath = getParentPath(pagePath);
  49. const parentNode = generatePageNode(
  50. pathToNodeMap,
  51. rootPagePath,
  52. parentPath,
  53. depthRange,
  54. );
  55. // associate to patent
  56. if (parentNode != null) {
  57. parentNode.children.push(node);
  58. }
  59. return node;
  60. }
  61. export function generatePageNodeTree(
  62. rootPagePath: string,
  63. pages: IPageHasId[],
  64. depthRange?: ParseRangeResult | null,
  65. ): PageNode[] {
  66. const pathToNodeMap: Record<string, PageNode> = {};
  67. pages.forEach((page) => {
  68. const node = generatePageNode(
  69. pathToNodeMap,
  70. rootPagePath,
  71. page.path,
  72. depthRange,
  73. ); // this will not be null
  74. // exclude rootPagePath itself
  75. if (node == null) {
  76. return;
  77. }
  78. // set the Page substance
  79. node.page = page;
  80. });
  81. // return root objects
  82. const rootNodes: PageNode[] = [];
  83. Object.keys(pathToNodeMap).forEach((pagePath) => {
  84. const parentPath = getParentPath(pagePath);
  85. // pick up what parent doesn't exist
  86. if (parentPath === '/' || !(parentPath in pathToNodeMap)) {
  87. rootNodes.push(pathToNodeMap[pagePath]);
  88. }
  89. });
  90. return rootNodes;
  91. }