erudit 4.3.3 → 4.3.4
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/components/main/MainBreadcrumbs.vue +27 -19
- package/app/components/main/MainKeyLinks.vue +13 -7
- package/app/components/main/MainSubTitle.vue +4 -3
- package/app/components/main/MainTopicPartPage.vue +3 -0
- package/app/components/main/connections/MainConnections.vue +47 -40
- package/app/components/main/contentStats/MainContentStats.vue +16 -17
- package/app/composables/favicon.ts +1 -1
- package/app/composables/jsonLd.ts +89 -7
- package/app/composables/og.ts +10 -0
- package/app/pages/book/[...bookId].vue +4 -0
- package/app/pages/group/[...groupId].vue +4 -0
- package/app/pages/page/[...pageId].vue +3 -0
- package/package.json +6 -6
- package/server/erudit/build.ts +12 -29
- package/server/erudit/db/repository/pushProblemScript.ts +7 -4
- package/server/erudit/language/list/en.ts +1 -0
- package/server/erudit/language/list/ru.ts +1 -0
- package/server/erudit/prose/repository/finalize.ts +68 -0
- package/shared/types/language.ts +1 -0
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
defineProps<{ breadcrumbs: Breadcrumbs }>();
|
|
3
|
+
|
|
4
|
+
const phrase = await usePhrases('breadcrumb');
|
|
3
5
|
</script>
|
|
4
6
|
|
|
5
7
|
<template>
|
|
6
|
-
<
|
|
8
|
+
<nav
|
|
7
9
|
v-if="breadcrumbs.length > 0"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
:aria-label="phrase.breadcrumb"
|
|
11
|
+
class="px-main py-main-half"
|
|
10
12
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class="gap-small text-text-dimmed hocus:text-text-muted flex items-center
|
|
15
|
-
transition-[color]"
|
|
13
|
+
<ol
|
|
14
|
+
class="gap-small max-micro:justify-center m-0 flex list-none flex-wrap
|
|
15
|
+
p-0"
|
|
16
16
|
>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
<li v-for="(breadcrumb, i) of breadcrumbs">
|
|
18
|
+
<EruditLink
|
|
19
|
+
:to="breadcrumb.link"
|
|
20
|
+
class="gap-small text-text-dimmed hocus:text-text-muted flex
|
|
21
|
+
items-center transition-[color]"
|
|
22
|
+
>
|
|
23
|
+
<MaybeMyIcon :name="breadcrumb.icon" class="text-[1.2em]" />
|
|
24
|
+
<span>{{ formatText(breadcrumb.title) }}</span>
|
|
25
|
+
<MyIcon
|
|
26
|
+
name="chevron-right"
|
|
27
|
+
:class="{
|
|
28
|
+
'relative -left-[3px]': true,
|
|
29
|
+
'rotate-90': i === breadcrumbs.length - 1,
|
|
30
|
+
}"
|
|
31
|
+
/>
|
|
32
|
+
</EruditLink>
|
|
33
|
+
</li>
|
|
34
|
+
</ol>
|
|
35
|
+
</nav>
|
|
28
36
|
</template>
|
|
@@ -19,16 +19,22 @@ const phrase = await usePhrases('key_elements');
|
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
21
|
<template v-if="keyLinks">
|
|
22
|
-
<
|
|
22
|
+
<nav
|
|
23
|
+
v-if="mode === 'single'"
|
|
24
|
+
:aria-label="phrase.key_elements"
|
|
25
|
+
class="px-main py-main-half"
|
|
26
|
+
>
|
|
23
27
|
<MainSubTitle :title="phrase.key_elements + ':'" />
|
|
24
|
-
<
|
|
28
|
+
<ul
|
|
25
29
|
:style="{ '--keyBg': 'var(--color-bg-aside)' }"
|
|
26
|
-
class="gap-small micro:gap-normal micro:justify-start flex
|
|
27
|
-
justify-center"
|
|
30
|
+
class="gap-small micro:gap-normal micro:justify-start m-0 flex list-none
|
|
31
|
+
flex-wrap justify-center p-0"
|
|
28
32
|
>
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
<li v-for="keyLink of keyLinks">
|
|
34
|
+
<MainKeyLink :keyLink />
|
|
35
|
+
</li>
|
|
36
|
+
</ul>
|
|
37
|
+
</nav>
|
|
32
38
|
<div
|
|
33
39
|
v-else
|
|
34
40
|
:style="{ '--keyBg': 'var(--color-bg-main)' }"
|
|
@@ -3,9 +3,10 @@ defineProps<{ title: string }>();
|
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
|
-
<
|
|
7
|
-
class="text-main-sm micro:text-left pb-main-half text-center
|
|
6
|
+
<h2
|
|
7
|
+
class="text-main-sm micro:text-left pb-main-half m-0 text-center
|
|
8
|
+
font-semibold"
|
|
8
9
|
>
|
|
9
10
|
{{ formatText(title) }}
|
|
10
|
-
</
|
|
11
|
+
</h2>
|
|
11
12
|
</template>
|
|
@@ -49,6 +49,9 @@ await useContentSeo({
|
|
|
49
49
|
snippets: mainContent.snippets,
|
|
50
50
|
breadcrumbs: mainContent.breadcrumbs,
|
|
51
51
|
lastmod: mainContent.lastmod,
|
|
52
|
+
contributions: mainContent.contributions,
|
|
53
|
+
flags: mainContent.flags,
|
|
54
|
+
connections: mainContent.connections,
|
|
52
55
|
});
|
|
53
56
|
</script>
|
|
54
57
|
|
|
@@ -27,11 +27,15 @@ const parentExternalsCount = computed(() => {
|
|
|
27
27
|
</script>
|
|
28
28
|
|
|
29
29
|
<template>
|
|
30
|
-
<section
|
|
30
|
+
<section
|
|
31
|
+
v-if="connections"
|
|
32
|
+
:aria-label="phrase.connections"
|
|
33
|
+
class="px-main py-main-half"
|
|
34
|
+
>
|
|
31
35
|
<MainSubTitle :title="phrase.connections + ':'" />
|
|
32
|
-
<
|
|
33
|
-
class="gap-small micro:gap-normal micro:justify-start flex
|
|
34
|
-
justify-center"
|
|
36
|
+
<ul
|
|
37
|
+
class="gap-small micro:gap-normal micro:justify-start m-0 flex list-none
|
|
38
|
+
flex-wrap justify-center p-0"
|
|
35
39
|
>
|
|
36
40
|
<template
|
|
37
41
|
v-for="(items, type) of {
|
|
@@ -40,49 +44,52 @@ const parentExternalsCount = computed(() => {
|
|
|
40
44
|
dependents: connections.dependents,
|
|
41
45
|
}"
|
|
42
46
|
>
|
|
47
|
+
<li v-if="items && items.length > 0">
|
|
48
|
+
<MainConnectionsButton
|
|
49
|
+
:type="type"
|
|
50
|
+
:count="items.length"
|
|
51
|
+
:active="currentType === type"
|
|
52
|
+
@click="
|
|
53
|
+
currentType === type
|
|
54
|
+
? (currentType = undefined)
|
|
55
|
+
: (currentType = type)
|
|
56
|
+
"
|
|
57
|
+
/>
|
|
58
|
+
</li>
|
|
59
|
+
</template>
|
|
60
|
+
<li v-if="connections.externals">
|
|
43
61
|
<MainConnectionsButton
|
|
44
|
-
|
|
45
|
-
:
|
|
46
|
-
:count="items.length"
|
|
47
|
-
:active="currentType === type"
|
|
62
|
+
type="externals"
|
|
63
|
+
:active="currentType === 'externals'"
|
|
48
64
|
@click="
|
|
49
|
-
currentType ===
|
|
65
|
+
currentType === 'externals'
|
|
50
66
|
? (currentType = undefined)
|
|
51
|
-
: (currentType =
|
|
67
|
+
: (currentType = 'externals')
|
|
52
68
|
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<MainConnectionsButton
|
|
56
|
-
v-if="connections.externals"
|
|
57
|
-
type="externals"
|
|
58
|
-
:active="currentType === 'externals'"
|
|
59
|
-
@click="
|
|
60
|
-
currentType === 'externals'
|
|
61
|
-
? (currentType = undefined)
|
|
62
|
-
: (currentType = 'externals')
|
|
63
|
-
"
|
|
64
|
-
>
|
|
65
|
-
<template #after>
|
|
66
|
-
<div
|
|
67
|
-
v-if="connections.externals"
|
|
68
|
-
class="gap-small *:border-border *:pl-small flex items-center
|
|
69
|
-
font-bold *:border-l"
|
|
70
|
-
>
|
|
69
|
+
>
|
|
70
|
+
<template #after>
|
|
71
71
|
<div
|
|
72
|
-
v-if="
|
|
73
|
-
class="
|
|
72
|
+
v-if="connections.externals"
|
|
73
|
+
class="gap-small *:border-border *:pl-small flex items-center
|
|
74
|
+
font-bold *:border-l"
|
|
74
75
|
>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
<div
|
|
77
|
+
v-if="ownExternalsCount"
|
|
78
|
+
class="flex items-center gap-1 text-amber-600
|
|
79
|
+
dark:text-amber-400"
|
|
80
|
+
>
|
|
81
|
+
<MyIcon name="arrow/left" class="-scale-x-100" />
|
|
82
|
+
<span>{{ ownExternalsCount }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div v-if="parentExternalsCount" class="flex items-center gap-1">
|
|
85
|
+
<MyIcon name="arrow/up-to-right" />
|
|
86
|
+
<span>{{ parentExternalsCount }}</span>
|
|
87
|
+
</div>
|
|
81
88
|
</div>
|
|
82
|
-
</
|
|
83
|
-
</
|
|
84
|
-
</
|
|
85
|
-
</
|
|
89
|
+
</template>
|
|
90
|
+
</MainConnectionsButton>
|
|
91
|
+
</li>
|
|
92
|
+
</ul>
|
|
86
93
|
<template v-if="currentType && connections[currentType]">
|
|
87
94
|
<Deps
|
|
88
95
|
v-if="currentType !== 'externals'"
|
|
@@ -15,27 +15,26 @@ const phrase = await usePhrases('stats');
|
|
|
15
15
|
<template>
|
|
16
16
|
<section
|
|
17
17
|
v-if="mode === 'single' && (stats || lastChangedDate)"
|
|
18
|
+
:aria-label="phrase.stats"
|
|
18
19
|
class="px-main py-main-half"
|
|
19
20
|
>
|
|
20
21
|
<MainSubTitle :title="phrase.stats + ':'" />
|
|
21
|
-
<
|
|
22
|
-
class="micro:justify-start gap-small micro:gap-normal flex
|
|
23
|
-
justify-center"
|
|
22
|
+
<ul
|
|
23
|
+
class="micro:justify-start gap-small micro:gap-normal m-0 flex list-none
|
|
24
|
+
flex-wrap justify-center p-0"
|
|
24
25
|
>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<ItemLastChanged v-if="lastChangedDate" :date="lastChangedDate" />
|
|
38
|
-
</div>
|
|
26
|
+
<li v-if="stats?.materials">
|
|
27
|
+
<ItemMaterials :count="stats.materials" mode="detailed" />
|
|
28
|
+
</li>
|
|
29
|
+
<template v-if="stats?.elements">
|
|
30
|
+
<li v-for="(count, schemaName) of stats.elements">
|
|
31
|
+
<ItemElement :schemaName :count mode="detailed" />
|
|
32
|
+
</li>
|
|
33
|
+
</template>
|
|
34
|
+
<li v-if="lastChangedDate">
|
|
35
|
+
<ItemLastChanged :date="lastChangedDate" />
|
|
36
|
+
</li>
|
|
37
|
+
</ul>
|
|
39
38
|
</section>
|
|
40
39
|
<div
|
|
41
40
|
v-else-if="mode === 'children' && stats"
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import type { ContentContribution } from '@erudit-js/core/content/contributions';
|
|
2
|
+
import type { ContentFlags } from '@erudit-js/core/content/flags';
|
|
3
|
+
|
|
1
4
|
import type { Breadcrumbs } from '../../shared/types/breadcrumbs';
|
|
5
|
+
import type { ContentConnections } from '../../shared/types/contentConnections';
|
|
6
|
+
import type { ElementSnippet } from '../../shared/types/elementSnippet';
|
|
7
|
+
import type { MainContentChildrenItem } from '../../shared/types/mainContent';
|
|
8
|
+
|
|
9
|
+
function contentTypeToSchemaType(type: string): string {
|
|
10
|
+
return type === 'book' ? 'Book' : type === 'group' ? 'Course' : 'Article';
|
|
11
|
+
}
|
|
2
12
|
|
|
3
13
|
export function useJsonLd(key: string, data: Record<string, unknown>) {
|
|
4
14
|
useHead({
|
|
@@ -60,18 +70,19 @@ export function useContentArticleJsonLd(args: {
|
|
|
60
70
|
urlPath: string;
|
|
61
71
|
contentType: string;
|
|
62
72
|
lastmod?: string;
|
|
73
|
+
keyElements?: ElementSnippet[];
|
|
74
|
+
breadcrumbs?: Breadcrumbs;
|
|
75
|
+
children?: MainContentChildrenItem[];
|
|
76
|
+
contributions?: ContentContribution[];
|
|
77
|
+
flags?: ContentFlags;
|
|
78
|
+
connections?: ContentConnections;
|
|
63
79
|
}) {
|
|
64
80
|
const withSiteUrl = useSiteUrl();
|
|
65
81
|
|
|
66
82
|
const siteTitle =
|
|
67
83
|
ERUDIT.config.seo?.siteTitle || ERUDIT.config.asideMajor?.siteInfo?.title;
|
|
68
84
|
|
|
69
|
-
const schemaType =
|
|
70
|
-
args.contentType === 'book'
|
|
71
|
-
? 'Book'
|
|
72
|
-
: args.contentType === 'group'
|
|
73
|
-
? 'Course'
|
|
74
|
-
: 'Article';
|
|
85
|
+
const schemaType = contentTypeToSchemaType(args.contentType);
|
|
75
86
|
|
|
76
87
|
const data: Record<string, unknown> = {
|
|
77
88
|
'@context': 'https://schema.org',
|
|
@@ -80,6 +91,7 @@ export function useContentArticleJsonLd(args: {
|
|
|
80
91
|
? { headline: formatText(args.title) }
|
|
81
92
|
: { name: formatText(args.title) }),
|
|
82
93
|
url: withSiteUrl(args.urlPath),
|
|
94
|
+
inLanguage: ERUDIT.config.language?.current || 'en',
|
|
83
95
|
};
|
|
84
96
|
|
|
85
97
|
if (args.description) {
|
|
@@ -90,12 +102,82 @@ export function useContentArticleJsonLd(args: {
|
|
|
90
102
|
data.dateModified = args.lastmod;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
const parentBreadcrumb =
|
|
106
|
+
args.breadcrumbs && args.breadcrumbs.length >= 1
|
|
107
|
+
? args.breadcrumbs[args.breadcrumbs.length - 1]
|
|
108
|
+
: undefined;
|
|
109
|
+
|
|
110
|
+
if (parentBreadcrumb) {
|
|
111
|
+
data.isPartOf = {
|
|
112
|
+
'@type': 'WebPage',
|
|
113
|
+
name: formatText(parentBreadcrumb.title),
|
|
114
|
+
url: withSiteUrl(parentBreadcrumb.link),
|
|
115
|
+
};
|
|
116
|
+
} else if (siteTitle) {
|
|
94
117
|
data.isPartOf = {
|
|
95
118
|
'@type': 'WebSite',
|
|
96
119
|
name: siteTitle,
|
|
97
120
|
};
|
|
98
121
|
}
|
|
99
122
|
|
|
123
|
+
const keyElementTerms = args.keyElements?.length
|
|
124
|
+
? args.keyElements.map((el) => ({
|
|
125
|
+
'@type': 'DefinedTerm',
|
|
126
|
+
name: formatText(el.seo?.title || el.key?.title || el.title),
|
|
127
|
+
url: withSiteUrl(el.link),
|
|
128
|
+
}))
|
|
129
|
+
: [];
|
|
130
|
+
|
|
131
|
+
if (keyElementTerms.length > 0) {
|
|
132
|
+
data.about = keyElementTerms;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hasPart: Record<string, unknown>[] = [];
|
|
136
|
+
|
|
137
|
+
if (args.children?.length) {
|
|
138
|
+
for (const child of args.children) {
|
|
139
|
+
const part: Record<string, unknown> = {
|
|
140
|
+
'@type': contentTypeToSchemaType(child.type),
|
|
141
|
+
name: formatText(child.title),
|
|
142
|
+
url: withSiteUrl(child.link),
|
|
143
|
+
};
|
|
144
|
+
if (child.description) {
|
|
145
|
+
part.description = formatText(child.description);
|
|
146
|
+
}
|
|
147
|
+
hasPart.push(part);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
hasPart.push(...keyElementTerms);
|
|
152
|
+
|
|
153
|
+
if (hasPart.length > 0) {
|
|
154
|
+
data.hasPart = hasPart;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (args.contributions?.length) {
|
|
158
|
+
data.author = args.contributions.map((c) => {
|
|
159
|
+
const person: Record<string, unknown> = {
|
|
160
|
+
'@type': 'Person',
|
|
161
|
+
name: c.name || c.contributorId,
|
|
162
|
+
};
|
|
163
|
+
if (c.avatarUrl) {
|
|
164
|
+
person.image = withSiteUrl(c.avatarUrl);
|
|
165
|
+
}
|
|
166
|
+
return person;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (args.flags?.advanced) {
|
|
171
|
+
data.educationalLevel = 'Advanced';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (args.connections?.hardDependencies?.length) {
|
|
175
|
+
data.isBasedOn = args.connections.hardDependencies.map((dep) => ({
|
|
176
|
+
'@type': contentTypeToSchemaType(dep.contentType),
|
|
177
|
+
name: formatText(dep.title),
|
|
178
|
+
url: withSiteUrl(dep.link),
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
100
182
|
useJsonLd('jsonld-content', data);
|
|
101
183
|
}
|
package/app/composables/og.ts
CHANGED
|
@@ -95,6 +95,10 @@ export async function useContentSeo(args: {
|
|
|
95
95
|
snippets?: ElementSnippet[];
|
|
96
96
|
breadcrumbs?: Breadcrumbs;
|
|
97
97
|
lastmod?: string;
|
|
98
|
+
children?: MainContentChildrenItem[];
|
|
99
|
+
contributions?: ContentContribution[];
|
|
100
|
+
flags?: ContentFlags;
|
|
101
|
+
connections?: ContentConnections;
|
|
98
102
|
}) {
|
|
99
103
|
const canUseBookTitle = ERUDIT.config.seo?.useBookSiteTitle;
|
|
100
104
|
|
|
@@ -161,6 +165,12 @@ export async function useContentSeo(args: {
|
|
|
161
165
|
? 'article'
|
|
162
166
|
: args.contentTypePath.type,
|
|
163
167
|
lastmod: args.lastmod,
|
|
168
|
+
keyElements: args.snippets?.filter((snippet) => !!snippet.key),
|
|
169
|
+
breadcrumbs: args.breadcrumbs,
|
|
170
|
+
children: args.children,
|
|
171
|
+
contributions: args.contributions,
|
|
172
|
+
flags: args.flags,
|
|
173
|
+
connections: args.connections,
|
|
164
174
|
});
|
|
165
175
|
|
|
166
176
|
//
|
|
@@ -31,6 +31,10 @@ await useContentSeo({
|
|
|
31
31
|
seo: mainContent.seo,
|
|
32
32
|
breadcrumbs: mainContent.breadcrumbs,
|
|
33
33
|
lastmod: mainContent.lastmod,
|
|
34
|
+
children: mainContent.children,
|
|
35
|
+
contributions: mainContent.contributions,
|
|
36
|
+
flags: mainContent.flags,
|
|
37
|
+
connections: mainContent.connections,
|
|
34
38
|
});
|
|
35
39
|
</script>
|
|
36
40
|
|
|
@@ -33,6 +33,10 @@ await useContentSeo({
|
|
|
33
33
|
seo: mainContent.seo,
|
|
34
34
|
breadcrumbs: mainContent.breadcrumbs,
|
|
35
35
|
lastmod: mainContent.lastmod,
|
|
36
|
+
children: mainContent.children,
|
|
37
|
+
contributions: mainContent.contributions,
|
|
38
|
+
flags: mainContent.flags,
|
|
39
|
+
connections: mainContent.connections,
|
|
36
40
|
});
|
|
37
41
|
</script>
|
|
38
42
|
|
|
@@ -31,6 +31,9 @@ await useContentSeo({
|
|
|
31
31
|
snippets: mainContent.snippets,
|
|
32
32
|
breadcrumbs: mainContent.breadcrumbs,
|
|
33
33
|
lastmod: mainContent.lastmod,
|
|
34
|
+
contributions: mainContent.contributions,
|
|
35
|
+
flags: mainContent.flags,
|
|
36
|
+
connections: mainContent.connections,
|
|
34
37
|
});
|
|
35
38
|
</script>
|
|
36
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "erudit",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "🤓 CMS for perfect educational sites.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@erudit-js/cli": "4.3.
|
|
28
|
-
"@erudit-js/core": "4.3.
|
|
29
|
-
"@erudit-js/prose": "4.3.
|
|
27
|
+
"@erudit-js/cli": "4.3.4",
|
|
28
|
+
"@erudit-js/core": "4.3.4",
|
|
29
|
+
"@erudit-js/prose": "4.3.4",
|
|
30
30
|
"@floating-ui/vue": "^1.1.11",
|
|
31
31
|
"@resvg/resvg-js": "^2.6.2",
|
|
32
32
|
"@tailwindcss/vite": "^4.2.1",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"chokidar": "^5.0.0",
|
|
35
35
|
"consola": "^3.4.2",
|
|
36
36
|
"drizzle-kit": "^0.31.10",
|
|
37
|
-
"drizzle-orm": "^0.45.
|
|
37
|
+
"drizzle-orm": "^0.45.2",
|
|
38
38
|
"esbuild": "^0.27.4",
|
|
39
39
|
"flexsearch": "^0.8.212",
|
|
40
40
|
"glob": "^13.0.6",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"nuxt": "4.4.2",
|
|
44
44
|
"nuxt-my-icons": "1.2.2",
|
|
45
45
|
"perfect-debounce": "^2.1.0",
|
|
46
|
-
"satori": "^0.
|
|
46
|
+
"satori": "^0.26.0",
|
|
47
47
|
"sharp": "^0.34.5",
|
|
48
48
|
"tailwindcss": "^4.2.1",
|
|
49
49
|
"ts-xor": "^1.3.0",
|
package/server/erudit/build.ts
CHANGED
|
@@ -61,6 +61,14 @@ export async function buildServerErudit() {
|
|
|
61
61
|
// Watcher
|
|
62
62
|
//
|
|
63
63
|
|
|
64
|
+
const watchedProjectDirs = [
|
|
65
|
+
'content',
|
|
66
|
+
'contributors',
|
|
67
|
+
'cameos',
|
|
68
|
+
'sponsors',
|
|
69
|
+
'news',
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
64
72
|
export async function tryServerWatchProject() {
|
|
65
73
|
if (ERUDIT.mode === 'static') {
|
|
66
74
|
return;
|
|
@@ -93,39 +101,14 @@ export async function tryServerWatchProject() {
|
|
|
93
101
|
}
|
|
94
102
|
}, 300);
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (path.startsWith(ERUDIT.paths.project('contributors') + '/')) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (path.startsWith(ERUDIT.paths.project('cameos') + '/')) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (path.startsWith(ERUDIT.paths.project('sponsors') + '/')) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (path.startsWith(ERUDIT.paths.project('news') + '/')) {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const watcher = chokidar.watch(ERUDIT.paths.project(), {
|
|
119
|
-
ignoreInitial: true,
|
|
120
|
-
});
|
|
104
|
+
const watcher = chokidar.watch(
|
|
105
|
+
watchedProjectDirs.map((dir) => ERUDIT.paths.project(dir)),
|
|
106
|
+
{ ignoreInitial: true },
|
|
107
|
+
);
|
|
121
108
|
|
|
122
109
|
watcher.on('all', (_, path) => {
|
|
123
110
|
path = sn(path);
|
|
124
111
|
|
|
125
|
-
if (!isWatched(path)) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
112
|
if (pendingRebuild) {
|
|
130
113
|
return;
|
|
131
114
|
}
|
|
@@ -23,8 +23,11 @@ export async function pushProblemScript(
|
|
|
23
23
|
relativePath = problemScriptSrc;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
await ERUDIT.db
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
await ERUDIT.db
|
|
27
|
+
.insert(ERUDIT.db.schema.problemScripts)
|
|
28
|
+
.values({
|
|
29
|
+
problemScriptSrc: relativePath,
|
|
30
|
+
contentFullId,
|
|
31
|
+
})
|
|
32
|
+
.onConflictDoNothing();
|
|
30
33
|
}
|
|
@@ -46,6 +46,7 @@ export const phrases: LanguagePhrases = {
|
|
|
46
46
|
flag_secondary: 'Additional',
|
|
47
47
|
flag_secondary_description:
|
|
48
48
|
'This is an optional material is for learners who want to dive deeper and gain additional knowledge and context.',
|
|
49
|
+
breadcrumb: 'Breadcrumb',
|
|
49
50
|
key_elements: 'Key elements',
|
|
50
51
|
stats: 'Statistics',
|
|
51
52
|
connections: 'Connections',
|
|
@@ -47,6 +47,7 @@ export const phrases: LanguagePhrases = {
|
|
|
47
47
|
flag_secondary: 'Дополнение',
|
|
48
48
|
flag_secondary_description:
|
|
49
49
|
'Это дополнительный материал для тех, кто хочет глубже погрузиться в предмет и получить дополнительные знания и контекст.',
|
|
50
|
+
breadcrumb: 'Путь',
|
|
50
51
|
key_elements: 'Ключевые элементы',
|
|
51
52
|
stats: 'Статистика',
|
|
52
53
|
connections: 'Связи',
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fillProseStorage,
|
|
3
3
|
isProseElement,
|
|
4
|
+
isRawElement,
|
|
4
5
|
type ProseElement,
|
|
6
|
+
type ProseStorage,
|
|
5
7
|
type ProseWithStorage,
|
|
8
|
+
type RawElement,
|
|
6
9
|
} from 'tsprose';
|
|
7
10
|
import { imageSchema } from '@erudit-js/prose/elements/image/core';
|
|
8
11
|
import { videoSchema } from '@erudit-js/prose/elements/video/core';
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
} from '@erudit-js/prose/elements/link/dependency/core';
|
|
18
21
|
import { problemSchema } from '@erudit-js/prose/elements/problem/problem';
|
|
19
22
|
import { subProblemSchema } from '@erudit-js/prose/elements/problem/problems';
|
|
23
|
+
import { problemCheckSchema } from '@erudit-js/prose/elements/problem/problemCheck';
|
|
20
24
|
|
|
21
25
|
import { createImageStorage } from '../storage/image';
|
|
22
26
|
import { createVideoStorage } from '../storage/video';
|
|
@@ -26,6 +30,68 @@ import { createProblemScriptStorage } from '../storage/problemScript';
|
|
|
26
30
|
|
|
27
31
|
import { coreElements } from '#erudit/prose/global';
|
|
28
32
|
|
|
33
|
+
async function createStorageForRawElement(
|
|
34
|
+
rawElement: RawElement,
|
|
35
|
+
storageKey: string,
|
|
36
|
+
) {
|
|
37
|
+
switch (true) {
|
|
38
|
+
case isRawElement(rawElement, imageSchema):
|
|
39
|
+
return await createImageStorage(rawElement as any);
|
|
40
|
+
case isRawElement(rawElement, videoSchema):
|
|
41
|
+
return createVideoStorage(rawElement as any);
|
|
42
|
+
case isRawElement(rawElement, calloutSchema):
|
|
43
|
+
return createCalloutStorage(rawElement as any);
|
|
44
|
+
case isRawElement(rawElement, refSchema):
|
|
45
|
+
case isRawElement(rawElement, referenceSchema):
|
|
46
|
+
case isRawElement(rawElement, depSchema):
|
|
47
|
+
case isRawElement(rawElement, dependencySchema):
|
|
48
|
+
return await createLinkStorage(rawElement as any, storageKey);
|
|
49
|
+
case isRawElement(rawElement, problemSchema):
|
|
50
|
+
case isRawElement(rawElement, subProblemSchema):
|
|
51
|
+
return createProblemScriptStorage(rawElement as any, storageKey);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function collectEnsureStorage(
|
|
56
|
+
rawElements: RawElement[],
|
|
57
|
+
storage: ProseStorage,
|
|
58
|
+
) {
|
|
59
|
+
for (const rawElement of rawElements) {
|
|
60
|
+
if (rawElement.storageKey && !(rawElement.storageKey in storage)) {
|
|
61
|
+
const value = await createStorageForRawElement(
|
|
62
|
+
rawElement,
|
|
63
|
+
rawElement.storageKey,
|
|
64
|
+
);
|
|
65
|
+
if (value !== undefined) {
|
|
66
|
+
storage[rawElement.storageKey] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (rawElement.children) {
|
|
70
|
+
await collectEnsureStorage(rawElement.children as RawElement[], storage);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function processEnsureStorage(
|
|
76
|
+
element: ProseElement,
|
|
77
|
+
storage: ProseStorage,
|
|
78
|
+
) {
|
|
79
|
+
if (
|
|
80
|
+
(isProseElement(element, problemSchema) ||
|
|
81
|
+
isProseElement(element, subProblemSchema) ||
|
|
82
|
+
isProseElement(element, problemCheckSchema)) &&
|
|
83
|
+
element.data.ensureStorage
|
|
84
|
+
) {
|
|
85
|
+
await collectEnsureStorage(element.data.ensureStorage, storage);
|
|
86
|
+
delete element.data.ensureStorage;
|
|
87
|
+
}
|
|
88
|
+
if (element.children) {
|
|
89
|
+
for (const child of element.children as ProseElement[]) {
|
|
90
|
+
await processEnsureStorage(child, storage);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
29
95
|
export async function finalizeProse(
|
|
30
96
|
prose: ProseElement,
|
|
31
97
|
): Promise<ProseWithStorage> {
|
|
@@ -58,6 +124,8 @@ export async function finalizeProse(
|
|
|
58
124
|
},
|
|
59
125
|
});
|
|
60
126
|
|
|
127
|
+
await processEnsureStorage(prose, storage);
|
|
128
|
+
|
|
61
129
|
return {
|
|
62
130
|
prose,
|
|
63
131
|
storage,
|
package/shared/types/language.ts
CHANGED