erudit 4.1.1 → 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 +14 -2
- package/app/components/main/connections/Externals.vue +1 -1
- 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 +101 -13
- 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/page.ts +15 -35
- package/server/erudit/content/resolve/topic.ts +33 -164
- package/server/erudit/content/resolve/utils/insertContentResolved.ts +74 -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
|
@@ -1,50 +1,58 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import ItemElement from './ItemElement.vue';
|
|
3
|
+
import ItemLastChanged from './ItemLastChanged.vue';
|
|
3
4
|
import ItemMaterials from './ItemMaterials.vue';
|
|
4
5
|
|
|
5
|
-
defineProps<{
|
|
6
|
+
const props = defineProps<{
|
|
6
7
|
mode: 'single' | 'children';
|
|
7
8
|
stats?: ContentStats;
|
|
9
|
+
contentRelativePath?: string;
|
|
8
10
|
}>();
|
|
9
11
|
|
|
10
12
|
const phrase = await usePhrases('stats');
|
|
13
|
+
const lastChangedSource = useLastChangedSource(() => props.contentRelativePath);
|
|
11
14
|
</script>
|
|
12
15
|
|
|
13
16
|
<template>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
mode="detailed"
|
|
25
|
-
/>
|
|
26
|
-
<ItemElement
|
|
27
|
-
v-if="stats.elements"
|
|
28
|
-
v-for="(count, schemaName) of stats.elements"
|
|
29
|
-
:schemaName
|
|
30
|
-
:count
|
|
31
|
-
mode="detailed"
|
|
32
|
-
/>
|
|
33
|
-
</div>
|
|
34
|
-
</section>
|
|
35
|
-
<div v-else class="gap-small micro:gap-normal text-main-sm flex flex-wrap">
|
|
17
|
+
<section
|
|
18
|
+
v-if="mode === 'single' && (stats || lastChangedSource)"
|
|
19
|
+
class="px-main py-main-half"
|
|
20
|
+
>
|
|
21
|
+
<MainSubTitle :title="phrase.stats + ':'" />
|
|
22
|
+
<div
|
|
23
|
+
class="micro:justify-start gap-small micro:gap-normal flex flex-wrap
|
|
24
|
+
justify-center"
|
|
25
|
+
>
|
|
26
|
+
<ItemLastChanged v-if="lastChangedSource" :source="lastChangedSource" />
|
|
36
27
|
<ItemMaterials
|
|
37
|
-
v-if="stats
|
|
28
|
+
v-if="stats?.materials"
|
|
38
29
|
:count="stats.materials"
|
|
39
|
-
mode="
|
|
30
|
+
mode="detailed"
|
|
40
31
|
/>
|
|
41
32
|
<ItemElement
|
|
42
|
-
v-if="stats
|
|
33
|
+
v-if="stats?.elements"
|
|
43
34
|
v-for="(count, schemaName) of stats.elements"
|
|
44
35
|
:schemaName
|
|
45
36
|
:count
|
|
46
|
-
mode="
|
|
37
|
+
mode="detailed"
|
|
47
38
|
/>
|
|
48
39
|
</div>
|
|
49
|
-
</
|
|
40
|
+
</section>
|
|
41
|
+
<div
|
|
42
|
+
v-else-if="mode === 'children' && stats"
|
|
43
|
+
class="gap-small micro:gap-normal text-main-sm flex flex-wrap"
|
|
44
|
+
>
|
|
45
|
+
<ItemMaterials
|
|
46
|
+
v-if="stats.materials"
|
|
47
|
+
:count="stats.materials"
|
|
48
|
+
mode="compact"
|
|
49
|
+
/>
|
|
50
|
+
<ItemElement
|
|
51
|
+
v-if="stats.elements"
|
|
52
|
+
v-for="(count, schemaName) of stats.elements"
|
|
53
|
+
:schemaName
|
|
54
|
+
:count
|
|
55
|
+
mode="compact"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
50
58
|
</template>
|
|
@@ -30,14 +30,14 @@ const { closePreview, hasPreviousRequest, setPreviousPreview } = usePreview();
|
|
|
30
30
|
class="micro:text-sm overflow-hidden text-xs font-bold text-nowrap
|
|
31
31
|
overflow-ellipsis"
|
|
32
32
|
>
|
|
33
|
-
{{ main }}
|
|
33
|
+
{{ formatText(main) }}
|
|
34
34
|
</div>
|
|
35
35
|
<div
|
|
36
36
|
v-if="secondary"
|
|
37
37
|
class="text-text-muted text-tiny micro:text-xs overflow-hidden
|
|
38
38
|
text-nowrap overflow-ellipsis"
|
|
39
39
|
>
|
|
40
|
-
{{ secondary }}
|
|
40
|
+
{{ formatText(secondary) }}
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
43
|
<div class="gap-small flex shrink-0 items-center">
|
|
@@ -6,13 +6,10 @@ const { request } = defineProps<{ request: PreviewRequestContentPage }>();
|
|
|
6
6
|
const contentTypeKey =
|
|
7
7
|
request.contentType === 'topic' ? request.topicPart : request.contentType;
|
|
8
8
|
|
|
9
|
-
const previewData: PreviewContentPage = await
|
|
9
|
+
const previewData: PreviewContentPage = await fetchJson(
|
|
10
10
|
'/api/preview/contentPage/' +
|
|
11
11
|
stringifyContentTypePath(contentTypeKey, request.fullId) +
|
|
12
12
|
'.json',
|
|
13
|
-
{
|
|
14
|
-
responseType: 'json',
|
|
15
|
-
},
|
|
16
13
|
);
|
|
17
14
|
|
|
18
15
|
const icon = ICONS[contentTypeKey];
|
|
@@ -6,12 +6,9 @@ const { request } = defineProps<{ request: PreviewRequestUnique }>();
|
|
|
6
6
|
const contentTypeKey =
|
|
7
7
|
request.contentType === 'topic' ? request.topicPart : request.contentType;
|
|
8
8
|
|
|
9
|
-
const previewData = await
|
|
9
|
+
const previewData = await fetchJson<PreviewContentUnique>(
|
|
10
10
|
`/api/preview/contentUnique/${stringifyContentTypePath(contentTypeKey, request.contentFullId)}/${request.uniqueName}` +
|
|
11
11
|
'.json',
|
|
12
|
-
{
|
|
13
|
-
responseType: 'json',
|
|
14
|
-
},
|
|
15
12
|
);
|
|
16
13
|
|
|
17
14
|
const elementIcon = await getElementIcon(previewData.schemaName);
|
|
@@ -38,10 +35,11 @@ const secondary = (() => {
|
|
|
38
35
|
class="nice-scrollbars py-small relative max-h-[inherit] overflow-auto"
|
|
39
36
|
>
|
|
40
37
|
<Prose
|
|
41
|
-
:element="previewData.
|
|
38
|
+
:element="previewData.prose"
|
|
42
39
|
:storage="previewData.storage"
|
|
43
40
|
:urlPath="'/' + previewData.link.split('#')[0]"
|
|
44
41
|
:useHashUrl="false"
|
|
42
|
+
:setHtmlIds="false"
|
|
45
43
|
/>
|
|
46
44
|
<div
|
|
47
45
|
v-if="previewData.fadeOverlay"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const appElements: Record<string, AppElement> = { ..._appElements };
|
|
1
|
+
import { appElements } from '#erudit/prose/app';
|
|
2
|
+
export { appElements } from '#erudit/prose/app';
|
|
5
3
|
|
|
6
4
|
export async function initAppElements() {
|
|
7
5
|
for (const [name, element] of Object.entries(appElements)) {
|
|
@@ -78,9 +78,9 @@ export async function initGlobalContentNav() {
|
|
|
78
78
|
const payloadValue: FrontGlobalContentNav =
|
|
79
79
|
(nuxtApp.static.data[payloadKey] ||=
|
|
80
80
|
nuxtApp.payload.data[payloadKey] ||=
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
await fetchJson<FrontGlobalContentNav>(
|
|
82
|
+
'/api/aside/major/frontNav/global',
|
|
83
|
+
));
|
|
84
84
|
|
|
85
85
|
globalContentNav = payloadValue;
|
|
86
86
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type LastChangedSource =
|
|
2
|
+
| { type: 'date'; value: string }
|
|
3
|
+
| { type: 'github'; url: string; path: string };
|
|
4
|
+
|
|
5
|
+
export function useLastChangedSource(
|
|
6
|
+
contentRelativePath: MaybeRefOrGetter<string | undefined>,
|
|
7
|
+
) {
|
|
8
|
+
return computed((): LastChangedSource | null => {
|
|
9
|
+
const path = toValue(contentRelativePath);
|
|
10
|
+
if (!path) return null;
|
|
11
|
+
|
|
12
|
+
const debug = ERUDIT.config.debug.fakeApi.lastChanged;
|
|
13
|
+
if (debug === true) return { type: 'date', value: '2024-01-15T12:00:00Z' };
|
|
14
|
+
if (typeof debug === 'string') return { type: 'date', value: debug };
|
|
15
|
+
|
|
16
|
+
const repo = ERUDIT.config.repository;
|
|
17
|
+
if (!repo || repo.type !== 'github') return null;
|
|
18
|
+
const parts = repo.name.split('/');
|
|
19
|
+
if (parts.length !== 2) return null;
|
|
20
|
+
const [owner, repoName] = parts;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
type: 'github',
|
|
24
|
+
url: `https://api.github.com/repos/${owner}/${repoName}/commits`,
|
|
25
|
+
path: `content/${path}`,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -16,11 +16,8 @@ export async function useMainContent<TMainContent extends MainContent>(
|
|
|
16
16
|
let mainContentPromise = async () => {
|
|
17
17
|
if (payloadMainContent.contentPath !== contentTypePath) {
|
|
18
18
|
payloadMainContent.contentPath = contentTypePath;
|
|
19
|
-
payloadMainContent.mainContent = await
|
|
19
|
+
payloadMainContent.mainContent = await fetchJson<MainContent>(
|
|
20
20
|
'/api/main/content/' + contentTypePath,
|
|
21
|
-
{
|
|
22
|
-
responseType: 'json',
|
|
23
|
-
},
|
|
24
21
|
);
|
|
25
22
|
}
|
|
26
23
|
|
package/app/composables/og.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentSeo } from '@erudit-js/core/content/seo';
|
|
2
|
+
import { toSeoSnippet } from '@erudit-js/prose';
|
|
2
3
|
|
|
3
4
|
export function initOgSiteName() {
|
|
4
5
|
const siteTitle =
|
|
@@ -105,54 +106,61 @@ export async function useContentSeo(args: {
|
|
|
105
106
|
|
|
106
107
|
const seoSnippets = args.snippets?.filter((snippet) => snippet.seo);
|
|
107
108
|
|
|
109
|
+
const route = useRoute();
|
|
110
|
+
|
|
111
|
+
function findElementSnippet(elementId: string | undefined) {
|
|
112
|
+
if (!elementId || !seoSnippets) return undefined;
|
|
113
|
+
return seoSnippets.find((candidate) => {
|
|
114
|
+
const url = new URL(candidate.link, 'http://x');
|
|
115
|
+
const sameParam = url.searchParams.get('element') === elementId;
|
|
116
|
+
const sameType =
|
|
117
|
+
candidate.link.split('/')[1] ===
|
|
118
|
+
(args.contentTypePath.type === 'topic'
|
|
119
|
+
? args.contentTypePath.topicPart
|
|
120
|
+
: args.contentTypePath.type);
|
|
121
|
+
return sameParam && sameType;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// On a static site the server always serves the canonical-path HTML,
|
|
126
|
+
// so SSR-time element SEO is not possible. Instead we set SEO
|
|
127
|
+
// synchronously (before the first await) so the title/description are
|
|
128
|
+
// applied during the first client render without a visible flash.
|
|
129
|
+
// Google's crawler executes JS and will see the final values.
|
|
108
130
|
if (!import.meta.client || !seoSnippets || seoSnippets.length === 0) {
|
|
109
131
|
return;
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
const
|
|
113
|
-
|
|
134
|
+
const elementQuery = computed(
|
|
135
|
+
() => route.query.element as string | undefined,
|
|
136
|
+
);
|
|
114
137
|
|
|
115
138
|
const stopWatch = watch(
|
|
116
|
-
|
|
117
|
-
async (
|
|
118
|
-
const snippet =
|
|
119
|
-
? seoSnippets.find((candidate) => {
|
|
120
|
-
const sameHash = candidate.link.split('#')[1] === hash;
|
|
121
|
-
const sameType =
|
|
122
|
-
candidate.link.split('/')[1] ===
|
|
123
|
-
(args.contentTypePath.type === 'topic'
|
|
124
|
-
? args.contentTypePath.topicPart
|
|
125
|
-
: args.contentTypePath.type);
|
|
126
|
-
return sameHash && sameType;
|
|
127
|
-
})
|
|
128
|
-
: undefined;
|
|
129
|
-
|
|
139
|
+
elementQuery,
|
|
140
|
+
async (elementId) => {
|
|
141
|
+
const snippet = findElementSnippet(elementId);
|
|
130
142
|
if (!snippet) {
|
|
131
143
|
setupSeo(baseSeo);
|
|
132
144
|
return;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const description = (() => {
|
|
146
|
-
if (snippet.seo?.description) {
|
|
147
|
-
return snippet.seo.description;
|
|
148
|
-
} else {
|
|
149
|
-
return snippet.description;
|
|
150
|
-
}
|
|
151
|
-
})();
|
|
147
|
+
// ── Synchronous: set title/description immediately so there is no
|
|
148
|
+
// flash of the base-page title on first render.
|
|
149
|
+
const seoSnippet = toSeoSnippet(snippet)!;
|
|
150
|
+
const quickTitle = (() => seoSnippet.title)();
|
|
151
|
+
const quickDescription = (() => seoSnippet.description)();
|
|
152
|
+
setupSeo({
|
|
153
|
+
title: `${quickTitle} - ${seoSiteTitle}`,
|
|
154
|
+
description: quickDescription || '',
|
|
155
|
+
urlPath: snippet.link,
|
|
156
|
+
});
|
|
152
157
|
|
|
158
|
+
// ── Async: refine title with element-type phrase once loaded.
|
|
159
|
+
const elementPhrase = await getElementPhrase(snippet.schemaName);
|
|
160
|
+
const fullTitle = (() => seoSnippet.title)();
|
|
153
161
|
setupSeo({
|
|
154
|
-
title: `${
|
|
155
|
-
description:
|
|
162
|
+
title: `${fullTitle} [${elementPhrase.element_name}] - ${seoSiteTitle}`,
|
|
163
|
+
description: quickDescription || '',
|
|
156
164
|
urlPath: snippet.link,
|
|
157
165
|
});
|
|
158
166
|
},
|
|
@@ -69,9 +69,6 @@ export function usePhrases<const T extends readonly LanguagePhraseKey[]>(
|
|
|
69
69
|
try {
|
|
70
70
|
payloadPhraseValue = await $fetch<PayloadLanguagePhraseValue>(
|
|
71
71
|
`/api/language/phrase/${phraseKey}`,
|
|
72
|
-
{
|
|
73
|
-
responseType: 'json',
|
|
74
|
-
},
|
|
75
72
|
);
|
|
76
73
|
|
|
77
74
|
payloadLanguage.phrases[phraseKey] = payloadPhraseValue;
|
|
@@ -40,7 +40,11 @@ await useContentSeo({
|
|
|
40
40
|
<MainFlags :flags="mainContent.flags" />
|
|
41
41
|
<MainDescription :description="mainContent.description" />
|
|
42
42
|
<MainConnections :connections="mainContent.connections" />
|
|
43
|
-
<MainContentStats
|
|
43
|
+
<MainContentStats
|
|
44
|
+
mode="single"
|
|
45
|
+
:stats="mainContent.stats"
|
|
46
|
+
:contentRelativePath="mainContent.contentRelativePath"
|
|
47
|
+
/>
|
|
44
48
|
<div class="h-main-half"></div>
|
|
45
49
|
<MainAction
|
|
46
50
|
v-if="mainContent.children[0]"
|
|
@@ -17,11 +17,8 @@ const pageContributor = await (async () => {
|
|
|
17
17
|
return cachedContributor;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
let fetchedContributor: PageContributor = await
|
|
20
|
+
let fetchedContributor: PageContributor = await fetchJson(
|
|
21
21
|
'/api/contributor/page/' + contributorId.value,
|
|
22
|
-
{
|
|
23
|
-
responseType: 'json',
|
|
24
|
-
},
|
|
25
22
|
);
|
|
26
23
|
|
|
27
24
|
fetchedContributor =
|
|
@@ -136,9 +133,10 @@ useStandartSeo({
|
|
|
136
133
|
</MainSectionPreamble>
|
|
137
134
|
<MainSection v-if="pageContributor.description">
|
|
138
135
|
<Prose
|
|
139
|
-
:element="pageContributor.description.
|
|
136
|
+
:element="pageContributor.description.prose"
|
|
140
137
|
:storage="pageContributor.description.storage"
|
|
141
138
|
:useHashUrl="false"
|
|
139
|
+
:setHtmlIds="false"
|
|
142
140
|
/>
|
|
143
141
|
</MainSection>
|
|
144
142
|
</template>
|
|
@@ -11,7 +11,7 @@ const payloadKey = 'list-contributors';
|
|
|
11
11
|
const listContributors: ListContributor[] =
|
|
12
12
|
(nuxtApp.static.data[payloadKey] ||=
|
|
13
13
|
nuxtApp.payload.data[payloadKey] ||=
|
|
14
|
-
await
|
|
14
|
+
await fetchJson('/api/contributor/list'));
|
|
15
15
|
|
|
16
16
|
const phrase = await usePhrases(
|
|
17
17
|
'contributors',
|
|
@@ -42,7 +42,11 @@ await useContentSeo({
|
|
|
42
42
|
<MainFlags :flags="mainContent.flags" />
|
|
43
43
|
<MainDescription :description="mainContent.description" />
|
|
44
44
|
<MainConnections :connections="mainContent.connections" />
|
|
45
|
-
<MainContentStats
|
|
45
|
+
<MainContentStats
|
|
46
|
+
mode="single"
|
|
47
|
+
:stats="mainContent.stats"
|
|
48
|
+
:contentRelativePath="mainContent.contentRelativePath"
|
|
49
|
+
/>
|
|
46
50
|
<div class="h-main-half"></div>
|
|
47
51
|
<MainAction
|
|
48
52
|
v-if="mainContent.children[0]"
|
package/app/pages/index.vue
CHANGED
|
@@ -9,7 +9,7 @@ const payloadKey = 'index-page';
|
|
|
9
9
|
const indexPage: IndexPage =
|
|
10
10
|
(nuxtApp.static.data[payloadKey] ||=
|
|
11
11
|
nuxtApp.payload.data[payloadKey] ||=
|
|
12
|
-
await
|
|
12
|
+
await fetchJson<IndexPage>('/api/indexPage'));
|
|
13
13
|
|
|
14
14
|
useIndexSeo(indexPage);
|
|
15
15
|
|
|
@@ -38,9 +38,13 @@ await useContentSeo({
|
|
|
38
38
|
<MainTitle icon="lines" :title="mainContent.title" />
|
|
39
39
|
<MainFlags :flags="mainContent.flags" />
|
|
40
40
|
<MainDescription :description="mainContent.description" />
|
|
41
|
-
<
|
|
41
|
+
<MainKeyLinks mode="single" :elementSnippets="mainContent.snippets" />
|
|
42
42
|
<MainConnections :connections="mainContent.connections" />
|
|
43
|
-
<MainContentStats
|
|
43
|
+
<MainContentStats
|
|
44
|
+
mode="single"
|
|
45
|
+
:stats="mainContent.stats"
|
|
46
|
+
:contentRelativePath="mainContent.contentRelativePath"
|
|
47
|
+
/>
|
|
44
48
|
<div class="h-main-half"></div>
|
|
45
49
|
<MainQuoteLoader />
|
|
46
50
|
<div class="h-main-half"></div>
|
|
@@ -48,10 +52,11 @@ await useContentSeo({
|
|
|
48
52
|
</MainSectionPreamble>
|
|
49
53
|
<MainSection>
|
|
50
54
|
<Prose
|
|
51
|
-
:element="mainContent.
|
|
55
|
+
:element="mainContent.prose"
|
|
52
56
|
:storage="mainContent.storage"
|
|
53
57
|
:urlPath="'/' + mainContent.fullId"
|
|
54
58
|
:useHashUrl="true"
|
|
59
|
+
:setHtmlIds="true"
|
|
55
60
|
@vue:mounted="proseMounted"
|
|
56
61
|
/>
|
|
57
62
|
</MainSection>
|
package/app/pages/sponsors.vue
CHANGED
|
@@ -11,7 +11,7 @@ const payloadKey = 'page-sponsors';
|
|
|
11
11
|
const pageSponsors: PageSponsor[] =
|
|
12
12
|
(nuxtApp.static.data[payloadKey] ||=
|
|
13
13
|
nuxtApp.payload.data[payloadKey] ||=
|
|
14
|
-
await
|
|
14
|
+
await fetchJson('/api/pageSponsors'));
|
|
15
15
|
|
|
16
16
|
const phrase = await usePhrases(
|
|
17
17
|
'sponsors',
|
|
@@ -8,11 +8,6 @@ import { setupWelcomeMessage } from './client/welcome';
|
|
|
8
8
|
export default defineNuxtPlugin({
|
|
9
9
|
name: 'erudit-app-setup',
|
|
10
10
|
async setup() {
|
|
11
|
-
// Forgive me God for doing this but this is necessary because Nuxt App server side shares
|
|
12
|
-
// same globalThis with Nitro server but same imports are done again causing @jsprose/core to throw "multipe singleton instances" error.
|
|
13
|
-
// @ts-ignore
|
|
14
|
-
//delete globalThis['__JSPROSE__'];
|
|
15
|
-
|
|
16
11
|
await setupAppRuntimeConfig();
|
|
17
12
|
},
|
|
18
13
|
hooks: {
|
package/app/router.options.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RawElement } from 'tsprose';
|
|
2
|
+
export { defineDocument as defineProse, type RawElement } from 'tsprose';
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
export type AnyProseElement = RawElement<AnySchema>;
|
|
4
|
+
export type ProseElement = RawElement;
|
|
@@ -15,7 +15,7 @@ export function createAppTemplate(nuxt: Nuxt, elementsData: ElementData[]) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const template = `
|
|
18
|
-
import type {
|
|
18
|
+
import type { ProseAppElements } from '@erudit-js/prose/app';
|
|
19
19
|
|
|
20
20
|
${Object.entries(apps)
|
|
21
21
|
.map(
|
|
@@ -24,12 +24,11 @@ ${Object.entries(apps)
|
|
|
24
24
|
)
|
|
25
25
|
.join('\n')}
|
|
26
26
|
|
|
27
|
-
export const
|
|
28
|
-
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
appElementsArray.map(element => [element.schema.name, element])
|
|
27
|
+
export const appElements: ProseAppElements = Object.fromEntries([
|
|
28
|
+
${Object.keys(apps).join(',\n ')}
|
|
29
|
+
]
|
|
30
|
+
.flatMap((element: any) => (Array.isArray(element) ? element : [element]))
|
|
31
|
+
.map((element: any) => [element.schema.name, element])
|
|
33
32
|
);
|
|
34
33
|
`.trim();
|
|
35
34
|
|
|
@@ -5,13 +5,13 @@ import { sn } from 'unslash';
|
|
|
5
5
|
import type { ElementData } from './shared';
|
|
6
6
|
import { PROJECT_PATH } from '../../env';
|
|
7
7
|
|
|
8
|
-
export function
|
|
8
|
+
export function createElementGlobalTypes(elementsData: ElementData[]) {
|
|
9
9
|
for (const elementData of elementsData) {
|
|
10
|
-
// Collect
|
|
11
|
-
const
|
|
12
|
-
for (const
|
|
13
|
-
for (const tagName of
|
|
14
|
-
|
|
10
|
+
// Collect tag names from core element
|
|
11
|
+
const allCoreTagNames = new Set<string>();
|
|
12
|
+
for (const coreElement of elementData.coreElements) {
|
|
13
|
+
for (const tagName of Object.keys(coreElement.tags)) {
|
|
14
|
+
allCoreTagNames.add(tagName);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -32,8 +32,8 @@ export function createGlobalTypes(elementsData: ElementData[]) {
|
|
|
32
32
|
other = parsed.other;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Add missing tags from
|
|
36
|
-
for (const tagName of
|
|
35
|
+
// Add missing tags from core element
|
|
36
|
+
for (const tagName of allCoreTagNames) {
|
|
37
37
|
if (!(tagName in tags)) {
|
|
38
38
|
tags[tagName] = '';
|
|
39
39
|
}
|
|
@@ -58,18 +58,18 @@ export function createGlobalTypes(elementsData: ElementData[]) {
|
|
|
58
58
|
);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
// Create typeof imports for tags that exist in element's
|
|
61
|
+
// Create typeof imports for tags that exist in element's core elements
|
|
62
62
|
const tagDefinitions = Object.entries(tags).map(([tagName, jsdoc]) => {
|
|
63
|
-
// Find which
|
|
63
|
+
// Find which core element contains this tag
|
|
64
64
|
let tagDefinition = "'_tag_'";
|
|
65
|
-
for (let i = 0; i < elementData.
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
65
|
+
for (let i = 0; i < elementData.coreElements.length; i++) {
|
|
66
|
+
const coreElement = elementData.coreElements[i]!;
|
|
67
|
+
if (coreElement.tags[tagName] !== undefined) {
|
|
68
68
|
// If there are multiple schemas, include the index
|
|
69
|
-
if (elementData.
|
|
70
|
-
tagDefinition = `typeof import('${elementData.absCorePath}')['default'][${i}]['
|
|
69
|
+
if (elementData.coreElements.length > 1) {
|
|
70
|
+
tagDefinition = `typeof import('${elementData.absCorePath}')['default'][${i}]['tags']['${coreElement.tags[tagName]}']`;
|
|
71
71
|
} else {
|
|
72
|
-
tagDefinition = `typeof import('${elementData.absCorePath}')['default']['
|
|
72
|
+
tagDefinition = `typeof import('${elementData.absCorePath}')['default']['tags']['${coreElement.tags[tagName]}']`;
|
|
73
73
|
}
|
|
74
74
|
break;
|
|
75
75
|
}
|
|
@@ -78,8 +78,8 @@ export function createGlobalTypes(elementsData: ElementData[]) {
|
|
|
78
78
|
const indentedJsdoc = indentJsDoc(jsdoc);
|
|
79
79
|
|
|
80
80
|
return indentedJsdoc
|
|
81
|
-
? `${indentedJsdoc}\n
|
|
82
|
-
: `
|
|
81
|
+
? `${indentedJsdoc}\n const ${tagName}: ${tagDefinition};`
|
|
82
|
+
: ` const ${tagName}: ${tagDefinition};`;
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
const finalDts = `
|
|
@@ -96,8 +96,8 @@ ${Object.entries(other)
|
|
|
96
96
|
? `type ${typeName} = ${definition};`
|
|
97
97
|
: `const ${typeName}: ${definition};`;
|
|
98
98
|
return indentedJsdoc
|
|
99
|
-
? `${indentedJsdoc}\n
|
|
100
|
-
: `
|
|
99
|
+
? `${indentedJsdoc}\n ${declaration}`
|
|
100
|
+
: ` ${declaration}`;
|
|
101
101
|
})
|
|
102
102
|
.join('\n')}
|
|
103
103
|
}
|
|
@@ -250,7 +250,7 @@ function parseGlobalDts(dts: string): {
|
|
|
250
250
|
return { imports, tags, other };
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
function indentJsDoc(jsdoc: string, indent: string = '
|
|
253
|
+
function indentJsDoc(jsdoc: string, indent: string = ' '): string {
|
|
254
254
|
if (!jsdoc) return '';
|
|
255
255
|
return jsdoc
|
|
256
256
|
.split('\n')
|