erudit 3.0.0-dev.2 → 3.0.0-dev.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/app/app.vue +195 -172
  2. package/app/assets/icons/cameo-add.svg +3 -0
  3. package/app/assets/icons/diamond.svg +3 -0
  4. package/app/assets/icons/graduation.svg +3 -0
  5. package/app/components/Avatar.vue +118 -0
  6. package/app/components/EruditLink.vue +17 -0
  7. package/app/components/Loading.vue +23 -23
  8. package/app/components/SiteAside.vue +393 -382
  9. package/app/components/SiteMain.vue +32 -35
  10. package/app/components/ads/Ads.vue +33 -0
  11. package/app/components/ads/AdsBannerAside.vue +61 -0
  12. package/app/components/ads/AdsBannerBottom.vue +22 -0
  13. package/app/components/ads/AdsProviderCustom.vue +35 -0
  14. package/app/components/ads/AdsProviderYandex.vue +91 -0
  15. package/app/components/ads/AdsReplacer.vue +73 -0
  16. package/app/components/aside/AsideListItem.vue +91 -74
  17. package/app/components/aside/AsideMajor.vue +56 -56
  18. package/app/components/aside/AsideMinor.vue +97 -71
  19. package/app/components/aside/major/PaneContentScroll.vue +23 -23
  20. package/app/components/aside/major/PaneSwitch.vue +54 -54
  21. package/app/components/aside/major/PaneSwitchButton.vue +63 -63
  22. package/app/components/aside/major/SiteInfo.vue +85 -85
  23. package/app/components/aside/major/panes/Language.vue +79 -79
  24. package/app/components/aside/major/panes/Pages.vue +50 -34
  25. package/app/components/aside/major/panes/Search.vue +11 -11
  26. package/app/components/aside/major/panes/nav/Nav.vue +92 -91
  27. package/app/components/aside/major/panes/nav/NavBook.vue +95 -86
  28. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -24
  29. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -16
  30. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -105
  31. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -32
  32. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -40
  33. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -60
  34. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -34
  35. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -80
  36. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -24
  37. package/app/components/aside/major/panes/other/ItemContent.vue +29 -29
  38. package/app/components/aside/major/panes/other/ItemGenerator.vue +13 -15
  39. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -54
  40. package/app/components/aside/major/panes/other/Other.vue +16 -16
  41. package/app/components/aside/minor/{Contribute.vue → AsideMinorContribute.vue} +175 -145
  42. package/app/components/aside/minor/AsideMinorNews.vue +11 -11
  43. package/app/components/aside/minor/AsideMinorPane.vue +15 -15
  44. package/app/components/aside/minor/AsideMinorTopLink.vue +66 -67
  45. package/app/components/aside/minor/content/AsideMinorContent.vue +89 -92
  46. package/app/components/aside/minor/contributor/AsideMinorContributor.vue +78 -0
  47. package/app/components/aside/minor/contributor/BookContribution.vue +64 -0
  48. package/app/components/aside/minor/topic/AsideMinorTopic.vue +31 -32
  49. package/app/components/aside/minor/topic/TopicContributors.vue +183 -177
  50. package/app/components/aside/minor/topic/TopicNav.vue +49 -49
  51. package/app/components/aside/minor/topic/TopicToc.vue +212 -203
  52. package/app/components/aside/minor/topic/TopicTocItem.vue +19 -31
  53. package/app/components/aside/utils/AsideOverlayPane.vue +40 -40
  54. package/app/components/bitran/BitranContent.vue +90 -63
  55. package/app/components/bitran/RenderWrapper.vue +10 -10
  56. package/app/components/contributor/ContributorListItem.vue +43 -35
  57. package/app/components/main/MainActionButton.vue +51 -0
  58. package/app/components/main/MainBitranContent.vue +55 -0
  59. package/app/components/main/{utils/Breadcrumb.vue → MainBreadcrumb.vue} +63 -75
  60. package/app/components/main/MainDescription.vue +24 -0
  61. package/app/components/main/MainSection.vue +58 -0
  62. package/app/components/main/MainTitle.vue +76 -0
  63. package/app/components/main/cameo/MainCameo.vue +135 -0
  64. package/app/components/main/cameo/MainCameoData.vue +232 -0
  65. package/app/components/main/content/ContentBreadcrumb.vue +28 -0
  66. package/app/components/main/{utils → content}/ContentDecoration.vue +29 -29
  67. package/app/components/main/{utils → content}/ContentPopover.vue +188 -176
  68. package/app/components/main/{utils → content}/ContentPopovers.vue +111 -105
  69. package/app/components/main/{utils → content}/ContentReferences.vue +70 -70
  70. package/app/components/main/{utils → content}/reference/ReferenceGroup.vue +38 -38
  71. package/app/components/main/{utils → content}/reference/ReferenceItem.vue +70 -68
  72. package/app/components/main/{utils → content}/reference/ReferenceSource.vue +120 -116
  73. package/app/components/main/topic/MainTopic.vue +76 -79
  74. package/app/components/main/topic/TopicPartSwitch.vue +119 -118
  75. package/app/components/preview/Preview.vue +186 -177
  76. package/app/components/preview/PreviewDisplay.vue +139 -139
  77. package/app/components/preview/PreviewFooterAction.vue +73 -73
  78. package/app/components/preview/PreviewLoading.vue +14 -14
  79. package/app/components/preview/PreviewScreen.vue +141 -99
  80. package/app/components/preview/display/Alert.vue +50 -50
  81. package/app/components/preview/display/Custom.vue +18 -18
  82. package/app/components/preview/display/GenericLink.vue +48 -48
  83. package/app/components/preview/display/PageLink.vue +22 -20
  84. package/app/components/preview/display/Unique.vue +46 -49
  85. package/app/components/sponsor/SponsorTier1.vue +89 -0
  86. package/app/components/sponsor/SponsorTier2.vue +109 -0
  87. package/app/components/transition/Fade.vue +19 -19
  88. package/app/components/tree/TreeContainer.vue +11 -11
  89. package/app/components/tree/TreeItem.vue +93 -89
  90. package/app/composables/adsAllowed.ts +11 -0
  91. package/app/composables/asset.ts +12 -0
  92. package/app/composables/bitran.ts +108 -127
  93. package/app/composables/bitranLocation.ts +7 -7
  94. package/app/composables/contentData.ts +38 -36
  95. package/app/composables/contentPage.ts +168 -156
  96. package/app/composables/contentRoute.ts +45 -45
  97. package/app/composables/darkMagic.ts +24 -24
  98. package/app/composables/externalApi.ts +69 -63
  99. package/app/composables/favicon.ts +8 -8
  100. package/app/composables/formatText.ts +99 -86
  101. package/app/composables/head.ts +24 -0
  102. package/app/composables/majorPane.ts +62 -60
  103. package/app/composables/phrases.ts +77 -80
  104. package/app/composables/theme.ts +29 -29
  105. package/app/composables/url.ts +43 -33
  106. package/app/pages/_test/preview.vue +110 -110
  107. package/app/pages/article/[...articleId].vue +3 -3
  108. package/app/pages/book/[...bookId].vue +42 -47
  109. package/app/pages/contributor/[contributorId].vue +227 -0
  110. package/app/pages/contributors.vue +184 -0
  111. package/app/pages/group/[...groupId].vue +53 -65
  112. package/app/pages/index.vue +32 -32
  113. package/app/pages/practice/[...practice].vue +3 -3
  114. package/app/pages/sponsors.vue +95 -0
  115. package/app/pages/summary/[...summaryId].vue +3 -3
  116. package/app/plugins/analytics.ts +95 -0
  117. package/app/plugins/prerender.server.ts +34 -0
  118. package/app/public/favicon/article.svg +5 -5
  119. package/app/public/favicon/default.svg +3 -3
  120. package/app/public/favicon/practice.svg +3 -3
  121. package/app/public/favicon/summary.svg +4 -4
  122. package/app/public/logotype.svg +2 -2
  123. package/app/scripts/_immediate.js +9 -9
  124. package/app/scripts/aside/index.ts +59 -59
  125. package/app/scripts/aside/major/nav.ts +26 -26
  126. package/app/scripts/aside/minor/state.ts +37 -37
  127. package/app/scripts/flag.ts +28 -28
  128. package/app/scripts/og.ts +28 -27
  129. package/app/scripts/preview/build.ts +76 -73
  130. package/app/scripts/preview/data/alert.ts +19 -19
  131. package/app/scripts/preview/data/custom.ts +8 -8
  132. package/app/scripts/preview/data/genericLink.ts +24 -24
  133. package/app/scripts/preview/data/pageLink.ts +23 -20
  134. package/app/scripts/preview/data/unique.ts +69 -70
  135. package/app/scripts/preview/data.ts +24 -24
  136. package/app/scripts/preview/display.ts +37 -37
  137. package/app/scripts/preview/footer.ts +9 -9
  138. package/app/scripts/preview/request.ts +51 -51
  139. package/app/scripts/preview/state.ts +63 -63
  140. package/app/styles/_immediate.css +7 -7
  141. package/app/styles/_util.scss +43 -43
  142. package/app/styles/_utils.scss +44 -44
  143. package/app/styles/app.scss +91 -91
  144. package/app/styles/def/_bp.scss +27 -27
  145. package/app/styles/def/_size.scss +7 -7
  146. package/app/styles/def/_z.scss +5 -5
  147. package/app/styles/normalize.scss +49 -63
  148. package/app/styles/partials/_darkMagic.scss +5 -5
  149. package/app/styles/partials/_fnav.scss +15 -15
  150. package/app/styles/partials/_preview.scss +5 -5
  151. package/bin/erudit.mjs +2 -0
  152. package/const.ts +3 -0
  153. package/globalPath.ts +21 -21
  154. package/globals/bitran.ts +1 -47
  155. package/globals/cameo.ts +5 -0
  156. package/globals/content.ts +27 -22
  157. package/globals/contributor.ts +5 -5
  158. package/globals/erudit.ts +5 -5
  159. package/globals/register.ts +23 -18
  160. package/globals/sponsor.ts +17 -0
  161. package/languages/en.ts +108 -95
  162. package/languages/ru.ts +112 -99
  163. package/module/bitran.ts +66 -34
  164. package/module/config.ts +35 -34
  165. package/module/imports.ts +74 -46
  166. package/module/index.ts +47 -47
  167. package/module/logger.ts +10 -10
  168. package/module/paths.ts +22 -22
  169. package/module/restart.ts +61 -61
  170. package/nuxt.config.ts +126 -112
  171. package/package.json +22 -15
  172. package/server/api/aside/major/nav/bookIds.ts +5 -5
  173. package/server/api/aside/major/nav/bookNav/[...bookId].ts +17 -20
  174. package/server/api/aside/major/nav/global.ts +7 -7
  175. package/server/api/aside/minor/book/[...bookId].ts +18 -0
  176. package/server/api/aside/minor/contributor/[contributorId].ts +18 -0
  177. package/server/api/aside/minor/group/[...groupId].ts +18 -0
  178. package/server/api/aside/minor/news.ts +7 -7
  179. package/server/api/aside/minor/topic.ts +36 -0
  180. package/server/api/bitran/content/[...location].ts +13 -0
  181. package/server/api/bitran/toc/[...location].ts +9 -0
  182. package/server/api/cameo/data/[cameoId].ts +42 -0
  183. package/server/api/cameo/ids.ts +5 -0
  184. package/server/api/content/data.ts +75 -72
  185. package/server/api/contributor/count.ts +6 -6
  186. package/server/api/contributor/list.ts +44 -0
  187. package/server/api/contributor/page/[contributorId].ts +14 -0
  188. package/server/api/fake/content.ts +11 -11
  189. package/server/api/fake/shared/languages.ts +12 -12
  190. package/server/api/language/functions.ts +12 -12
  191. package/server/api/language/phrase/[phraseId].ts +19 -19
  192. package/server/api/language/phraseIds.ts +8 -8
  193. package/server/api/prerender/assets/cameo.ts +14 -0
  194. package/server/api/prerender/assets/contributor.ts +12 -0
  195. package/server/api/prerender/assets/sponsor.ts +13 -0
  196. package/server/api/prerender/cameos.ts +12 -0
  197. package/server/api/prerender/language.ts +9 -0
  198. package/server/api/preview/page/[...parts].ts +78 -51
  199. package/server/api/preview/unique/{[location].ts → [...location].ts} +48 -55
  200. package/server/api/problem/generator/[...path].ts +26 -0
  201. package/server/api/sponsor/cameo/data/[sponsorId].ts +51 -0
  202. package/server/api/sponsor/cameo/ids.ts +5 -0
  203. package/server/api/sponsor/count.ts +5 -0
  204. package/server/api/sponsor/list.ts +36 -0
  205. package/server/plugin/bitran/content.ts +252 -187
  206. package/server/plugin/bitran/{products → elements}/include.ts +229 -230
  207. package/server/plugin/bitran/location.ts +43 -25
  208. package/server/plugin/bitran/toc.ts +94 -83
  209. package/server/plugin/bitran/transpiler.ts +18 -46
  210. package/server/plugin/build/close.ts +12 -10
  211. package/server/plugin/build/jobs/content/builderArgs.ts +8 -8
  212. package/server/plugin/build/jobs/content/generic.ts +191 -176
  213. package/server/plugin/build/jobs/content/parse.ts +113 -100
  214. package/server/plugin/build/jobs/content/path.ts +6 -6
  215. package/server/plugin/build/jobs/content/type/book.ts +9 -9
  216. package/server/plugin/build/jobs/content/type/group.ts +37 -37
  217. package/server/plugin/build/jobs/content/type/topic.ts +36 -36
  218. package/server/plugin/build/jobs/contributors.ts +69 -66
  219. package/server/plugin/build/jobs/language.ts +36 -36
  220. package/server/plugin/build/jobs/nav.ts +345 -210
  221. package/server/plugin/build/process.ts +34 -25
  222. package/server/plugin/build/rebuild.ts +68 -55
  223. package/server/plugin/build/setup.ts +19 -21
  224. package/server/plugin/content/context.ts +119 -112
  225. package/server/plugin/db/entities/Book.ts +7 -7
  226. package/server/plugin/db/entities/Content.ts +45 -49
  227. package/server/plugin/db/entities/Contribution.ts +10 -10
  228. package/server/plugin/db/entities/Contributor.ts +25 -16
  229. package/server/plugin/db/entities/File.ts +10 -0
  230. package/server/plugin/db/entities/Group.ts +14 -14
  231. package/server/plugin/db/entities/Hash.ts +15 -15
  232. package/server/plugin/db/entities/Topic.ts +20 -20
  233. package/server/plugin/db/entities/Unique.ts +21 -21
  234. package/server/plugin/db/reset.ts +12 -0
  235. package/server/plugin/db/setup.ts +49 -34
  236. package/server/plugin/global.ts +18 -18
  237. package/server/plugin/importer.ts +16 -12
  238. package/server/plugin/index.ts +9 -9
  239. package/server/plugin/logger.ts +23 -23
  240. package/server/plugin/nav/node.ts +27 -26
  241. package/server/plugin/nav/utils.ts +179 -129
  242. package/server/plugin/repository/asideMinor.ts +51 -0
  243. package/server/plugin/repository/book.ts +39 -21
  244. package/server/plugin/repository/cameo.ts +16 -0
  245. package/server/plugin/repository/content.ts +240 -238
  246. package/server/plugin/repository/contentId.ts +40 -0
  247. package/server/plugin/repository/contributor.ts +129 -8
  248. package/server/plugin/repository/file.ts +10 -0
  249. package/server/plugin/repository/frontNav.ts +145 -148
  250. package/server/plugin/repository/topic.ts +35 -32
  251. package/server/plugin/sponsor/build.ts +82 -0
  252. package/server/plugin/sponsor/index.ts +5 -0
  253. package/server/plugin/sponsor/repository.ts +56 -0
  254. package/server/routes/asset/[...assetPath].ts +34 -0
  255. package/server/routes/robots.txt.ts +9 -0
  256. package/server/routes/sitemap.xml.ts +103 -0
  257. package/server/tsconfig.json +9 -9
  258. package/shared/aside/minor.ts +55 -50
  259. package/shared/asset.ts +17 -15
  260. package/shared/bitran/content.ts +9 -0
  261. package/shared/bitran/contentId.ts +56 -0
  262. package/shared/bitran/toc.ts +8 -8
  263. package/shared/breadcrumb.ts +7 -0
  264. package/shared/content/bookId.ts +12 -0
  265. package/shared/content/context.ts +9 -9
  266. package/shared/content/data/base.ts +32 -32
  267. package/shared/content/data/index.ts +5 -5
  268. package/shared/content/data/type/book.ts +5 -5
  269. package/shared/content/data/type/group.ts +6 -6
  270. package/shared/content/data/type/topic.ts +11 -11
  271. package/shared/content/previousNext.ts +9 -9
  272. package/shared/contributor.ts +34 -5
  273. package/shared/frontNav.ts +41 -41
  274. package/shared/icons.ts +38 -38
  275. package/shared/image.ts +5 -5
  276. package/shared/link.ts +28 -25
  277. package/shared/popover.ts +8 -0
  278. package/shared/types/language.ts +84 -75
  279. package/shared/utils/objectsEqual.ts +4 -4
  280. package/shared/utils/stringColor.ts +9 -9
  281. package/test/contentId.test.ts +91 -0
  282. package/test/utils/url.test.ts +99 -0
  283. package/tsconfig.json +8 -8
  284. package/utils/contentPath.ts +67 -0
  285. package/utils/ext.ts +41 -0
  286. package/utils/stress.ts +9 -9
  287. package/utils/url.ts +23 -0
  288. package/app/components/ads/BannerTemplate.vue +0 -51
  289. package/app/components/ads/BottomBanner.vue +0 -45
  290. package/app/components/ads/LeftBanner.vue +0 -50
  291. package/app/components/aside/minor/AsideMinorContributor.vue +0 -5
  292. package/app/components/contributor/ContributorAvatar.vue +0 -43
  293. package/app/components/main/utils/ContentDescription.vue +0 -19
  294. package/app/components/main/utils/ContentFlag.vue +0 -15
  295. package/app/components/main/utils/ContentSection.vue +0 -45
  296. package/app/components/main/utils/ContentTitle.vue +0 -39
  297. package/app/composables/bitranContent.ts +0 -37
  298. package/app/pages/members.vue +0 -6
  299. package/app/public/user.svg +0 -10
  300. package/app/scripts/aside/minor/topic.ts +0 -3
  301. package/app/styles/default.scss +0 -83
  302. package/server/api/aside/minor/path.ts +0 -78
  303. package/server/api/bitran/content/[location].ts +0 -7
  304. package/server/api/bitran/toc/[location].ts +0 -7
  305. package/server/plugin/bitran/products/link.ts +0 -116
  306. package/server/plugin/bitran/setup.ts +0 -9
  307. package/server/plugin/content/absoluteId.ts +0 -94
  308. package/shared/bitran/context.ts +0 -8
  309. package/shared/bitran/default.ts +0 -46
  310. package/shared/bitran/link/Link.vue +0 -167
  311. package/shared/bitran/link/factory.ts +0 -24
  312. package/shared/bitran/link/icon.svg +0 -3
  313. package/shared/bitran/link/languages/en.ts +0 -7
  314. package/shared/bitran/link/languages/ru.ts +0 -7
  315. package/shared/bitran/link/renderer.ts +0 -21
  316. package/shared/bitran/link/shared.ts +0 -17
  317. package/shared/bitran/link/target.ts +0 -134
  318. package/shared/bitran/link/transpiler.ts +0 -10
  319. package/shared/bitran/location.ts +0 -166
  320. package/test/bitran/link/target.test.ts +0 -141
  321. package/test/bitran/location.test.ts +0 -143
@@ -1,210 +1,345 @@
1
- import { globSync } from 'glob';
2
- import chalk from 'chalk';
3
- import { resolvePaths } from 'erudit-cog/kit';
4
- import { contentTypes, topicParts, type ContentType } from 'erudit-cog/schema';
5
-
6
- import { PROJECT_DIR } from '#erudit/globalPaths';
7
- import { stress } from '@erudit/utils/stress';
8
- import { debug, logger } from '@server/logger';
9
- import {
10
- createRootNode,
11
- isRootNode,
12
- type NavNode,
13
- type RootNavNode,
14
- } from '@server/nav/node';
15
- import { ERUDIT_SERVER } from '../../global';
16
-
17
- type Ids = Record<string, string>;
18
- let ids: Ids;
19
-
20
- const contentTargets = ERUDIT_SERVER.CONFIG?.contentTargets || [];
21
-
22
- const nodePathRegexp = new RegExp(
23
- `(?<pos>\\d+)(?<sep>-|\\+)(?<id>[\\w-]+)\\/(?<type>${contentTypes.join('|')})\\..*`,
24
- );
25
-
26
- export async function buildNav() {
27
- debug.start('Building navigation tree...');
28
-
29
- ids = {};
30
-
31
- const rootNode = createRootNode();
32
- rootNode.children = (await scanChildNodes(rootNode, false)).children;
33
- ERUDIT_SERVER.NAV = rootNode.children ? rootNode : undefined;
34
-
35
- const nodeCount = Object.values(ids).length;
36
-
37
- if (nodeCount === 0) {
38
- logger.warn('No content nodes detected! The site will be empty!');
39
- } else {
40
- if (ERUDIT_SERVER.CONFIG?.debug?.log) debugPrintNav(rootNode);
41
-
42
- logger.success(
43
- 'Navigation tree built successfully!',
44
- chalk.dim(`(${nodeCount})`),
45
- );
46
- }
47
- }
48
-
49
- async function scanChildNodes(
50
- parent: NavNode | RootNavNode,
51
- insideBook: boolean,
52
- ): Promise<{ children: NavNode[] | undefined; newIds: Ids }> {
53
- const currentFsPath = isRootNode(parent) ? '' : parent.path;
54
-
55
- const nodeFsPaths = globSync(
56
- `${PROJECT_DIR}/content/${currentFsPath}/*/{${contentTypes.join(',')}}.{ts,js}`,
57
- )
58
- .sort()
59
- .map((path) => resolvePaths(path));
60
-
61
- let newIds: Ids = {};
62
- const children: NavNode[] = [];
63
-
64
- for (const nodeFsPath of nodeFsPaths) {
65
- const pathParts:
66
- | {
67
- pos: string;
68
- sep: string;
69
- id: string;
70
- type: ContentType;
71
- }
72
- | undefined = nodeFsPath.match(nodePathRegexp)?.groups as any;
73
-
74
- if (!pathParts) continue; // Wrong path pattern
75
-
76
- const nodePath = nodeFsPath
77
- .replace(PROJECT_DIR + '/content/', '')
78
- .split('/')
79
- .slice(0, -1)
80
- .join('/');
81
-
82
- if (pathParts.type === 'book' && insideBook) {
83
- logger.warn(
84
- 'Books inside books are not allowed!\n',
85
- `Skipping ${stress(nodePath)} nav node!`,
86
- );
87
- continue;
88
- }
89
-
90
- if (!satisfiesContentTargets(nodePath)) continue; // Not a content target
91
-
92
- const parentId = isRootNode(parent)
93
- ? ''
94
- : parent.skip
95
- ? parent.id.split('/').slice(0, -1).join('/')
96
- : parent.id;
97
-
98
- // Regular id might not include parent id part if it is skipped
99
- const id = parentId ? `${parentId}/${pathParts.id}` : pathParts.id;
100
-
101
- if (ids[id]) {
102
- logger.warn(
103
- `Nav node ${stress(id)} ID collision!\n\n`,
104
- `This ID belongs to ${stress(ids[id], chalk.greenBright)} nav node.\n`,
105
- `Nav node ${stress(nodePath, chalk.redBright)} tries to use the same ID!\n`,
106
- 'Skipping nav node which causes collision!',
107
- );
108
- continue;
109
- }
110
-
111
- // Full id always includes parent id
112
- const fullId = isRootNode(parent)
113
- ? pathParts.id
114
- : `${parent.fullId}/${pathParts.id}`;
115
-
116
- const skip = pathParts.sep === '+';
117
-
118
- const childNode: NavNode = {
119
- parent,
120
- type: pathParts.type,
121
- path: nodePath,
122
- id,
123
- fullId,
124
- skip,
125
- };
126
-
127
- if (!validNode(childNode)) continue;
128
-
129
- ids[id] = nodePath;
130
- newIds[id] = nodePath;
131
-
132
- const scanResult = await scanChildNodes(
133
- childNode,
134
- insideBook || childNode.type === 'book',
135
- );
136
-
137
- if (
138
- ['book', 'group'].includes(childNode.type) &&
139
- !scanResult.children
140
- ) {
141
- delete ids[id];
142
- delete newIds[id];
143
- for (const childNewId of Object.keys(scanResult.newIds))
144
- delete ids[childNewId];
145
- continue;
146
- }
147
-
148
- if (childNode.type === 'book') {
149
- ERUDIT_SERVER.NAV_BOOKS ||= {};
150
- ERUDIT_SERVER.NAV_BOOKS[id] = childNode;
151
- }
152
-
153
- childNode.children = scanResult.children;
154
- children.push(childNode);
155
- }
156
-
157
- return {
158
- children: children.length > 0 ? children : undefined,
159
- newIds,
160
- };
161
- }
162
-
163
- function satisfiesContentTargets(nodePath: string): boolean {
164
- if (contentTargets.length === 0) return true;
165
-
166
- for (const target of contentTargets)
167
- if (nodePath.startsWith(target) || target.search(nodePath) === 0)
168
- return true;
169
-
170
- return false;
171
- }
172
-
173
- function validNode(node: NavNode): boolean {
174
- switch (node.type) {
175
- case 'topic':
176
- const partPaths = globSync(
177
- PROJECT_DIR +
178
- `/content/${node.path}` +
179
- `/{${topicParts.join(',')}}.bi`,
180
- );
181
- if (partPaths.length === 0) return false; // Topic is empty
182
- break;
183
- case 'book':
184
- if (node.skip) {
185
- logger.warn(
186
- `Books can't skip their ID part!\n`,
187
- `Skipping ${stress(node.path)} nav node!`,
188
- );
189
- return false;
190
- }
191
- break;
192
- }
193
-
194
- return true;
195
- }
196
-
197
- function debugPrintNav(node: RootNavNode) {
198
- const logNode = (node: NavNode | RootNavNode, indent: number) => {
199
- console.log(
200
- isRootNode(node)
201
- ? chalk.dim('#root')
202
- : `${' '.repeat(indent)}${node.id.split('/').pop()} ${chalk.dim(`[${node.type}${node.skip ? ', ' + chalk.yellow('skip') : ''}]`)}`,
203
- );
204
-
205
- if (node.children)
206
- for (const child of node.children) logNode(child, indent + 1);
207
- };
208
-
209
- logNode(node, 0);
210
- }
1
+ import { globSync } from 'glob';
2
+ import chalk from 'chalk';
3
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ import {
6
+ contentTypes,
7
+ topicParts,
8
+ type ContentType,
9
+ } from '@erudit-js/cog/schema';
10
+
11
+ import { PROJECT_DIR } from '#erudit/globalPaths';
12
+ import { stress } from '@erudit/utils/stress';
13
+ import { debug, logger } from '@server/logger';
14
+ import {
15
+ createRootNode,
16
+ isRootNode,
17
+ type NavNode,
18
+ type RootNavNode,
19
+ } from '@server/nav/node';
20
+ import { ERUDIT_SERVER } from '@server/global';
21
+ import { jiti } from '@server/importer';
22
+
23
+ type Ids = Record<string, string>;
24
+ let fullIds: Ids;
25
+
26
+ const nodePathRegexp = new RegExp(
27
+ `(?<pos>\\d+)(?<sep>-|\\+)(?<id>[\\w-]+)\\/(?<type>${contentTypes.join('|')})\\..*`,
28
+ );
29
+
30
+ const contentFilter = createContentFilter();
31
+
32
+ export async function buildNav() {
33
+ debug.start('Building navigation tree...');
34
+
35
+ fullIds = {};
36
+
37
+ const rootNode = createRootNode();
38
+ rootNode.children = (await scanChildNodes(rootNode, false)).children;
39
+ ERUDIT_SERVER.NAV = rootNode.children ? rootNode : undefined;
40
+
41
+ const nodeCount = Object.values(fullIds).length;
42
+
43
+ if (nodeCount === 0) {
44
+ logger.warn('No content nodes detected! The site will be empty!');
45
+ } else {
46
+ if (ERUDIT_SERVER.CONFIG?.debug?.log) debugPrintNav(rootNode);
47
+
48
+ writeCompilerOptionsPaths();
49
+ writeJitiAliases();
50
+
51
+ logger.success(
52
+ 'Navigation tree built successfully!',
53
+ chalk.dim(`(${nodeCount})`),
54
+ );
55
+ }
56
+ }
57
+
58
+ async function scanChildNodes(
59
+ parent: NavNode | RootNavNode,
60
+ insideBook: boolean,
61
+ ): Promise<{ children: NavNode[] | undefined; newIds: Ids }> {
62
+ const currentFsPath = isRootNode(parent) ? '' : parent.fsPath + '/';
63
+
64
+ const nodeFsPaths = globSync(
65
+ `${currentFsPath}*/{${contentTypes.join(',')}}.{ts,js}`,
66
+ {
67
+ cwd: PROJECT_DIR + '/content',
68
+ posix: true,
69
+ },
70
+ ).sort();
71
+
72
+ let newIds: Ids = {};
73
+ const children: NavNode[] = [];
74
+
75
+ for (const nodeFsPath of nodeFsPaths) {
76
+ const pathParts:
77
+ | {
78
+ pos: string;
79
+ sep: string;
80
+ id: string;
81
+ type: ContentType;
82
+ }
83
+ | undefined = nodeFsPath.match(nodePathRegexp)?.groups as any;
84
+
85
+ if (!pathParts) continue; // Wrong path pattern
86
+
87
+ const nodePath = nodeFsPath.split('/').slice(0, -1).join('/');
88
+
89
+ if (pathParts.type === 'book' && insideBook) {
90
+ logger.warn(
91
+ 'Books inside books are not allowed!\n',
92
+ `Skipping ${stress(nodePath)} nav node!`,
93
+ );
94
+ continue;
95
+ }
96
+
97
+ const parentId = isRootNode(parent)
98
+ ? ''
99
+ : parent.skip
100
+ ? parent.fullId.split('/').slice(0, -1).join('/')
101
+ : parent.fullId;
102
+
103
+ // Regular id might not include parent id part if it is skipped
104
+ const id = parentId ? `${parentId}/${pathParts.id}` : pathParts.id;
105
+
106
+ if (fullIds[id]) {
107
+ logger.warn(
108
+ `Nav node ${stress(id)} ID collision!\n\n`,
109
+ `This ID belongs to ${stress(fullIds[id], chalk.greenBright)} nav node.\n`,
110
+ `Nav node ${stress(nodePath, chalk.redBright)} tries to use the same ID!\n`,
111
+ 'Skipping nav node which causes collision!',
112
+ );
113
+ continue;
114
+ }
115
+
116
+ // Full id always includes parent id
117
+ const fullId = isRootNode(parent)
118
+ ? pathParts.id
119
+ : `${parent.fullId}/${pathParts.id}`;
120
+
121
+ // Short id skips parent ids for nodes with skip=true, except current node
122
+ const shortId = isRootNode(parent)
123
+ ? pathParts.id
124
+ : (() => {
125
+ // Traverse up, skipping parent ids where skip=true
126
+ let ids: string[] = [];
127
+ let p: NavNode | RootNavNode | undefined = parent;
128
+ while (p && !isRootNode(p)) {
129
+ if (!p.skip) ids.unshift(p.idPart);
130
+ p = p.parent;
131
+ }
132
+ ids.push(pathParts.id);
133
+ return ids.join('/');
134
+ })();
135
+
136
+ const shouldSkip = (() => {
137
+ const targets = [
138
+ ...contentFilter.cliContentTargets,
139
+ ...(ERUDIT_SERVER.CONFIG?.contentTargets || []),
140
+ ];
141
+
142
+ // If there are no filters, allow all nodes
143
+ if (targets.length === 0) {
144
+ return false;
145
+ }
146
+
147
+ // If the node passes at least one filter, keep it
148
+ for (const target of targets) {
149
+ if (contentFilter.strFilter(fullId, target)) {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ // Node failed all filters, skip it
155
+ return true;
156
+ })();
157
+
158
+ if (shouldSkip) continue;
159
+
160
+ const skip = pathParts.sep === '+';
161
+
162
+ const childNode: NavNode = {
163
+ parent,
164
+ type: pathParts.type,
165
+ fsPath: nodePath,
166
+ idPart: pathParts.id,
167
+ fullId,
168
+ shortId,
169
+ skip,
170
+ };
171
+
172
+ if (!validNode(childNode)) continue;
173
+
174
+ fullIds[fullId] = nodePath;
175
+ newIds[fullId] = nodePath;
176
+
177
+ const scanResult = await scanChildNodes(
178
+ childNode,
179
+ insideBook || childNode.type === 'book',
180
+ );
181
+
182
+ if (
183
+ ['book', 'group'].includes(childNode.type) &&
184
+ !scanResult.children
185
+ ) {
186
+ delete fullIds[fullId];
187
+ delete newIds[fullId];
188
+ for (const childNewId of Object.keys(scanResult.newIds))
189
+ delete fullIds[childNewId];
190
+ continue;
191
+ }
192
+
193
+ if (childNode.type === 'book') {
194
+ ERUDIT_SERVER.NAV_BOOKS ||= {};
195
+ ERUDIT_SERVER.NAV_BOOKS[fullId] = childNode;
196
+ }
197
+
198
+ childNode.children = scanResult.children;
199
+ children.push(childNode);
200
+ }
201
+
202
+ return {
203
+ children: children.length > 0 ? children : undefined,
204
+ newIds,
205
+ };
206
+ }
207
+
208
+ function createContentFilter() {
209
+ const cliContentTargets = (() => {
210
+ try {
211
+ return JSON.parse(
212
+ readFileSync(PROJECT_DIR + '/.erudit/targets.json', 'utf-8'),
213
+ );
214
+ } catch (error) {}
215
+
216
+ return [];
217
+ })();
218
+
219
+ const strFilter = (fullId: string, filterItem: string) => {
220
+ if (fullId.startsWith(filterItem) || filterItem.search(fullId) === 0) {
221
+ return true;
222
+ }
223
+
224
+ return false;
225
+ };
226
+
227
+ return {
228
+ cliContentTargets,
229
+ strFilter,
230
+ };
231
+ }
232
+
233
+ function validNode(node: NavNode): boolean {
234
+ switch (node.type) {
235
+ case 'topic':
236
+ const partPaths = globSync(
237
+ PROJECT_DIR +
238
+ `/content/${node.fsPath}` +
239
+ `/{${topicParts.join(',')}}.bi`,
240
+ );
241
+ if (partPaths.length === 0) return false; // Topic is empty
242
+ break;
243
+ case 'book':
244
+ if (node.skip) {
245
+ logger.warn(
246
+ `Books can't skip their ID part!\n`,
247
+ `Skipping ${stress(node.fsPath)} nav node!`,
248
+ );
249
+ return false;
250
+ }
251
+ break;
252
+ }
253
+
254
+ return true;
255
+ }
256
+
257
+ function debugPrintNav(node: RootNavNode) {
258
+ const logNode = (node: NavNode | RootNavNode, indent: number) => {
259
+ console.log(
260
+ isRootNode(node)
261
+ ? chalk.dim('#root')
262
+ : `${' '.repeat(indent)}${node.idPart} ${chalk.dim(`[${node.type}${node.skip ? ', ' + chalk.yellow('skip') : ''}]`)}`,
263
+ );
264
+
265
+ if (node.children)
266
+ for (const child of node.children) logNode(child, indent + 1);
267
+ };
268
+
269
+ logNode(node, 0);
270
+ }
271
+
272
+ function writeCompilerOptionsPaths() {
273
+ const tsconfigPath = `${PROJECT_DIR}/.erudit/tsconfig.json`;
274
+
275
+ try {
276
+ let tsconfig: any = {};
277
+
278
+ if (existsSync(tsconfigPath)) {
279
+ try {
280
+ tsconfig = JSON.parse(readFileSync(tsconfigPath, 'utf8'));
281
+ } catch (err) {
282
+ logger.warn(
283
+ `Error parsing ${tsconfigPath}, creating a new one.`,
284
+ );
285
+ }
286
+ }
287
+
288
+ tsconfig.compilerOptions = tsconfig.compilerOptions || {};
289
+ tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
290
+
291
+ const paths = tsconfig.compilerOptions.paths;
292
+
293
+ Object.keys(paths).forEach((key) => {
294
+ if (key === '#content/*') {
295
+ return;
296
+ }
297
+
298
+ if (key.startsWith('#content/')) {
299
+ delete paths[key];
300
+ }
301
+ });
302
+
303
+ Object.entries(fullIds).forEach(([id, fsPath]) => {
304
+ const pathKey = `#content/${id}/*`;
305
+ paths[pathKey] = [`../content/${fsPath}/*`];
306
+ });
307
+
308
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 4), 'utf8');
309
+ logger.success(`Updated tsconfig content paths!`);
310
+ } catch (_error) {
311
+ logger.error(`Failed to write compiler paths: ${_error}`);
312
+ }
313
+ }
314
+
315
+ function writeJitiAliases() {
316
+ try {
317
+ if (!jiti.options) {
318
+ jiti.options = {};
319
+ }
320
+
321
+ if (!jiti.options.alias) {
322
+ jiti.options.alias = {};
323
+ }
324
+
325
+ const aliases = jiti.options.alias;
326
+
327
+ Object.keys(aliases).forEach((key) => {
328
+ if (key === '#content/*') {
329
+ return;
330
+ }
331
+
332
+ if (key.startsWith('#content/')) {
333
+ delete aliases[key];
334
+ }
335
+ });
336
+
337
+ Object.entries(fullIds).forEach(([id, fsPath]) => {
338
+ aliases[`#content/${id}`] = `${PROJECT_DIR}/content/${fsPath}`;
339
+ });
340
+
341
+ logger.success('Updated jiti content aliases!');
342
+ } catch (_error) {
343
+ logger.error(`Failed to write jiti aliases: ${_error}`);
344
+ }
345
+ }
@@ -1,25 +1,34 @@
1
- import { debug, logger } from '@server/logger';
2
-
3
- import { setup } from './setup';
4
- import { setupLanguage } from './jobs/language';
5
- import { buildContributors } from './jobs/contributors';
6
- import { buildNav } from './jobs/nav';
7
- import { buildContent } from './jobs/content/generic';
8
-
9
- let initial = true;
10
-
11
- export async function build() {
12
- debug.start('Building data...');
13
-
14
- if (initial) {
15
- await setup();
16
- initial = false;
17
- }
18
-
19
- await setupLanguage();
20
- await buildContributors();
21
- await buildNav();
22
- await buildContent();
23
-
24
- logger.success('Build successful!');
25
- }
1
+ import { resetDatabase } from '@server/db/reset';
2
+ import { ERUDIT_SERVER } from '@server/global';
3
+ import { debug, logger } from '@server/logger';
4
+
5
+ import { setup } from './setup';
6
+ import { setupLanguage } from './jobs/language';
7
+ import { buildContributors } from './jobs/contributors';
8
+ import { buildNav } from './jobs/nav';
9
+ import { buildContent } from './jobs/content/generic';
10
+ import { buildSponsors } from '@server/sponsor/build';
11
+
12
+ let initial = true;
13
+
14
+ async function _build() {
15
+ debug.start('Building data...');
16
+
17
+ if (initial) {
18
+ await setup();
19
+ initial = false;
20
+ }
21
+
22
+ await resetDatabase();
23
+ await setupLanguage();
24
+ await buildContributors();
25
+ await buildNav();
26
+ await buildContent();
27
+ await buildSponsors();
28
+
29
+ logger.success('Build successful!');
30
+ }
31
+
32
+ export async function build() {
33
+ ERUDIT_SERVER.BUILD_PROMISE = _build();
34
+ }