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,83 +1,94 @@
1
- import { BlockErrorNode, type ElementNode } from '@bitran-js/core';
2
- import { IncludeNode } from '@erudit-js/bitran-elements/include/shared';
3
- import {
4
- headingName,
5
- HeadingNode,
6
- } from '@erudit-js/bitran-elements/heading/shared';
7
-
8
- import type { BitranLocation } from '@erudit/shared/bitran/location';
9
- import type { Toc } from '@erudit/shared/bitran/toc';
10
- import { ERUDIT_SERVER } from '@server/global';
11
- import { getRawBitranContent } from './content';
12
- import { createBitranTranspiler } from './transpiler';
13
- import { traverseInclude } from './products/include';
14
-
15
- export async function getBitranToc(location: BitranLocation) {
16
- const rawContent = await getRawBitranContent(location);
17
- const bitranCore = await createBitranTranspiler();
18
-
19
- const toc: Toc = [];
20
-
21
- await bitranCore.parser.parse(rawContent.biCode, {
22
- async step(node) {
23
- if (node instanceof IncludeNode) {
24
- await traverseInclude(node, rawContent.context, {
25
- step: async ({ _node }) => tryAddToToc(_node),
26
- });
27
- } else tryAddToToc(node);
28
- },
29
- });
30
-
31
- return toc;
32
-
33
- function tryAddToToc(node: ElementNode) {
34
- const tocItemBase = {
35
- id: node.id,
36
- productName: node.name,
37
- };
38
-
39
- // Check if node is manually excluded from TOC
40
- if (typeof node.meta?.toc === 'boolean' && !node.meta?.toc) return;
41
-
42
- if (node instanceof HeadingNode) {
43
- toc.push({
44
- ...tocItemBase,
45
- level: node.parseData.level,
46
- title: node.parseData.title,
47
- });
48
- return;
49
- }
50
-
51
- const notHeadingLevel = () => {
52
- // Toc items without heading are at top level
53
- if (toc.length === 0) return 0;
54
-
55
- const lastTocItem = toc.at(-1)!;
56
-
57
- // Going one level deep if previous item was a heading and copy deep level if not
58
- return lastTocItem.productName === headingName
59
- ? lastTocItem.level + 1
60
- : lastTocItem.level;
61
- };
62
-
63
- if (ERUDIT_SERVER.BITRAN_CONFIG?.toc?.includes(node.name)) {
64
- // Erudit Config says to add these products to TOC
65
- toc.push({
66
- ...tocItemBase,
67
- level: notHeadingLevel(),
68
- title: node.meta?.title || node.parseData?.title,
69
- });
70
- return;
71
- }
72
-
73
- if (node.meta?.toc) {
74
- // Show in Toc any product with truthy `toc` meta property
75
- toc.push({
76
- ...tocItemBase,
77
- level: notHeadingLevel(),
78
- title: node.meta?.title || node.parseData?.title,
79
- });
80
- return;
81
- }
82
- }
83
- }
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,46 +1,18 @@
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
+ 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,10 +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
- 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
+ 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,176 +1,191 @@
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 { 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
+ }