erudit 3.0.0-dev.20 → 3.0.0-dev.21
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 +2 -0
- package/app/assets/icons/cameo-add.svg +3 -0
- package/app/assets/icons/diamond.svg +3 -0
- package/app/components/Avatar.vue +118 -0
- package/app/components/EruditLink.vue +17 -0
- package/app/components/SiteMain.vue +4 -4
- package/app/components/ads/Ads.vue +1 -3
- package/app/components/ads/AdsProviderYandex.vue +59 -22
- package/app/components/aside/AsideListItem.vue +21 -4
- package/app/components/aside/AsideMinor.vue +1 -1
- package/app/components/aside/major/SiteInfo.vue +4 -4
- package/app/components/aside/major/panes/Pages.vue +20 -1
- package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +2 -2
- package/app/components/aside/minor/AsideMinorTopLink.vue +3 -4
- package/app/components/aside/minor/contributor/AsideMinorContributor.vue +9 -9
- package/app/components/aside/minor/topic/AsideMinorTopic.vue +3 -1
- package/app/components/aside/minor/topic/TopicContributors.vue +9 -3
- package/app/components/aside/minor/topic/TopicNav.vue +1 -1
- package/app/components/aside/minor/topic/TopicToc.vue +12 -13
- package/app/components/aside/minor/topic/TopicTocItem.vue +1 -14
- package/app/components/bitran/BitranContent.vue +0 -1
- package/app/components/contributor/ContributorListItem.vue +13 -5
- package/app/components/main/MainActionButton.vue +51 -0
- package/app/components/main/MainBitranContent.vue +11 -3
- package/app/components/main/MainBreadcrumb.vue +2 -6
- package/app/components/main/MainSection.vue +58 -0
- package/app/components/main/cameo/MainCameo.vue +135 -0
- package/app/components/main/cameo/MainCameoData.vue +232 -0
- package/app/components/main/content/ContentPopovers.vue +1 -1
- package/app/components/main/topic/MainTopic.vue +13 -18
- package/app/components/main/topic/TopicPartSwitch.vue +7 -12
- package/app/components/preview/PreviewFooterAction.vue +1 -1
- package/app/components/sponsor/SponsorTier1.vue +89 -0
- package/app/components/sponsor/SponsorTier2.vue +109 -0
- package/app/components/tree/TreeItem.vue +8 -4
- package/app/composables/asset.ts +12 -0
- package/app/composables/contentData.ts +1 -1
- package/app/composables/head.ts +24 -0
- package/app/composables/majorPane.ts +1 -0
- package/app/composables/url.ts +17 -7
- package/app/pages/contributor/[contributorId].vue +9 -6
- package/app/pages/contributors.vue +73 -72
- package/app/pages/group/[...groupId].vue +4 -3
- package/app/pages/sponsors.vue +95 -0
- package/app/plugins/prerender.server.ts +14 -2
- package/app/scripts/og.ts +2 -1
- package/const.ts +0 -1
- package/globals/cameo.ts +5 -0
- package/globals/register.ts +5 -0
- package/globals/sponsor.ts +17 -0
- package/languages/en.ts +8 -3
- package/languages/ru.ts +8 -3
- package/module/imports.ts +13 -6
- package/nuxt.config.ts +2 -7
- package/package.json +4 -4
- package/server/api/cameo/data/[cameoId].ts +42 -0
- package/server/api/cameo/ids.ts +5 -0
- package/server/api/prerender/assets/cameo.ts +14 -0
- package/server/api/prerender/assets/contributor.ts +12 -0
- package/server/api/prerender/assets/sponsor.ts +13 -0
- package/server/api/prerender/cameos.ts +12 -0
- package/server/api/{prerender.ts → prerender/language.ts} +3 -13
- package/server/api/sponsor/cameo/data/[sponsorId].ts +51 -0
- package/server/api/sponsor/cameo/ids.ts +5 -0
- package/server/api/sponsor/count.ts +5 -0
- package/server/api/sponsor/list.ts +36 -0
- package/server/plugin/build/process.ts +2 -0
- package/server/plugin/build/rebuild.ts +2 -0
- package/server/plugin/global.ts +2 -0
- package/server/plugin/repository/cameo.ts +16 -0
- package/server/plugin/repository/contributor.ts +35 -4
- package/server/plugin/sponsor/build.ts +82 -0
- package/server/plugin/sponsor/index.ts +5 -0
- package/server/plugin/sponsor/repository.ts +56 -0
- package/server/routes/asset/[...assetPath].ts +34 -0
- package/server/routes/robots.txt.ts +9 -0
- package/server/routes/sitemap.xml.ts +103 -0
- package/shared/asset.ts +0 -5
- package/shared/contributor.ts +1 -0
- package/shared/link.ts +4 -4
- package/shared/types/language.ts +6 -1
- package/test/utils/url.test.ts +99 -0
- package/utils/ext.ts +41 -0
- package/utils/url.ts +23 -0
- package/app/components/contributor/ContributorAvatar.vue +0 -45
- package/app/components/main/content/ContentSection.vue +0 -45
- package/app/public/user.svg +0 -10
- package/app/scripts/aside/minor/topic.ts +0 -3
- package/utils/slash.ts +0 -11
|
@@ -14,8 +14,20 @@ export default defineNuxtPlugin({
|
|
|
14
14
|
|
|
15
15
|
isPrerendering = true;
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const routeProviders = [
|
|
18
|
+
'/api/prerender/language',
|
|
19
|
+
'/api/prerender/cameos',
|
|
20
|
+
|
|
21
|
+
// Assets
|
|
22
|
+
'/api/prerender/assets/cameo',
|
|
23
|
+
'/api/prerender/assets/contributor',
|
|
24
|
+
'/api/prerender/assets/sponsor',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const provider of routeProviders) {
|
|
28
|
+
const routes = await $fetch<string[]>(provider);
|
|
29
|
+
_nuxt.runWithContext(() => prerenderRoutes(routes));
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
alreadyPrerendered = true;
|
|
21
33
|
},
|
package/app/scripts/og.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import eruditConfig from '#erudit/config';
|
|
2
2
|
import type { ImageData } from '@erudit/shared/image';
|
|
3
|
+
import { normalizeUrl } from '@erudit/utils/url';
|
|
3
4
|
|
|
4
5
|
export const defaultOgImage = eruditConfig.seo?.defaultOgImage || {
|
|
5
6
|
src: eruditAsset('og-default.png'),
|
|
@@ -13,7 +14,7 @@ export function createOgImageTags(siteUrl: string, data: ImageData | string) {
|
|
|
13
14
|
|
|
14
15
|
tags.push({
|
|
15
16
|
name: 'og:image',
|
|
16
|
-
content: siteUrl + (isImage ? data.src : data),
|
|
17
|
+
content: normalizeUrl(siteUrl + (isImage ? data.src : data)),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
if (isImage) {
|
package/const.ts
CHANGED
package/globals/cameo.ts
ADDED
package/globals/register.ts
CHANGED
|
@@ -2,6 +2,9 @@ import * as erudit from './erudit';
|
|
|
2
2
|
import * as contributor from './contributor';
|
|
3
3
|
import * as bitran from './bitran';
|
|
4
4
|
import * as content from './content';
|
|
5
|
+
import * as cameo from './cameo';
|
|
6
|
+
import * as sponsor from './sponsor';
|
|
7
|
+
|
|
5
8
|
import * as asset from '@shared/asset';
|
|
6
9
|
|
|
7
10
|
export function registerGlobals() {
|
|
@@ -11,6 +14,8 @@ export function registerGlobals() {
|
|
|
11
14
|
...bitran,
|
|
12
15
|
...content,
|
|
13
16
|
...asset,
|
|
17
|
+
...cameo,
|
|
18
|
+
...sponsor,
|
|
14
19
|
})) {
|
|
15
20
|
// @ts-ignore
|
|
16
21
|
globalThis[key] = value;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SponsorConfig,
|
|
3
|
+
Tier1Sponsor,
|
|
4
|
+
Tier2Sponsor,
|
|
5
|
+
} from '@erudit-js/cog/schema';
|
|
6
|
+
|
|
7
|
+
export function defineTier1Sponsor(
|
|
8
|
+
sponsorConfig: SponsorConfig<Tier1Sponsor>,
|
|
9
|
+
): SponsorConfig<Tier1Sponsor> {
|
|
10
|
+
return sponsorConfig;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function defineTier2Sponsor(
|
|
14
|
+
sponsorConfig: SponsorConfig<Tier2Sponsor>,
|
|
15
|
+
): SponsorConfig<Tier2Sponsor> {
|
|
16
|
+
return sponsorConfig;
|
|
17
|
+
}
|
package/languages/en.ts
CHANGED
|
@@ -29,8 +29,9 @@ const english: EruditPhrases = {
|
|
|
29
29
|
contributors: 'Contributors',
|
|
30
30
|
contributors_page_description:
|
|
31
31
|
'List of people who contributed to the project materials: suggested valuable ideas, made corrections to existing material or wrote their own!',
|
|
32
|
-
contributors_page_invite:
|
|
33
|
-
|
|
32
|
+
contributors_page_invite:
|
|
33
|
+
'You can also help the project, make a contribution and get on this page!',
|
|
34
|
+
become_contributor: 'Become a contributor',
|
|
34
35
|
contributor: 'Contributor',
|
|
35
36
|
contribution: 'Contribution',
|
|
36
37
|
contributions_explain: (count) =>
|
|
@@ -87,7 +88,11 @@ const english: EruditPhrases = {
|
|
|
87
88
|
references: 'References',
|
|
88
89
|
reference_source_featured: 'Featured source',
|
|
89
90
|
references_description:
|
|
90
|
-
'
|
|
91
|
+
'A list of external sources that were used to write this material. If there is an asterisk next to the title, it is a featured source and is worth reading if you want to delve deeper into the material.',
|
|
92
|
+
sponsors: 'Sponsors',
|
|
93
|
+
sponsors_description:
|
|
94
|
+
'People and organizations that support the project financially. Thanks to them, we can continue to develop the project and improve the quality of materials. If you want to support the project, you can become a sponsor too!',
|
|
95
|
+
become_sponsor: 'Become a sponsor',
|
|
91
96
|
};
|
|
92
97
|
|
|
93
98
|
export default english;
|
package/languages/ru.ts
CHANGED
|
@@ -29,8 +29,9 @@ const russian: EruditPhrases = {
|
|
|
29
29
|
contributors: 'Авторы',
|
|
30
30
|
contributors_page_description:
|
|
31
31
|
'Список людей, которые внесли вклад в материалы проекта: предложили ценные идеи, вносили корректировки в существующий материал или же написали собственный!',
|
|
32
|
-
contributors_page_invite:
|
|
33
|
-
|
|
32
|
+
contributors_page_invite:
|
|
33
|
+
'Вы тоже можете помочь проекту, внести свой вклад и попасть на эту страницу!',
|
|
34
|
+
become_contributor: 'Стать автором',
|
|
34
35
|
contributor: 'Автор',
|
|
35
36
|
contribution: 'Вклад',
|
|
36
37
|
contributions_explain: (count) =>
|
|
@@ -88,7 +89,11 @@ const russian: EruditPhrases = {
|
|
|
88
89
|
references: 'Источники',
|
|
89
90
|
reference_source_featured: 'Избранный источник',
|
|
90
91
|
references_description:
|
|
91
|
-
'Список внешних источников, которые использовались при написании этого материала.
|
|
92
|
+
'Список внешних источников, которые использовались при написании этого материала. Если рядом с названием стоит звездочка, то это избранный источник и с ним стоит ознакомиться, если вы хотите глубже погрузиться в материал.',
|
|
93
|
+
sponsors: 'Спонсоры',
|
|
94
|
+
sponsors_description:
|
|
95
|
+
'Список людей и организаций, которые поддерживают проект финансово. Благодаря им проект может существовать и развиваться. Если вы хотите помочь проекту, то тоже можете стать спонсором и попасть на эту страницу!',
|
|
96
|
+
become_sponsor: 'Стать спонсором',
|
|
92
97
|
};
|
|
93
98
|
|
|
94
99
|
export default russian;
|
package/module/imports.ts
CHANGED
|
@@ -12,6 +12,18 @@ export function setupGlobalImports() {
|
|
|
12
12
|
name: 'defineContributor',
|
|
13
13
|
from: eruditPath('globals/contributor'),
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
name: 'defineCameo',
|
|
17
|
+
from: eruditPath('globals/cameo'),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'defineTier1Sponsor',
|
|
21
|
+
from: eruditPath('globals/sponsor'),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'defineTier2Sponsor',
|
|
25
|
+
from: eruditPath('globals/sponsor'),
|
|
26
|
+
},
|
|
15
27
|
// Bitran
|
|
16
28
|
...(() => {
|
|
17
29
|
const imports = ['defineAppBitran'];
|
|
@@ -41,12 +53,7 @@ export function setupGlobalImports() {
|
|
|
41
53
|
},
|
|
42
54
|
// Helper Asset Path Functions
|
|
43
55
|
...(() => {
|
|
44
|
-
const imports = [
|
|
45
|
-
'eruditAsset',
|
|
46
|
-
'contributorAsset',
|
|
47
|
-
'contentAsset',
|
|
48
|
-
'publicAsset',
|
|
49
|
-
];
|
|
56
|
+
const imports = ['eruditAsset', 'contentAsset', 'publicAsset'];
|
|
50
57
|
return imports.map((name) => ({
|
|
51
58
|
name,
|
|
52
59
|
from: eruditPath(`shared/asset`),
|
package/nuxt.config.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PUBLIC_CONTENT_ASSET,
|
|
3
|
-
PUBLIC_CONTRIBUTOR_ASSET,
|
|
4
3
|
PUBLIC_ERUDIT_ASSET,
|
|
5
4
|
PUBLIC_USER_ASSET,
|
|
6
5
|
} from './const';
|
|
@@ -57,9 +56,10 @@ export default defineNuxtConfig({
|
|
|
57
56
|
crawlLinks: true,
|
|
58
57
|
failOnError: false,
|
|
59
58
|
concurrency: 10,
|
|
59
|
+
routes: ['/robots.txt', '/sitemap.xml'],
|
|
60
60
|
ignore: [
|
|
61
61
|
(path: string) => {
|
|
62
|
-
const regexps = [
|
|
62
|
+
const regexps = [/#/gm, /\.json\?/gm];
|
|
63
63
|
const shouldIgnore = regexps.some((re) => re.test(path));
|
|
64
64
|
return shouldIgnore;
|
|
65
65
|
},
|
|
@@ -87,11 +87,6 @@ export default defineNuxtConfig({
|
|
|
87
87
|
dir: projectPath('content'),
|
|
88
88
|
maxAge: 60 * 60 * 24 * 30,
|
|
89
89
|
},
|
|
90
|
-
{
|
|
91
|
-
baseURL: PUBLIC_CONTRIBUTOR_ASSET,
|
|
92
|
-
dir: projectPath('contributors'),
|
|
93
|
-
maxAge: 60 * 60 * 24 * 30,
|
|
94
|
-
},
|
|
95
90
|
],
|
|
96
91
|
esbuild: {
|
|
97
92
|
options: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "erudit",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "🤓 CMS for perfect educational sites.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"erudit": "bin/erudit.mjs"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@erudit-js/cog": "3.0.0-dev.
|
|
19
|
-
"@erudit-js/cli": "3.0.0-dev.
|
|
20
|
-
"@erudit-js/bitran-elements": "3.0.0-dev.
|
|
18
|
+
"@erudit-js/cog": "3.0.0-dev.21",
|
|
19
|
+
"@erudit-js/cli": "3.0.0-dev.21",
|
|
20
|
+
"@erudit-js/bitran-elements": "3.0.0-dev.21"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@bitran-js/core": "1.0.0-dev.13",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { type Cameo } from '@erudit-js/cog/schema';
|
|
3
|
+
|
|
4
|
+
import { PROJECT_DIR } from '@erudit/globalPath';
|
|
5
|
+
import { IMPORT } from '@server/importer';
|
|
6
|
+
|
|
7
|
+
export default defineEventHandler<Promise<Cameo>>(async (event) => {
|
|
8
|
+
const cameoId = event.context.params?.cameoId;
|
|
9
|
+
|
|
10
|
+
if (typeof cameoId !== 'string' || !cameoId) {
|
|
11
|
+
throw createError({
|
|
12
|
+
statusCode: 400,
|
|
13
|
+
message: 'Cameo ID is required and must be a string!',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const avatarPaths = await glob(`cameos/${cameoId}/avatars/*`, {
|
|
18
|
+
cwd: PROJECT_DIR,
|
|
19
|
+
absolute: false,
|
|
20
|
+
posix: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const avatarRoutes = avatarPaths.map((path) => `/asset/${path}`);
|
|
24
|
+
|
|
25
|
+
let cameoConfig;
|
|
26
|
+
try {
|
|
27
|
+
cameoConfig = await IMPORT(`${PROJECT_DIR}/cameos/${cameoId}/cameo`, {
|
|
28
|
+
default: true,
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw createError({
|
|
32
|
+
statusCode: 500,
|
|
33
|
+
message: `Failed to import config for ID "${cameoId}"! Error: ${error}`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
...cameoConfig,
|
|
39
|
+
cameoId,
|
|
40
|
+
avatars: avatarRoutes,
|
|
41
|
+
} satisfies Cameo;
|
|
42
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PROJECT_DIR } from '#erudit/globalPaths';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
|
|
4
|
+
const includePatterns = [`cameos/*/avatars/*`];
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async () => {
|
|
7
|
+
const files = await glob(includePatterns, {
|
|
8
|
+
cwd: PROJECT_DIR,
|
|
9
|
+
absolute: false,
|
|
10
|
+
posix: true,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return files.map((file) => '/asset/' + file);
|
|
14
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { PROJECT_DIR } from '#erudit/globalPaths';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async () => {
|
|
5
|
+
const avatars = await glob('contributors/*/avatar.*', {
|
|
6
|
+
cwd: PROJECT_DIR,
|
|
7
|
+
absolute: false,
|
|
8
|
+
posix: true,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return avatars.map((avatar) => '/asset/' + avatar);
|
|
12
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ERUDIT_SERVER } from '@server/global';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async () => {
|
|
4
|
+
const serverSponsors = ERUDIT_SERVER.SPONSORS;
|
|
5
|
+
|
|
6
|
+
if (!serverSponsors) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return Object.values(serverSponsors.avatars)
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.map((avatar) => '/asset/' + avatar);
|
|
13
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getCameoIds } from '@server/repository/cameo';
|
|
2
|
+
import { getTier2SponsorIds } from '@server/sponsor/repository';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async () => {
|
|
5
|
+
const cameoIds = await getCameoIds();
|
|
6
|
+
return [
|
|
7
|
+
'/api/cameo/ids',
|
|
8
|
+
'/api/sponsor/cameo/ids',
|
|
9
|
+
...cameoIds.map((id) => `/api/cameo/data/${id}`),
|
|
10
|
+
...getTier2SponsorIds().map((id) => `/api/sponsor/cameo/data/${id}`),
|
|
11
|
+
];
|
|
12
|
+
});
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { ERUDIT_SERVER } from '@server/global';
|
|
2
2
|
|
|
3
|
-
export default defineEventHandler(
|
|
4
|
-
const routes: string[] = [];
|
|
5
|
-
routes.push(...language());
|
|
6
|
-
|
|
7
|
-
return routes;
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
function language() {
|
|
3
|
+
export default defineEventHandler(() => {
|
|
15
4
|
const phraseRoutes = Object.keys(ERUDIT_SERVER.LANGUAGE?.phrases || []).map(
|
|
16
5
|
(phraseId) => `/api/language/phrase/${phraseId}`,
|
|
17
6
|
);
|
|
7
|
+
|
|
18
8
|
return ['/api/language/functions', ...phraseRoutes];
|
|
19
|
-
}
|
|
9
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type Cameo, type Sponsor } from '@erudit-js/cog/schema';
|
|
2
|
+
|
|
3
|
+
import { retrieveSponsor } from '@server/sponsor/repository';
|
|
4
|
+
import { ERUDIT_SERVER } from '@server/global';
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler<Promise<Cameo>>(async (event) => {
|
|
7
|
+
const sponsorId = event.context.params?.sponsorId;
|
|
8
|
+
|
|
9
|
+
if (typeof sponsorId !== 'string' || !sponsorId) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 400,
|
|
12
|
+
message: 'Sponsor ID is required and must be a string!',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let sponsor: Sponsor;
|
|
17
|
+
try {
|
|
18
|
+
sponsor = await retrieveSponsor(sponsorId);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw createError({
|
|
21
|
+
statusCode: 500,
|
|
22
|
+
message: `Failed to retrieve sponsor with ID "${sponsorId}"! Error: ${error}`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (sponsor.tier !== 'tier2') {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 404,
|
|
29
|
+
message: `Sponsor with ID "${sponsorId}" is not a Tier 2 sponsor!`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const defaultMessages = ERUDIT_SERVER.CONFIG.sponsors?.defaultCameoMessages;
|
|
34
|
+
|
|
35
|
+
if (!defaultMessages) {
|
|
36
|
+
throw createError({
|
|
37
|
+
statusCode: 500,
|
|
38
|
+
message: 'Default sponsor cameo messages are not configured!',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
cameoId: sponsorId,
|
|
44
|
+
name: sponsor.name,
|
|
45
|
+
icon: sponsor.icon,
|
|
46
|
+
avatars: [sponsor.avatar],
|
|
47
|
+
color: sponsor.color,
|
|
48
|
+
link: sponsor.link,
|
|
49
|
+
messages: [...(sponsor.messages || []), ...defaultMessages],
|
|
50
|
+
} satisfies Cameo;
|
|
51
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Tier1Sponsor, Tier2Sponsor } from '@erudit-js/cog/schema';
|
|
2
|
+
|
|
3
|
+
import { getSponsorIds, retrieveSponsor } from '@server/sponsor/repository';
|
|
4
|
+
|
|
5
|
+
const BATCH_SIZE = 10;
|
|
6
|
+
|
|
7
|
+
export default defineEventHandler(async () => {
|
|
8
|
+
const sponsorIds = getSponsorIds();
|
|
9
|
+
const sponsors = await processSponsorsBatched(sponsorIds);
|
|
10
|
+
return categorizeSponsorsByTier(sponsors);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function processSponsorsBatched(sponsorIds: string[]) {
|
|
14
|
+
const allSponsors = [];
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < sponsorIds.length; i += BATCH_SIZE) {
|
|
17
|
+
const batch = sponsorIds.slice(i, i + BATCH_SIZE);
|
|
18
|
+
const batchResults = await Promise.all(
|
|
19
|
+
batch.map((sponsorId) => retrieveSponsor(sponsorId!)),
|
|
20
|
+
);
|
|
21
|
+
allSponsors.push(...batchResults);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return allSponsors;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function categorizeSponsorsByTier(sponsors: (Tier1Sponsor | Tier2Sponsor)[]) {
|
|
28
|
+
return {
|
|
29
|
+
tier1: sponsors.filter(
|
|
30
|
+
(sponsor): sponsor is Tier1Sponsor => sponsor.tier === 'tier1',
|
|
31
|
+
),
|
|
32
|
+
tier2: sponsors.filter(
|
|
33
|
+
(sponsor): sponsor is Tier2Sponsor => sponsor.tier === 'tier2',
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -7,6 +7,7 @@ import { setupLanguage } from './jobs/language';
|
|
|
7
7
|
import { buildContributors } from './jobs/contributors';
|
|
8
8
|
import { buildNav } from './jobs/nav';
|
|
9
9
|
import { buildContent } from './jobs/content/generic';
|
|
10
|
+
import { buildSponsors } from '@server/sponsor/build';
|
|
10
11
|
|
|
11
12
|
let initial = true;
|
|
12
13
|
|
|
@@ -23,6 +24,7 @@ async function _build() {
|
|
|
23
24
|
await buildContributors();
|
|
24
25
|
await buildNav();
|
|
25
26
|
await buildContent();
|
|
27
|
+
await buildSponsors();
|
|
26
28
|
|
|
27
29
|
logger.success('Build successful!');
|
|
28
30
|
}
|
package/server/plugin/global.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { EruditConfig } from '@erudit-js/cog/schema';
|
|
|
3
3
|
|
|
4
4
|
import type { EruditPhrases } from '@shared/types/language';
|
|
5
5
|
import type { NavNode, RootNavNode } from '@server/nav/node';
|
|
6
|
+
import type { ServerSponsors } from '@server/sponsor';
|
|
6
7
|
|
|
7
8
|
interface EruditServer {
|
|
8
9
|
BUILD_PROMISE: Promise<void>;
|
|
@@ -11,6 +12,7 @@ interface EruditServer {
|
|
|
11
12
|
DB: DataSource;
|
|
12
13
|
NAV?: RootNavNode;
|
|
13
14
|
NAV_BOOKS?: Record<string, NavNode>;
|
|
15
|
+
SPONSORS?: ServerSponsors;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const ERUDIT_SERVER: EruditServer = {} as any;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { PROJECT_DIR } from '#erudit/globalPaths';
|
|
3
|
+
|
|
4
|
+
export async function getCameoIds() {
|
|
5
|
+
try {
|
|
6
|
+
const dirents = await readdir(`${PROJECT_DIR}/cameos`, {
|
|
7
|
+
withFileTypes: true,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return dirents
|
|
11
|
+
.filter((dirent) => dirent.isDirectory())
|
|
12
|
+
.map((dirent) => dirent.name);
|
|
13
|
+
} catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -62,13 +62,20 @@ export async function getContributions(
|
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
const contributions: Contribution[] = [];
|
|
65
|
+
const existingBookTitles = new Set<string>();
|
|
65
66
|
|
|
66
67
|
for (const dbContribution of dbContributions) {
|
|
67
68
|
const contentId = dbContribution.contentId;
|
|
68
69
|
const contentNavNode = getNavNode(contentId);
|
|
69
70
|
|
|
70
|
-
const
|
|
71
|
-
const
|
|
71
|
+
const bookData = await (async () => {
|
|
72
|
+
const bookNavNode = getNavBookOf(contentId);
|
|
73
|
+
|
|
74
|
+
if (!bookNavNode) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const bookId = bookNavNode.fullId;
|
|
72
79
|
|
|
73
80
|
if (!bookId) {
|
|
74
81
|
return undefined;
|
|
@@ -78,14 +85,38 @@ export async function getContributions(
|
|
|
78
85
|
return undefined;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
let bookTitle = await getBookTitleFor(bookId);
|
|
89
|
+
|
|
90
|
+
let cursor = bookNavNode;
|
|
91
|
+
while (cursor) {
|
|
92
|
+
if (cursor.type === 'group') {
|
|
93
|
+
const groupTitle = await getContentTitle(cursor.fullId);
|
|
94
|
+
const newBookTitle = groupTitle + ' / ' + bookTitle;
|
|
95
|
+
|
|
96
|
+
if (bookTitle && existingBookTitles.has(bookTitle)) {
|
|
97
|
+
bookTitle = newBookTitle;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
cursor = cursor.parent as any;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (bookTitle) {
|
|
105
|
+
existingBookTitles.add(bookTitle);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
bookId,
|
|
110
|
+
bookTitle,
|
|
111
|
+
};
|
|
82
112
|
})();
|
|
83
113
|
|
|
84
114
|
const contentTitle = await getContentTitle(contentId);
|
|
85
115
|
const contentLink = await getContentLink(contentId);
|
|
86
116
|
|
|
87
117
|
const contribution: Contribution = {
|
|
88
|
-
|
|
118
|
+
bookId: bookData?.bookId,
|
|
119
|
+
bookTitle: bookData?.bookTitle,
|
|
89
120
|
contentType: contentNavNode.type,
|
|
90
121
|
contentTitle,
|
|
91
122
|
contentLink,
|