erudit 4.3.1 → 4.3.3
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/app.vue +1 -0
- package/app/components/aside/major/PaneHolder.vue +3 -3
- package/app/components/aside/major/contentNav/items/Flags.vue +4 -8
- package/app/components/main/MainTopicPartPage.vue +3 -1
- package/app/composables/analytics.ts +15 -8
- package/app/composables/favicon.ts +62 -15
- package/app/composables/formatText.ts +9 -9
- package/app/composables/jsonLd.ts +101 -0
- package/app/composables/lastmod.ts +6 -0
- package/app/composables/og.ts +21 -0
- package/app/pages/book/[...bookId].vue +3 -1
- package/app/pages/group/[...groupId].vue +3 -1
- package/app/pages/page/[...pageId].vue +3 -1
- package/app/plugins/prerender.server.ts +1 -0
- package/modules/erudit/setup/runtimeConfig.ts +39 -3
- package/nuxt.config.ts +1 -1
- package/package.json +12 -11
- package/server/api/main/content/[...contentTypePath].ts +2 -0
- package/server/api/prerender/favicons.ts +31 -0
- package/server/erudit/build.ts +2 -0
- package/server/erudit/content/lastmod.ts +206 -0
- package/server/erudit/content/repository/lastmod.ts +12 -0
- package/server/erudit/db/schema/content.ts +1 -0
- package/server/erudit/favicon/convertToPng.ts +48 -0
- package/server/erudit/favicon/loadSource.ts +139 -0
- package/server/erudit/favicon/shared.ts +48 -0
- package/server/erudit/language/list/en.ts +0 -9
- package/server/erudit/language/list/ru.ts +2 -11
- package/server/erudit/repository.ts +2 -0
- package/server/routes/favicon/[...path].ts +89 -0
- package/server/routes/sitemap.xml.ts +19 -10
- package/shared/types/language.ts +0 -6
- package/shared/types/mainContent.ts +1 -0
- package/shared/types/runtimeConfig.ts +4 -1
- package/app/composables/lastChanged.ts +0 -61
package/app/app.vue
CHANGED
|
@@ -5,28 +5,24 @@ import type { MyIconName } from '#my-icons';
|
|
|
5
5
|
|
|
6
6
|
defineProps<{ flags: ContentFlags }>();
|
|
7
7
|
|
|
8
|
-
const phrase = await usePhrases(
|
|
9
|
-
'flag_title_dev',
|
|
10
|
-
'flag_title_advanced',
|
|
11
|
-
'flag_title_secondary',
|
|
12
|
-
);
|
|
8
|
+
const phrase = await usePhrases('flag_dev', 'flag_advanced', 'flag_secondary');
|
|
13
9
|
|
|
14
10
|
const flagData = ((flagType: ContentFlag) => {
|
|
15
11
|
switch (flagType) {
|
|
16
12
|
case 'dev':
|
|
17
13
|
return {
|
|
18
14
|
icon: 'construction',
|
|
19
|
-
title: phrase.
|
|
15
|
+
title: phrase.flag_dev,
|
|
20
16
|
};
|
|
21
17
|
case 'advanced':
|
|
22
18
|
return {
|
|
23
19
|
icon: 'asterisk',
|
|
24
|
-
title: phrase.
|
|
20
|
+
title: phrase.flag_advanced,
|
|
25
21
|
};
|
|
26
22
|
case 'secondary':
|
|
27
23
|
return {
|
|
28
24
|
icon: 'puzzle',
|
|
29
|
-
title: phrase.
|
|
25
|
+
title: phrase.flag_secondary,
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
28
|
}) as (flagType: ContentFlag) => { icon: MyIconName; title: string };
|
|
@@ -24,7 +24,7 @@ const phrase = await usePhrases(
|
|
|
24
24
|
'summary_seo_description',
|
|
25
25
|
'practice_seo_description',
|
|
26
26
|
);
|
|
27
|
-
const lastChangedDate = useLastChanged(() => mainContent.
|
|
27
|
+
const lastChangedDate = useLastChanged(() => mainContent.lastmod);
|
|
28
28
|
|
|
29
29
|
await useContentSeo({
|
|
30
30
|
title: mainContent.title,
|
|
@@ -47,6 +47,8 @@ await useContentSeo({
|
|
|
47
47
|
: undefined,
|
|
48
48
|
seo: mainContent.seo,
|
|
49
49
|
snippets: mainContent.snippets,
|
|
50
|
+
breadcrumbs: mainContent.breadcrumbs,
|
|
51
|
+
lastmod: mainContent.lastmod,
|
|
50
52
|
});
|
|
51
53
|
</script>
|
|
52
54
|
|
|
@@ -73,17 +73,24 @@ function yandexAnalytics(analytics: YandexAnalytics) {
|
|
|
73
73
|
(function(m,e,t,r,i,k,a){
|
|
74
74
|
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
|
75
75
|
m[i].l=1*new Date();
|
|
76
|
-
|
|
77
|
-
k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
|
78
|
-
})(window, document,
|
|
76
|
+
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
|
77
|
+
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
|
78
|
+
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=${analytics.metricsId}', 'ym');
|
|
79
79
|
|
|
80
|
-
ym(
|
|
80
|
+
ym(
|
|
81
|
+
${analytics.metricsId},
|
|
82
|
+
'init',
|
|
83
|
+
{
|
|
84
|
+
ssr:true,
|
|
85
|
+
webvisor:true,
|
|
81
86
|
clickmap:true,
|
|
82
|
-
|
|
87
|
+
referrer: document.referrer,
|
|
88
|
+
url: location.href,
|
|
83
89
|
accurateTrackBounce:true,
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
90
|
+
trackLinks:true
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
`.trim(),
|
|
87
94
|
},
|
|
88
95
|
],
|
|
89
96
|
});
|
|
@@ -1,18 +1,41 @@
|
|
|
1
1
|
import { isTopicPart, type TopicPart } from '@erudit-js/core/content/topic';
|
|
2
2
|
import { isContentType, type ContentType } from '@erudit-js/core/content/type';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const FALLBACK_FAVICON_EXT = '.svg';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const mimeByExt: Record<string, string> = {
|
|
7
|
+
'.svg': 'image/svg+xml',
|
|
8
|
+
'.png': 'image/png',
|
|
9
|
+
'.ico': 'image/x-icon',
|
|
10
|
+
'.jpg': 'image/jpeg',
|
|
11
|
+
'.jpeg': 'image/jpeg',
|
|
12
|
+
'.gif': 'image/gif',
|
|
13
|
+
'.webp': 'image/webp',
|
|
14
|
+
'.bmp': 'image/bmp',
|
|
8
15
|
};
|
|
9
16
|
|
|
17
|
+
function extFromConfigHref(key: string): string {
|
|
18
|
+
const href =
|
|
19
|
+
(ERUDIT.config.favicon as Record<string, string> | undefined)?.[key] ||
|
|
20
|
+
ERUDIT.config.favicon?.default;
|
|
21
|
+
if (!href) return FALLBACK_FAVICON_EXT;
|
|
22
|
+
const path = href.split(/[?#]/)[0] ?? '';
|
|
23
|
+
const dot = path.lastIndexOf('.');
|
|
24
|
+
return dot === -1 ? '' : path.slice(dot).toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mimeFromConfigHref(key: string): string | undefined {
|
|
28
|
+
return mimeByExt[extFromConfigHref(key)];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const useFaviconKey = () =>
|
|
32
|
+
useState<string>('favicon-key', () => 'default');
|
|
33
|
+
|
|
10
34
|
export function useFavicon() {
|
|
11
|
-
const
|
|
35
|
+
const faviconKey = useFaviconKey();
|
|
12
36
|
|
|
13
37
|
function showDefaultFavicon() {
|
|
14
|
-
|
|
15
|
-
faviconHref.value = defaultFaviconHref || fallbackFaviconHref;
|
|
38
|
+
faviconKey.value = 'default';
|
|
16
39
|
}
|
|
17
40
|
|
|
18
41
|
function showContentFavicon(
|
|
@@ -21,15 +44,13 @@ export function useFavicon() {
|
|
|
21
44
|
| { type: 'topic'; part: TopicPart },
|
|
22
45
|
) {
|
|
23
46
|
if (args.type === 'topic') {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
faviconHref.value = topicPartHref;
|
|
47
|
+
if (ERUDIT.config.favicon?.[args.part]) {
|
|
48
|
+
faviconKey.value = args.part;
|
|
27
49
|
return;
|
|
28
50
|
}
|
|
29
51
|
} else {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
faviconHref.value = typeHref;
|
|
52
|
+
if (ERUDIT.config.favicon?.[args.type]) {
|
|
53
|
+
faviconKey.value = args.type;
|
|
33
54
|
return;
|
|
34
55
|
}
|
|
35
56
|
}
|
|
@@ -45,16 +66,42 @@ export function useFavicon() {
|
|
|
45
66
|
|
|
46
67
|
export function initFavicon() {
|
|
47
68
|
const withBaseUrl = useBaseUrl();
|
|
48
|
-
const
|
|
69
|
+
const faviconKey = useFaviconKey();
|
|
49
70
|
const { showDefaultFavicon, showContentFavicon } = useFavicon();
|
|
50
71
|
showDefaultFavicon();
|
|
51
72
|
|
|
52
73
|
useHead({
|
|
53
74
|
link: [
|
|
75
|
+
computed(() => {
|
|
76
|
+
const key = faviconKey.value;
|
|
77
|
+
const ext = extFromConfigHref(key);
|
|
78
|
+
const mime = mimeFromConfigHref(key);
|
|
79
|
+
return {
|
|
80
|
+
key: 'favicon',
|
|
81
|
+
rel: 'icon',
|
|
82
|
+
href: withBaseUrl(`/favicon/${key}/source${ext}`),
|
|
83
|
+
...(mime && { type: mime }),
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
54
86
|
computed(() => ({
|
|
55
|
-
key: 'favicon',
|
|
87
|
+
key: 'favicon-png-48',
|
|
56
88
|
rel: 'icon',
|
|
57
|
-
|
|
89
|
+
type: 'image/png',
|
|
90
|
+
sizes: '48x48',
|
|
91
|
+
href: withBaseUrl(`/favicon/${faviconKey.value}/48.png`),
|
|
92
|
+
})),
|
|
93
|
+
computed(() => ({
|
|
94
|
+
key: 'favicon-png-32',
|
|
95
|
+
rel: 'icon',
|
|
96
|
+
type: 'image/png',
|
|
97
|
+
sizes: '32x32',
|
|
98
|
+
href: withBaseUrl(`/favicon/${faviconKey.value}/32.png`),
|
|
99
|
+
})),
|
|
100
|
+
computed(() => ({
|
|
101
|
+
key: 'apple-touch-icon',
|
|
102
|
+
rel: 'apple-touch-icon',
|
|
103
|
+
sizes: '180x180',
|
|
104
|
+
href: withBaseUrl(`/favicon/${faviconKey.value}/180.png`),
|
|
58
105
|
})),
|
|
59
106
|
],
|
|
60
107
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { FormatText } from '@erudit-js/core/formatText';
|
|
2
|
-
import { createFormatTextFn } from '../../shared/utils/formatText';
|
|
3
|
-
|
|
4
|
-
export let formatText: FormatText;
|
|
5
|
-
|
|
6
|
-
export async function initFormatText() {
|
|
7
|
-
const languageCode = ERUDIT.config.language.current;
|
|
8
|
-
formatText = createFormatTextFn(languageCode);
|
|
9
|
-
}
|
|
1
|
+
import type { FormatText } from '@erudit-js/core/formatText';
|
|
2
|
+
import { createFormatTextFn } from '../../shared/utils/formatText';
|
|
3
|
+
|
|
4
|
+
export let formatText: FormatText;
|
|
5
|
+
|
|
6
|
+
export async function initFormatText() {
|
|
7
|
+
const languageCode = ERUDIT.config.language.current;
|
|
8
|
+
formatText = createFormatTextFn(languageCode);
|
|
9
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Breadcrumbs } from '../../shared/types/breadcrumbs';
|
|
2
|
+
|
|
3
|
+
export function useJsonLd(key: string, data: Record<string, unknown>) {
|
|
4
|
+
useHead({
|
|
5
|
+
script: [
|
|
6
|
+
{
|
|
7
|
+
key,
|
|
8
|
+
type: 'application/ld+json',
|
|
9
|
+
innerHTML: JSON.stringify(data),
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function initWebSiteJsonLd() {
|
|
16
|
+
const siteTitle =
|
|
17
|
+
ERUDIT.config.seo?.siteTitle || ERUDIT.config.asideMajor?.siteInfo?.title;
|
|
18
|
+
|
|
19
|
+
if (!siteTitle) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const runtimeConfig = useRuntimeConfig();
|
|
24
|
+
const siteUrl = runtimeConfig.public.siteUrl as string;
|
|
25
|
+
|
|
26
|
+
if (!siteUrl) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
useJsonLd('jsonld-website', {
|
|
31
|
+
'@context': 'https://schema.org',
|
|
32
|
+
'@type': 'WebSite',
|
|
33
|
+
name: siteTitle,
|
|
34
|
+
url: siteUrl,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useContentBreadcrumbsJsonLd(breadcrumbs?: Breadcrumbs) {
|
|
39
|
+
if (!breadcrumbs || breadcrumbs.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const withSiteUrl = useSiteUrl();
|
|
44
|
+
|
|
45
|
+
useJsonLd('jsonld-breadcrumbs', {
|
|
46
|
+
'@context': 'https://schema.org',
|
|
47
|
+
'@type': 'BreadcrumbList',
|
|
48
|
+
itemListElement: breadcrumbs.map((item, index) => ({
|
|
49
|
+
'@type': 'ListItem',
|
|
50
|
+
position: index + 1,
|
|
51
|
+
name: formatText(item.title),
|
|
52
|
+
item: withSiteUrl(item.link),
|
|
53
|
+
})),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useContentArticleJsonLd(args: {
|
|
58
|
+
title: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
urlPath: string;
|
|
61
|
+
contentType: string;
|
|
62
|
+
lastmod?: string;
|
|
63
|
+
}) {
|
|
64
|
+
const withSiteUrl = useSiteUrl();
|
|
65
|
+
|
|
66
|
+
const siteTitle =
|
|
67
|
+
ERUDIT.config.seo?.siteTitle || ERUDIT.config.asideMajor?.siteInfo?.title;
|
|
68
|
+
|
|
69
|
+
const schemaType =
|
|
70
|
+
args.contentType === 'book'
|
|
71
|
+
? 'Book'
|
|
72
|
+
: args.contentType === 'group'
|
|
73
|
+
? 'Course'
|
|
74
|
+
: 'Article';
|
|
75
|
+
|
|
76
|
+
const data: Record<string, unknown> = {
|
|
77
|
+
'@context': 'https://schema.org',
|
|
78
|
+
'@type': schemaType,
|
|
79
|
+
...(schemaType === 'Article'
|
|
80
|
+
? { headline: formatText(args.title) }
|
|
81
|
+
: { name: formatText(args.title) }),
|
|
82
|
+
url: withSiteUrl(args.urlPath),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (args.description) {
|
|
86
|
+
data.description = formatText(args.description);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (args.lastmod) {
|
|
90
|
+
data.dateModified = args.lastmod;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (siteTitle) {
|
|
94
|
+
data.isPartOf = {
|
|
95
|
+
'@type': 'WebSite',
|
|
96
|
+
name: siteTitle,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
useJsonLd('jsonld-content', data);
|
|
101
|
+
}
|
package/app/composables/og.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ContentSeo } from '@erudit-js/core/content/seo';
|
|
2
2
|
import { toSeoSnippet } from '@erudit-js/prose';
|
|
3
3
|
|
|
4
|
+
import type { Breadcrumbs } from '../../shared/types/breadcrumbs';
|
|
5
|
+
|
|
4
6
|
export function initOgSiteName() {
|
|
5
7
|
const siteTitle =
|
|
6
8
|
ERUDIT.config.seo?.siteTitle || ERUDIT.config.asideMajor?.siteInfo?.title;
|
|
@@ -91,6 +93,8 @@ export async function useContentSeo(args: {
|
|
|
91
93
|
description?: string;
|
|
92
94
|
seo?: ContentSeo;
|
|
93
95
|
snippets?: ElementSnippet[];
|
|
96
|
+
breadcrumbs?: Breadcrumbs;
|
|
97
|
+
lastmod?: string;
|
|
94
98
|
}) {
|
|
95
99
|
const canUseBookTitle = ERUDIT.config.seo?.useBookSiteTitle;
|
|
96
100
|
|
|
@@ -142,6 +146,23 @@ export async function useContentSeo(args: {
|
|
|
142
146
|
});
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
//
|
|
150
|
+
// JSON-LD structured data
|
|
151
|
+
//
|
|
152
|
+
|
|
153
|
+
useContentBreadcrumbsJsonLd(args.breadcrumbs);
|
|
154
|
+
|
|
155
|
+
useContentArticleJsonLd({
|
|
156
|
+
title: args.seo?.title || args.title,
|
|
157
|
+
description: args.seo?.description || args.description,
|
|
158
|
+
urlPath: canonicalPath,
|
|
159
|
+
contentType:
|
|
160
|
+
args.contentTypePath.type === 'topic'
|
|
161
|
+
? 'article'
|
|
162
|
+
: args.contentTypePath.type,
|
|
163
|
+
lastmod: args.lastmod,
|
|
164
|
+
});
|
|
165
|
+
|
|
145
166
|
//
|
|
146
167
|
// SEO snippets
|
|
147
168
|
//
|
|
@@ -19,7 +19,7 @@ if (ERUDIT.config.contributors?.enabled) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const phrase = await usePhrases('begin_learning');
|
|
22
|
-
const lastChangedDate = useLastChanged(() => mainContent.
|
|
22
|
+
const lastChangedDate = useLastChanged(() => mainContent.lastmod);
|
|
23
23
|
|
|
24
24
|
await useContentSeo({
|
|
25
25
|
title: mainContent.title,
|
|
@@ -29,6 +29,8 @@ await useContentSeo({
|
|
|
29
29
|
contentId: mainContent.shortId,
|
|
30
30
|
},
|
|
31
31
|
seo: mainContent.seo,
|
|
32
|
+
breadcrumbs: mainContent.breadcrumbs,
|
|
33
|
+
lastmod: mainContent.lastmod,
|
|
32
34
|
});
|
|
33
35
|
</script>
|
|
34
36
|
|
|
@@ -19,7 +19,7 @@ if (ERUDIT.config.contributors?.enabled) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const phrase = await usePhrases('group', 'begin_learning');
|
|
22
|
-
const lastChangedDate = useLastChanged(() => mainContent.
|
|
22
|
+
const lastChangedDate = useLastChanged(() => mainContent.lastmod);
|
|
23
23
|
|
|
24
24
|
await useContentSeo({
|
|
25
25
|
title: mainContent.title,
|
|
@@ -31,6 +31,8 @@ await useContentSeo({
|
|
|
31
31
|
contentId: mainContent.shortId,
|
|
32
32
|
},
|
|
33
33
|
seo: mainContent.seo,
|
|
34
|
+
breadcrumbs: mainContent.breadcrumbs,
|
|
35
|
+
lastmod: mainContent.lastmod,
|
|
34
36
|
});
|
|
35
37
|
</script>
|
|
36
38
|
|
|
@@ -17,7 +17,7 @@ async function proseMounted() {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const lastChangedDate = useLastChanged(() => mainContent.
|
|
20
|
+
const lastChangedDate = useLastChanged(() => mainContent.lastmod);
|
|
21
21
|
|
|
22
22
|
await useContentSeo({
|
|
23
23
|
title: mainContent.title,
|
|
@@ -29,6 +29,8 @@ await useContentSeo({
|
|
|
29
29
|
},
|
|
30
30
|
seo: mainContent.seo,
|
|
31
31
|
snippets: mainContent.snippets,
|
|
32
|
+
breadcrumbs: mainContent.breadcrumbs,
|
|
33
|
+
lastmod: mainContent.lastmod,
|
|
32
34
|
});
|
|
33
35
|
</script>
|
|
34
36
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import type { Nuxt } from '@nuxt/schema';
|
|
3
3
|
import { sn } from 'unslash';
|
|
4
|
+
import { findPath } from 'nuxt/kit';
|
|
4
5
|
import type { EruditConfig } from '@erudit-js/core/eruditConfig/config';
|
|
5
6
|
import { isDevLikeMode } from '@erudit-js/core/mode';
|
|
6
7
|
|
|
@@ -31,6 +32,7 @@ export async function setupEruditRuntimeConfig(nuxt: Nuxt) {
|
|
|
31
32
|
elements: eruditConfig.elements || [],
|
|
32
33
|
countElements: eruditConfig.countElements || [],
|
|
33
34
|
indexPage: eruditConfig.indexPage,
|
|
35
|
+
lastmod: await resolveLastmodConfig(eruditConfig),
|
|
34
36
|
}) satisfies EruditRuntimeConfig;
|
|
35
37
|
|
|
36
38
|
//
|
|
@@ -46,9 +48,6 @@ export async function setupEruditRuntimeConfig(nuxt: Nuxt) {
|
|
|
46
48
|
repository:
|
|
47
49
|
eruditConfig.debug?.fakeApi?.repository ??
|
|
48
50
|
(nuxt.options.dev || isDevLikeMode(ERUDIT_MODE)),
|
|
49
|
-
lastChanged:
|
|
50
|
-
eruditConfig.debug?.fakeApi?.lastChanged ??
|
|
51
|
-
(nuxt.options.dev || isDevLikeMode(ERUDIT_MODE)),
|
|
52
51
|
},
|
|
53
52
|
analytics: eruditConfig.debug?.analytics,
|
|
54
53
|
},
|
|
@@ -106,3 +105,40 @@ export async function setupEruditRuntimeConfig(nuxt: Nuxt) {
|
|
|
106
105
|
nuxtAugmentations: eruditConfig.nuxtAugmentations,
|
|
107
106
|
};
|
|
108
107
|
}
|
|
108
|
+
|
|
109
|
+
async function resolveLastmodConfig(
|
|
110
|
+
eruditConfig: EruditConfig,
|
|
111
|
+
): Promise<EruditRuntimeConfig['lastmod']> {
|
|
112
|
+
const lastmod = eruditConfig.lastmod;
|
|
113
|
+
|
|
114
|
+
if (!lastmod || lastmod.enabled === false) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (lastmod.type === 'git') {
|
|
119
|
+
return { type: 'git' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (lastmod.type === 'custom') {
|
|
123
|
+
if (!lastmod.scriptPath) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
'Lastmod config with type "custom" requires a "scriptPath"!',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const absPath = await findPath(lastmod.scriptPath, {
|
|
130
|
+
cwd: PROJECT_PATH,
|
|
131
|
+
extensions: ['.ts', '.js'],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!absPath) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Failed to resolve lastmod provider path "${lastmod.scriptPath}"!`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { type: 'custom', scriptPath: absPath };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
package/nuxt.config.ts
CHANGED
|
@@ -53,7 +53,7 @@ export default defineNuxtConfig({
|
|
|
53
53
|
rollupConfig: {
|
|
54
54
|
// Prevent inlining some packages
|
|
55
55
|
external(source) {
|
|
56
|
-
const ignore = ['jiti', 'tsprose', '@resvg/resvg-js'];
|
|
56
|
+
const ignore = ['jiti', 'tsprose', '@resvg/resvg-js', 'sharp'];
|
|
57
57
|
|
|
58
58
|
for (const ignoreItem of ignore) {
|
|
59
59
|
if (source.includes(ignoreItem)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "erudit",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "🤓 CMS for perfect educational sites.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,32 +24,33 @@
|
|
|
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.
|
|
30
|
-
"unslash": "^2.0.0",
|
|
27
|
+
"@erudit-js/cli": "4.3.3",
|
|
28
|
+
"@erudit-js/core": "4.3.3",
|
|
29
|
+
"@erudit-js/prose": "4.3.3",
|
|
31
30
|
"@floating-ui/vue": "^1.1.11",
|
|
32
|
-
"
|
|
31
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
33
32
|
"@tailwindcss/vite": "^4.2.1",
|
|
34
33
|
"better-sqlite3": "^12.8.0",
|
|
35
34
|
"chokidar": "^5.0.0",
|
|
36
35
|
"consola": "^3.4.2",
|
|
37
|
-
"drizzle-kit": "^0.31.
|
|
36
|
+
"drizzle-kit": "^0.31.10",
|
|
38
37
|
"drizzle-orm": "^0.45.1",
|
|
39
38
|
"esbuild": "^0.27.4",
|
|
40
39
|
"flexsearch": "^0.8.212",
|
|
41
40
|
"glob": "^13.0.6",
|
|
42
41
|
"image-size": "^2.0.2",
|
|
43
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
44
|
-
"satori": "^0.25.0",
|
|
45
42
|
"jiti": "^2.6.1",
|
|
46
43
|
"nuxt": "4.4.2",
|
|
47
44
|
"nuxt-my-icons": "1.2.2",
|
|
48
45
|
"perfect-debounce": "^2.1.0",
|
|
46
|
+
"satori": "^0.25.0",
|
|
47
|
+
"sharp": "^0.34.5",
|
|
49
48
|
"tailwindcss": "^4.2.1",
|
|
49
|
+
"ts-xor": "^1.3.0",
|
|
50
|
+
"tsprose": "^1.0.1",
|
|
51
|
+
"unslash": "^2.0.0",
|
|
50
52
|
"vue": "latest",
|
|
51
|
-
"vue-router": "latest"
|
|
52
|
-
"ts-xor": "^1.3.0"
|
|
53
|
+
"vue-router": "latest"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/better-sqlite3": "^7.6.13"
|
|
@@ -20,6 +20,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
|
|
|
20
20
|
['topic', 'page'].includes(contentTypePath.type),
|
|
21
21
|
);
|
|
22
22
|
const seo = await ERUDIT.repository.content.seo(fullId);
|
|
23
|
+
const lastmod = await ERUDIT.repository.content.lastmod(fullId);
|
|
23
24
|
|
|
24
25
|
const bookNode = ERUDIT.contentNav.getBookFor(fullId);
|
|
25
26
|
const bookTitle = bookNode
|
|
@@ -37,6 +38,7 @@ export default defineEventHandler<Promise<MainContent>>(async (event) => {
|
|
|
37
38
|
bookTitle,
|
|
38
39
|
breadcrumbs: await ERUDIT.repository.content.breadcrumbs(fullId),
|
|
39
40
|
seo,
|
|
41
|
+
lastmod,
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
if (description) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { loadFaviconSource } from '#layers/erudit/server/erudit/favicon/loadSource';
|
|
2
|
+
import {
|
|
3
|
+
FAVICON_KEYS,
|
|
4
|
+
FAVICON_SIZES,
|
|
5
|
+
getFaviconHref,
|
|
6
|
+
extFromHref,
|
|
7
|
+
} from '#layers/erudit/server/erudit/favicon/shared';
|
|
8
|
+
|
|
9
|
+
export default defineEventHandler(async () => {
|
|
10
|
+
const routes: string[] = [];
|
|
11
|
+
|
|
12
|
+
for (const key of FAVICON_KEYS) {
|
|
13
|
+
const href = getFaviconHref(key);
|
|
14
|
+
if (!href) continue;
|
|
15
|
+
|
|
16
|
+
const source = await loadFaviconSource(href);
|
|
17
|
+
if (!source) continue;
|
|
18
|
+
|
|
19
|
+
const ext = extFromHref(href);
|
|
20
|
+
if (ext) {
|
|
21
|
+
routes.push(`/favicon/${key}/source${ext}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const size of FAVICON_SIZES) {
|
|
25
|
+
if (source.width !== undefined && source.width < size) continue;
|
|
26
|
+
routes.push(`/favicon/${key}/${size}.png`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return routes;
|
|
31
|
+
});
|
package/server/erudit/build.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { buildSponsors } from './sponsors/build';
|
|
|
9
9
|
import { buildCameos } from './cameos/build';
|
|
10
10
|
import { buildContentNav } from './content/nav/build';
|
|
11
11
|
import { requestFullContentResolve, resolveContent } from './content/resolve';
|
|
12
|
+
import { buildContentLastmod } from './content/lastmod';
|
|
12
13
|
import { buildGlobalContent } from './content/global/build';
|
|
13
14
|
import { buildNews } from './news/build';
|
|
14
15
|
import { triggerReload } from './reloadSignal';
|
|
@@ -29,6 +30,7 @@ export async function buildServerErudit() {
|
|
|
29
30
|
await buildContentNav();
|
|
30
31
|
await buildGlobalContent();
|
|
31
32
|
await resolveContent();
|
|
33
|
+
await buildContentLastmod();
|
|
32
34
|
ERUDIT.log.success(styleText('green', 'Build Complete!'));
|
|
33
35
|
} catch (buildError) {
|
|
34
36
|
requestFullContentResolve();
|