erudit 4.0.0-dev.1 → 4.0.0-dev.2
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 -2
- package/app/components/FancyBold.vue +0 -1
- package/app/components/FancyCard.vue +1 -2
- package/app/components/ads/AdsBannerAside.vue +1 -2
- package/app/components/ads/AdsReplacer.vue +2 -2
- package/app/components/aside/AsideListItem.vue +1 -1
- package/app/components/aside/AsideSwitch.vue +18 -8
- package/app/components/aside/major/PaneSwitcher.vue +1 -1
- package/app/components/aside/minor/AsideMinorPlainHeader.vue +2 -3
- package/app/components/aside/minor/content/AsideMinorContentTopic.vue +1 -4
- package/app/components/aside/minor/content/ButtonPaneContributions.vue +2 -4
- package/app/components/aside/minor/content/ButtonPaneImprove.vue +2 -3
- package/app/components/aside/minor/content/Contribution.vue +1 -1
- package/app/components/aside/minor/content/TocItem.vue +35 -21
- package/app/components/aside/minor/news/AsideMinorNews.vue +1 -2
- package/app/components/aside/minor/news/NewsItem.vue +2 -2
- package/app/components/aside/minor/news/elements/Ref.vue +1 -1
- package/app/components/main/MainContentChild.vue +2 -3
- package/app/components/main/MainDescription.vue +1 -1
- package/app/components/main/MainQuickLink.vue +20 -5
- package/app/components/main/MainQuickLinks.vue +1 -3
- package/app/components/main/MainQuote.vue +3 -6
- package/app/components/main/MainSection.vue +6 -21
- package/app/components/main/MainTitle.vue +1 -2
- package/app/components/main/MainTopicPartSwitch.vue +1 -1
- package/app/components/main/connections/Deps.vue +1 -1
- package/app/components/main/connections/Externals.vue +92 -34
- package/app/components/main/connections/MainConnections.vue +61 -8
- package/app/components/main/connections/MainConnectionsButton.vue +3 -2
- package/app/components/main/connections/ScrollPane.vue +2 -3
- package/app/components/main/contentStats/Item.vue +1 -2
- package/app/components/main/contentStats/MainContentStats.vue +6 -3
- package/app/components/preview/Preview.vue +1 -2
- package/app/components/preview/PreviewScreen.vue +1 -2
- package/app/components/site/SiteAside.vue +2 -4
- package/app/components/site/SiteMain.vue +1 -4
- package/app/components/tree/TreeItem.vue +1 -1
- package/app/composables/og.ts +19 -2
- package/app/pages/contributor/[contributorId].vue +1 -2
- package/app/pages/index.vue +1 -4
- package/app/styles/main.css +0 -1
- package/package.json +4 -4
- package/server/erudit/cameos/build.ts +77 -27
- package/server/erudit/content/global/build.ts +27 -1
- package/server/erudit/content/nav/build.ts +36 -4
- package/server/erudit/content/repository/elementSnippets.ts +51 -11
- package/server/erudit/content/repository/externals.ts +38 -9
- package/server/erudit/content/resolve/index.ts +172 -21
- package/server/erudit/content/resolve/topic.ts +93 -32
- package/server/erudit/content/resolve/utils/insertContentResolved.ts +48 -9
- package/server/erudit/content/search.ts +31 -3
- package/server/erudit/contributors/build.ts +106 -51
- package/server/erudit/db/repository/pushFile.ts +7 -4
- package/server/erudit/db/schema/content.ts +2 -2
- package/server/erudit/db/schema/contentSnippets.ts +3 -4
- package/server/erudit/language/list/en.ts +2 -0
- package/server/erudit/language/list/ru.ts +2 -0
- package/server/erudit/news/build.ts +85 -48
- package/server/erudit/sponsors/build.ts +77 -26
- package/shared/types/contentConnections.ts +2 -2
- package/shared/types/elementSnippet.ts +9 -3
- package/shared/types/language.ts +2 -0
|
@@ -1,50 +1,111 @@
|
|
|
1
|
-
import { readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { like } from 'drizzle-orm';
|
|
1
|
+
import { existsSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { eq, like } from 'drizzle-orm';
|
|
3
3
|
import { globSync } from 'glob';
|
|
4
4
|
import { isRawElement, type AnySchema, type ProseElement } from '@jsprose/core';
|
|
5
5
|
import {
|
|
6
|
+
contributorIdToPropertyName,
|
|
6
7
|
globalContributorsObject,
|
|
7
8
|
globalContributorsTypes,
|
|
8
9
|
type ContributorDefinition,
|
|
9
10
|
} from '@erudit-js/core/contributor';
|
|
10
11
|
|
|
11
|
-
// Trigger globalThis update
|
|
12
12
|
$CONTRIBUTOR;
|
|
13
13
|
|
|
14
|
+
let initialBuild = true;
|
|
15
|
+
|
|
16
|
+
const contributorsRoot = () => `${ERUDIT.config.paths.project}/contributors`;
|
|
17
|
+
|
|
18
|
+
const contributorsTypesPath = () =>
|
|
19
|
+
`${ERUDIT.config.paths.build}/types/contributors.d.ts`;
|
|
20
|
+
|
|
14
21
|
export async function buildContributors() {
|
|
15
|
-
if (!ERUDIT.config.public.project.contributors?.enabled)
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
22
|
+
if (!ERUDIT.config.public.project.contributors?.enabled) return;
|
|
18
23
|
|
|
19
24
|
ERUDIT.log.debug.start('Building contributors...');
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.delete(ERUDIT.db.schema.files)
|
|
24
|
-
.where(like(ERUDIT.db.schema.files.role, 'contributor:%'));
|
|
26
|
+
const isInitial = initialBuild;
|
|
27
|
+
initialBuild = false;
|
|
25
28
|
|
|
26
|
-
const contributorIds =
|
|
27
|
-
`${ERUDIT.config.paths.project}/contributors/*/`,
|
|
28
|
-
{ posix: true },
|
|
29
|
-
).map((dirPath) => dirPath.split('/').pop() as string);
|
|
29
|
+
const contributorIds = collectContributorIds(isInitial);
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if (!contributorIds.size) {
|
|
32
|
+
ERUDIT.log.info(
|
|
33
|
+
isInitial
|
|
34
|
+
? 'Skipping contributors — no contributors found.'
|
|
35
|
+
: 'Skipping contributors — nothing changed.',
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
for (const id of contributorIds) {
|
|
41
|
+
await cleanupContributor(id);
|
|
42
|
+
}
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
`${
|
|
39
|
-
globalContributorsTypes($CONTRIBUTOR),
|
|
44
|
+
const existingIds = [...contributorIds].filter((id) =>
|
|
45
|
+
existsSync(`${contributorsRoot()}/${id}`),
|
|
40
46
|
);
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
syncContributorGlobals(existingIds);
|
|
49
|
+
|
|
50
|
+
if (!existingIds.length) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const id of existingIds) {
|
|
55
|
+
await buildContributor(id);
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
ERUDIT.log.success(
|
|
47
|
-
|
|
59
|
+
isInitial
|
|
60
|
+
? `Contributors build complete! (${ERUDIT.log.stress(contributorIds.size)})`
|
|
61
|
+
: `Contributors updated: ${ERUDIT.log.stress(existingIds.join(', '))}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//
|
|
66
|
+
//
|
|
67
|
+
//
|
|
68
|
+
|
|
69
|
+
function collectContributorIds(initial: boolean): Set<string> {
|
|
70
|
+
if (initial) {
|
|
71
|
+
return new Set(
|
|
72
|
+
globSync(`${contributorsRoot()}/*/`, { posix: true }).map(
|
|
73
|
+
(p) => p.split('/').at(-1)!,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ids = new Set<string>();
|
|
79
|
+
|
|
80
|
+
for (const file of ERUDIT.changedFiles.values()) {
|
|
81
|
+
if (!file.startsWith(`${contributorsRoot()}/`)) continue;
|
|
82
|
+
const id = file.replace(`${contributorsRoot()}/`, '').split('/')[0];
|
|
83
|
+
if (id) ids.add(id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return ids;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function cleanupContributor(contributorId: string) {
|
|
90
|
+
await ERUDIT.db
|
|
91
|
+
.delete(ERUDIT.db.schema.contributors)
|
|
92
|
+
.where(eq(ERUDIT.db.schema.contributors.contributorId, contributorId));
|
|
93
|
+
|
|
94
|
+
await ERUDIT.db
|
|
95
|
+
.delete(ERUDIT.db.schema.files)
|
|
96
|
+
.where(
|
|
97
|
+
like(ERUDIT.db.schema.files.role, `contributor:${contributorId}%`),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
delete $CONTRIBUTOR[contributorIdToPropertyName(contributorId)];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function syncContributorGlobals(contributorIds: string[]) {
|
|
104
|
+
Object.assign($CONTRIBUTOR, globalContributorsObject(contributorIds));
|
|
105
|
+
|
|
106
|
+
writeFileSync(
|
|
107
|
+
contributorsTypesPath(),
|
|
108
|
+
globalContributorsTypes($CONTRIBUTOR),
|
|
48
109
|
);
|
|
49
110
|
}
|
|
50
111
|
|
|
@@ -53,59 +114,53 @@ async function buildContributor(contributorId: string) {
|
|
|
53
114
|
`Building contributor ${ERUDIT.log.stress(contributorId)}...`,
|
|
54
115
|
);
|
|
55
116
|
|
|
56
|
-
const
|
|
57
|
-
const files = readdirSync(
|
|
117
|
+
const dir = `${contributorsRoot()}/${contributorId}`;
|
|
118
|
+
const files = readdirSync(dir);
|
|
58
119
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
?.split('.')
|
|
62
|
-
.pop();
|
|
63
|
-
|
|
64
|
-
if (avatarExtension) {
|
|
120
|
+
const avatar = files.find((f) => f.startsWith('avatar.'));
|
|
121
|
+
if (avatar) {
|
|
65
122
|
await ERUDIT.repository.db.pushFile(
|
|
66
|
-
`${
|
|
123
|
+
`${dir}/${avatar}`,
|
|
67
124
|
`contributor:${contributorId}`,
|
|
68
125
|
);
|
|
69
126
|
}
|
|
70
127
|
|
|
71
|
-
let
|
|
128
|
+
let def: ContributorDefinition | undefined;
|
|
72
129
|
|
|
73
130
|
try {
|
|
74
|
-
|
|
75
|
-
} catch (
|
|
76
|
-
if (!String(
|
|
77
|
-
ERUDIT.log.error(
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
console.log(error);
|
|
131
|
+
def = await ERUDIT.import(`${dir}/contributor`);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (!String(err).includes('Cannot find module')) {
|
|
134
|
+
ERUDIT.log.error(`Failed to load contributor ${contributorId}:`);
|
|
135
|
+
console.error(err);
|
|
81
136
|
}
|
|
82
137
|
}
|
|
83
138
|
|
|
84
139
|
let description: ProseElement<AnySchema> | undefined;
|
|
85
140
|
|
|
86
|
-
if (isRawElement(
|
|
87
|
-
const
|
|
88
|
-
|
|
141
|
+
if (isRawElement(def?.description)) {
|
|
142
|
+
const resolved = await ERUDIT.repository.prose.resolve(
|
|
143
|
+
def.description,
|
|
89
144
|
false,
|
|
90
145
|
);
|
|
91
146
|
|
|
92
|
-
for (const file of
|
|
147
|
+
for (const file of resolved.files) {
|
|
93
148
|
await ERUDIT.repository.db.pushFile(
|
|
94
149
|
file,
|
|
95
150
|
`contributor:${contributorId}`,
|
|
96
151
|
);
|
|
97
152
|
}
|
|
98
153
|
|
|
99
|
-
description =
|
|
154
|
+
description = resolved.proseElement;
|
|
100
155
|
}
|
|
101
156
|
|
|
102
157
|
await ERUDIT.db.insert(ERUDIT.db.schema.contributors).values({
|
|
103
158
|
contributorId,
|
|
104
|
-
avatarExtension,
|
|
105
|
-
displayName:
|
|
106
|
-
short:
|
|
107
|
-
links:
|
|
108
|
-
editor:
|
|
109
|
-
description
|
|
159
|
+
avatarExtension: avatar?.split('.').pop(),
|
|
160
|
+
displayName: def?.displayName,
|
|
161
|
+
short: def?.short,
|
|
162
|
+
links: def?.links,
|
|
163
|
+
editor: def?.editor,
|
|
164
|
+
description,
|
|
110
165
|
});
|
|
111
166
|
}
|
|
@@ -16,8 +16,11 @@ export async function pushFile(filepath: string, role: string): Promise<void> {
|
|
|
16
16
|
'',
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
-
await ERUDIT.db
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
await ERUDIT.db
|
|
20
|
+
.insert(ERUDIT.db.schema.files)
|
|
21
|
+
.values({
|
|
22
|
+
path: relativePath,
|
|
23
|
+
role,
|
|
24
|
+
})
|
|
25
|
+
.onConflictDoNothing();
|
|
23
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
2
|
import type { ContentFlags } from '@erudit-js/core/content/flags';
|
|
3
3
|
import type { ContentType } from '@erudit-js/core/content/type';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ContentExternalItem } from '@erudit-js/core/content/externals';
|
|
5
5
|
import type { ContentSeo } from '@erudit-js/core/content/seo';
|
|
6
6
|
|
|
7
7
|
export const content = sqliteTable('content', {
|
|
@@ -13,6 +13,6 @@ export const content = sqliteTable('content', {
|
|
|
13
13
|
hidden: integer({ mode: 'boolean' }).notNull(),
|
|
14
14
|
flags: text({ mode: 'json' }).$type<ContentFlags>(),
|
|
15
15
|
decorationExtension: text(),
|
|
16
|
-
externals: text({ mode: 'json' }).$type<
|
|
16
|
+
externals: text({ mode: 'json' }).$type<ContentExternalItem[]>(),
|
|
17
17
|
seo: text({ mode: 'json' }).$type<ContentSeo>(),
|
|
18
18
|
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
2
|
import type { ContentProseType } from '@erudit-js/core/content/prose';
|
|
3
|
+
import type { SnippetData } from '@erudit-js/prose';
|
|
3
4
|
|
|
4
5
|
export const contentSnippets = sqliteTable('contentSnippets', {
|
|
5
6
|
snippetId: integer().primaryKey({ autoIncrement: true }),
|
|
6
7
|
contentFullId: text().notNull(),
|
|
7
8
|
contentProseType: text().notNull().$type<ContentProseType>(),
|
|
8
|
-
title: text().notNull(),
|
|
9
|
-
description: text(),
|
|
10
|
-
elementId: text().notNull(),
|
|
11
9
|
schemaName: text().notNull(),
|
|
10
|
+
elementId: text().notNull(),
|
|
11
|
+
snippetData: text({ mode: 'json' }).$type<SnippetData>().notNull(),
|
|
12
12
|
search: integer({ mode: 'boolean' }).notNull(),
|
|
13
|
-
searchSynonyms: text({ mode: 'json' }).$type<string[]>(),
|
|
14
13
|
quick: integer({ mode: 'boolean' }).notNull(),
|
|
15
14
|
seo: integer({ mode: 'boolean' }).notNull(),
|
|
16
15
|
});
|
|
@@ -97,6 +97,8 @@ const en: LanguagePhrases = {
|
|
|
97
97
|
`Summary of the topic "${contentTitle}": key definitions, theorems, properties and examples of their use. All the most important things in a concise form!`,
|
|
98
98
|
practice_seo_description: (contentTitle: string) =>
|
|
99
99
|
`Various problems with hints and answers on the topic "${contentTitle}". Interesting conditions, hints and detailed solutions. Turn knowledge into a skill!`,
|
|
100
|
+
externals_own: 'Own',
|
|
101
|
+
externals_from: 'From',
|
|
100
102
|
|
|
101
103
|
default_index_title: 'Erudit',
|
|
102
104
|
default_index_short: 'Modern digital textbooks!',
|
|
@@ -98,6 +98,8 @@ const ru: LanguagePhrases = {
|
|
|
98
98
|
`Конспект темы «${contentTitle}»: ключевые определения, теоремы, свойства и примеры их использвания. Все самое важное и в кратком виде!`,
|
|
99
99
|
practice_seo_description: (contentTitle: string) =>
|
|
100
100
|
`Разнообразные задачи с подсказками и ответами по теме «${contentTitle}». Интересные условия, подсказки и подробные решения. Превратите знания в навык!`,
|
|
101
|
+
externals_own: 'Собственные',
|
|
102
|
+
externals_from: 'Из',
|
|
101
103
|
|
|
102
104
|
default_index_title: 'Erudit',
|
|
103
105
|
default_index_short: 'Современные цифровые учебники!',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { inArray } from 'drizzle-orm';
|
|
2
3
|
import { globSync } from 'glob';
|
|
3
4
|
import type { AnySchema, RawElement } from '@jsprose/core';
|
|
@@ -6,72 +7,108 @@ import { resolveEruditProse } from '../prose/repository/resolve';
|
|
|
6
7
|
|
|
7
8
|
let initialBuild = true;
|
|
8
9
|
|
|
10
|
+
const newsRoot = () => `${ERUDIT.config.paths.project}/news`;
|
|
11
|
+
|
|
9
12
|
export async function buildNews() {
|
|
10
13
|
ERUDIT.log.debug.start('Building news...');
|
|
11
14
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
globSync(`[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9].tsx`, {
|
|
17
|
-
posix: true,
|
|
18
|
-
cwd: ERUDIT.config.paths.project + '/news',
|
|
19
|
-
}).forEach((filePath) => newsFilenames.add(filePath));
|
|
20
|
-
} else {
|
|
21
|
-
for (const changedFile of ERUDIT.changedFiles.values()) {
|
|
22
|
-
if (
|
|
23
|
-
changedFile.startsWith(ERUDIT.config.paths.project + '/news/')
|
|
24
|
-
) {
|
|
25
|
-
const relativePath = changedFile.replace(
|
|
26
|
-
ERUDIT.config.paths.project + '/news/',
|
|
27
|
-
'',
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (relativePath.match(/^[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.tsx$/)) {
|
|
31
|
-
newsFilenames.add(relativePath);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
15
|
+
const isInitial = initialBuild;
|
|
16
|
+
initialBuild = false;
|
|
17
|
+
|
|
18
|
+
const newsFilenames = collectNewsFilenames(isInitial);
|
|
35
19
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
),
|
|
42
|
-
),
|
|
20
|
+
if (!newsFilenames.size) {
|
|
21
|
+
ERUDIT.log.info(
|
|
22
|
+
isInitial
|
|
23
|
+
? 'Skipping news — no news found.'
|
|
24
|
+
: 'Skipping news — nothing changed.',
|
|
43
25
|
);
|
|
26
|
+
return;
|
|
44
27
|
}
|
|
45
28
|
|
|
46
29
|
for (const filename of newsFilenames) {
|
|
30
|
+
await cleanupNews(filename);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const existingFilenames = [...newsFilenames].filter((filename) =>
|
|
34
|
+
existsSync(`${newsRoot()}/${filename}`),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (!existingFilenames.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const filename of existingFilenames) {
|
|
47
42
|
await buildNewsItem(filename);
|
|
48
43
|
}
|
|
44
|
+
|
|
45
|
+
ERUDIT.log.success(
|
|
46
|
+
isInitial
|
|
47
|
+
? `News build complete! (${ERUDIT.log.stress(newsFilenames.size)})`
|
|
48
|
+
: `News updated: ${ERUDIT.log.stress(existingFilenames.join(', '))}`,
|
|
49
|
+
);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
//
|
|
53
|
+
//
|
|
54
|
+
//
|
|
55
|
+
|
|
56
|
+
function collectNewsFilenames(initial: boolean): Set<string> {
|
|
57
|
+
if (initial) {
|
|
58
|
+
return new Set(
|
|
59
|
+
globSync(`[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9].tsx`, {
|
|
60
|
+
posix: true,
|
|
61
|
+
cwd: newsRoot(),
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const filenames = new Set<string>();
|
|
67
|
+
|
|
68
|
+
for (const file of ERUDIT.changedFiles.values()) {
|
|
69
|
+
if (!file.startsWith(`${newsRoot()}/`)) continue;
|
|
70
|
+
const relativePath = file.replace(`${newsRoot()}/`, '');
|
|
71
|
+
|
|
72
|
+
if (relativePath.match(/^[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.tsx$/)) {
|
|
73
|
+
filenames.add(relativePath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return filenames;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function cleanupNews(filename: string) {
|
|
81
|
+
await ERUDIT.db
|
|
82
|
+
.delete(ERUDIT.db.schema.news)
|
|
83
|
+
.where(
|
|
84
|
+
inArray(ERUDIT.db.schema.news.date, [filename.replace('.tsx', '')]),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function buildNewsItem(filename: string) {
|
|
52
89
|
ERUDIT.log.debug.start(
|
|
53
90
|
`Building news item ${ERUDIT.log.stress(filename)}...`,
|
|
54
91
|
);
|
|
55
92
|
|
|
56
|
-
|
|
57
|
-
const newsModule = await ERUDIT.import<{
|
|
58
|
-
default: RawElement<AnySchema>;
|
|
59
|
-
}>(`${ERUDIT.config.paths.project}/news/${filename}`);
|
|
93
|
+
let newsModule: { default: RawElement<AnySchema> } | undefined;
|
|
60
94
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
95
|
+
try {
|
|
96
|
+
newsModule = await ERUDIT.import(`${newsRoot()}/${filename}`);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
ERUDIT.log.error(`Failed to load news item ${filename}:`);
|
|
99
|
+
console.error(err);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
65
102
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
prose: resolvedProse.proseElement,
|
|
69
|
-
});
|
|
70
|
-
} catch (error) {
|
|
71
|
-
ERUDIT.log.error(
|
|
72
|
-
`Failed to build news item ${ERUDIT.log.stress(filename)}!\n` +
|
|
73
|
-
String(error),
|
|
74
|
-
);
|
|
103
|
+
if (!newsModule?.default) {
|
|
104
|
+
ERUDIT.log.error(`No default export in news item ${filename}`);
|
|
75
105
|
return;
|
|
76
106
|
}
|
|
107
|
+
|
|
108
|
+
const resolvedProse = await resolveEruditProse(newsModule.default, false);
|
|
109
|
+
|
|
110
|
+
await ERUDIT.db.insert(ERUDIT.db.schema.news).values({
|
|
111
|
+
date: filename.replace('.tsx', ''),
|
|
112
|
+
prose: resolvedProse.proseElement,
|
|
113
|
+
});
|
|
77
114
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { readdirSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { eq } from 'drizzle-orm';
|
|
3
3
|
import type { Sponsor, SponsorConfig } from '@erudit-js/core/sponsor';
|
|
4
4
|
|
|
5
|
+
let initialBuild = true;
|
|
6
|
+
|
|
7
|
+
const sponsorsRoot = () => `${ERUDIT.config.paths.project}/sponsors`;
|
|
8
|
+
|
|
5
9
|
export async function buildSponsors() {
|
|
6
10
|
if (!ERUDIT.config.public.project.sponsors?.enabled) {
|
|
7
11
|
return;
|
|
@@ -9,43 +13,90 @@ export async function buildSponsors() {
|
|
|
9
13
|
|
|
10
14
|
ERUDIT.log.debug.start('Building sponsors...');
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.delete(ERUDIT.db.schema.files)
|
|
15
|
-
.where(eq(ERUDIT.db.schema.files.role, 'sponsor-avatar'));
|
|
16
|
-
|
|
17
|
-
let sponsorIds: string[] = [];
|
|
16
|
+
const isInitial = initialBuild;
|
|
17
|
+
initialBuild = false;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
sponsorIds = readdirSync(ERUDIT.config.paths.project + '/sponsors', {
|
|
21
|
-
withFileTypes: true,
|
|
22
|
-
})
|
|
23
|
-
.filter((entry) => entry.isDirectory())
|
|
24
|
-
.map((entry) => entry.name);
|
|
25
|
-
} catch {}
|
|
19
|
+
const sponsorIds = collectSponsorIds(isInitial);
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
if (!sponsorIds.size) {
|
|
22
|
+
ERUDIT.log.info(
|
|
23
|
+
isInitial
|
|
24
|
+
? 'Skipping sponsors — no sponsors found.'
|
|
25
|
+
: 'Skipping sponsors — nothing changed.',
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
28
29
|
|
|
29
30
|
for (const sponsorId of sponsorIds) {
|
|
31
|
+
await cleanupSponsor(sponsorId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const existingIds = [...sponsorIds].filter((id) =>
|
|
35
|
+
existsSync(`${sponsorsRoot()}/${id}`),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!existingIds.length) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const sponsorId of existingIds) {
|
|
30
43
|
await buildSponsor(sponsorId);
|
|
31
|
-
sponsorCount++;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
ERUDIT.log.success(
|
|
35
|
-
|
|
47
|
+
isInitial
|
|
48
|
+
? `Sponsors build complete! (${ERUDIT.log.stress(sponsorIds.size)})`
|
|
49
|
+
: `Sponsors updated: ${ERUDIT.log.stress(existingIds.join(', '))}`,
|
|
36
50
|
);
|
|
37
51
|
}
|
|
38
52
|
|
|
53
|
+
//
|
|
54
|
+
//
|
|
55
|
+
//
|
|
56
|
+
|
|
57
|
+
function collectSponsorIds(initial: boolean): Set<string> {
|
|
58
|
+
if (initial) {
|
|
59
|
+
try {
|
|
60
|
+
return new Set(
|
|
61
|
+
readdirSync(sponsorsRoot(), { withFileTypes: true })
|
|
62
|
+
.filter((entry) => entry.isDirectory())
|
|
63
|
+
.map((entry) => entry.name),
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
return new Set();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const ids = new Set<string>();
|
|
71
|
+
|
|
72
|
+
for (const file of ERUDIT.changedFiles.values()) {
|
|
73
|
+
if (!file.startsWith(`${sponsorsRoot()}/`)) continue;
|
|
74
|
+
const id = file.replace(`${sponsorsRoot()}/`, '').split('/')[0];
|
|
75
|
+
if (id) ids.add(id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return ids;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function cleanupSponsor(sponsorId: string) {
|
|
82
|
+
await ERUDIT.db
|
|
83
|
+
.delete(ERUDIT.db.schema.sponsors)
|
|
84
|
+
.where(eq(ERUDIT.db.schema.sponsors.sponsorId, sponsorId));
|
|
85
|
+
|
|
86
|
+
await ERUDIT.db
|
|
87
|
+
.delete(ERUDIT.db.schema.files)
|
|
88
|
+
.where(eq(ERUDIT.db.schema.files.role, 'sponsor-avatar'));
|
|
89
|
+
}
|
|
90
|
+
|
|
39
91
|
async function buildSponsor(sponsorId: string) {
|
|
40
92
|
ERUDIT.log.debug.start(
|
|
41
93
|
`Building sponsor ${ERUDIT.log.stress(sponsorId)}...`,
|
|
42
94
|
);
|
|
43
95
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const sponsorFiles = readdirSync(sponsorDirectory);
|
|
96
|
+
const dir = `${sponsorsRoot()}/${sponsorId}`;
|
|
97
|
+
const files = readdirSync(dir);
|
|
47
98
|
|
|
48
|
-
const hasConfig =
|
|
99
|
+
const hasConfig = files.some(
|
|
49
100
|
(file) => file === 'sponsor.ts' || file === 'sponsor.js',
|
|
50
101
|
);
|
|
51
102
|
|
|
@@ -60,7 +111,7 @@ async function buildSponsor(sponsorId: string) {
|
|
|
60
111
|
|
|
61
112
|
try {
|
|
62
113
|
sponsorConfig = (await ERUDIT.import(
|
|
63
|
-
`${
|
|
114
|
+
`${dir}/sponsor`,
|
|
64
115
|
)) as SponsorConfig;
|
|
65
116
|
} catch (error) {
|
|
66
117
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -70,14 +121,14 @@ async function buildSponsor(sponsorId: string) {
|
|
|
70
121
|
return;
|
|
71
122
|
}
|
|
72
123
|
|
|
73
|
-
const avatarExtension =
|
|
124
|
+
const avatarExtension = files
|
|
74
125
|
.find((file) => file.startsWith('avatar.'))
|
|
75
126
|
?.split('.')
|
|
76
127
|
.pop();
|
|
77
128
|
|
|
78
129
|
if (avatarExtension) {
|
|
79
130
|
await ERUDIT.repository.db.pushFile(
|
|
80
|
-
`${
|
|
131
|
+
`${dir}/avatar.${avatarExtension}`,
|
|
81
132
|
'sponsor-avatar',
|
|
82
133
|
);
|
|
83
134
|
}
|
|
@@ -87,9 +138,9 @@ async function buildSponsor(sponsorId: string) {
|
|
|
87
138
|
return sponsorConfig.icon;
|
|
88
139
|
}
|
|
89
140
|
|
|
90
|
-
const iconFile =
|
|
141
|
+
const iconFile = files.find((file) => file === 'icon.svg');
|
|
91
142
|
if (iconFile) {
|
|
92
|
-
return readFileSync(
|
|
143
|
+
return readFileSync(dir + '/' + iconFile, 'utf-8');
|
|
93
144
|
}
|
|
94
145
|
})();
|
|
95
146
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContentExternals } from '@erudit-js/core/content/externals';
|
|
2
2
|
import type { ContentType } from '@erudit-js/core/content/type';
|
|
3
3
|
|
|
4
4
|
interface BaseContentDep {
|
|
@@ -23,5 +23,5 @@ export interface ContentConnections {
|
|
|
23
23
|
hardDependencies?: ContentHardDep[];
|
|
24
24
|
autoDependencies?: ContentAutoDep[];
|
|
25
25
|
dependents?: ContentAutoDep[];
|
|
26
|
-
externals?:
|
|
26
|
+
externals?: ContentExternals;
|
|
27
27
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
export interface ElementSnippet {
|
|
2
2
|
schemaName: string;
|
|
3
|
-
title: string;
|
|
4
3
|
link: string;
|
|
5
|
-
|
|
6
|
-
seo?: boolean;
|
|
4
|
+
title: string;
|
|
7
5
|
description?: string;
|
|
6
|
+
quick?: {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
};
|
|
10
|
+
seo?: {
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
8
14
|
}
|
package/shared/types/language.ts
CHANGED
|
@@ -93,6 +93,8 @@ export type LanguagePhrases = Phrases<{
|
|
|
93
93
|
article_seo_description: (contentTitle: string) => string;
|
|
94
94
|
summary_seo_description: (contentTitle: string) => string;
|
|
95
95
|
practice_seo_description: (contentTitle: string) => string;
|
|
96
|
+
externals_own: string;
|
|
97
|
+
externals_from: string;
|
|
96
98
|
|
|
97
99
|
default_index_title: string;
|
|
98
100
|
default_index_short: string;
|