erudit 2.0.0-dev.7

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 (314) hide show
  1. package/.nuxtrc +1 -0
  2. package/app/app.vue +172 -0
  3. package/app/assets/icons/alert.svg +3 -0
  4. package/app/assets/icons/angle-right.svg +3 -0
  5. package/app/assets/icons/arrow-in-text.svg +3 -0
  6. package/app/assets/icons/arrow-left.svg +3 -0
  7. package/app/assets/icons/arrow-up-to-right.svg +3 -0
  8. package/app/assets/icons/aside-open.svg +3 -0
  9. package/app/assets/icons/asterisk.svg +3 -0
  10. package/app/assets/icons/book-question.svg +3 -0
  11. package/app/assets/icons/book.svg +3 -0
  12. package/app/assets/icons/bug.svg +3 -0
  13. package/app/assets/icons/chip.svg +3 -0
  14. package/app/assets/icons/circle-help.svg +3 -0
  15. package/app/assets/icons/construction.svg +3 -0
  16. package/app/assets/icons/cross.svg +3 -0
  17. package/app/assets/icons/draw.svg +3 -0
  18. package/app/assets/icons/ellipsis-vertical.svg +3 -0
  19. package/app/assets/icons/file-check.svg +3 -0
  20. package/app/assets/icons/file-lines.svg +3 -0
  21. package/app/assets/icons/file-star.svg +3 -0
  22. package/app/assets/icons/folder-open.svg +3 -0
  23. package/app/assets/icons/folder.svg +3 -0
  24. package/app/assets/icons/globe.svg +3 -0
  25. package/app/assets/icons/house.svg +3 -0
  26. package/app/assets/icons/link-external.svg +3 -0
  27. package/app/assets/icons/link.svg +3 -0
  28. package/app/assets/icons/moon.svg +3 -0
  29. package/app/assets/icons/outline/book.svg +3 -0
  30. package/app/assets/icons/outline/file-lines.svg +3 -0
  31. package/app/assets/icons/pirate.svg +3 -0
  32. package/app/assets/icons/plus-circle.svg +3 -0
  33. package/app/assets/icons/plus.svg +3 -0
  34. package/app/assets/icons/puzzle.svg +3 -0
  35. package/app/assets/icons/search.svg +3 -0
  36. package/app/assets/icons/shuffle.svg +3 -0
  37. package/app/assets/icons/star.svg +3 -0
  38. package/app/assets/icons/sun-moon.svg +3 -0
  39. package/app/assets/icons/sun.svg +3 -0
  40. package/app/assets/icons/user.svg +3 -0
  41. package/app/assets/icons/users.svg +3 -0
  42. package/app/components/Loading.vue +23 -0
  43. package/app/components/SiteAside.vue +382 -0
  44. package/app/components/SiteMain.vue +35 -0
  45. package/app/components/ads/BannerTemplate.vue +51 -0
  46. package/app/components/ads/BottomBanner.vue +45 -0
  47. package/app/components/ads/LeftBanner.vue +50 -0
  48. package/app/components/aside/AsideListItem.vue +74 -0
  49. package/app/components/aside/AsideMajor.vue +56 -0
  50. package/app/components/aside/AsideMinor.vue +71 -0
  51. package/app/components/aside/major/PaneContentScroll.vue +23 -0
  52. package/app/components/aside/major/PaneSwitch.vue +54 -0
  53. package/app/components/aside/major/PaneSwitchButton.vue +63 -0
  54. package/app/components/aside/major/SiteInfo.vue +85 -0
  55. package/app/components/aside/major/panes/Language.vue +79 -0
  56. package/app/components/aside/major/panes/Pages.vue +34 -0
  57. package/app/components/aside/major/panes/Search.vue +3 -0
  58. package/app/components/aside/major/panes/nav/Nav.vue +91 -0
  59. package/app/components/aside/major/panes/nav/NavBook.vue +86 -0
  60. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -0
  61. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -0
  62. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -0
  63. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -0
  64. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -0
  65. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -0
  66. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -0
  67. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -0
  68. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -0
  69. package/app/components/aside/major/panes/other/ItemContent.vue +29 -0
  70. package/app/components/aside/major/panes/other/ItemGenerator.vue +15 -0
  71. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -0
  72. package/app/components/aside/major/panes/other/Other.vue +16 -0
  73. package/app/components/aside/minor/AsideMinorContributor.vue +5 -0
  74. package/app/components/aside/minor/AsideMinorNews.vue +11 -0
  75. package/app/components/aside/minor/AsideMinorPane.vue +16 -0
  76. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -0
  77. package/app/components/aside/minor/Contribute.vue +145 -0
  78. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -0
  79. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -0
  80. package/app/components/aside/minor/topic/TopicContributors.vue +177 -0
  81. package/app/components/aside/minor/topic/TopicNav.vue +49 -0
  82. package/app/components/aside/minor/topic/TopicToc.vue +202 -0
  83. package/app/components/aside/minor/topic/TopicTocItem.vue +31 -0
  84. package/app/components/aside/utils/AsideOverlayPane.vue +40 -0
  85. package/app/components/bitran/BitranContent.vue +64 -0
  86. package/app/components/bitran/RenderWrapper.vue +12 -0
  87. package/app/components/contributor/ContributorAvatar.vue +43 -0
  88. package/app/components/contributor/ContributorListItem.vue +35 -0
  89. package/app/components/main/topic/MainTopic.vue +79 -0
  90. package/app/components/main/topic/TopicPartSwitch.vue +118 -0
  91. package/app/components/main/utils/Breadcrumb.vue +75 -0
  92. package/app/components/main/utils/ContentDecoration.vue +29 -0
  93. package/app/components/main/utils/ContentDescription.vue +20 -0
  94. package/app/components/main/utils/ContentFlag.vue +16 -0
  95. package/app/components/main/utils/ContentPopover.vue +176 -0
  96. package/app/components/main/utils/ContentPopovers.vue +105 -0
  97. package/app/components/main/utils/ContentReferences.vue +76 -0
  98. package/app/components/main/utils/ContentSection.vue +42 -0
  99. package/app/components/main/utils/ContentTitle.vue +37 -0
  100. package/app/components/main/utils/reference/ReferenceGroup.vue +41 -0
  101. package/app/components/main/utils/reference/ReferenceItem.vue +64 -0
  102. package/app/components/main/utils/reference/ReferenceSource.vue +110 -0
  103. package/app/components/preview/Preview.vue +177 -0
  104. package/app/components/preview/PreviewDisplay.vue +139 -0
  105. package/app/components/preview/PreviewFooterAction.vue +73 -0
  106. package/app/components/preview/PreviewLoading.vue +15 -0
  107. package/app/components/preview/PreviewScreen.vue +99 -0
  108. package/app/components/preview/display/Alert.vue +50 -0
  109. package/app/components/preview/display/Custom.vue +18 -0
  110. package/app/components/preview/display/GenericLink.vue +48 -0
  111. package/app/components/preview/display/PageLink.vue +20 -0
  112. package/app/components/preview/display/Unique.vue +49 -0
  113. package/app/components/transition/Fade.vue +22 -0
  114. package/app/components/tree/TreeContainer.vue +12 -0
  115. package/app/components/tree/TreeItem.vue +89 -0
  116. package/app/composables/bitran.ts +132 -0
  117. package/app/composables/bitranContent.ts +36 -0
  118. package/app/composables/bitranLocation.ts +7 -0
  119. package/app/composables/contentData.ts +36 -0
  120. package/app/composables/contentPage.ts +156 -0
  121. package/app/composables/contentRoute.ts +45 -0
  122. package/app/composables/darkMagic.ts +24 -0
  123. package/app/composables/externalApi.ts +63 -0
  124. package/app/composables/favicon.ts +8 -0
  125. package/app/composables/formatText.ts +86 -0
  126. package/app/composables/majorPane.ts +60 -0
  127. package/app/composables/phrases.ts +80 -0
  128. package/app/composables/theme.ts +29 -0
  129. package/app/composables/url.ts +33 -0
  130. package/app/pages/_test/preview.vue +110 -0
  131. package/app/pages/article/[...articleId].vue +3 -0
  132. package/app/pages/book/[...bookId].vue +47 -0
  133. package/app/pages/group/[...groupId].vue +64 -0
  134. package/app/pages/index.vue +32 -0
  135. package/app/pages/members.vue +7 -0
  136. package/app/pages/practice/[...practice].vue +3 -0
  137. package/app/pages/summary/[...summaryId].vue +3 -0
  138. package/app/public/favicon/article.svg +10 -0
  139. package/app/public/favicon/default.svg +10 -0
  140. package/app/public/favicon/practice.svg +10 -0
  141. package/app/public/favicon/summary.svg +10 -0
  142. package/app/public/logotype.svg +17 -0
  143. package/app/public/og-default.png +0 -0
  144. package/app/public/user.svg +10 -0
  145. package/app/scripts/_immediate.js +4 -0
  146. package/app/scripts/aside/index.ts +59 -0
  147. package/app/scripts/aside/major/nav.ts +26 -0
  148. package/app/scripts/aside/minor/state.ts +37 -0
  149. package/app/scripts/aside/minor/topic.ts +3 -0
  150. package/app/scripts/flag.ts +28 -0
  151. package/app/scripts/og.ts +27 -0
  152. package/app/scripts/preview/build.ts +84 -0
  153. package/app/scripts/preview/data/alert.ts +19 -0
  154. package/app/scripts/preview/data/custom.ts +8 -0
  155. package/app/scripts/preview/data/genericLink.ts +24 -0
  156. package/app/scripts/preview/data/pageLink.ts +20 -0
  157. package/app/scripts/preview/data/unique.ts +70 -0
  158. package/app/scripts/preview/data.ts +26 -0
  159. package/app/scripts/preview/display.ts +39 -0
  160. package/app/scripts/preview/footer.ts +9 -0
  161. package/app/scripts/preview/request.ts +51 -0
  162. package/app/scripts/preview/state.ts +63 -0
  163. package/app/styles/_immediate.css +3 -0
  164. package/app/styles/_util.scss +50 -0
  165. package/app/styles/_utils.scss +44 -0
  166. package/app/styles/app.scss +91 -0
  167. package/app/styles/def/_bp.scss +24 -0
  168. package/app/styles/def/_size.scss +7 -0
  169. package/app/styles/def/_z.scss +5 -0
  170. package/app/styles/default.scss +85 -0
  171. package/app/styles/normalize.scss +63 -0
  172. package/app/styles/partials/_darkMagic.scss +7 -0
  173. package/app/styles/partials/_fnav.scss +18 -0
  174. package/app/styles/partials/_preview.scss +7 -0
  175. package/globalPath.ts +24 -0
  176. package/globals/bitran.ts +39 -0
  177. package/globals/content.ts +22 -0
  178. package/globals/contributor.ts +5 -0
  179. package/globals/erudit.ts +5 -0
  180. package/globals/register.ts +18 -0
  181. package/languages/en.ts +95 -0
  182. package/languages/ru.ts +99 -0
  183. package/module/bitran.ts +34 -0
  184. package/module/config.ts +34 -0
  185. package/module/imports.ts +46 -0
  186. package/module/index.ts +35 -0
  187. package/module/logger.ts +10 -0
  188. package/module/paths.ts +22 -0
  189. package/module/restart.ts +61 -0
  190. package/nuxt.config.ts +96 -0
  191. package/package.json +32 -0
  192. package/server/api/aside/major/nav/bookIds.ts +5 -0
  193. package/server/api/aside/major/nav/bookNav/[...bookId].ts +20 -0
  194. package/server/api/aside/major/nav/global.ts +7 -0
  195. package/server/api/aside/minor/news.ts +7 -0
  196. package/server/api/aside/minor/path.ts +78 -0
  197. package/server/api/bitran/content/[location].ts +7 -0
  198. package/server/api/bitran/toc/[location].ts +7 -0
  199. package/server/api/content/data.ts +72 -0
  200. package/server/api/contributor/count.ts +6 -0
  201. package/server/api/fake/content.ts +11 -0
  202. package/server/api/fake/shared/languages.ts +12 -0
  203. package/server/api/language/functions.ts +12 -0
  204. package/server/api/language/phrase/[phraseId].ts +19 -0
  205. package/server/api/language/phraseIds.ts +8 -0
  206. package/server/api/preview/page/[...parts].ts +51 -0
  207. package/server/api/preview/unique/[location].ts +55 -0
  208. package/server/plugin/bitran/content.ts +176 -0
  209. package/server/plugin/bitran/core.ts +51 -0
  210. package/server/plugin/bitran/location.ts +25 -0
  211. package/server/plugin/bitran/products/include.ts +229 -0
  212. package/server/plugin/bitran/products/link.ts +114 -0
  213. package/server/plugin/bitran/setup.ts +10 -0
  214. package/server/plugin/bitran/toc.ts +82 -0
  215. package/server/plugin/build/close.ts +10 -0
  216. package/server/plugin/build/jobs/content/builderArgs.ts +8 -0
  217. package/server/plugin/build/jobs/content/generic.ts +176 -0
  218. package/server/plugin/build/jobs/content/parse.ts +90 -0
  219. package/server/plugin/build/jobs/content/path.ts +6 -0
  220. package/server/plugin/build/jobs/content/type/book.ts +9 -0
  221. package/server/plugin/build/jobs/content/type/group.ts +37 -0
  222. package/server/plugin/build/jobs/content/type/topic.ts +36 -0
  223. package/server/plugin/build/jobs/contributors.ts +66 -0
  224. package/server/plugin/build/jobs/language.ts +36 -0
  225. package/server/plugin/build/jobs/nav.ts +209 -0
  226. package/server/plugin/build/process.ts +25 -0
  227. package/server/plugin/build/rebuild.ts +55 -0
  228. package/server/plugin/build/setup.ts +21 -0
  229. package/server/plugin/content/absoluteId.ts +94 -0
  230. package/server/plugin/content/context.ts +112 -0
  231. package/server/plugin/db/entities/Book.ts +7 -0
  232. package/server/plugin/db/entities/Content.ts +49 -0
  233. package/server/plugin/db/entities/Contribution.ts +10 -0
  234. package/server/plugin/db/entities/Contributor.ts +16 -0
  235. package/server/plugin/db/entities/Group.ts +14 -0
  236. package/server/plugin/db/entities/Hash.ts +15 -0
  237. package/server/plugin/db/entities/Topic.ts +20 -0
  238. package/server/plugin/db/entities/Unique.ts +21 -0
  239. package/server/plugin/db/setup.ts +34 -0
  240. package/server/plugin/global.ts +18 -0
  241. package/server/plugin/importer.ts +12 -0
  242. package/server/plugin/index.ts +9 -0
  243. package/server/plugin/logger.ts +23 -0
  244. package/server/plugin/nav/node.ts +26 -0
  245. package/server/plugin/nav/utils.ts +129 -0
  246. package/server/plugin/repository/book.ts +21 -0
  247. package/server/plugin/repository/content.ts +238 -0
  248. package/server/plugin/repository/contributor.ts +8 -0
  249. package/server/plugin/repository/frontNav.ts +148 -0
  250. package/server/plugin/repository/topic.ts +32 -0
  251. package/server/tsconfig.json +7 -0
  252. package/shared/aside/minor.ts +50 -0
  253. package/shared/asset.ts +15 -0
  254. package/shared/bitran/alias.ts +17 -0
  255. package/shared/bitran/context.ts +7 -0
  256. package/shared/bitran/location.ts +166 -0
  257. package/shared/bitran/products/alias/core/factory.ts +46 -0
  258. package/shared/bitran/products/alias/core/index.ts +13 -0
  259. package/shared/bitran/products/alias/render/Alias.vue +10 -0
  260. package/shared/bitran/products/alias/render/icon.svg +3 -0
  261. package/shared/bitran/products/alias/render/index.ts +17 -0
  262. package/shared/bitran/products/alias/render/languages/en.ts +5 -0
  263. package/shared/bitran/products/alias/render/languages/ru.ts +5 -0
  264. package/shared/bitran/products/alias/shared.ts +11 -0
  265. package/shared/bitran/products/heading/core/factory.ts +53 -0
  266. package/shared/bitran/products/heading/core/index.ts +19 -0
  267. package/shared/bitran/products/heading/render/Heading.vue +47 -0
  268. package/shared/bitran/products/heading/render/icon.svg +3 -0
  269. package/shared/bitran/products/heading/render/index.ts +17 -0
  270. package/shared/bitran/products/heading/render/languages/en.ts +5 -0
  271. package/shared/bitran/products/heading/render/languages/ru.ts +5 -0
  272. package/shared/bitran/products/heading/shared.ts +13 -0
  273. package/shared/bitran/products/include/core/factory.ts +61 -0
  274. package/shared/bitran/products/include/core/index.ts +13 -0
  275. package/shared/bitran/products/include/render/Include.vue +13 -0
  276. package/shared/bitran/products/include/render/icon.svg +3 -0
  277. package/shared/bitran/products/include/render/index.ts +18 -0
  278. package/shared/bitran/products/include/render/languages/en.ts +5 -0
  279. package/shared/bitran/products/include/render/languages/ru.ts +5 -0
  280. package/shared/bitran/products/include/shared.ts +15 -0
  281. package/shared/bitran/products/link/core/factory.ts +20 -0
  282. package/shared/bitran/products/link/core/index.ts +17 -0
  283. package/shared/bitran/products/link/render/Link.vue +174 -0
  284. package/shared/bitran/products/link/render/icon.svg +3 -0
  285. package/shared/bitran/products/link/render/index.ts +17 -0
  286. package/shared/bitran/products/link/render/languages/en.ts +5 -0
  287. package/shared/bitran/products/link/render/languages/ru.ts +5 -0
  288. package/shared/bitran/products/link/shared.ts +15 -0
  289. package/shared/bitran/products/link/target.ts +134 -0
  290. package/shared/bitran/toc.ts +8 -0
  291. package/shared/content/context.ts +9 -0
  292. package/shared/content/data/base.ts +32 -0
  293. package/shared/content/data/index.ts +5 -0
  294. package/shared/content/data/type/book.ts +5 -0
  295. package/shared/content/data/type/group.ts +6 -0
  296. package/shared/content/data/type/topic.ts +11 -0
  297. package/shared/content/previousNext.ts +9 -0
  298. package/shared/contributor.ts +5 -0
  299. package/shared/frontNav.ts +41 -0
  300. package/shared/icons.ts +38 -0
  301. package/shared/image.ts +5 -0
  302. package/shared/link.ts +25 -0
  303. package/shared/types/language.ts +75 -0
  304. package/shared/utils/objectsEqual.ts +4 -0
  305. package/shared/utils/stringColor.ts +9 -0
  306. package/test/bitran/alias.test.ts +44 -0
  307. package/test/bitran/location.test.ts +143 -0
  308. package/test/bitran/products/alias.test.ts +83 -0
  309. package/test/bitran/products/heading.test.ts +119 -0
  310. package/test/bitran/products/include.test.ts +77 -0
  311. package/test/bitran/products/link/factory.test.ts +30 -0
  312. package/test/bitran/products/link/target.test.ts +138 -0
  313. package/tsconfig.json +8 -0
  314. package/utils/stress.ts +9 -0
@@ -0,0 +1,10 @@
1
+ import { PROJECT_DIR } from '#erudit/globalPaths';
2
+ import { IMPORT } from '@server/importer';
3
+ import { ERUDIT_SERVER } from '@server/global';
4
+
5
+ export async function setupBitranConfig() {
6
+ try {
7
+ ERUDIT_SERVER.BITRAN_CONFIG = await IMPORT(PROJECT_DIR + '/bitran.ts');
8
+ console.log(ERUDIT_SERVER.BITRAN_CONFIG);
9
+ } catch {}
10
+ }
@@ -0,0 +1,82 @@
1
+ import type { ProductNode } from 'bitran/dom';
2
+
3
+ import { getRawBitranContent } from './content';
4
+ import { createBitranCore } from './core';
5
+ import type { BitranLocation } from '@erudit/shared/bitran/location';
6
+ import type { Toc } from '@erudit/shared/bitran/toc';
7
+ import { IncludeNode } from '@erudit/shared/bitran/products/include/core';
8
+ import { traverseInclude } from './products/include';
9
+ import { HeadingNode } from '@erudit/shared/bitran/products/heading/core';
10
+ import { headingName } from '@erudit/shared/bitran/products/heading/shared';
11
+ import { ERUDIT_SERVER } from '../global';
12
+
13
+ export async function getBitranToc(location: BitranLocation) {
14
+ const rawContent = await getRawBitranContent(location);
15
+ const bitranCore = await createBitranCore(rawContent.context);
16
+
17
+ const toc: Toc = [];
18
+
19
+ await bitranCore.parser.parse(rawContent.biCode, {
20
+ step: async (productNode) => {
21
+ if (productNode instanceof IncludeNode) {
22
+ await traverseInclude(productNode, rawContent.context, {
23
+ step: async ({ _node }) => tryAddToToc(_node),
24
+ });
25
+ } else tryAddToToc(productNode);
26
+ },
27
+ });
28
+
29
+ return toc;
30
+
31
+ function tryAddToToc(product: ProductNode) {
32
+ const tocItemBase = {
33
+ id: product.getId(),
34
+ productName: product.name,
35
+ };
36
+
37
+ // Check if node is manually excluded from TOC
38
+ if (typeof product.meta?.toc === 'boolean' && !product.meta?.toc)
39
+ return;
40
+
41
+ if (product instanceof HeadingNode) {
42
+ toc.push({
43
+ ...tocItemBase,
44
+ level: product.parseData.level,
45
+ title: product.parseData.title,
46
+ });
47
+ return;
48
+ }
49
+
50
+ const notHeadingLevel = () => {
51
+ // Toc items without heading are at top level
52
+ if (toc.length === 0) return 0;
53
+
54
+ const lastTocItem = toc.at(-1)!;
55
+
56
+ // Going one level deep if previous item was a heading and copy deep level if not
57
+ return lastTocItem.productName === headingName
58
+ ? lastTocItem.level + 1
59
+ : lastTocItem.level;
60
+ };
61
+
62
+ if (ERUDIT_SERVER.BITRAN_CONFIG?.toc?.includes(product.name)) {
63
+ // Erudit Config says to add these products to TOC
64
+ toc.push({
65
+ ...tocItemBase,
66
+ level: notHeadingLevel(),
67
+ title: product.meta?.title || product.parseData?.title,
68
+ });
69
+ return;
70
+ }
71
+
72
+ if (product.meta?.toc) {
73
+ // Show in Toc any product with truthy `toc` meta property
74
+ toc.push({
75
+ ...tocItemBase,
76
+ level: notHeadingLevel(),
77
+ title: product.meta?.title || product.parseData?.title,
78
+ });
79
+ return;
80
+ }
81
+ }
82
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,176 @@
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
+ }
@@ -0,0 +1,90 @@
1
+ import type { BitranCore } from 'bitran/core';
2
+ import { assumeGroupItem, type ProductNode } from 'bitran/dom';
3
+
4
+ import { createBitranCore } from '@server/bitran/core';
5
+ import { DbUnique } from '@server/db/entities/Unique';
6
+ import { ERUDIT_SERVER } from '@server/global';
7
+
8
+ import { NO_ALIASES } from '@shared/bitran/alias';
9
+ import type { BitranContext } from '@shared/bitran/context';
10
+ import {
11
+ stringifyBitranLocation,
12
+ type BitranLocation,
13
+ } from '@shared/bitran/location';
14
+ import { AliasNode } from '@shared/bitran/products/alias/core';
15
+ import { HeadingNode } from '@shared/bitran/products/heading/core';
16
+
17
+ let context: BitranContext = {} as any;
18
+ let bitranCore: BitranCore;
19
+
20
+ const blocksAfterHeading = 2;
21
+
22
+ export async function parseBitranContent(
23
+ location: BitranLocation,
24
+ biCode: string,
25
+ ) {
26
+ // Reset Bitran context in order not to create core each time
27
+ context.location = location;
28
+ context.aliases = NO_ALIASES();
29
+
30
+ bitranCore ||= await createBitranCore(context);
31
+
32
+ // Tracking heading nodes to deal with them later
33
+ let headings: HeadingNode[] = [];
34
+
35
+ await bitranCore.parser.parse(biCode, {
36
+ async step(product) {
37
+ const meta = product.meta;
38
+ const uniqueId = meta?.id;
39
+
40
+ if (uniqueId) {
41
+ if (product instanceof HeadingNode) {
42
+ headings.push(product);
43
+ return;
44
+ }
45
+
46
+ await addUnique(product);
47
+ }
48
+ },
49
+ });
50
+
51
+ for (const heading of headings) {
52
+ let blocksAfter = 0;
53
+ let content = bitranCore.stringifier.stringify(heading);
54
+
55
+ assumeGroupItem(heading).walkForward((product) => {
56
+ if (blocksAfter >= blocksAfterHeading) return false;
57
+
58
+ if (product instanceof AliasNode) return;
59
+ // Spoiler
60
+ // Todo
61
+
62
+ content += '\n\n' + bitranCore.stringifier.stringify(product);
63
+ blocksAfter++;
64
+ });
65
+
66
+ await addUnique(heading, content);
67
+ }
68
+
69
+ //
70
+ //
71
+ //
72
+
73
+ function createUniqueLocation(product: ProductNode) {
74
+ return stringifyBitranLocation({
75
+ ...location,
76
+ ...{ unique: product.meta.id! },
77
+ });
78
+ }
79
+
80
+ async function addUnique(product: ProductNode, content?: string) {
81
+ const dbUnique = new DbUnique();
82
+ dbUnique.location = createUniqueLocation(product);
83
+ dbUnique.content = content || bitranCore.stringifier.stringify(product);
84
+ dbUnique.title = product.parseData?.title || product.meta?.title;
85
+ dbUnique.productName = product.name;
86
+ dbUnique.context = context;
87
+
88
+ await ERUDIT_SERVER.DB.manager.save(dbUnique);
89
+ }
90
+ }
@@ -0,0 +1,6 @@
1
+ import { PROJECT_DIR } from '#erudit/globalPaths';
2
+ import type { NavNode } from '@server/nav/node';
3
+
4
+ export function contentItemPath(navNode: NavNode, path?: string) {
5
+ return PROJECT_DIR + `/content/${navNode.path}${path ? '/' + path : ''}`;
6
+ }
@@ -0,0 +1,9 @@
1
+ import { DbBook } from '@server/db/entities/Book';
2
+ import { ERUDIT_SERVER } from '@server/global';
3
+ import type { BuilderFunctionArgs } from '../builderArgs';
4
+
5
+ export async function buildBook({ dbContent }: BuilderFunctionArgs) {
6
+ const dbBook = new DbBook();
7
+ dbBook.contentId = dbContent.contentId;
8
+ await ERUDIT_SERVER.DB.manager.save(dbBook);
9
+ }
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import type { GroupConfig } from 'erudit-cog/schema';
3
+
4
+ import { DbGroup } from '@server/db/entities/Group';
5
+ import { ERUDIT_SERVER } from '@erudit/server/plugin/global';
6
+ import type { BuilderFunctionArgs } from '../builderArgs';
7
+ import { contentItemPath } from '../path';
8
+ import { parseBitranContent } from '../parse';
9
+
10
+ export async function buildGroup({
11
+ navNode,
12
+ config,
13
+ }: BuilderFunctionArgs<GroupConfig>) {
14
+ const dbGroup = new DbGroup();
15
+ dbGroup.contentId = navNode.id;
16
+ dbGroup.type = config?.type || 'folder';
17
+
18
+ try {
19
+ const strContent = readFileSync(
20
+ contentItemPath(navNode, 'content.bi'),
21
+ 'utf-8',
22
+ );
23
+
24
+ if (strContent) {
25
+ dbGroup.content = strContent;
26
+ await parseBitranContent(
27
+ {
28
+ type: 'group',
29
+ path: navNode.id,
30
+ },
31
+ strContent,
32
+ );
33
+ }
34
+ } catch {}
35
+
36
+ await ERUDIT_SERVER.DB.manager.save(dbGroup);
37
+ }
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { topicParts, type TopicPart } from 'erudit-cog/schema';
3
+
4
+ import { ERUDIT_SERVER } from '@server/global';
5
+ import { DbTopic } from '@server/db/entities/Topic';
6
+ import type { BuilderFunctionArgs } from '../builderArgs';
7
+ import { contentItemPath } from '../path';
8
+ import { parseBitranContent } from '../parse';
9
+
10
+ export async function buildTopic({ navNode }: BuilderFunctionArgs) {
11
+ const dbTopic = new DbTopic();
12
+ dbTopic.contentId = navNode.id;
13
+ const existingTopicParts: TopicPart[] = [];
14
+
15
+ for (const topicPart of topicParts) {
16
+ const partPath = contentItemPath(navNode, `${topicPart}.bi`);
17
+ const strContent =
18
+ existsSync(partPath) && readFileSync(partPath, 'utf-8');
19
+
20
+ if (strContent === false) continue;
21
+
22
+ existingTopicParts.push(topicPart);
23
+ dbTopic[topicPart] = strContent;
24
+
25
+ await parseBitranContent(
26
+ {
27
+ type: topicPart,
28
+ path: navNode.id,
29
+ },
30
+ strContent,
31
+ );
32
+ }
33
+
34
+ dbTopic.parts = existingTopicParts;
35
+ await ERUDIT_SERVER.DB.manager.save(dbTopic);
36
+ }
@@ -0,0 +1,66 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { globSync } from 'glob';
3
+ import path from 'node:path';
4
+ import chalk from 'chalk';
5
+ import { resolvePaths } from 'erudit-cog/kit';
6
+ import type { ContributorConfig } from 'erudit-cog/schema';
7
+
8
+ import { PROJECT_DIR } from '#erudit/globalPaths';
9
+ import { stress } from '@erudit/utils/stress';
10
+ import { debug, logger } from '@server/logger';
11
+ import { ERUDIT_SERVER } from '@server/global';
12
+ import { IMPORT } from '@server/importer';
13
+ import { DbContributor } from '@server/db/entities/Contributor';
14
+
15
+ export async function buildContributors() {
16
+ debug.start('Building contributors...');
17
+
18
+ const contributorPaths = globSync(PROJECT_DIR + '/contributors/*/');
19
+ for (const contributorPath of contributorPaths)
20
+ await addContributor(resolvePaths(contributorPath));
21
+
22
+ logger.success(
23
+ `Contributors built successfully!`,
24
+ chalk.dim(`(${contributorPaths.length})`),
25
+ );
26
+ }
27
+
28
+ async function addContributor(contributorPath: string) {
29
+ const contributorId = contributorPath.split('/').pop() as string;
30
+
31
+ debug.start(`Adding contributor ${stress(contributorId)}...`);
32
+
33
+ const avatarFilename = (() => {
34
+ const avatarPaths = globSync(`${contributorPath}/avatar.*`);
35
+ if (avatarPaths[0])
36
+ return avatarPaths[0].split(path.sep).slice(-2).join('/');
37
+
38
+ return undefined;
39
+ })();
40
+
41
+ const config: Partial<ContributorConfig> = await (async () => {
42
+ try {
43
+ return await IMPORT(`${contributorPath}/contributor`, {
44
+ default: true,
45
+ });
46
+ } catch {
47
+ return {};
48
+ }
49
+ })();
50
+
51
+ const description = (() => {
52
+ try {
53
+ return readFileSync(`${contributorPath}/description.bi`, 'utf-8');
54
+ } catch {
55
+ return undefined;
56
+ }
57
+ })();
58
+
59
+ const dbContributor = new DbContributor();
60
+ dbContributor.contributorId = contributorId;
61
+ dbContributor.avatar = avatarFilename;
62
+ dbContributor.displayName = config.displayName;
63
+ dbContributor.description = description;
64
+
65
+ await ERUDIT_SERVER.DB.manager.save(dbContributor);
66
+ }
@@ -0,0 +1,36 @@
1
+ import { existsSync } from 'node:fs';
2
+
3
+ import { ERUDIT_DIR } from '#erudit/globalPaths';
4
+ import { stress } from '@erudit/utils/stress';
5
+ import { ERUDIT_SERVER } from '@server/global';
6
+ import { IMPORT } from '@server/importer';
7
+
8
+ export async function setupLanguage() {
9
+ const languageCode = ERUDIT_SERVER.CONFIG?.language || 'en';
10
+ const languageFilePath = ERUDIT_DIR + `/languages/${languageCode}.ts`;
11
+
12
+ if (!existsSync(languageFilePath))
13
+ throw new Error(`Language file for ${stress(languageCode)} not found!`);
14
+
15
+ try {
16
+ const languageModule = await IMPORT(languageFilePath);
17
+
18
+ if (!languageModule.default)
19
+ throw new Error(
20
+ `Language file for ${stress(languageCode)} is missing default export with phrases!`,
21
+ );
22
+
23
+ const { default: phrases, ...context } = { ...languageModule };
24
+
25
+ ERUDIT_SERVER.LANGUAGE = {
26
+ phrases,
27
+ functions: Object.entries(context)
28
+ .filter(([, value]) => typeof value === 'function')
29
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
30
+ };
31
+ } catch (error) {
32
+ throw new Error(
33
+ `Error while loading language ${stress(languageCode)}!\n\n${error}`,
34
+ );
35
+ }
36
+ }