erudit 3.0.0-dev.15 → 3.0.0-dev.17

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 (243) hide show
  1. package/app/app.vue +191 -195
  2. package/app/components/Loading.vue +23 -23
  3. package/app/components/SiteAside.vue +393 -393
  4. package/app/components/SiteMain.vue +32 -32
  5. package/app/components/ads/Ads.vue +35 -0
  6. package/app/components/ads/AdsBannerAside.vue +61 -0
  7. package/app/components/ads/AdsBannerBottom.vue +22 -0
  8. package/app/components/ads/AdsProviderCustom.vue +35 -0
  9. package/app/components/ads/AdsProviderYandex.vue +54 -0
  10. package/app/components/ads/AdsReplacer.vue +73 -0
  11. package/app/components/aside/AsideListItem.vue +74 -74
  12. package/app/components/aside/AsideMajor.vue +56 -56
  13. package/app/components/aside/AsideMinor.vue +73 -71
  14. package/app/components/aside/major/PaneContentScroll.vue +23 -23
  15. package/app/components/aside/major/PaneSwitch.vue +54 -54
  16. package/app/components/aside/major/PaneSwitchButton.vue +63 -63
  17. package/app/components/aside/major/SiteInfo.vue +85 -85
  18. package/app/components/aside/major/panes/Language.vue +79 -79
  19. package/app/components/aside/major/panes/Pages.vue +34 -34
  20. package/app/components/aside/major/panes/Search.vue +11 -11
  21. package/app/components/aside/major/panes/nav/Nav.vue +92 -92
  22. package/app/components/aside/major/panes/nav/NavBook.vue +95 -95
  23. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -24
  24. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -16
  25. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -105
  26. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -32
  27. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -40
  28. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -60
  29. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -34
  30. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -80
  31. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -24
  32. package/app/components/aside/major/panes/other/ItemContent.vue +29 -29
  33. package/app/components/aside/major/panes/other/ItemGenerator.vue +13 -13
  34. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -54
  35. package/app/components/aside/major/panes/other/Other.vue +16 -16
  36. package/app/components/aside/minor/AsideMinorContributor.vue +5 -5
  37. package/app/components/aside/minor/AsideMinorNews.vue +11 -11
  38. package/app/components/aside/minor/AsideMinorPane.vue +15 -15
  39. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -67
  40. package/app/components/aside/minor/Contribute.vue +145 -145
  41. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -92
  42. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -32
  43. package/app/components/aside/minor/topic/TopicContributors.vue +177 -177
  44. package/app/components/aside/minor/topic/TopicNav.vue +49 -49
  45. package/app/components/aside/minor/topic/TopicToc.vue +214 -214
  46. package/app/components/aside/minor/topic/TopicTocItem.vue +32 -32
  47. package/app/components/aside/utils/AsideOverlayPane.vue +40 -40
  48. package/app/components/bitran/BitranContent.vue +91 -91
  49. package/app/components/bitran/RenderWrapper.vue +10 -10
  50. package/app/components/contributor/ContributorAvatar.vue +45 -45
  51. package/app/components/contributor/ContributorListItem.vue +35 -35
  52. package/app/components/main/topic/MainTopic.vue +88 -78
  53. package/app/components/main/topic/TopicPartSwitch.vue +118 -118
  54. package/app/components/main/utils/Breadcrumb.vue +74 -70
  55. package/app/components/main/utils/ContentDecoration.vue +29 -29
  56. package/app/components/main/utils/ContentDescription.vue +19 -19
  57. package/app/components/main/utils/ContentPopover.vue +188 -188
  58. package/app/components/main/utils/ContentPopovers.vue +111 -111
  59. package/app/components/main/utils/ContentReferences.vue +70 -70
  60. package/app/components/main/utils/ContentSection.vue +45 -45
  61. package/app/components/main/utils/ContentTitle.vue +63 -63
  62. package/app/components/main/utils/reference/ReferenceGroup.vue +38 -38
  63. package/app/components/main/utils/reference/ReferenceItem.vue +70 -70
  64. package/app/components/main/utils/reference/ReferenceSource.vue +120 -118
  65. package/app/components/preview/Preview.vue +186 -186
  66. package/app/components/preview/PreviewDisplay.vue +139 -139
  67. package/app/components/preview/PreviewFooterAction.vue +73 -73
  68. package/app/components/preview/PreviewLoading.vue +14 -14
  69. package/app/components/preview/PreviewScreen.vue +141 -141
  70. package/app/components/preview/display/Alert.vue +50 -50
  71. package/app/components/preview/display/Custom.vue +18 -18
  72. package/app/components/preview/display/GenericLink.vue +48 -48
  73. package/app/components/preview/display/PageLink.vue +22 -20
  74. package/app/components/preview/display/Unique.vue +55 -55
  75. package/app/components/transition/Fade.vue +19 -19
  76. package/app/components/tree/TreeContainer.vue +11 -11
  77. package/app/components/tree/TreeItem.vue +89 -89
  78. package/app/composables/bitran.ts +108 -108
  79. package/app/composables/bitranContent.ts +92 -92
  80. package/app/composables/bitranLocation.ts +7 -7
  81. package/app/composables/contentData.ts +38 -38
  82. package/app/composables/contentPage.ts +158 -158
  83. package/app/composables/contentRoute.ts +45 -45
  84. package/app/composables/darkMagic.ts +24 -24
  85. package/app/composables/externalApi.ts +69 -63
  86. package/app/composables/favicon.ts +8 -8
  87. package/app/composables/formatText.ts +99 -99
  88. package/app/composables/majorPane.ts +60 -60
  89. package/app/composables/phrases.ts +65 -65
  90. package/app/composables/theme.ts +29 -29
  91. package/app/composables/url.ts +33 -33
  92. package/app/pages/_test/preview.vue +110 -110
  93. package/app/pages/article/[...articleId].vue +3 -3
  94. package/app/pages/book/[...bookId].vue +47 -47
  95. package/app/pages/group/[...groupId].vue +66 -64
  96. package/app/pages/index.vue +32 -32
  97. package/app/pages/members.vue +6 -6
  98. package/app/pages/practice/[...practice].vue +3 -3
  99. package/app/pages/summary/[...summaryId].vue +3 -3
  100. package/app/plugins/analytics.ts +87 -0
  101. package/app/plugins/prerender.server.ts +22 -22
  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 +76 -76
  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 +23 -23
  114. package/app/scripts/preview/data/unique.ts +72 -72
  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 +49 -49
  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 -2
  132. package/const.ts +4 -4
  133. package/globalPath.ts +21 -21
  134. package/globals/bitran.ts +1 -1
  135. package/globals/content.ts +27 -27
  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 -94
  140. package/languages/ru.ts +98 -98
  141. package/module/bitran.ts +66 -66
  142. package/module/config.ts +35 -35
  143. package/module/imports.ts +67 -67
  144. package/module/index.ts +47 -49
  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 +131 -134
  149. package/package.json +8 -7
  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 +82 -82
  155. package/server/api/bitran/content/[...location].ts +10 -10
  156. package/server/api/bitran/toc/[...location].ts +9 -9
  157. package/server/api/content/data.ts +75 -75
  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/prerender.ts +120 -120
  165. package/server/api/preview/page/[...parts].ts +78 -78
  166. package/server/api/preview/unique/[...location].ts +61 -61
  167. package/server/api/problem/generator/[...path].ts +26 -0
  168. package/server/plugin/bitran/content.ts +190 -190
  169. package/server/plugin/bitran/elements/include.ts +229 -229
  170. package/server/plugin/bitran/location.ts +39 -39
  171. package/server/plugin/bitran/toc.ts +94 -94
  172. package/server/plugin/bitran/transpiler.ts +18 -18
  173. package/server/plugin/build/close.ts +12 -12
  174. package/server/plugin/build/jobs/content/builderArgs.ts +8 -8
  175. package/server/plugin/build/jobs/content/generic.ts +191 -191
  176. package/server/plugin/build/jobs/content/parse.ts +113 -113
  177. package/server/plugin/build/jobs/content/path.ts +6 -6
  178. package/server/plugin/build/jobs/content/type/book.ts +9 -9
  179. package/server/plugin/build/jobs/content/type/group.ts +37 -37
  180. package/server/plugin/build/jobs/content/type/topic.ts +36 -36
  181. package/server/plugin/build/jobs/contributors.ts +66 -66
  182. package/server/plugin/build/jobs/language.ts +36 -36
  183. package/server/plugin/build/jobs/nav.ts +345 -265
  184. package/server/plugin/build/process.ts +32 -32
  185. package/server/plugin/build/rebuild.ts +66 -66
  186. package/server/plugin/build/setup.ts +19 -19
  187. package/server/plugin/content/context.ts +119 -119
  188. package/server/plugin/db/entities/Book.ts +7 -7
  189. package/server/plugin/db/entities/Content.ts +45 -45
  190. package/server/plugin/db/entities/Contribution.ts +10 -10
  191. package/server/plugin/db/entities/Contributor.ts +16 -16
  192. package/server/plugin/db/entities/File.ts +10 -10
  193. package/server/plugin/db/entities/Group.ts +14 -14
  194. package/server/plugin/db/entities/Hash.ts +15 -15
  195. package/server/plugin/db/entities/Topic.ts +20 -20
  196. package/server/plugin/db/entities/Unique.ts +21 -21
  197. package/server/plugin/db/reset.ts +12 -12
  198. package/server/plugin/db/setup.ts +49 -49
  199. package/server/plugin/global.ts +16 -16
  200. package/server/plugin/importer.ts +16 -16
  201. package/server/plugin/index.ts +9 -9
  202. package/server/plugin/logger.ts +23 -23
  203. package/server/plugin/nav/node.ts +27 -27
  204. package/server/plugin/nav/utils.ts +175 -175
  205. package/server/plugin/repository/book.ts +21 -21
  206. package/server/plugin/repository/content.ts +240 -240
  207. package/server/plugin/repository/contentId.ts +40 -40
  208. package/server/plugin/repository/contributor.ts +8 -8
  209. package/server/plugin/repository/file.ts +10 -10
  210. package/server/plugin/repository/frontNav.ts +145 -145
  211. package/server/plugin/repository/topic.ts +35 -35
  212. package/server/tsconfig.json +9 -9
  213. package/shared/aside/minor.ts +51 -51
  214. package/shared/asset.ts +22 -22
  215. package/shared/bitran/contentId.ts +56 -56
  216. package/shared/bitran/stringContent.ts +6 -6
  217. package/shared/bitran/toc.ts +8 -8
  218. package/shared/content/bookId.ts +12 -12
  219. package/shared/content/context.ts +9 -9
  220. package/shared/content/data/base.ts +32 -32
  221. package/shared/content/data/index.ts +5 -5
  222. package/shared/content/data/type/book.ts +5 -5
  223. package/shared/content/data/type/group.ts +6 -6
  224. package/shared/content/data/type/topic.ts +11 -11
  225. package/shared/content/previousNext.ts +9 -9
  226. package/shared/contributor.ts +5 -5
  227. package/shared/frontNav.ts +41 -41
  228. package/shared/icons.ts +38 -38
  229. package/shared/image.ts +5 -5
  230. package/shared/link.ts +28 -28
  231. package/shared/popover.ts +8 -8
  232. package/shared/types/language.ts +74 -74
  233. package/shared/utils/objectsEqual.ts +4 -4
  234. package/shared/utils/stringColor.ts +9 -9
  235. package/test/contentId.test.ts +91 -91
  236. package/tsconfig.json +8 -8
  237. package/utils/contentPath.ts +67 -67
  238. package/utils/slash.ts +11 -11
  239. package/utils/stress.ts +9 -9
  240. package/app/components/ads/BannerTemplate.vue +0 -51
  241. package/app/components/ads/BottomBanner.vue +0 -45
  242. package/app/components/ads/LeftBanner.vue +0 -50
  243. package/module/problemGenerators.ts +0 -46
@@ -1,94 +1,94 @@
1
- import { type ElementNode } from '@bitran-js/core';
2
- import { NO_ALIASES, type BitranLocation } from '@erudit-js/cog/schema';
3
- import {
4
- headingName,
5
- HeadingNode,
6
- } from '@erudit-js/bitran-elements/heading/shared';
7
- import {
8
- ProblemNode,
9
- ProblemsNode,
10
- } from '@erudit-js/bitran-elements/problem/shared';
11
-
12
- import type { Toc } from '@erudit/shared/bitran/toc';
13
- import { ERUDIT_SERVER } from '@server/global';
14
- import { getBitranContent } from './content';
15
- import { createBitranTranspiler } from './transpiler';
16
-
17
- export async function getBitranToc(location: BitranLocation) {
18
- const content = await getBitranContent(location, false);
19
-
20
- const bitranCore = await createBitranTranspiler({
21
- eruditConfig: ERUDIT_SERVER.CONFIG,
22
- insideInclude: false,
23
- context: { aliases: NO_ALIASES(), location },
24
- });
25
-
26
- const toc: Toc = [];
27
-
28
- await bitranCore.parser.parse(content.biCode, {
29
- async step(node) {
30
- tryAddToToc(node as ElementNode);
31
- },
32
- });
33
-
34
- return toc;
35
-
36
- function tryAddToToc(node: ElementNode) {
37
- const tocItemBase = {
38
- id: node.id,
39
- productName: node.name,
40
- };
41
-
42
- // Check if node is manually excluded from TOC
43
- if (typeof node.meta?.toc === 'boolean' && !node.meta?.toc) return;
44
-
45
- if (node instanceof HeadingNode) {
46
- toc.push({
47
- ...tocItemBase,
48
- level: node.parseData.level - 1,
49
- title: node.parseData.title,
50
- });
51
- return;
52
- }
53
-
54
- const notHeadingLevel = () => {
55
- // If TOC is empty, we're at the root level
56
- if (toc.length === 0) return 0;
57
-
58
- // Find the most recent heading to determine parent level
59
- for (let i = toc.length - 1; i >= 0; i--) {
60
- if (toc[i].productName === headingName) {
61
- // Found a heading, return its level + 1
62
- return toc[i].level + 1;
63
- }
64
- }
65
-
66
- // No heading found in TOC, place at root level
67
- return 0;
68
- };
69
-
70
- // Problems by default are in TOC
71
- if (node instanceof ProblemsNode || node instanceof ProblemNode) {
72
- toc.push({
73
- ...tocItemBase,
74
- level: notHeadingLevel(),
75
- title: node.meta?.title || node.parseData.info.title,
76
- });
77
- return;
78
- }
79
-
80
- if (
81
- ERUDIT_SERVER.CONFIG.bitran?.toc?.includes(node.name) ||
82
- node.meta?.toc
83
- ) {
84
- // Erudit Bitran config says to add these products to TOC
85
- // Or show in TOC any product with truthy `toc` meta property
86
- toc.push({
87
- ...tocItemBase,
88
- level: notHeadingLevel(),
89
- title: node.meta?.title || node.parseData?.title,
90
- });
91
- return;
92
- }
93
- }
94
- }
1
+ import { type ElementNode } from '@bitran-js/core';
2
+ import { NO_ALIASES, type BitranLocation } from '@erudit-js/cog/schema';
3
+ import {
4
+ headingName,
5
+ HeadingNode,
6
+ } from '@erudit-js/bitran-elements/heading/shared';
7
+ import {
8
+ ProblemNode,
9
+ ProblemsNode,
10
+ } from '@erudit-js/bitran-elements/problem/shared';
11
+
12
+ import type { Toc } from '@erudit/shared/bitran/toc';
13
+ import { ERUDIT_SERVER } from '@server/global';
14
+ import { getBitranContent } from './content';
15
+ import { createBitranTranspiler } from './transpiler';
16
+
17
+ export async function getBitranToc(location: BitranLocation) {
18
+ const content = await getBitranContent(location, false);
19
+
20
+ const bitranCore = await createBitranTranspiler({
21
+ eruditConfig: ERUDIT_SERVER.CONFIG,
22
+ insideInclude: false,
23
+ context: { aliases: NO_ALIASES(), location },
24
+ });
25
+
26
+ const toc: Toc = [];
27
+
28
+ await bitranCore.parser.parse(content.biCode, {
29
+ async step(node) {
30
+ tryAddToToc(node as ElementNode);
31
+ },
32
+ });
33
+
34
+ return toc;
35
+
36
+ function tryAddToToc(node: ElementNode) {
37
+ const tocItemBase = {
38
+ id: node.id,
39
+ productName: node.name,
40
+ };
41
+
42
+ // Check if node is manually excluded from TOC
43
+ if (typeof node.meta?.toc === 'boolean' && !node.meta?.toc) return;
44
+
45
+ if (node instanceof HeadingNode) {
46
+ toc.push({
47
+ ...tocItemBase,
48
+ level: node.parseData.level - 1,
49
+ title: node.parseData.title,
50
+ });
51
+ return;
52
+ }
53
+
54
+ const notHeadingLevel = () => {
55
+ // If TOC is empty, we're at the root level
56
+ if (toc.length === 0) return 0;
57
+
58
+ // Find the most recent heading to determine parent level
59
+ for (let i = toc.length - 1; i >= 0; i--) {
60
+ if (toc[i].productName === headingName) {
61
+ // Found a heading, return its level + 1
62
+ return toc[i].level + 1;
63
+ }
64
+ }
65
+
66
+ // No heading found in TOC, place at root level
67
+ return 0;
68
+ };
69
+
70
+ // Problems by default are in TOC
71
+ if (node instanceof ProblemsNode || node instanceof ProblemNode) {
72
+ toc.push({
73
+ ...tocItemBase,
74
+ level: notHeadingLevel(),
75
+ title: node.meta?.title || node.parseData.info.title,
76
+ });
77
+ return;
78
+ }
79
+
80
+ if (
81
+ ERUDIT_SERVER.CONFIG.bitran?.toc?.includes(node.name) ||
82
+ node.meta?.toc
83
+ ) {
84
+ // Erudit Bitran config says to add these products to TOC
85
+ // Or show in TOC any product with truthy `toc` meta property
86
+ toc.push({
87
+ ...tocItemBase,
88
+ level: notHeadingLevel(),
89
+ title: node.meta?.title || node.parseData?.title,
90
+ });
91
+ return;
92
+ }
93
+ }
94
+ }
@@ -1,18 +1,18 @@
1
- import { defineBitranTranspiler } from '@bitran-js/transpiler';
2
- import {
3
- setEruditBitranRuntime,
4
- type EruditBitranRuntime,
5
- } from '@erudit-js/cog/schema';
6
-
7
- import getServerTranspilers from '#erudit/bitran/server';
8
-
9
- export async function createBitranTranspiler(runtime: EruditBitranRuntime) {
10
- const serverTranspiler = await getServerTranspilers();
11
- const bitranTranspiler = defineBitranTranspiler(serverTranspiler);
12
-
13
- [bitranTranspiler.parser, bitranTranspiler.stringifier].forEach((item) => {
14
- setEruditBitranRuntime(item, runtime);
15
- });
16
-
17
- return bitranTranspiler;
18
- }
1
+ import { defineBitranTranspiler } from '@bitran-js/transpiler';
2
+ import {
3
+ setEruditBitranRuntime,
4
+ type EruditBitranRuntime,
5
+ } from '@erudit-js/cog/schema';
6
+
7
+ import getServerTranspilers from '#erudit/bitran/server';
8
+
9
+ export async function createBitranTranspiler(runtime: EruditBitranRuntime) {
10
+ const serverTranspiler = await getServerTranspilers();
11
+ const bitranTranspiler = defineBitranTranspiler(serverTranspiler);
12
+
13
+ [bitranTranspiler.parser, bitranTranspiler.stringifier].forEach((item) => {
14
+ setEruditBitranRuntime(item, runtime);
15
+ });
16
+
17
+ return bitranTranspiler;
18
+ }
@@ -1,12 +1,12 @@
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
- if (ERUDIT_SERVER.DB && ERUDIT_SERVER.DB.isInitialized) {
8
- await ERUDIT_SERVER.DB.destroy();
9
- }
10
-
11
- logger.success('Server shut down gracefully!');
12
- }
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
+ if (ERUDIT_SERVER.DB && ERUDIT_SERVER.DB.isInitialized) {
8
+ await ERUDIT_SERVER.DB.destroy();
9
+ }
10
+
11
+ logger.success('Server shut down gracefully!');
12
+ }
@@ -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
+ }
@@ -1,191 +1,191 @@
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 { scanContentPaths } from '@erudit/utils/contentPath';
15
- import { stress } from '@erudit/utils/stress';
16
-
17
- import { debug, logger } from '@server/logger';
18
- import { ERUDIT_SERVER } from '@server/global';
19
- import { walkNav } from '@server/nav/utils';
20
- import { isRootNode, type NavNode } from '@server/nav/node';
21
- import { DbContent } from '@server/db/entities/Content';
22
- import { IMPORT } from '@server/importer';
23
- import { contributorExists } from '@server/repository/contributor';
24
- import { DbContribution } from '@server/db/entities/Contribution';
25
- import { DbFile } from '@server/db/entities/File';
26
-
27
- import { contentAsset } from '@erudit/shared/asset';
28
- import type { ImageData } from '@erudit/shared/image';
29
-
30
- import type { BuilderFunctionArgs } from './builderArgs';
31
- import { contentItemPath } from './path';
32
- import { buildBook } from './type/book';
33
- import { buildGroup } from './type/group';
34
- import { buildTopic } from './type/topic';
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 = scanContentPaths(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.shortId)}...`,
84
- );
85
-
86
- const dbContent = new DbContent();
87
- dbContent.contentId = navNode.fullId;
88
- dbContent.type = navNode.type;
89
- dbContent.decoration = getDecoration(navNode);
90
- dbContent.ogImage = await getOgImageData(navNode);
91
- dbContent.references = await getContentReferences(navNode);
92
-
93
- let config: Partial<ContentConfig> | undefined;
94
-
95
- try {
96
- config = (await IMPORT(contentItemPath(navNode, navNode.type), {
97
- default: true,
98
- })) as Partial<ContentConfig>;
99
-
100
- dbContent.title = config.title;
101
- dbContent.navTitle = config.navTitle;
102
- dbContent.description = config.description;
103
- dbContent.flags = config.flags;
104
- dbContent.dependencies = config.dependencies;
105
-
106
- if (config.seo) {
107
- dbContent.seo = {
108
- title: config.seo?.title,
109
- description: config.seo?.description,
110
- };
111
- }
112
-
113
- await addContributions(navNode, config.contributors);
114
- } catch {}
115
-
116
- await ERUDIT_SERVER.DB.manager.save(dbContent);
117
-
118
- await typeBuilders[navNode.type](<BuilderFunctionArgs>{
119
- navNode,
120
- dbContent,
121
- config,
122
- });
123
- }
124
-
125
- function getDecoration(navNode: NavNode) {
126
- if (existsSync(contentItemPath(navNode, 'decoration.svg')))
127
- return `/${navNode.path}/decoration.svg`;
128
-
129
- return undefined;
130
- }
131
-
132
- async function addContributions(navNode: NavNode, contributors?: string[]) {
133
- if (!contributors || !contributors.length) {
134
- if (navNode.type !== 'book' && navNode.type !== 'group')
135
- logger.warn(
136
- `${navNode.type.at(0)!.toUpperCase() + navNode.type.slice(1)} ${stress(navNode.fullId)} has no contributors!`,
137
- );
138
-
139
- return;
140
- }
141
-
142
- for (const contributorId of contributors) {
143
- if (!(await contributorExists(contributorId))) {
144
- logger.warn(
145
- `Skipping unknown contributor ${stress(contributorId)} when adding ${navNode.type} ${stress(navNode.fullId)}!`,
146
- );
147
- continue;
148
- }
149
-
150
- const dbContribution = new DbContribution();
151
- dbContribution.contentId = navNode.fullId;
152
- dbContribution.contributorId = contributorId;
153
- await ERUDIT_SERVER.DB.manager.save(dbContribution);
154
- }
155
- }
156
-
157
- async function getOgImageData(
158
- navNode: NavNode,
159
- ): Promise<ImageData | undefined> {
160
- const ogImagePath = globSync(
161
- contentItemPath(navNode, 'og-image.{svg,webp,jpg,png}'),
162
- ).pop();
163
-
164
- if (ogImagePath) {
165
- const size = await imageSizeFromFile(ogImagePath);
166
- return {
167
- src: contentAsset(
168
- resolvePaths(ogImagePath).replace(
169
- PROJECT_DIR + '/content/',
170
- '',
171
- ),
172
- ),
173
- width: size.width,
174
- height: size.height,
175
- };
176
- }
177
-
178
- return undefined;
179
- }
180
-
181
- async function getContentReferences(navNode: NavNode) {
182
- try {
183
- const references = await IMPORT(
184
- contentItemPath(navNode, `references`),
185
- { default: true },
186
- );
187
- return references as ContentReferences;
188
- } catch (error) {}
189
-
190
- return undefined;
191
- }
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 { scanContentPaths } from '@erudit/utils/contentPath';
15
+ import { stress } from '@erudit/utils/stress';
16
+
17
+ import { debug, logger } from '@server/logger';
18
+ import { ERUDIT_SERVER } from '@server/global';
19
+ import { walkNav } from '@server/nav/utils';
20
+ import { isRootNode, type NavNode } from '@server/nav/node';
21
+ import { DbContent } from '@server/db/entities/Content';
22
+ import { IMPORT } from '@server/importer';
23
+ import { contributorExists } from '@server/repository/contributor';
24
+ import { DbContribution } from '@server/db/entities/Contribution';
25
+ import { DbFile } from '@server/db/entities/File';
26
+
27
+ import { contentAsset } from '@erudit/shared/asset';
28
+ import type { ImageData } from '@erudit/shared/image';
29
+
30
+ import type { BuilderFunctionArgs } from './builderArgs';
31
+ import { contentItemPath } from './path';
32
+ import { buildBook } from './type/book';
33
+ import { buildGroup } from './type/group';
34
+ import { buildTopic } from './type/topic';
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 = scanContentPaths(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.shortId)}...`,
84
+ );
85
+
86
+ const dbContent = new DbContent();
87
+ dbContent.contentId = navNode.fullId;
88
+ dbContent.type = navNode.type;
89
+ dbContent.decoration = getDecoration(navNode);
90
+ dbContent.ogImage = await getOgImageData(navNode);
91
+ dbContent.references = await getContentReferences(navNode);
92
+
93
+ let config: Partial<ContentConfig> | undefined;
94
+
95
+ try {
96
+ config = (await IMPORT(contentItemPath(navNode, navNode.type), {
97
+ default: true,
98
+ })) as Partial<ContentConfig>;
99
+
100
+ dbContent.title = config.title;
101
+ dbContent.navTitle = config.navTitle;
102
+ dbContent.description = config.description;
103
+ dbContent.flags = config.flags;
104
+ dbContent.dependencies = config.dependencies;
105
+
106
+ if (config.seo) {
107
+ dbContent.seo = {
108
+ title: config.seo?.title,
109
+ description: config.seo?.description,
110
+ };
111
+ }
112
+
113
+ await addContributions(navNode, config.contributors);
114
+ } catch {}
115
+
116
+ await ERUDIT_SERVER.DB.manager.save(dbContent);
117
+
118
+ await typeBuilders[navNode.type](<BuilderFunctionArgs>{
119
+ navNode,
120
+ dbContent,
121
+ config,
122
+ });
123
+ }
124
+
125
+ function getDecoration(navNode: NavNode) {
126
+ if (existsSync(contentItemPath(navNode, 'decoration.svg')))
127
+ return `/${navNode.fsPath}/decoration.svg`;
128
+
129
+ return undefined;
130
+ }
131
+
132
+ async function addContributions(navNode: NavNode, contributors?: string[]) {
133
+ if (!contributors || !contributors.length) {
134
+ if (navNode.type !== 'book' && navNode.type !== 'group')
135
+ logger.warn(
136
+ `${navNode.type.at(0)!.toUpperCase() + navNode.type.slice(1)} ${stress(navNode.fullId)} has no contributors!`,
137
+ );
138
+
139
+ return;
140
+ }
141
+
142
+ for (const contributorId of contributors) {
143
+ if (!(await contributorExists(contributorId))) {
144
+ logger.warn(
145
+ `Skipping unknown contributor ${stress(contributorId)} when adding ${navNode.type} ${stress(navNode.fullId)}!`,
146
+ );
147
+ continue;
148
+ }
149
+
150
+ const dbContribution = new DbContribution();
151
+ dbContribution.contentId = navNode.fullId;
152
+ dbContribution.contributorId = contributorId;
153
+ await ERUDIT_SERVER.DB.manager.save(dbContribution);
154
+ }
155
+ }
156
+
157
+ async function getOgImageData(
158
+ navNode: NavNode,
159
+ ): Promise<ImageData | undefined> {
160
+ const ogImagePath = globSync(
161
+ contentItemPath(navNode, 'og-image.{svg,webp,jpg,png}'),
162
+ ).pop();
163
+
164
+ if (ogImagePath) {
165
+ const size = await imageSizeFromFile(ogImagePath);
166
+ return {
167
+ src: contentAsset(
168
+ resolvePaths(ogImagePath).replace(
169
+ PROJECT_DIR + '/content/',
170
+ '',
171
+ ),
172
+ ),
173
+ width: size.width,
174
+ height: size.height,
175
+ };
176
+ }
177
+
178
+ return undefined;
179
+ }
180
+
181
+ async function getContentReferences(navNode: NavNode) {
182
+ try {
183
+ const references = await IMPORT(
184
+ contentItemPath(navNode, `references`),
185
+ { default: true },
186
+ );
187
+ return references as ContentReferences;
188
+ } catch (error) {}
189
+
190
+ return undefined;
191
+ }