erudit 4.1.1 → 4.2.0

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 (116) hide show
  1. package/app/assets/icons/update.svg +3 -0
  2. package/app/components/Prose.vue +7 -7
  3. package/app/components/SmartMedia.vue +4 -4
  4. package/app/components/aside/major/contentNav/PaneBookNav.vue +1 -4
  5. package/app/components/aside/minor/content/Toc.vue +24 -1
  6. package/app/components/aside/minor/content/TocItem.vue +2 -1
  7. package/app/components/aside/minor/news/AsideMinorNews.vue +2 -4
  8. package/app/components/aside/minor/news/NewsItem.vue +3 -4
  9. package/app/components/aside/minor/news/RenderNewsElement.vue +4 -11
  10. package/app/components/aside/minor/news/elements/Mix.vue +2 -3
  11. package/app/components/aside/minor/news/elements/P.vue +3 -3
  12. package/app/components/aside/minor/news/elements/Ref.vue +3 -3
  13. package/app/components/aside/minor/news/elements/Text.vue +2 -2
  14. package/app/components/main/MainContentChild.vue +3 -3
  15. package/app/components/main/{MainQuickLink.vue → MainKeyLink.vue} +11 -11
  16. package/app/components/main/{MainQuickLinks.vue → MainKeyLinks.vue} +7 -7
  17. package/app/components/main/MainQuoteLoader.vue +2 -4
  18. package/app/components/main/MainStickyHeader.vue +1 -1
  19. package/app/components/main/MainStickyHeaderPreamble.vue +6 -2
  20. package/app/components/main/MainTopicPartPage.vue +8 -3
  21. package/app/components/main/connections/DepUnique.vue +45 -0
  22. package/app/components/main/connections/Deps.vue +14 -2
  23. package/app/components/main/connections/Externals.vue +1 -1
  24. package/app/components/main/connections/MainConnections.vue +1 -0
  25. package/app/components/main/contentStats/ItemLastChanged.vue +68 -0
  26. package/app/components/main/contentStats/MainContentStats.vue +36 -28
  27. package/app/components/preview/PreviewScreen.vue +2 -2
  28. package/app/components/preview/screen/ContentPage.vue +1 -4
  29. package/app/components/preview/screen/Unique.vue +3 -5
  30. package/app/composables/appElements.ts +2 -4
  31. package/app/composables/asideMajorPane.ts +3 -3
  32. package/app/composables/fetchJson.ts +4 -0
  33. package/app/composables/lastChanged.ts +28 -0
  34. package/app/composables/mainContent.ts +1 -4
  35. package/app/composables/og.ts +43 -35
  36. package/app/composables/phrases.ts +2 -3
  37. package/app/composables/scrollUp.ts +1 -1
  38. package/app/pages/book/[...bookId].vue +5 -1
  39. package/app/pages/contributor/[contributorId].vue +3 -5
  40. package/app/pages/contributors.vue +1 -1
  41. package/app/pages/group/[...groupId].vue +5 -1
  42. package/app/pages/index.vue +1 -1
  43. package/app/pages/page/[...pageId].vue +8 -3
  44. package/app/pages/sponsors.vue +1 -1
  45. package/app/plugins/appSetup/index.ts +0 -5
  46. package/app/plugins/fetchJson.ts +11 -0
  47. package/app/plugins/prerender.server.ts +1 -1
  48. package/app/router.options.ts +1 -1
  49. package/modules/erudit/globals/prose.ts +3 -4
  50. package/modules/erudit/setup/elements/appTemplate.ts +6 -7
  51. package/modules/erudit/setup/elements/{globalTypes.ts → elementGlobalTypes.ts} +21 -21
  52. package/modules/erudit/setup/elements/globalTemplate.ts +29 -23
  53. package/modules/erudit/setup/elements/setup.ts +18 -16
  54. package/modules/erudit/setup/elements/shared.ts +2 -2
  55. package/modules/erudit/setup/elements/tagsTable.ts +1 -1
  56. package/modules/erudit/setup/runtimeConfig.ts +2 -0
  57. package/nuxt.config.ts +2 -2
  58. package/package.json +14 -13
  59. package/server/api/main/content/[...contentTypePath].ts +5 -4
  60. package/server/api/prerender/content.ts +1 -3
  61. package/server/api/preview/contentPage/[...contentTypePath].ts +1 -2
  62. package/server/api/preview/contentUnique/[...contentTypePathUnique].ts +16 -31
  63. package/server/api/problemScript/[...problemScriptPath].ts +81 -4
  64. package/server/erudit/content/global/build.ts +64 -10
  65. package/server/erudit/content/nav/build.ts +4 -4
  66. package/server/erudit/content/repository/children.ts +3 -3
  67. package/server/erudit/content/repository/deps.ts +110 -13
  68. package/server/erudit/content/repository/elementSnippets.ts +16 -16
  69. package/server/erudit/content/repository/stats.ts +30 -22
  70. package/server/erudit/content/repository/topicParts.ts +1 -1
  71. package/server/erudit/content/repository/unique.ts +14 -15
  72. package/server/erudit/content/resolve/page.ts +15 -35
  73. package/server/erudit/content/resolve/topic.ts +33 -164
  74. package/server/erudit/content/resolve/utils/insertContentResolved.ts +59 -31
  75. package/server/erudit/content/search.ts +5 -22
  76. package/server/erudit/contributors/build.ts +7 -8
  77. package/server/erudit/db/repository/pushFile.ts +10 -3
  78. package/server/erudit/db/repository/pushProblemScript.ts +14 -3
  79. package/server/erudit/db/schema/contentDeps.ts +3 -0
  80. package/server/erudit/db/schema/contentSnippets.ts +3 -3
  81. package/server/erudit/db/schema/contentUniques.ts +2 -2
  82. package/server/erudit/db/schema/contributors.ts +2 -2
  83. package/server/erudit/db/schema/news.ts +2 -2
  84. package/server/erudit/db/schema/pages.ts +2 -2
  85. package/server/erudit/db/schema/topics.ts +4 -4
  86. package/server/erudit/global.ts +4 -0
  87. package/server/erudit/importer.ts +16 -8
  88. package/server/erudit/index.ts +0 -3
  89. package/server/erudit/language/list/en.ts +1 -0
  90. package/server/erudit/language/list/ru.ts +1 -0
  91. package/server/erudit/news/build.ts +6 -6
  92. package/server/erudit/news/repository/batch.ts +2 -2
  93. package/server/erudit/prose/repository/finalize.ts +22 -25
  94. package/server/erudit/prose/repository/get.ts +3 -5
  95. package/server/erudit/prose/repository/rawToProse.ts +31 -0
  96. package/server/erudit/prose/storage/callout.ts +9 -7
  97. package/server/erudit/prose/storage/image.ts +8 -11
  98. package/server/erudit/prose/storage/link.ts +24 -32
  99. package/server/erudit/prose/storage/problemScript.ts +8 -14
  100. package/server/erudit/prose/storage/video.ts +9 -7
  101. package/server/erudit/repository.ts +4 -4
  102. package/server/routes/file/[...path].ts +1 -1
  103. package/shared/types/contentChildren.ts +5 -2
  104. package/shared/types/contentConnections.ts +9 -0
  105. package/shared/types/elementSnippet.ts +1 -1
  106. package/shared/types/indexPage.ts +3 -0
  107. package/shared/types/language.ts +1 -83
  108. package/shared/types/mainContent.ts +11 -5
  109. package/shared/types/news.ts +2 -2
  110. package/shared/types/preview.ts +3 -2
  111. package/shared/types/runtimeConfig.ts +1 -0
  112. package/shared/types/search.ts +2 -0
  113. package/shared/utils/pages.ts +4 -2
  114. package/shared/utils/stringColor.ts +16 -6
  115. package/server/erudit/prose/repository/resolve.ts +0 -17
  116. package/server/erudit/prose/transform/bundleProblemScript.ts +0 -6
@@ -22,9 +22,8 @@ export function createGlobalTemplate(nuxt: Nuxt, elementsData: ElementData[]) {
22
22
  }
23
23
 
24
24
  const template = `
25
- import { PROSE_REGISTRY } from '@jsprose/core';
26
- import { jsx, jsxs, Fragment } from '@jsprose/core/jsx-runtime';
27
- import type { EruditProseCoreElement } from '@erudit-js/prose';
25
+ import { jsx, jsxs, Fragment } from 'tsprose/jsx-runtime';
26
+ import type { ProseCoreElements } from '@erudit-js/prose';
28
27
  import { defineProblemScript } from '@erudit-js/prose/elements/problem/problemScript';
29
28
 
30
29
  ${Object.entries(cores)
@@ -41,32 +40,39 @@ ${Object.entries(globals)
41
40
  )
42
41
  .join('\n')}
43
42
 
44
- const coreElements: EruditProseCoreElement[] = [
45
- ${Object.keys(cores).join(',\n ')}
46
- ].flatMap(element => (Array.isArray(element) ? element : [element]) as any);
43
+ export const coreElements: ProseCoreElements = Object.fromEntries([
44
+ ${Object.keys(cores).join(',\n ')}
45
+ ]
46
+ .flatMap((element: any) => (Array.isArray(element) ? element : [element]))
47
+ .map((element: any) => [element.schema.name, element])
48
+ );
47
49
 
48
- const elementsGlobals = {
49
- ${Object.keys(globals)
50
- .map((key) => `...${key}`)
51
- .join(',\n ')}
50
+ export const elementsGlobals = {
51
+ ${Object.keys(globals)
52
+ .map((key) => `...${key}`)
53
+ .join(',\n ')}
52
54
  }
53
55
 
54
56
  export function registerProseGlobals() {
55
- for (const element of coreElements) {
56
- PROSE_REGISTRY.addItem(element.registryItem);
57
- Object.assign(globalThis, element.registryItem.tags || {});
57
+ for (const coreElement of Object.values(coreElements)) {
58
+ const tags = coreElement.tags || [];
59
+ for (const tag of tags) {
60
+ Object.assign(globalThis, {
61
+ [tag.tagName]: tag,
62
+ });
58
63
  }
64
+ }
59
65
 
60
- Object.assign(globalThis, {
61
- // Make jsx runtime globally available (for prose generation in isolated modules like problem scripts)
62
- jsx,
63
- jsxs,
64
- Fragment,
65
- // Problem globals
66
- defineProblemScript,
67
- // Elements globals
68
- ...elementsGlobals
69
- });
66
+ Object.assign(globalThis, {
67
+ // Make jsx runtime globally available (for prose generation in isolated modules like problem scripts)
68
+ jsx,
69
+ jsxs,
70
+ Fragment,
71
+ // Problem globals
72
+ defineProblemScript,
73
+ // Elements globals
74
+ ...elementsGlobals
75
+ });
70
76
  }
71
77
  `.trim();
72
78
 
@@ -1,13 +1,14 @@
1
1
  import chalk from 'chalk';
2
2
  import type { Nuxt } from 'nuxt/schema';
3
3
  import { findPath } from 'nuxt/kit';
4
- import type { EruditProseCoreElement } from '@erudit-js/prose';
4
+
5
+ import type { ProseCoreElement } from '@erudit-js/prose';
5
6
 
6
7
  import type { EruditRuntimeConfig } from '../../../../shared/types/runtimeConfig';
7
8
  import { moduleLogger } from '../../logger';
8
9
  import type { ElementData } from './shared';
9
10
  import { createTagsTable } from './tagsTable';
10
- import { createGlobalTypes } from './globalTypes';
11
+ import { createElementGlobalTypes } from './elementGlobalTypes';
11
12
  import { createGlobalTemplate } from './globalTemplate';
12
13
  import { createAppTemplate } from './appTemplate';
13
14
  import { PROJECT_PATH } from '../../env';
@@ -60,7 +61,7 @@ export async function setupProseElements(
60
61
 
61
62
  const elementData: ElementData = {
62
63
  name: uniqueElementName,
63
- registryItems: [],
64
+ coreElements: [],
64
65
  absDirectory: '',
65
66
  absCorePath: '',
66
67
  absAppPath: undefined,
@@ -91,7 +92,7 @@ export async function setupProseElements(
91
92
  elementData.absAppPath = appAbsPath;
92
93
  }
93
94
 
94
- const coreDefault: EruditProseCoreElement | EruditProseCoreElement[] = (
95
+ const coreDefault: ProseCoreElement | ProseCoreElement[] = (
95
96
  await import(coreAbsPath)
96
97
  ).default;
97
98
 
@@ -100,7 +101,7 @@ export async function setupProseElements(
100
101
  : [coreDefault];
101
102
 
102
103
  for (const coreElement of coreElements) {
103
- const schemaName = coreElement.registryItem.schema.name;
104
+ const schemaName = coreElement.schema.name;
104
105
 
105
106
  if (seenSchemas.has(schemaName)) {
106
107
  throw new Error(
@@ -109,23 +110,24 @@ export async function setupProseElements(
109
110
  }
110
111
 
111
112
  seenSchemas.add(schemaName);
112
- elementData.registryItems.push({
113
+ elementData.coreElements.push({
113
114
  schemaName,
114
- tagNames: [],
115
+ tags: {},
115
116
  });
116
117
 
117
- if (coreElement.registryItem.tags) {
118
- for (const tagName of Object.keys(coreElement.registryItem.tags)) {
119
- if (seenTags.has(tagName)) {
118
+ if (coreElement.tags) {
119
+ for (let i = 0; i < coreElement.tags.length; i++) {
120
+ const tag = coreElement.tags[i]!;
121
+ if (seenTags.has(tag.tagName)) {
120
122
  throw new Error(
121
- `Prose element tag name "<${tagName}>" is already registered by another element!`,
123
+ `Prose element tag name "<${tag.tagName}>" is already registered by another element!`,
122
124
  );
123
125
  }
124
126
 
125
- seenTags.add(tagName);
126
- elementData.registryItems[
127
- elementData.registryItems.length - 1
128
- ]!.tagNames.push(tagName);
127
+ seenTags.add(tag.tagName);
128
+ elementData.coreElements[elementData.coreElements.length - 1]!.tags[
129
+ tag.tagName
130
+ ] = i;
129
131
  }
130
132
  }
131
133
 
@@ -144,7 +146,7 @@ export async function setupProseElements(
144
146
  elementsData.push(elementData);
145
147
  }
146
148
 
147
- createGlobalTypes(elementsData);
149
+ createElementGlobalTypes(elementsData);
148
150
  createGlobalTemplate(nuxt, elementsData);
149
151
  createAppTemplate(nuxt, elementsData);
150
152
 
@@ -1,8 +1,8 @@
1
1
  export interface ElementData {
2
2
  name: string;
3
- registryItems: {
3
+ coreElements: {
4
4
  schemaName: string;
5
- tagNames: string[];
5
+ tags: Record<string, number>;
6
6
  }[];
7
7
  absDirectory: string;
8
8
  absCorePath: string;
@@ -2,7 +2,7 @@ import type { ElementData } from './shared';
2
2
 
3
3
  export function createTagsTable(elementsData: ElementData[], columns = 4) {
4
4
  const tagNames = elementsData.flatMap((data) =>
5
- data.registryItems.flatMap((item) => item.tagNames),
5
+ data.coreElements.flatMap((coreElement) => Object.keys(coreElement.tags)),
6
6
  );
7
7
 
8
8
  if (tagNames.length === 0) return '';
@@ -43,6 +43,8 @@ export async function setupEruditRuntimeConfig(nuxt: Nuxt) {
43
43
  slowTransition: eruditConfig.debug?.slowTransition ?? false,
44
44
  fakeApi: {
45
45
  repository: eruditConfig.debug?.fakeApi?.repository ?? nuxt.options.dev,
46
+ lastChanged:
47
+ eruditConfig.debug?.fakeApi?.lastChanged ?? nuxt.options.dev,
46
48
  },
47
49
  analytics: eruditConfig.debug?.analytics,
48
50
  },
package/nuxt.config.ts CHANGED
@@ -50,9 +50,9 @@ export default defineNuxtConfig({
50
50
  },
51
51
  },
52
52
  rollupConfig: {
53
- // Prevent inlining some packages to avoid singleton and Symbol duplication issues
53
+ // Prevent inlining some packages
54
54
  external(source) {
55
- const ignore = ['jiti', '@jsprose'];
55
+ const ignore = ['jiti', 'tsprose'];
56
56
 
57
57
  for (const ignoreItem of ignore) {
58
58
  if (source.includes(ignoreItem)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erudit",
3
- "version": "4.1.1",
3
+ "version": "4.2.0",
4
4
  "type": "module",
5
5
  "description": "🤓 CMS for perfect educational sites.",
6
6
  "license": "MIT",
@@ -24,30 +24,31 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@erudit-js/cli": "4.1.1",
28
- "@erudit-js/core": "4.1.1",
29
- "@erudit-js/prose": "4.1.1",
27
+ "@erudit-js/cli": "4.2.0",
28
+ "@erudit-js/core": "4.2.0",
29
+ "@erudit-js/prose": "4.2.0",
30
30
  "unslash": "^2.0.0",
31
31
  "@floating-ui/vue": "^1.1.10",
32
- "@jsprose/core": "^1.0.0",
33
- "@tailwindcss/vite": "^4.1.18",
32
+ "tsprose": "^1.0.0",
33
+ "@tailwindcss/vite": "^4.2.0",
34
34
  "better-sqlite3": "^12.6.2",
35
35
  "chalk": "^5.6.2",
36
36
  "chokidar": "^5.0.0",
37
37
  "consola": "^3.4.2",
38
- "drizzle-kit": "^0.31.8",
38
+ "drizzle-kit": "^0.31.9",
39
39
  "drizzle-orm": "^0.45.1",
40
- "esbuild": "^0.27.2",
40
+ "esbuild": "^0.27.3",
41
41
  "flexsearch": "^0.8.212",
42
- "glob": "^13.0.0",
42
+ "glob": "^13.0.6",
43
43
  "image-size": "^2.0.2",
44
44
  "jiti": "^2.6.1",
45
- "nuxt": "4.3.0",
45
+ "nuxt": "4.3.1",
46
46
  "nuxt-my-icons": "1.2.2",
47
47
  "perfect-debounce": "^2.1.0",
48
- "tailwindcss": "^4.1.18",
49
- "vue": "^3.5.27",
50
- "vue-router": "^5.0.2"
48
+ "tailwindcss": "^4.2.0",
49
+ "vue": "^3.5.28",
50
+ "vue-router": "^5.0.3",
51
+ "ts-xor": "^1.3.0"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/better-sqlite3": "^7.6.13"
@@ -64,7 +64,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
64
64
  }
65
65
 
66
66
  if (contentTypePath.type === 'page' || contentTypePath.type === 'topic') {
67
- const proseElement = await ERUDIT.repository.prose.getContent(
67
+ const prose = await ERUDIT.repository.prose.getContent(
68
68
  contentTypePath.type === 'topic'
69
69
  ? contentTypePath.topicPart
70
70
  : contentTypePath.type,
@@ -72,7 +72,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
72
72
  );
73
73
 
74
74
  const topicParts = await ERUDIT.repository.content.topicParts(fullId);
75
- const { storage } = await ERUDIT.repository.prose.finalize(proseElement);
75
+ const { storage } = await ERUDIT.repository.prose.finalize(prose);
76
76
 
77
77
  const where = (() => {
78
78
  if (contentTypePath.type === 'topic') {
@@ -101,7 +101,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
101
101
  type: 'topic',
102
102
  part: contentTypePath.topicPart,
103
103
  parts: topicParts,
104
- proseElement,
104
+ prose,
105
105
  storage,
106
106
  };
107
107
 
@@ -123,7 +123,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
123
123
  const mainContentPage: MainContentPage = {
124
124
  ...mainContentBase,
125
125
  type: 'page',
126
- proseElement,
126
+ prose,
127
127
  storage,
128
128
  };
129
129
 
@@ -141,6 +141,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
141
141
  //
142
142
  // Rest content types
143
143
  //
144
+
144
145
  return {
145
146
  ...mainContentBase,
146
147
  type: contentTypePath.type,
@@ -68,9 +68,7 @@ export async function problemScripts() {
68
68
  for (const dbProblemScript of dbProblemScripts) {
69
69
  routes.push(
70
70
  `/api/problemScript/` +
71
- dbProblemScript
72
- .problemScript!.replace(ERUDIT.paths.project() + '/', '')
73
- .replace('.tsx', '') +
71
+ dbProblemScript.problemScript!.replace('.tsx', '') +
74
72
  '.js',
75
73
  );
76
74
  }
@@ -3,7 +3,7 @@ import { eq } from 'drizzle-orm';
3
3
  export default defineEventHandler<Promise<PreviewContentPage>>(
4
4
  async (event) => {
5
5
  // <typeOrPart>/<fullOrShortId>.json
6
- const strContentTypePath = event.context.params!.contentTypePath.slice(
6
+ const strContentTypePath = event.context.params!.contentTypePath!.slice(
7
7
  0,
8
8
  -5,
9
9
  );
@@ -17,7 +17,6 @@ export default defineEventHandler<Promise<PreviewContentPage>>(
17
17
  columns: {
18
18
  title: true,
19
19
  description: true,
20
- // TODO: decoration svg!
21
20
  },
22
21
  where: eq(ERUDIT.db.schema.content.fullId, fullId),
23
22
  }))!;
@@ -1,73 +1,58 @@
1
- import { mixSchema, uniqueName2Id, type ProseElement } from '@jsprose/core';
2
1
  import { headingSchema } from '@erudit-js/prose/elements/heading/core';
3
2
  import { detailsSchema } from '@erudit-js/prose/elements/details/core';
3
+ import { isProseElement, makeProseElement, mixSchema } from 'tsprose';
4
4
 
5
5
  export default defineEventHandler<Promise<PreviewContentUnique>>(
6
6
  async (event) => {
7
7
  // <typeOrPart>/<fullContentId>/<uniqueName>.json
8
8
  const strContentTypePathUnique =
9
- event.context.params!.contentTypePathUnique.slice(0, -5);
9
+ event.context.params!.contentTypePathUnique!.slice(0, -5);
10
10
  const parts = strContentTypePathUnique.split('/');
11
-
12
11
  const strContentTypePath = parts.slice(0, -1).join('/');
13
- const uniqueName = parts.at(-1)!;
14
-
12
+ const uniqueName = decodeURIComponent(parts.at(-1)!);
15
13
  const contentTypePath = parseContentTypePath(strContentTypePath);
16
14
  const fullId = contentTypePath.contentId;
17
15
  const navNode = ERUDIT.contentNav.getNodeOrThrow(fullId);
18
16
  const shortId = navNode.shortId;
19
-
20
17
  const contentTitle = await ERUDIT.repository.content.title(fullId);
21
-
22
18
  const unique = await ERUDIT.repository.content.unique(
23
19
  fullId,
24
20
  contentTypePath.type === 'topic' ? contentTypePath.topicPart : 'page',
25
21
  uniqueName,
26
22
  );
27
-
28
23
  const uniqueProse = await (async () => {
29
- if (unique.prose.schemaName === headingSchema.name) {
24
+ if (isProseElement(unique.prose, headingSchema)) {
30
25
  return await ERUDIT.repository.content.uniqueHeading(
31
26
  fullId,
32
27
  contentTypePath.type === 'topic' ? contentTypePath.topicPart : 'page',
33
- uniqueName,
28
+ unique.prose.id,
34
29
  );
35
- } else if (unique.prose.schemaName === detailsSchema.name) {
36
- const mix: ProseElement<typeof mixSchema> = {
37
- __JSPROSE_element: true,
38
- schemaName: mixSchema.name,
39
- children: unique.prose.children!,
40
- } as ProseElement<typeof mixSchema>;
41
-
42
- return mix;
30
+ } else if (isProseElement(unique.prose, detailsSchema)) {
31
+ return makeProseElement({
32
+ schema: mixSchema,
33
+ elementHandler: (element) => {
34
+ element.children = unique.prose.children!;
35
+ },
36
+ });
43
37
  } else {
44
38
  return unique.prose;
45
39
  }
46
40
  })();
47
-
48
41
  const finalizedProse = await ERUDIT.repository.prose.finalize(uniqueProse);
49
-
50
42
  const link = (() => {
51
43
  if (contentTypePath.type === 'topic') {
52
- return PAGES.topic(
53
- contentTypePath.topicPart,
54
- shortId,
55
- uniqueName2Id(uniqueName),
56
- );
44
+ return PAGES.topic(contentTypePath.topicPart, shortId, unique.prose.id);
57
45
  }
58
-
59
- return PAGES[contentTypePath.type](shortId, uniqueName2Id(uniqueName));
46
+ return PAGES[contentTypePath.type](shortId, unique.prose.id);
60
47
  })();
61
-
62
48
  const previewContentUnique: PreviewContentUnique = {
63
- schemaName: unique.prose.schemaName,
49
+ schemaName: unique.prose.schema.name,
64
50
  elementTitle: unique.title || undefined,
65
- fadeOverlay: unique.prose.schemaName === headingSchema.name,
51
+ fadeOverlay: isProseElement(unique.prose, headingSchema),
66
52
  contentTitle,
67
53
  link,
68
54
  ...finalizedProse,
69
55
  };
70
-
71
56
  return previewContentUnique;
72
57
  },
73
58
  );
@@ -1,12 +1,14 @@
1
1
  import { resolve } from 'node:path';
2
+ import { readFileSync } from 'node:fs';
2
3
  import { build, type Plugin } from 'esbuild';
3
4
 
4
5
  import { STATIC_ASSET_EXTENSIONS } from '#layers/erudit/server/erudit/prose/transform/extensions';
5
6
  import { createGlobalContent } from '@erudit-js/core/content/global';
7
+ import { coreElements } from '#erudit/prose/global';
6
8
 
7
9
  export default defineEventHandler<Promise<string>>(async (event) => {
8
10
  // <filepathToScriptFile>.js
9
- const problemScriptPath = event.context.params!.problemScriptPath.slice(
11
+ const problemScriptPath = event.context.params!.problemScriptPath!.slice(
10
12
  0,
11
13
  -3,
12
14
  ); // remove .js
@@ -23,14 +25,14 @@ export default defineEventHandler<Promise<string>>(async (event) => {
23
25
  $CONTRIBUTOR: '{}',
24
26
  },
25
27
  jsx: 'automatic',
26
- plugins: [jsxRuntimePlugin, staticFilesPlugin],
28
+ plugins: [jsxRuntimePlugin, proseGlobalsPlugin, staticFilesPlugin],
27
29
  alias: {
28
30
  '#project': ERUDIT.paths.project() + '/',
29
31
  '#content': ERUDIT.paths.project('content') + '/',
30
32
  },
31
33
  });
32
34
 
33
- let code = buildResult.outputFiles[0].text;
35
+ let code = buildResult.outputFiles[0]!.text;
34
36
 
35
37
  // Transform $CONTENT patterns to link objects
36
38
  code = code.replace(/\$CONTENT(\.[a-zA-Z_$][\w$]*)+/g, (match) => {
@@ -71,6 +73,77 @@ const jsxRuntimePlugin: Plugin = {
71
73
  },
72
74
  };
73
75
 
76
+ // Names that are available on globalThis and should not be bundled as real imports
77
+ const globalNames = new Set<string>([
78
+ // JSX runtime
79
+ 'jsx',
80
+ '_jsx',
81
+ 'jsxs',
82
+ '_jsxs',
83
+ 'Fragment',
84
+ '_Fragment',
85
+ // Prose tag names registered in globalThis
86
+ ...Object.values(coreElements).flatMap((el: any) =>
87
+ (el.tags ?? []).map((t: any) => String(t.tagName)),
88
+ ),
89
+ ]);
90
+
91
+ // Pre-transform: rewrite any import of a known globalThis tag name → const from globalThis.
92
+ // Non-tag imports are left as real imports and bundled normally.
93
+ // Applies recursively to every .ts/.tsx file esbuild processes (including utility files).
94
+ const proseGlobalsPlugin: Plugin = {
95
+ name: 'prose-globals',
96
+ setup(build) {
97
+ build.onLoad({ filter: /\.[jt]sx?$/ }, (args) => {
98
+ const source = readFileSync(args.path, 'utf8');
99
+
100
+ const transformed = source.replace(
101
+ /^import\s+\{([^}]+)\}\s+from\s+(['"])([^'"]+)\2.*$/gm,
102
+ (_match, bindings: string, _quote: string, pkg: string) => {
103
+ const keepParts: string[] = [];
104
+ const shimLines: string[] = [];
105
+
106
+ for (const part of bindings
107
+ .split(',')
108
+ .map((s) => s.trim())
109
+ .filter(Boolean)) {
110
+ // handle "ExportName as LocalName"
111
+ const m = part.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
112
+ if (!m) {
113
+ keepParts.push(part);
114
+ continue;
115
+ }
116
+ const localName = m[2] ?? m[1]!;
117
+ if (globalNames.has(localName)) {
118
+ shimLines.push(
119
+ `const ${localName} = globalThis[${JSON.stringify(localName)}];`,
120
+ );
121
+ } else {
122
+ keepParts.push(part);
123
+ }
124
+ }
125
+
126
+ const lines: string[] = [];
127
+ if (keepParts.length > 0)
128
+ lines.push(`import { ${keepParts.join(', ')} } from '${pkg}';`);
129
+ lines.push(...shimLines);
130
+ return lines.join('\n');
131
+ },
132
+ );
133
+
134
+ const ext = (args.path.match(/[jt]sx?$/)?.[0] ?? 'js') as
135
+ | 'js'
136
+ | 'jsx'
137
+ | 'ts'
138
+ | 'tsx';
139
+ return {
140
+ contents: transformed,
141
+ loader: ext,
142
+ };
143
+ });
144
+ },
145
+ };
146
+
74
147
  const staticFilesPlugin: Plugin = {
75
148
  name: 'static-files',
76
149
  setup(build) {
@@ -80,7 +153,11 @@ const staticFilesPlugin: Plugin = {
80
153
  },
81
154
  async (args) => {
82
155
  const absPath = resolve(args.path).replace(/\\/g, '/');
83
- const contents = `export default ${JSON.stringify(absPath)};`;
156
+ const projectPath = ERUDIT.paths.project();
157
+ const relPath = absPath.startsWith(projectPath + '/')
158
+ ? absPath.slice(projectPath.length + 1)
159
+ : absPath;
160
+ const contents = `export default ${JSON.stringify(relPath)};`;
84
161
  return { contents, loader: 'js' };
85
162
  },
86
163
  );