erudit 3.0.0-dev.1 → 3.0.0-dev.10

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 (252) hide show
  1. package/app/app.vue +195 -172
  2. package/app/components/Loading.vue +23 -23
  3. package/app/components/SiteAside.vue +393 -382
  4. package/app/components/SiteMain.vue +34 -35
  5. package/app/components/ads/BannerTemplate.vue +51 -51
  6. package/app/components/ads/BottomBanner.vue +45 -45
  7. package/app/components/ads/LeftBanner.vue +50 -50
  8. package/app/components/aside/AsideListItem.vue +74 -74
  9. package/app/components/aside/AsideMajor.vue +56 -56
  10. package/app/components/aside/AsideMinor.vue +71 -71
  11. package/app/components/aside/major/PaneContentScroll.vue +23 -23
  12. package/app/components/aside/major/PaneSwitch.vue +54 -54
  13. package/app/components/aside/major/PaneSwitchButton.vue +63 -63
  14. package/app/components/aside/major/SiteInfo.vue +85 -85
  15. package/app/components/aside/major/panes/Language.vue +79 -79
  16. package/app/components/aside/major/panes/Pages.vue +34 -34
  17. package/app/components/aside/major/panes/Search.vue +11 -11
  18. package/app/components/aside/major/panes/nav/Nav.vue +87 -91
  19. package/app/components/aside/major/panes/nav/NavBook.vue +87 -86
  20. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -24
  21. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -16
  22. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -105
  23. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -32
  24. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -40
  25. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -60
  26. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -34
  27. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -80
  28. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -24
  29. package/app/components/aside/major/panes/other/ItemContent.vue +29 -29
  30. package/app/components/aside/major/panes/other/ItemGenerator.vue +13 -15
  31. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -54
  32. package/app/components/aside/major/panes/other/Other.vue +16 -16
  33. package/app/components/aside/minor/AsideMinorContributor.vue +5 -5
  34. package/app/components/aside/minor/AsideMinorNews.vue +11 -11
  35. package/app/components/aside/minor/AsideMinorPane.vue +15 -15
  36. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -67
  37. package/app/components/aside/minor/Contribute.vue +145 -145
  38. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -92
  39. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -32
  40. package/app/components/aside/minor/topic/TopicContributors.vue +177 -177
  41. package/app/components/aside/minor/topic/TopicNav.vue +49 -49
  42. package/app/components/aside/minor/topic/TopicToc.vue +219 -203
  43. package/app/components/aside/minor/topic/TopicTocItem.vue +30 -31
  44. package/app/components/aside/utils/AsideOverlayPane.vue +40 -40
  45. package/app/components/bitran/BitranContent.vue +91 -63
  46. package/app/components/bitran/RenderWrapper.vue +10 -10
  47. package/app/components/contributor/ContributorAvatar.vue +45 -43
  48. package/app/components/contributor/ContributorListItem.vue +35 -35
  49. package/app/components/main/topic/MainTopic.vue +78 -79
  50. package/app/components/main/topic/TopicPartSwitch.vue +118 -118
  51. package/app/components/main/utils/Breadcrumb.vue +70 -75
  52. package/app/components/main/utils/ContentDecoration.vue +29 -29
  53. package/app/components/main/utils/ContentDescription.vue +19 -19
  54. package/app/components/main/utils/ContentPopover.vue +188 -176
  55. package/app/components/main/utils/ContentPopovers.vue +111 -105
  56. package/app/components/main/utils/ContentReferences.vue +70 -70
  57. package/app/components/main/utils/ContentSection.vue +45 -45
  58. package/app/components/main/utils/ContentTitle.vue +63 -39
  59. package/app/components/main/utils/reference/ReferenceGroup.vue +38 -38
  60. package/app/components/main/utils/reference/ReferenceItem.vue +68 -68
  61. package/app/components/main/utils/reference/ReferenceSource.vue +116 -116
  62. package/app/components/preview/Preview.vue +186 -177
  63. package/app/components/preview/PreviewDisplay.vue +139 -139
  64. package/app/components/preview/PreviewFooterAction.vue +73 -73
  65. package/app/components/preview/PreviewLoading.vue +14 -14
  66. package/app/components/preview/PreviewScreen.vue +141 -99
  67. package/app/components/preview/display/Alert.vue +50 -50
  68. package/app/components/preview/display/Custom.vue +18 -18
  69. package/app/components/preview/display/GenericLink.vue +48 -48
  70. package/app/components/preview/display/PageLink.vue +20 -20
  71. package/app/components/preview/display/Unique.vue +55 -49
  72. package/app/components/transition/Fade.vue +19 -19
  73. package/app/components/tree/TreeContainer.vue +11 -11
  74. package/app/components/tree/TreeItem.vue +89 -89
  75. package/app/composables/bitran.ts +108 -127
  76. package/app/composables/bitranContent.ts +69 -37
  77. package/app/composables/bitranLocation.ts +7 -7
  78. package/app/composables/contentData.ts +36 -36
  79. package/app/composables/contentPage.ts +157 -156
  80. package/app/composables/contentRoute.ts +45 -45
  81. package/app/composables/darkMagic.ts +24 -24
  82. package/app/composables/externalApi.ts +63 -63
  83. package/app/composables/favicon.ts +8 -8
  84. package/app/composables/formatText.ts +86 -86
  85. package/app/composables/majorPane.ts +60 -60
  86. package/app/composables/phrases.ts +81 -80
  87. package/app/composables/theme.ts +29 -29
  88. package/app/composables/url.ts +33 -33
  89. package/app/pages/_test/preview.vue +110 -110
  90. package/app/pages/article/[...articleId].vue +3 -3
  91. package/app/pages/book/[...bookId].vue +47 -47
  92. package/app/pages/group/[...groupId].vue +64 -65
  93. package/app/pages/index.vue +32 -32
  94. package/app/pages/members.vue +6 -6
  95. package/app/pages/practice/[...practice].vue +3 -3
  96. package/app/pages/summary/[...summaryId].vue +3 -3
  97. package/app/public/favicon/article.svg +5 -5
  98. package/app/public/favicon/default.svg +3 -3
  99. package/app/public/favicon/practice.svg +3 -3
  100. package/app/public/favicon/summary.svg +4 -4
  101. package/app/public/logotype.svg +2 -2
  102. package/app/scripts/_immediate.js +9 -9
  103. package/app/scripts/aside/index.ts +59 -59
  104. package/app/scripts/aside/major/nav.ts +26 -26
  105. package/app/scripts/aside/minor/state.ts +37 -37
  106. package/app/scripts/aside/minor/topic.ts +3 -3
  107. package/app/scripts/flag.ts +28 -28
  108. package/app/scripts/og.ts +27 -27
  109. package/app/scripts/preview/build.ts +73 -73
  110. package/app/scripts/preview/data/alert.ts +19 -19
  111. package/app/scripts/preview/data/custom.ts +8 -8
  112. package/app/scripts/preview/data/genericLink.ts +24 -24
  113. package/app/scripts/preview/data/pageLink.ts +22 -20
  114. package/app/scripts/preview/data/unique.ts +72 -70
  115. package/app/scripts/preview/data.ts +24 -24
  116. package/app/scripts/preview/display.ts +37 -37
  117. package/app/scripts/preview/footer.ts +9 -9
  118. package/app/scripts/preview/request.ts +51 -51
  119. package/app/scripts/preview/state.ts +63 -63
  120. package/app/styles/_immediate.css +7 -7
  121. package/app/styles/_util.scss +43 -43
  122. package/app/styles/_utils.scss +44 -44
  123. package/app/styles/app.scss +91 -91
  124. package/app/styles/def/_bp.scss +27 -27
  125. package/app/styles/def/_size.scss +7 -7
  126. package/app/styles/def/_z.scss +5 -5
  127. package/app/styles/normalize.scss +63 -63
  128. package/app/styles/partials/_darkMagic.scss +5 -5
  129. package/app/styles/partials/_fnav.scss +15 -15
  130. package/app/styles/partials/_preview.scss +5 -5
  131. package/bin/erudit.mjs +2 -0
  132. package/const.ts +4 -0
  133. package/globalPath.ts +21 -21
  134. package/globals/bitran.ts +1 -47
  135. package/globals/content.ts +22 -22
  136. package/globals/contributor.ts +5 -5
  137. package/globals/erudit.ts +5 -5
  138. package/globals/register.ts +18 -18
  139. package/languages/en.ts +94 -95
  140. package/languages/ru.ts +98 -99
  141. package/module/bitran.ts +66 -34
  142. package/module/config.ts +35 -34
  143. package/module/imports.ts +61 -46
  144. package/module/index.ts +47 -47
  145. package/module/logger.ts +10 -10
  146. package/module/paths.ts +22 -22
  147. package/module/restart.ts +61 -61
  148. package/nuxt.config.ts +123 -111
  149. package/package.json +19 -13
  150. package/server/api/aside/major/nav/bookIds.ts +5 -5
  151. package/server/api/aside/major/nav/bookNav/[...bookId].ts +20 -20
  152. package/server/api/aside/major/nav/global.ts +7 -7
  153. package/server/api/aside/minor/news.ts +7 -7
  154. package/server/api/aside/minor/path.ts +77 -78
  155. package/server/api/bitran/content/{[location].ts → [...location].ts} +8 -7
  156. package/server/api/bitran/toc/{[location].ts → [...location].ts} +7 -7
  157. package/server/api/content/data.ts +72 -72
  158. package/server/api/contributor/count.ts +6 -6
  159. package/server/api/fake/content.ts +11 -11
  160. package/server/api/fake/shared/languages.ts +12 -12
  161. package/server/api/language/functions.ts +12 -12
  162. package/server/api/language/phrase/[phraseId].ts +19 -19
  163. package/server/api/language/phraseIds.ts +8 -8
  164. package/server/api/preview/page/[...parts].ts +52 -51
  165. package/server/api/preview/unique/[location].ts +60 -55
  166. package/server/plugin/bitran/content.ts +214 -187
  167. package/server/plugin/bitran/{products → elements}/include.ts +216 -230
  168. package/server/plugin/bitran/location.ts +25 -25
  169. package/server/plugin/bitran/toc.ts +75 -83
  170. package/server/plugin/bitran/transpiler.ts +9 -46
  171. package/server/plugin/build/close.ts +10 -10
  172. package/server/plugin/build/jobs/content/builderArgs.ts +8 -8
  173. package/server/plugin/build/jobs/content/files.ts +79 -0
  174. package/server/plugin/build/jobs/content/generic.ts +192 -176
  175. package/server/plugin/build/jobs/content/parse.ts +109 -100
  176. package/server/plugin/build/jobs/content/path.ts +6 -6
  177. package/server/plugin/build/jobs/content/type/book.ts +9 -9
  178. package/server/plugin/build/jobs/content/type/group.ts +37 -37
  179. package/server/plugin/build/jobs/content/type/topic.ts +36 -36
  180. package/server/plugin/build/jobs/contributors.ts +66 -66
  181. package/server/plugin/build/jobs/language.ts +36 -36
  182. package/server/plugin/build/jobs/nav.ts +214 -209
  183. package/server/plugin/build/process.ts +25 -25
  184. package/server/plugin/build/rebuild.ts +55 -55
  185. package/server/plugin/build/setup.ts +19 -21
  186. package/server/plugin/content/context.ts +116 -112
  187. package/server/plugin/db/entities/Book.ts +7 -7
  188. package/server/plugin/db/entities/Content.ts +49 -49
  189. package/server/plugin/db/entities/Contribution.ts +10 -10
  190. package/server/plugin/db/entities/Contributor.ts +16 -16
  191. package/server/plugin/db/entities/File.ts +10 -0
  192. package/server/plugin/db/entities/Group.ts +14 -14
  193. package/server/plugin/db/entities/Hash.ts +15 -15
  194. package/server/plugin/db/entities/Topic.ts +20 -20
  195. package/server/plugin/db/entities/Unique.ts +21 -21
  196. package/server/plugin/db/setup.ts +36 -34
  197. package/server/plugin/global.ts +16 -18
  198. package/server/plugin/importer.ts +12 -12
  199. package/server/plugin/index.ts +9 -9
  200. package/server/plugin/logger.ts +23 -23
  201. package/server/plugin/nav/node.ts +26 -26
  202. package/server/plugin/nav/utils.ts +133 -129
  203. package/server/plugin/repository/book.ts +21 -21
  204. package/server/plugin/repository/content.ts +237 -238
  205. package/server/plugin/repository/contributor.ts +8 -8
  206. package/server/plugin/repository/file.ts +10 -0
  207. package/server/plugin/repository/frontNav.ts +148 -148
  208. package/server/plugin/repository/topic.ts +32 -32
  209. package/server/tsconfig.json +9 -7
  210. package/shared/aside/minor.ts +51 -50
  211. package/shared/asset.ts +22 -15
  212. package/shared/bitran/contentId.ts +88 -0
  213. package/shared/bitran/stringContent.ts +6 -0
  214. package/shared/bitran/toc.ts +8 -8
  215. package/shared/content/context.ts +9 -9
  216. package/shared/content/data/base.ts +32 -32
  217. package/shared/content/data/index.ts +5 -5
  218. package/shared/content/data/type/book.ts +5 -5
  219. package/shared/content/data/type/group.ts +6 -6
  220. package/shared/content/data/type/topic.ts +11 -11
  221. package/shared/content/previousNext.ts +9 -9
  222. package/shared/contributor.ts +5 -5
  223. package/shared/frontNav.ts +41 -41
  224. package/shared/icons.ts +38 -38
  225. package/shared/image.ts +5 -5
  226. package/shared/link.ts +28 -25
  227. package/shared/popover.ts +8 -0
  228. package/shared/types/language.ts +74 -75
  229. package/shared/utils/objectsEqual.ts +4 -4
  230. package/shared/utils/stringColor.ts +9 -9
  231. package/test/contentId.test.ts +91 -0
  232. package/tsconfig.json +8 -8
  233. package/utils/stress.ts +9 -9
  234. package/app/components/main/utils/ContentFlag.vue +0 -15
  235. package/app/styles/default.scss +0 -83
  236. package/server/plugin/bitran/products/link.ts +0 -116
  237. package/server/plugin/bitran/setup.ts +0 -9
  238. package/server/plugin/content/absoluteId.ts +0 -94
  239. package/shared/bitran/context.ts +0 -8
  240. package/shared/bitran/default.ts +0 -46
  241. package/shared/bitran/link/Link.vue +0 -167
  242. package/shared/bitran/link/factory.ts +0 -24
  243. package/shared/bitran/link/icon.svg +0 -3
  244. package/shared/bitran/link/languages/en.ts +0 -7
  245. package/shared/bitran/link/languages/ru.ts +0 -7
  246. package/shared/bitran/link/renderer.ts +0 -21
  247. package/shared/bitran/link/shared.ts +0 -17
  248. package/shared/bitran/link/target.ts +0 -134
  249. package/shared/bitran/link/transpiler.ts +0 -10
  250. package/shared/bitran/location.ts +0 -166
  251. package/test/bitran/link/target.test.ts +0 -141
  252. package/test/bitran/location.test.ts +0 -143
@@ -1,46 +1,9 @@
1
- import {
2
- defineBitranTranspiler,
3
- type ElementTranspilers,
4
- } from '@bitran-js/transpiler';
5
-
6
- import { ERUDIT_SERVER } from '@server/global';
7
-
8
- // Default Elements
9
- import { aliasesName } from '@erudit-js/bitran-elements/aliases/shared';
10
- import { aliasesTranspiler } from '@erudit-js/bitran-elements/aliases/transpiler';
11
- import { headingName } from '@erudit-js/bitran-elements/heading/shared';
12
- import { headingTranspiler } from '@erudit-js/bitran-elements/heading/transpiler';
13
- import { includeName } from '@erudit-js/bitran-elements/include/shared';
14
- import { includeTranspiler } from '@erudit-js/bitran-elements/include/transpiler';
15
- import { linkName } from '@erudit/shared/bitran/link/shared';
16
- import { linkTranspiler } from '@erudit/shared/bitran/link/transpiler';
17
-
18
- export async function createBitranTranspiler() {
19
- const projectTranspilers = await getProjectTranspilers();
20
-
21
- const defaultTranspilers = {
22
- [aliasesName]: aliasesTranspiler,
23
- [includeName]: includeTranspiler,
24
- [headingName]: headingTranspiler,
25
- [linkName]: linkTranspiler,
26
- };
27
-
28
- const bitranTranspiler = defineBitranTranspiler({
29
- ...projectTranspilers,
30
- ...defaultTranspilers,
31
- });
32
-
33
- return bitranTranspiler;
34
- }
35
-
36
- async function getProjectTranspilers(): Promise<ElementTranspilers> {
37
- const bitranElements = ERUDIT_SERVER?.BITRAN_CONFIG?.elements;
38
-
39
- if (!bitranElements) return {};
40
-
41
- const projectTranspilers: ElementTranspilers = {};
42
- for (const [name, bitranElement] of Object.entries(bitranElements))
43
- projectTranspilers[name] = await bitranElement.transpiler();
44
-
45
- return projectTranspilers;
46
- }
1
+ import { defineBitranTranspiler } from '@bitran-js/transpiler';
2
+
3
+ import getServerTranspilers from '#erudit/bitran/server';
4
+
5
+ export async function createBitranTranspiler() {
6
+ const serverTranspiler = await getServerTranspilers();
7
+ const bitranTranspiler = defineBitranTranspiler(serverTranspiler);
8
+ return bitranTranspiler;
9
+ }
@@ -1,10 +1,10 @@
1
- import { debug, logger } from '@server/logger';
2
- import { ERUDIT_SERVER } from '@server/global';
3
-
4
- export async function close() {
5
- debug.start('Shutting down server...');
6
-
7
- await ERUDIT_SERVER.DB?.destroy();
8
-
9
- logger.success('Server shut down gracefully!');
10
- }
1
+ import { debug, logger } from '@server/logger';
2
+ import { ERUDIT_SERVER } from '@server/global';
3
+
4
+ export async function close() {
5
+ debug.start('Shutting down server...');
6
+
7
+ await ERUDIT_SERVER.DB?.destroy();
8
+
9
+ logger.success('Server shut down gracefully!');
10
+ }
@@ -1,8 +1,8 @@
1
- import type { DbContent } from '@server/db/entities/Content';
2
- import type { NavNode } from '@server/nav/node';
3
-
4
- export interface BuilderFunctionArgs<T = any> {
5
- navNode: NavNode;
6
- dbContent: DbContent;
7
- config?: Partial<T>;
8
- }
1
+ import type { DbContent } from '@server/db/entities/Content';
2
+ import type { NavNode } from '@server/nav/node';
3
+
4
+ export interface BuilderFunctionArgs<T = any> {
5
+ navNode: NavNode;
6
+ dbContent: DbContent;
7
+ config?: Partial<T>;
8
+ }
@@ -0,0 +1,79 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ interface FileMap {
5
+ [simplifiedPath: string]: string;
6
+ }
7
+
8
+ // Regex to check if a directory is a "content directory".
9
+ const contentFileRegex = /^(book|group|topic)\.(js|ts)$/i;
10
+
11
+ // Regex to simplify directory names (remove leading digits + '+' or '-').
12
+ const simplifyDirRegex = /^\d+(?:\+|-)/;
13
+
14
+ /**
15
+ * Recursively scans files starting from `dirPath` and builds a mapping where:
16
+ * - The key is the "simplified" path using forward slashes.
17
+ * - The value is the relative filesystem path, also using forward slashes.
18
+ *
19
+ * A directory is considered a "content directory" if it contains any file that matches:
20
+ * book, group, or topic with a .js or .ts extension.
21
+ * When a directory qualifies as a content directory, its name is simplified
22
+ * by removing any leading digits and a '+' or '-' (e.g., "29+foo" becomes "foo").
23
+ *
24
+ * @param dirPath The root directory to scan.
25
+ * @returns An object mapping simplified file paths to relative file paths.
26
+ */
27
+ export function scanFiles(dirPath: string): FileMap {
28
+ const result: FileMap = {};
29
+ const absoluteRoot = path.resolve(dirPath); // Ensure absolute root path
30
+
31
+ function isContentDirectory(directory: string): boolean {
32
+ try {
33
+ return fs
34
+ .readdirSync(directory)
35
+ .some((entry) => contentFileRegex.test(entry));
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function traverse(currentPath: string, simplifiedParts: string[]) {
42
+ let currentSimplifiedParts = simplifiedParts;
43
+ if (currentPath !== absoluteRoot) {
44
+ let dirName = path.basename(currentPath);
45
+ if (isContentDirectory(currentPath)) {
46
+ dirName = dirName.replace(simplifyDirRegex, '');
47
+ }
48
+ currentSimplifiedParts = [...simplifiedParts, dirName];
49
+ }
50
+
51
+ for (const entry of fs.readdirSync(currentPath)) {
52
+ const fullPath = path.join(currentPath, entry);
53
+ const relativePath = path
54
+ .relative(absoluteRoot, fullPath)
55
+ .replace(/\\/g, '/'); // Always forward slashes
56
+ const stat = fs.statSync(fullPath);
57
+
58
+ if (stat.isDirectory()) {
59
+ traverse(fullPath, currentSimplifiedParts);
60
+ } else if (stat.isFile()) {
61
+ const simplifiedFilePath = path.posix.join(
62
+ ...currentSimplifiedParts,
63
+ entry,
64
+ );
65
+
66
+ // Remove first repeating part in the key
67
+ const keyParts = simplifiedFilePath.split('/');
68
+ if (keyParts.length > 1 && keyParts[0] === keyParts[1]) {
69
+ keyParts.shift(); // Remove first repeating part
70
+ }
71
+
72
+ result[keyParts.join('/')] = relativePath;
73
+ }
74
+ }
75
+ }
76
+
77
+ traverse(absoluteRoot, []);
78
+ return result;
79
+ }
@@ -1,176 +1,192 @@
1
- import { existsSync } from 'node:fs';
2
- import { globSync } from 'glob';
3
- import chalk from 'chalk';
4
- import sizeOf from 'image-size';
5
- import {
6
- contentTypes,
7
- type ContentConfig,
8
- type ContentReferences,
9
- type ContentType,
10
- } from 'erudit-cog/schema';
11
- import { resolvePaths } from 'erudit-cog/kit';
12
-
13
- import { PROJECT_DIR } from '#erudit/globalPaths';
14
- import { stress } from '@erudit/utils/stress';
15
-
16
- import { debug, logger } from '@server/logger';
17
- import { ERUDIT_SERVER } from '@server/global';
18
- import { walkNav } from '@server/nav/utils';
19
- import { isRootNode, type NavNode } from '@server/nav/node';
20
- import { DbContent } from '@server/db/entities/Content';
21
- import { IMPORT } from '@server/importer';
22
- import { contributorExists } from '@server/repository/contributor';
23
- import { DbContribution } from '@server/db/entities/Contribution';
24
-
25
- import { contentAsset } from '@erudit/shared/asset';
26
- import type { ImageData } from '@erudit/shared/image';
27
-
28
- import type { BuilderFunctionArgs } from './builderArgs';
29
- import { contentItemPath } from './path';
30
- import { buildBook } from './type/book';
31
- import { buildGroup } from './type/group';
32
- import { buildTopic } from './type/topic';
33
-
34
- const typeBuilders: Record<ContentType, Function> = {
35
- book: buildBook,
36
- group: buildGroup,
37
- topic: buildTopic,
38
- };
39
-
40
- export async function buildContent() {
41
- if (!ERUDIT_SERVER.NAV) return;
42
-
43
- debug.start('Building content...');
44
-
45
- const counters: Record<ContentType, number> = Object.fromEntries(
46
- contentTypes.map((contentType) => [contentType, 0]),
47
- ) as any;
48
-
49
- await walkNav(async (node) => {
50
- if (isRootNode(node)) return;
51
- counters[node.type]++;
52
- await addContentItem(node);
53
- });
54
-
55
- logger.success(
56
- 'Content built successfully!',
57
- chalk.dim(
58
- '(' +
59
- Object.entries(counters)
60
- .map(([k, v]) => `${k.at(0)!.toUpperCase()}: ${v}`)
61
- .join(', ') +
62
- ')',
63
- ),
64
- );
65
- }
66
-
67
- async function addContentItem(navNode: NavNode) {
68
- debug.start(
69
- `Adding ${stress(navNode.type)} content item ${stress(navNode.id)}...`,
70
- );
71
-
72
- const dbContent = new DbContent();
73
- dbContent.contentId = navNode.id;
74
- dbContent.fullId = navNode.fullId;
75
- dbContent.type = navNode.type;
76
- dbContent.decoration = getDecoration(navNode);
77
- dbContent.ogImage = getOgImageData(navNode);
78
- dbContent.references = await getContentReferences(navNode);
79
-
80
- let config: Partial<ContentConfig> | undefined;
81
-
82
- try {
83
- config = (await IMPORT(contentItemPath(navNode, navNode.type), {
84
- default: true,
85
- })) as Partial<ContentConfig>;
86
-
87
- dbContent.title = config.title;
88
- dbContent.navTitle = config.navTitle;
89
- dbContent.description = config.description;
90
- dbContent.flags = config.flags;
91
- dbContent.dependencies = config.dependencies;
92
-
93
- if (config.seo) {
94
- dbContent.seo = {
95
- title: config.seo?.title,
96
- description: config.seo?.description,
97
- };
98
- }
99
-
100
- await addContributions(navNode, config.contributors);
101
- } catch {}
102
-
103
- await ERUDIT_SERVER.DB.manager.save(dbContent);
104
-
105
- await typeBuilders[navNode.type](<BuilderFunctionArgs>{
106
- navNode,
107
- dbContent,
108
- config,
109
- });
110
- }
111
-
112
- function getDecoration(navNode: NavNode) {
113
- if (existsSync(contentItemPath(navNode, 'decoration.svg')))
114
- return `/${navNode.path}/decoration.svg`;
115
-
116
- return undefined;
117
- }
118
-
119
- async function addContributions(navNode: NavNode, contributors?: string[]) {
120
- if (!contributors || !contributors.length) {
121
- if (navNode.type !== 'book' && navNode.type !== 'group')
122
- logger.warn(
123
- `${navNode.type.at(0)!.toUpperCase() + navNode.type.slice(1)} ${stress(navNode.id)} has no contributors!`,
124
- );
125
-
126
- return;
127
- }
128
-
129
- for (const contributorId of contributors) {
130
- if (!(await contributorExists(contributorId))) {
131
- logger.warn(
132
- `Skipping unknown contributor ${stress(contributorId)} when adding ${navNode.type} ${stress(navNode.id)}!`,
133
- );
134
- continue;
135
- }
136
-
137
- const dbContribution = new DbContribution();
138
- dbContribution.contentId = navNode.id;
139
- dbContribution.contributorId = contributorId;
140
- await ERUDIT_SERVER.DB.manager.save(dbContribution);
141
- }
142
- }
143
-
144
- function getOgImageData(navNode: NavNode): ImageData | undefined {
145
- const ogImagePath = globSync(
146
- contentItemPath(navNode, 'og-image.{svg,webp,jpg,png}'),
147
- ).pop();
148
-
149
- if (ogImagePath) {
150
- const size = sizeOf(ogImagePath);
151
- return {
152
- src: contentAsset(
153
- resolvePaths(ogImagePath).replace(
154
- PROJECT_DIR + '/content/',
155
- '',
156
- ),
157
- ),
158
- width: size.width!,
159
- height: size.height!,
160
- };
161
- }
162
-
163
- return undefined;
164
- }
165
-
166
- async function getContentReferences(navNode: NavNode) {
167
- try {
168
- const references = await IMPORT(
169
- contentItemPath(navNode, `references`),
170
- { default: true },
171
- );
172
- return references as ContentReferences;
173
- } catch (error) {}
174
-
175
- return undefined;
176
- }
1
+ import { existsSync } from 'node:fs';
2
+ import { globSync } from 'glob';
3
+ import chalk from 'chalk';
4
+ import { imageSizeFromFile } from 'image-size/fromFile';
5
+ import {
6
+ contentTypes,
7
+ type ContentConfig,
8
+ type ContentReferences,
9
+ type ContentType,
10
+ } from '@erudit-js/cog/schema';
11
+ import { resolvePaths } from '@erudit-js/cog/kit';
12
+
13
+ import { PROJECT_DIR } from '#erudit/globalPaths';
14
+ import { stress } from '@erudit/utils/stress';
15
+
16
+ import { debug, logger } from '@server/logger';
17
+ import { ERUDIT_SERVER } from '@server/global';
18
+ import { walkNav } from '@server/nav/utils';
19
+ import { isRootNode, type NavNode } from '@server/nav/node';
20
+ import { DbContent } from '@server/db/entities/Content';
21
+ import { IMPORT } from '@server/importer';
22
+ import { contributorExists } from '@server/repository/contributor';
23
+ import { DbContribution } from '@server/db/entities/Contribution';
24
+ import { DbFile } from '@server/db/entities/File';
25
+
26
+ import { contentAsset } from '@erudit/shared/asset';
27
+ import type { ImageData } from '@erudit/shared/image';
28
+
29
+ import type { BuilderFunctionArgs } from './builderArgs';
30
+ import { contentItemPath } from './path';
31
+ import { buildBook } from './type/book';
32
+ import { buildGroup } from './type/group';
33
+ import { buildTopic } from './type/topic';
34
+ import { scanFiles } from './files';
35
+
36
+ const typeBuilders: Record<ContentType, Function> = {
37
+ book: buildBook,
38
+ group: buildGroup,
39
+ topic: buildTopic,
40
+ };
41
+
42
+ export async function buildContent() {
43
+ if (!ERUDIT_SERVER.NAV) return;
44
+
45
+ debug.start('Building content...');
46
+
47
+ await scanContentFiles();
48
+
49
+ const counters: Record<ContentType, number> = Object.fromEntries(
50
+ contentTypes.map((contentType) => [contentType, 0]),
51
+ ) as any;
52
+
53
+ await walkNav(async (node) => {
54
+ if (isRootNode(node)) return;
55
+ counters[node.type]++;
56
+ await addContentItem(node);
57
+ });
58
+
59
+ logger.success(
60
+ 'Content built successfully!',
61
+ chalk.dim(
62
+ '(' +
63
+ Object.entries(counters)
64
+ .map(([k, v]) => `${k.at(0)!.toUpperCase()}: ${v}`)
65
+ .join(', ') +
66
+ ')',
67
+ ),
68
+ );
69
+ }
70
+
71
+ async function scanContentFiles() {
72
+ const contentFiles = scanFiles(PROJECT_DIR + '/content');
73
+ for (const [path, fullPath] of Object.entries(contentFiles)) {
74
+ const dbFile = new DbFile();
75
+ dbFile.path = path;
76
+ dbFile.fullPath = fullPath;
77
+ await ERUDIT_SERVER.DB.manager.save(dbFile);
78
+ }
79
+ }
80
+
81
+ async function addContentItem(navNode: NavNode) {
82
+ debug.start(
83
+ `Adding ${stress(navNode.type)} content item ${stress(navNode.id)}...`,
84
+ );
85
+
86
+ const dbContent = new DbContent();
87
+ dbContent.contentId = navNode.id;
88
+ dbContent.fullId = navNode.fullId;
89
+ dbContent.type = navNode.type;
90
+ dbContent.decoration = getDecoration(navNode);
91
+ dbContent.ogImage = await getOgImageData(navNode);
92
+ dbContent.references = await getContentReferences(navNode);
93
+
94
+ let config: Partial<ContentConfig> | undefined;
95
+
96
+ try {
97
+ config = (await IMPORT(contentItemPath(navNode, navNode.type), {
98
+ default: true,
99
+ })) as Partial<ContentConfig>;
100
+
101
+ dbContent.title = config.title;
102
+ dbContent.navTitle = config.navTitle;
103
+ dbContent.description = config.description;
104
+ dbContent.flags = config.flags;
105
+ dbContent.dependencies = config.dependencies;
106
+
107
+ if (config.seo) {
108
+ dbContent.seo = {
109
+ title: config.seo?.title,
110
+ description: config.seo?.description,
111
+ };
112
+ }
113
+
114
+ await addContributions(navNode, config.contributors);
115
+ } catch {}
116
+
117
+ await ERUDIT_SERVER.DB.manager.save(dbContent);
118
+
119
+ await typeBuilders[navNode.type](<BuilderFunctionArgs>{
120
+ navNode,
121
+ dbContent,
122
+ config,
123
+ });
124
+ }
125
+
126
+ function getDecoration(navNode: NavNode) {
127
+ if (existsSync(contentItemPath(navNode, 'decoration.svg')))
128
+ return `/${navNode.path}/decoration.svg`;
129
+
130
+ return undefined;
131
+ }
132
+
133
+ async function addContributions(navNode: NavNode, contributors?: string[]) {
134
+ if (!contributors || !contributors.length) {
135
+ if (navNode.type !== 'book' && navNode.type !== 'group')
136
+ logger.warn(
137
+ `${navNode.type.at(0)!.toUpperCase() + navNode.type.slice(1)} ${stress(navNode.id)} has no contributors!`,
138
+ );
139
+
140
+ return;
141
+ }
142
+
143
+ for (const contributorId of contributors) {
144
+ if (!(await contributorExists(contributorId))) {
145
+ logger.warn(
146
+ `Skipping unknown contributor ${stress(contributorId)} when adding ${navNode.type} ${stress(navNode.id)}!`,
147
+ );
148
+ continue;
149
+ }
150
+
151
+ const dbContribution = new DbContribution();
152
+ dbContribution.contentId = navNode.id;
153
+ dbContribution.contributorId = contributorId;
154
+ await ERUDIT_SERVER.DB.manager.save(dbContribution);
155
+ }
156
+ }
157
+
158
+ async function getOgImageData(
159
+ navNode: NavNode,
160
+ ): Promise<ImageData | undefined> {
161
+ const ogImagePath = globSync(
162
+ contentItemPath(navNode, 'og-image.{svg,webp,jpg,png}'),
163
+ ).pop();
164
+
165
+ if (ogImagePath) {
166
+ const size = await imageSizeFromFile(ogImagePath);
167
+ return {
168
+ src: contentAsset(
169
+ resolvePaths(ogImagePath).replace(
170
+ PROJECT_DIR + '/content/',
171
+ '',
172
+ ),
173
+ ),
174
+ width: size.width,
175
+ height: size.height,
176
+ };
177
+ }
178
+
179
+ return undefined;
180
+ }
181
+
182
+ async function getContentReferences(navNode: NavNode) {
183
+ try {
184
+ const references = await IMPORT(
185
+ contentItemPath(navNode, `references`),
186
+ { default: true },
187
+ );
188
+ return references as ContentReferences;
189
+ } catch (error) {}
190
+
191
+ return undefined;
192
+ }