erudit 4.1.0 → 4.2.0-dev.1
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.
- package/app/assets/icons/update.svg +3 -0
- package/app/components/Prose.vue +7 -7
- package/app/components/SmartMedia.vue +4 -4
- package/app/components/aside/major/contentNav/PaneBookNav.vue +1 -4
- package/app/components/aside/minor/content/Toc.vue +24 -1
- package/app/components/aside/minor/content/TocItem.vue +2 -1
- package/app/components/aside/minor/news/AsideMinorNews.vue +1 -3
- package/app/components/aside/minor/news/NewsItem.vue +3 -4
- package/app/components/aside/minor/news/RenderNewsElement.vue +4 -11
- package/app/components/aside/minor/news/elements/Mix.vue +2 -3
- package/app/components/aside/minor/news/elements/P.vue +3 -3
- package/app/components/aside/minor/news/elements/Ref.vue +3 -3
- package/app/components/aside/minor/news/elements/Text.vue +2 -2
- package/app/components/main/MainContentChild.vue +3 -3
- package/app/components/main/{MainQuickLink.vue → MainKeyLink.vue} +11 -11
- package/app/components/main/{MainQuickLinks.vue → MainKeyLinks.vue} +7 -7
- package/app/components/main/MainQuoteLoader.vue +2 -4
- package/app/components/main/MainStickyHeader.vue +1 -1
- package/app/components/main/MainStickyHeaderPreamble.vue +6 -2
- package/app/components/main/MainTopicPartPage.vue +8 -3
- package/app/components/main/connections/DepUnique.vue +45 -0
- package/app/components/main/connections/Deps.vue +15 -5
- package/app/components/main/connections/Externals.vue +4 -4
- package/app/components/main/connections/MainConnections.vue +1 -0
- package/app/components/main/contentStats/ItemLastChanged.vue +68 -0
- package/app/components/main/contentStats/MainContentStats.vue +36 -28
- package/app/components/preview/PreviewScreen.vue +2 -2
- package/app/components/preview/screen/ContentPage.vue +1 -4
- package/app/components/preview/screen/Unique.vue +3 -5
- package/app/composables/appElements.ts +2 -4
- package/app/composables/asideMajorPane.ts +3 -3
- package/app/composables/fetchJson.ts +4 -0
- package/app/composables/lastChanged.ts +28 -0
- package/app/composables/mainContent.ts +1 -4
- package/app/composables/og.ts +43 -35
- package/app/composables/phrases.ts +0 -3
- package/app/composables/scrollUp.ts +1 -1
- package/app/pages/book/[...bookId].vue +5 -1
- package/app/pages/contributor/[contributorId].vue +3 -5
- package/app/pages/contributors.vue +1 -1
- package/app/pages/group/[...groupId].vue +5 -1
- package/app/pages/index.vue +1 -1
- package/app/pages/page/[...pageId].vue +8 -3
- package/app/pages/sponsors.vue +1 -1
- package/app/plugins/appSetup/index.ts +0 -5
- package/app/plugins/fetchJson.ts +11 -0
- package/app/plugins/prerender.server.ts +1 -1
- package/app/router.options.ts +1 -1
- package/modules/erudit/globals/prose.ts +3 -4
- package/modules/erudit/setup/elements/appTemplate.ts +6 -7
- package/modules/erudit/setup/elements/{globalTypes.ts → elementGlobalTypes.ts} +21 -21
- package/modules/erudit/setup/elements/globalTemplate.ts +29 -23
- package/modules/erudit/setup/elements/setup.ts +18 -16
- package/modules/erudit/setup/elements/shared.ts +2 -2
- package/modules/erudit/setup/elements/tagsTable.ts +1 -1
- package/modules/erudit/setup/runtimeConfig.ts +2 -0
- package/nuxt.config.ts +2 -2
- package/package.json +14 -13
- package/server/api/main/content/[...contentTypePath].ts +5 -4
- package/server/api/prerender/content.ts +1 -3
- package/server/api/preview/contentPage/[...contentTypePath].ts +1 -2
- package/server/api/preview/contentUnique/[...contentTypePathUnique].ts +16 -31
- package/server/api/problemScript/[...problemScriptPath].ts +73 -4
- package/server/erudit/content/global/build.ts +21 -7
- package/server/erudit/content/nav/build.ts +4 -4
- package/server/erudit/content/repository/children.ts +3 -3
- package/server/erudit/content/repository/deps.ts +127 -39
- package/server/erudit/content/repository/elementSnippets.ts +16 -16
- package/server/erudit/content/repository/stats.ts +30 -22
- package/server/erudit/content/repository/topicParts.ts +1 -1
- package/server/erudit/content/repository/unique.ts +14 -15
- package/server/erudit/content/resolve/index.ts +6 -1
- package/server/erudit/content/resolve/page.ts +15 -35
- package/server/erudit/content/resolve/topic.ts +33 -164
- package/server/erudit/content/resolve/utils/insertContentItem.ts +2 -2
- package/server/erudit/content/resolve/utils/insertContentResolved.ts +82 -31
- package/server/erudit/content/search.ts +5 -22
- package/server/erudit/contributors/build.ts +7 -8
- package/server/erudit/db/repository/pushFile.ts +10 -3
- package/server/erudit/db/repository/pushProblemScript.ts +14 -3
- package/server/erudit/db/schema/contentDeps.ts +3 -0
- package/server/erudit/db/schema/contentSnippets.ts +3 -3
- package/server/erudit/db/schema/contentUniques.ts +2 -2
- package/server/erudit/db/schema/contributors.ts +2 -2
- package/server/erudit/db/schema/news.ts +2 -2
- package/server/erudit/db/schema/pages.ts +2 -2
- package/server/erudit/db/schema/topics.ts +4 -4
- package/server/erudit/global.ts +4 -0
- package/server/erudit/importer.ts +16 -8
- package/server/erudit/index.ts +0 -3
- package/server/erudit/language/list/en.ts +1 -0
- package/server/erudit/language/list/ru.ts +1 -0
- package/server/erudit/news/build.ts +6 -6
- package/server/erudit/news/repository/batch.ts +2 -2
- package/server/erudit/prose/repository/finalize.ts +22 -25
- package/server/erudit/prose/repository/get.ts +3 -5
- package/server/erudit/prose/repository/rawToProse.ts +31 -0
- package/server/erudit/prose/storage/callout.ts +9 -7
- package/server/erudit/prose/storage/image.ts +8 -11
- package/server/erudit/prose/storage/link.ts +24 -32
- package/server/erudit/prose/storage/problemScript.ts +8 -14
- package/server/erudit/prose/storage/video.ts +9 -7
- package/server/erudit/repository.ts +4 -4
- package/server/routes/file/[...path].ts +1 -1
- package/shared/types/contentChildren.ts +5 -2
- package/shared/types/contentConnections.ts +9 -0
- package/shared/types/elementSnippet.ts +1 -1
- package/shared/types/indexPage.ts +3 -0
- package/shared/types/language.ts +1 -83
- package/shared/types/mainContent.ts +11 -5
- package/shared/types/news.ts +2 -2
- package/shared/types/preview.ts +3 -2
- package/shared/types/runtimeConfig.ts +1 -0
- package/shared/types/search.ts +2 -0
- package/shared/utils/pages.ts +4 -2
- package/shared/utils/stringColor.ts +16 -6
- package/server/erudit/prose/repository/resolve.ts +0 -17
- 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 {
|
|
26
|
-
import {
|
|
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:
|
|
45
|
-
|
|
46
|
-
]
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
113
|
+
elementData.coreElements.push({
|
|
113
114
|
schemaName,
|
|
114
|
-
|
|
115
|
+
tags: {},
|
|
115
116
|
});
|
|
116
117
|
|
|
117
|
-
if (coreElement.
|
|
118
|
-
for (
|
|
119
|
-
|
|
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.
|
|
127
|
-
|
|
128
|
-
]
|
|
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
|
-
|
|
149
|
+
createElementGlobalTypes(elementsData);
|
|
148
150
|
createGlobalTemplate(nuxt, elementsData);
|
|
149
151
|
createAppTemplate(nuxt, elementsData);
|
|
150
152
|
|
|
@@ -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.
|
|
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
|
|
53
|
+
// Prevent inlining some packages
|
|
54
54
|
external(source) {
|
|
55
|
-
const ignore = ['jiti', '
|
|
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.
|
|
3
|
+
"version": "4.2.0-dev.1",
|
|
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.
|
|
28
|
-
"@erudit-js/core": "4.
|
|
29
|
-
"@erudit-js/prose": "4.
|
|
27
|
+
"@erudit-js/cli": "4.2.0-dev.1",
|
|
28
|
+
"@erudit-js/core": "4.2.0-dev.1",
|
|
29
|
+
"@erudit-js/prose": "4.2.0-dev.1",
|
|
30
30
|
"unslash": "^2.0.0",
|
|
31
31
|
"@floating-ui/vue": "^1.1.10",
|
|
32
|
-
"
|
|
33
|
-
"@tailwindcss/vite": "^4.
|
|
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.
|
|
38
|
+
"drizzle-kit": "^0.31.9",
|
|
39
39
|
"drizzle-orm": "^0.45.1",
|
|
40
|
-
"esbuild": "^0.27.
|
|
40
|
+
"esbuild": "^0.27.3",
|
|
41
41
|
"flexsearch": "^0.8.212",
|
|
42
|
-
"glob": "^13.0.
|
|
42
|
+
"glob": "^13.0.6",
|
|
43
43
|
"image-size": "^2.0.2",
|
|
44
44
|
"jiti": "^2.6.1",
|
|
45
|
-
"nuxt": "4.3.
|
|
45
|
+
"nuxt": "4.3.1",
|
|
46
46
|
"nuxt-my-icons": "1.2.2",
|
|
47
47
|
"perfect-debounce": "^2.1.0",
|
|
48
|
-
"tailwindcss": "^4.
|
|
49
|
-
"vue": "^3.5.
|
|
50
|
-
"vue-router": "^5.0.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
28
|
+
unique.prose.id,
|
|
34
29
|
);
|
|
35
|
-
} else if (unique.prose
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
49
|
+
schemaName: unique.prose.schema.name,
|
|
64
50
|
elementTitle: unique.title || undefined,
|
|
65
|
-
fadeOverlay: unique.prose
|
|
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
|
|
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]
|
|
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,69 @@ const jsxRuntimePlugin: Plugin = {
|
|
|
71
73
|
},
|
|
72
74
|
};
|
|
73
75
|
|
|
76
|
+
// Collect all tag names that are registered in globalThis
|
|
77
|
+
const proseTagNames = new Set<string>(
|
|
78
|
+
Object.values(coreElements).flatMap((el: any) =>
|
|
79
|
+
(el.tags ?? []).map((t: any) => String(t.tagName)),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Pre-transform: rewrite any import of a known globalThis tag name → const from globalThis.
|
|
84
|
+
// Non-tag imports are left as real imports and bundled normally.
|
|
85
|
+
// Applies recursively to every .ts/.tsx file esbuild processes (including utility files).
|
|
86
|
+
const proseGlobalsPlugin: Plugin = {
|
|
87
|
+
name: 'prose-globals',
|
|
88
|
+
setup(build) {
|
|
89
|
+
build.onLoad({ filter: /\.[jt]sx?$/ }, (args) => {
|
|
90
|
+
const source = readFileSync(args.path, 'utf8');
|
|
91
|
+
|
|
92
|
+
const transformed = source.replace(
|
|
93
|
+
/^import\s+\{([^}]+)\}\s+from\s+(['"])([^'"]+)\2.*$/gm,
|
|
94
|
+
(_match, bindings: string, _quote: string, pkg: string) => {
|
|
95
|
+
const keepParts: string[] = [];
|
|
96
|
+
const shimLines: string[] = [];
|
|
97
|
+
|
|
98
|
+
for (const part of bindings
|
|
99
|
+
.split(',')
|
|
100
|
+
.map((s) => s.trim())
|
|
101
|
+
.filter(Boolean)) {
|
|
102
|
+
// handle "ExportName as LocalName"
|
|
103
|
+
const m = part.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
|
|
104
|
+
if (!m) {
|
|
105
|
+
keepParts.push(part);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const localName = m[2] ?? m[1]!;
|
|
109
|
+
if (proseTagNames.has(localName)) {
|
|
110
|
+
shimLines.push(
|
|
111
|
+
`const ${localName} = globalThis[${JSON.stringify(localName)}];`,
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
keepParts.push(part);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const lines: string[] = [];
|
|
119
|
+
if (keepParts.length > 0)
|
|
120
|
+
lines.push(`import { ${keepParts.join(', ')} } from '${pkg}';`);
|
|
121
|
+
lines.push(...shimLines);
|
|
122
|
+
return lines.join('\n');
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const ext = (args.path.match(/[jt]sx?$/)?.[0] ?? 'js') as
|
|
127
|
+
| 'js'
|
|
128
|
+
| 'jsx'
|
|
129
|
+
| 'ts'
|
|
130
|
+
| 'tsx';
|
|
131
|
+
return {
|
|
132
|
+
contents: transformed,
|
|
133
|
+
loader: ext,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
74
139
|
const staticFilesPlugin: Plugin = {
|
|
75
140
|
name: 'static-files',
|
|
76
141
|
setup(build) {
|
|
@@ -80,7 +145,11 @@ const staticFilesPlugin: Plugin = {
|
|
|
80
145
|
},
|
|
81
146
|
async (args) => {
|
|
82
147
|
const absPath = resolve(args.path).replace(/\\/g, '/');
|
|
83
|
-
const
|
|
148
|
+
const projectPath = ERUDIT.paths.project();
|
|
149
|
+
const relPath = absPath.startsWith(projectPath + '/')
|
|
150
|
+
? absPath.slice(projectPath.length + 1)
|
|
151
|
+
: absPath;
|
|
152
|
+
const contents = `export default ${JSON.stringify(relPath)};`;
|
|
84
153
|
return { contents, loader: 'js' };
|
|
85
154
|
},
|
|
86
155
|
);
|
|
@@ -9,6 +9,8 @@ let initialBuild = true;
|
|
|
9
9
|
|
|
10
10
|
const contentRoot = () => ERUDIT.paths.project('content');
|
|
11
11
|
|
|
12
|
+
export let builtLinkObject: Record<string, any> | null = null;
|
|
13
|
+
|
|
12
14
|
export async function buildGlobalContent() {
|
|
13
15
|
ERUDIT.log.debug.start('Building global content...');
|
|
14
16
|
|
|
@@ -21,6 +23,7 @@ export async function buildGlobalContent() {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const linkObject = await buildLinkObject();
|
|
26
|
+
builtLinkObject = linkObject;
|
|
24
27
|
|
|
25
28
|
const linkTypes = linkObjectToTypes(linkObject);
|
|
26
29
|
writeFileSync(
|
|
@@ -51,6 +54,10 @@ function linkObjectToTypes(linkObject: any): string {
|
|
|
51
54
|
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase());
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
function isValidIdentifier(key: string): boolean {
|
|
58
|
+
return /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(key);
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
function processObject(obj: any, level: number): string {
|
|
55
62
|
const lines: string[] = [];
|
|
56
63
|
|
|
@@ -58,6 +65,9 @@ function linkObjectToTypes(linkObject: any): string {
|
|
|
58
65
|
if (key === '__jsdoc' || key === '__typeguard') continue;
|
|
59
66
|
|
|
60
67
|
const camelKey = toCamelCase(key);
|
|
68
|
+
const outputKey = isValidIdentifier(camelKey)
|
|
69
|
+
? camelKey
|
|
70
|
+
: `'${camelKey}'`;
|
|
61
71
|
|
|
62
72
|
// Add JSDoc comment if present
|
|
63
73
|
if (value && typeof value === 'object' && value.__jsdoc) {
|
|
@@ -77,11 +87,11 @@ function linkObjectToTypes(linkObject: any): string {
|
|
|
77
87
|
const typeguard = value?.__typeguard || 'GlobalContentItemTypeguard';
|
|
78
88
|
|
|
79
89
|
if (hasNestedProps) {
|
|
80
|
-
lines.push(indent(level) + `${
|
|
90
|
+
lines.push(indent(level) + `${outputKey}: ${typeguard} & {`);
|
|
81
91
|
lines.push(processObject(value, level + 1));
|
|
82
92
|
lines.push(indent(level) + `}`);
|
|
83
93
|
} else {
|
|
84
|
-
lines.push(indent(level) + `${
|
|
94
|
+
lines.push(indent(level) + `${outputKey}: ${typeguard} & {}`);
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
|
|
@@ -118,7 +128,7 @@ async function buildLinkObject() {
|
|
|
118
128
|
|
|
119
129
|
// Navigate through parent parts
|
|
120
130
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
121
|
-
cursor = cursor[pathParts[i]];
|
|
131
|
+
cursor = cursor[pathParts[i]!];
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
//
|
|
@@ -230,10 +240,10 @@ ${jsdoc}
|
|
|
230
240
|
}
|
|
231
241
|
|
|
232
242
|
function tryGetTitle(moduleContent: string) {
|
|
233
|
-
const titleMatch = moduleContent.match(/title:\s*['"`](.*?)
|
|
243
|
+
const titleMatch = moduleContent.match(/title:\s*(['"`])(.*?)\1/);
|
|
234
244
|
|
|
235
245
|
if (titleMatch) {
|
|
236
|
-
return titleMatch[
|
|
246
|
+
return titleMatch[2]!.trim();
|
|
237
247
|
}
|
|
238
248
|
}
|
|
239
249
|
|
|
@@ -259,7 +269,7 @@ function tryGetUniquesObject(
|
|
|
259
269
|
const uniquesContent = uniquesMatch[1];
|
|
260
270
|
|
|
261
271
|
// Parse key-value pairs from uniques object
|
|
262
|
-
const lines = uniquesContent
|
|
272
|
+
const lines = uniquesContent!.split('\n');
|
|
263
273
|
const result: any = {};
|
|
264
274
|
|
|
265
275
|
for (const line of lines) {
|
|
@@ -273,7 +283,11 @@ function tryGetUniquesObject(
|
|
|
273
283
|
continue;
|
|
274
284
|
}
|
|
275
285
|
|
|
276
|
-
|
|
286
|
+
// Support bracket notation ['any string'], quoted keys "any string" / 'any string', and plain identifiers
|
|
287
|
+
const pairMatch =
|
|
288
|
+
line.match(/\[['"](.*?)['"]\]:\s*(\w+)/) ||
|
|
289
|
+
line.match(/['"](.*?)['"]:\s*(\w+)/) ||
|
|
290
|
+
line.match(/(\w+):\s*(\w+)/);
|
|
277
291
|
if (!pairMatch) {
|
|
278
292
|
continue;
|
|
279
293
|
}
|
|
@@ -216,16 +216,16 @@ function parseContentPath(relPath: string):
|
|
|
216
216
|
|
|
217
217
|
for (let i = 0; i < parts.length; i++) {
|
|
218
218
|
const part = parts[i];
|
|
219
|
-
const typeDelimiterPos = part
|
|
219
|
+
const typeDelimiterPos = part!.search(/[+-]/);
|
|
220
220
|
if (typeDelimiterPos <= 0) return;
|
|
221
221
|
|
|
222
|
-
const posStr = part
|
|
222
|
+
const posStr = part!.slice(0, typeDelimiterPos);
|
|
223
223
|
if (!/^\d+$/.test(posStr)) return;
|
|
224
224
|
const posNum = Number(posStr);
|
|
225
225
|
|
|
226
|
-
const typeDelimiter = part
|
|
226
|
+
const typeDelimiter = part!.charAt(typeDelimiterPos);
|
|
227
227
|
skip = typeDelimiter === '+';
|
|
228
|
-
idPart = part
|
|
228
|
+
idPart = part!.slice(typeDelimiterPos + 1);
|
|
229
229
|
if (!idPart) return;
|
|
230
230
|
|
|
231
231
|
fullId += `/${idPart}`;
|
|
@@ -24,7 +24,7 @@ export async function getContentChildren(
|
|
|
24
24
|
const elementSnippets = await ERUDIT.repository.content.elementSnippets(
|
|
25
25
|
childNode.fullId,
|
|
26
26
|
);
|
|
27
|
-
const
|
|
27
|
+
const keyLinks = elementSnippets?.filter((snippet) => snippet.key);
|
|
28
28
|
const stats = await ERUDIT.repository.content.stats(childNode.fullId);
|
|
29
29
|
|
|
30
30
|
const child: MainContentChildrenItem = {
|
|
@@ -37,8 +37,8 @@ export async function getContentChildren(
|
|
|
37
37
|
child.description = description;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
if (
|
|
41
|
-
child.
|
|
40
|
+
if (keyLinks && keyLinks.length > 0) {
|
|
41
|
+
child.keyLinks = keyLinks;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
if (stats) {
|