erudit 3.0.0-dev.20 → 3.0.0-dev.22
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/app/scripts/preview/data/pageLink.ts +4 -3
- package/app/scripts/preview/data/unique.ts +6 -6
- 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
package/app/app.vue
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
|
2
|
+
<path d="M896 0h-768c-70.6 0-128 57.4-128 128v576c0 70.6 57.4 128 128 128h192v160c0 12.2 6.8 23.2 17.6 28.6s23.8 4.2 33.6-3l247.4-185.6h277.4c70.6 0 128-57.4 128-128v-576c0-70.6-57.4-128-128-128zM688.6 465.2h-127.4v127.4c0 27.2-22 49.2-49.2 49.2s-49.2-22-49.2-49.2v-127.4h-127.4c-27.2 0-49.2-22-49.2-49.2s22-49.2 49.2-49.2h127.4v-127.4c0-27.2 22-49.2 49.2-49.2s49.2 22 49.2 49.2v127.4h127.4c27.2 0 49.2 22 49.2 49.2s-22 49.2-49.2 49.2z"></path>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
|
2
|
+
<path class="cls-1" d="M12,22.5L.3,8.5,3.8,1.5h16.4l3.5,7-11.7,14.1ZM9.2,7.3h5.6l-1.8-3.5h-2l-1.8,3.5ZM10.8,17.5v-7.8h-6.5l6.5,7.8ZM13.2,17.5l6.5-7.8h-6.5v7.8ZM17.4,7.3h3.1l-1.8-3.5h-3.1l1.8,3.5ZM3.5,7.3h3.1l1.8-3.5h-3.1l-1.8,3.5Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { MyIconName } from '#my-icons';
|
|
3
|
+
import { detectStrictFileType } from '@erudit/utils/ext';
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
src?: string;
|
|
7
|
+
icon?: MyIconName;
|
|
8
|
+
color?: string;
|
|
9
|
+
styling?: Partial<{
|
|
10
|
+
glow: boolean;
|
|
11
|
+
border: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const assetRoute = useAssetRoute();
|
|
16
|
+
|
|
17
|
+
const avatarSrc = computed(() => {
|
|
18
|
+
return props.src ? assetRoute(props.src) : undefined;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const fallbackVisible = computed(() => {
|
|
22
|
+
return !avatarSrc.value /* || failed to load src */;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const avatarType = computed(() => {
|
|
26
|
+
return avatarSrc.value
|
|
27
|
+
? detectStrictFileType(avatarSrc.value, 'image', 'video')
|
|
28
|
+
: undefined;
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
:class="[
|
|
35
|
+
$style.avatar,
|
|
36
|
+
styling?.glow && $style.glow,
|
|
37
|
+
styling?.border && $style.border,
|
|
38
|
+
]"
|
|
39
|
+
:style="{ ['--avatarColor']: props.color }"
|
|
40
|
+
>
|
|
41
|
+
<div :class="$style.inner">
|
|
42
|
+
<div v-if="fallbackVisible" :class="$style.fallback">
|
|
43
|
+
<MyIcon :name="icon || '__missing'" />
|
|
44
|
+
</div>
|
|
45
|
+
<template v-else>
|
|
46
|
+
<img
|
|
47
|
+
v-if="avatarType === 'image'"
|
|
48
|
+
:src="avatarSrc"
|
|
49
|
+
loading="lazy"
|
|
50
|
+
/>
|
|
51
|
+
<video
|
|
52
|
+
v-else
|
|
53
|
+
:src="avatarSrc"
|
|
54
|
+
preload="metadata"
|
|
55
|
+
autoplay
|
|
56
|
+
loop
|
|
57
|
+
muted
|
|
58
|
+
></video>
|
|
59
|
+
</template>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style lang="scss" module>
|
|
65
|
+
.avatar {
|
|
66
|
+
--avatarSize: 110px;
|
|
67
|
+
--avatarColor: var(--border);
|
|
68
|
+
|
|
69
|
+
width: var(--avatarSize);
|
|
70
|
+
height: var(--avatarSize);
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
|
|
73
|
+
.inner {
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 100%;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
border-radius: 50%;
|
|
78
|
+
|
|
79
|
+
img,
|
|
80
|
+
video {
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
object-fit: cover;
|
|
84
|
+
object-position: center;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.fallback {
|
|
89
|
+
position: relative;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: 100%;
|
|
95
|
+
background: linear-gradient(
|
|
96
|
+
45deg,
|
|
97
|
+
color-mix(in srgb, var(--avatarColor), black 10%),
|
|
98
|
+
color-mix(in srgb, var(--avatarColor), white 40%)
|
|
99
|
+
);
|
|
100
|
+
color: color-mix(in srgb, black, var(--avatarColor) 50%);
|
|
101
|
+
font-size: calc(var(--avatarSize) / 1.85);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//
|
|
105
|
+
//
|
|
106
|
+
//
|
|
107
|
+
|
|
108
|
+
&.glow {
|
|
109
|
+
box-shadow: 0 0 7px 7px
|
|
110
|
+
color-mix(in srgb, var(--avatarColor), transparent 85%);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
&.border {
|
|
114
|
+
border: 2px solid var(--avatarColor);
|
|
115
|
+
padding: 2px;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
to?: string;
|
|
4
|
+
prefetch?: boolean;
|
|
5
|
+
}>();
|
|
6
|
+
|
|
7
|
+
const EruditLink = defineNuxtLink({
|
|
8
|
+
prefetch: props.prefetch ?? true,
|
|
9
|
+
trailingSlash: 'append',
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<EruditLink :to="props.to">
|
|
15
|
+
<slot></slot>
|
|
16
|
+
</EruditLink>
|
|
17
|
+
</template>
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
@use '$/def/bp';
|
|
12
12
|
|
|
13
13
|
.main {
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--_pMainY: calc(var(--_pMainBase) / 2);
|
|
14
|
+
--_pMainX: 30px;
|
|
15
|
+
--_pMainY: 18px;
|
|
17
16
|
|
|
18
17
|
position: relative;
|
|
19
18
|
min-height: 100dvh;
|
|
@@ -21,7 +20,8 @@
|
|
|
21
20
|
font-size: 18px;
|
|
22
21
|
|
|
23
22
|
@include bp.below('mobile') {
|
|
24
|
-
--
|
|
23
|
+
--_pMainX: 18px;
|
|
24
|
+
--_pMainY: 14px;
|
|
25
25
|
font-size: 15px;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -4,9 +4,7 @@ import type { EruditAds } from '@erudit-js/cog/schema';
|
|
|
4
4
|
import { LazyAdsProviderCustom, LazyAdsProviderYandex } from '#components';
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{ data: EruditAds }>();
|
|
7
|
-
|
|
8
7
|
const route = useRoute();
|
|
9
|
-
watch(() => route.path, updateAds);
|
|
10
8
|
|
|
11
9
|
const adsKey = ref(0);
|
|
12
10
|
const AdsComponent = shallowRef<Component>();
|
|
@@ -26,7 +24,7 @@ function updateAds() {
|
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
onMounted(() => {
|
|
29
|
-
|
|
27
|
+
watch(() => route.path, updateAds, { immediate: true });
|
|
30
28
|
});
|
|
31
29
|
</script>
|
|
32
30
|
|
|
@@ -4,39 +4,76 @@ import type { EruditAdsYandex } from '@erudit-js/cog/schema';
|
|
|
4
4
|
declare global {
|
|
5
5
|
interface Window {
|
|
6
6
|
yaContextCb: Array<() => void>;
|
|
7
|
+
__yandexScriptLoaded?: boolean;
|
|
8
|
+
__yandexScriptLoading?: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
var Ya: any;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
const props = defineProps<{ data: EruditAdsYandex }>();
|
|
15
|
+
const uid = useId();
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
key: '__darkMagicYandexContext',
|
|
22
|
-
src: 'https://yandex.ru/ads/system/context.js',
|
|
23
|
-
async: true,
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
});
|
|
17
|
+
const loadYandexScript = (): Promise<void> => {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
// Script already loaded
|
|
20
|
+
if (window.__yandexScriptLoaded) {
|
|
21
|
+
resolve();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
// Script is currently loading, wait for it
|
|
26
|
+
if (window.__yandexScriptLoading) {
|
|
27
|
+
const checkLoaded = () => {
|
|
28
|
+
if (window.__yandexScriptLoaded) {
|
|
29
|
+
resolve();
|
|
30
|
+
} else {
|
|
31
|
+
setTimeout(checkLoaded, 50);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
checkLoaded();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
// Load script for the first time
|
|
39
|
+
window.__yandexScriptLoading = true;
|
|
40
|
+
window.yaContextCb = window.yaContextCb || [];
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const script = document.createElement('script');
|
|
43
|
+
script.src = 'https://yandex.ru/ads/system/context.js';
|
|
44
|
+
script.async = true;
|
|
45
|
+
|
|
46
|
+
script.onload = () => {
|
|
47
|
+
window.__yandexScriptLoaded = true;
|
|
48
|
+
window.__yandexScriptLoading = false;
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
script.onerror = () => {
|
|
53
|
+
window.__yandexScriptLoading = false;
|
|
54
|
+
reject(new Error('Failed to load Yandex context script'));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
document.head.appendChild(script);
|
|
39
58
|
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
onMounted(async () => {
|
|
62
|
+
try {
|
|
63
|
+
await loadYandexScript();
|
|
64
|
+
|
|
65
|
+
const theme = useTheme();
|
|
66
|
+
|
|
67
|
+
window.yaContextCb.push(() => {
|
|
68
|
+
Ya?.Context?.AdvManager?.render({
|
|
69
|
+
blockId: props.data.blockId,
|
|
70
|
+
renderTo: uid,
|
|
71
|
+
darkTheme: theme.binaryTheme.value === 'dark',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Failed to load Yandex ads:', error);
|
|
76
|
+
}
|
|
40
77
|
});
|
|
41
78
|
</script>
|
|
42
79
|
|
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import type
|
|
2
|
+
import { isMyIcon, type MyIconName } from '#my-icons';
|
|
3
|
+
import { computed } from 'vue';
|
|
3
4
|
|
|
4
5
|
const props = defineProps<{
|
|
5
6
|
link?: string;
|
|
6
|
-
icon?:
|
|
7
|
+
icon?: string;
|
|
7
8
|
main?: string;
|
|
8
9
|
active?: boolean;
|
|
9
10
|
secondary?: string;
|
|
10
11
|
}>();
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const isExternalLink = computed(() => {
|
|
14
|
+
if (!props.link) return false;
|
|
15
|
+
return (
|
|
16
|
+
props.link.startsWith('http://') ||
|
|
17
|
+
props.link.startsWith('https://') ||
|
|
18
|
+
props.link.startsWith('//')
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const vnode = h(
|
|
23
|
+
props.link ? defineNuxtLink({ trailingSlash: 'append' }) : 'div',
|
|
24
|
+
);
|
|
13
25
|
</script>
|
|
14
26
|
|
|
15
27
|
<template>
|
|
16
28
|
<component
|
|
17
29
|
:is="vnode"
|
|
18
30
|
:to="link"
|
|
31
|
+
:target="isExternalLink ? '_blank' : undefined"
|
|
32
|
+
:rel="isExternalLink ? 'noopener noreferrer' : undefined"
|
|
19
33
|
:class="[$style.asideListItem, active ? $style.active : '']"
|
|
20
34
|
>
|
|
21
35
|
<slot>
|
|
22
|
-
<
|
|
36
|
+
<template v-if="icon">
|
|
37
|
+
<MyIcon v-if="isMyIcon(icon)" :name="icon as MyIconName" />
|
|
38
|
+
<MyRuntimeIcon v-else name="AsideListItem Icon" :svg="icon" />
|
|
39
|
+
</template>
|
|
23
40
|
<div :class="$style.main">{{ main }}</div>
|
|
24
41
|
<div v-if="secondary" :class="$style.secondary">
|
|
25
42
|
{{ secondary }}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { isTopicPart } from '@erudit-js/cog/schema';
|
|
3
3
|
|
|
4
4
|
import { type AsideMinorNews, type AsideMinorData } from '@shared/aside/minor';
|
|
5
|
-
import { trailingSlash } from '@erudit/utils/
|
|
5
|
+
import { trailingSlash } from '@erudit/utils/url';
|
|
6
6
|
import { asideMinorKey } from '@app/scripts/aside/minor/state';
|
|
7
7
|
|
|
8
8
|
import {
|
|
@@ -29,14 +29,14 @@ const siteInfo = computed<SiteInfo>(() => {
|
|
|
29
29
|
|
|
30
30
|
<template>
|
|
31
31
|
<section :class="$style.siteInfo">
|
|
32
|
-
<
|
|
32
|
+
<EruditLink v-if="siteInfo.logotype" to="/" :class="$style.logo">
|
|
33
33
|
<img :src="baseUrlPath(siteInfo.logotype)" :alt="siteInfo.title" />
|
|
34
|
-
</
|
|
34
|
+
</EruditLink>
|
|
35
35
|
<div :class="[$style.textInfo, !siteInfo.logotype && $style.noLogo]">
|
|
36
36
|
<h1 :class="$style.title">
|
|
37
|
-
<
|
|
37
|
+
<EruditLink to="/">{{
|
|
38
38
|
siteInfo.title || phrase.site_info_title
|
|
39
|
-
}}</
|
|
39
|
+
}}</EruditLink>
|
|
40
40
|
</h1>
|
|
41
41
|
<div v-if="siteInfo.slogan" :class="$style.description">
|
|
42
42
|
{{ siteInfo.slogan }}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import eruditConfig from '#erudit/config';
|
|
2
3
|
import PaneContentScroll from '../PaneContentScroll.vue';
|
|
3
4
|
|
|
4
5
|
const route = useRoute();
|
|
5
6
|
|
|
6
|
-
const phrase = await usePhrases('main_page', 'contributors');
|
|
7
|
+
const phrase = await usePhrases('main_page', 'contributors', 'sponsors');
|
|
7
8
|
|
|
8
9
|
const { data: contributorCount } = await useAsyncData<number>(
|
|
9
10
|
'contributor-count',
|
|
10
11
|
() => $fetch('/api/contributor/count'),
|
|
11
12
|
);
|
|
13
|
+
|
|
14
|
+
const { data: sponsorCount } = await useAsyncData<number>('sponsor-count', () =>
|
|
15
|
+
$fetch('/api/sponsor/count'),
|
|
16
|
+
);
|
|
12
17
|
</script>
|
|
13
18
|
|
|
14
19
|
<template>
|
|
@@ -27,5 +32,19 @@ const { data: contributorCount } = await useAsyncData<number>(
|
|
|
27
32
|
:main="phrase.contributors"
|
|
28
33
|
:secondary="contributorCount!.toString()"
|
|
29
34
|
/>
|
|
35
|
+
<AsideListItem
|
|
36
|
+
v-if="eruditConfig.sponsors"
|
|
37
|
+
icon="diamond"
|
|
38
|
+
link="/sponsors/"
|
|
39
|
+
:active="route.path === '/sponsors/'"
|
|
40
|
+
:main="phrase.sponsors"
|
|
41
|
+
:secondary="sponsorCount ? sponsorCount.toString() : ''"
|
|
42
|
+
/>
|
|
43
|
+
<AsideListItem
|
|
44
|
+
v-for="customLink in eruditConfig.customLinks"
|
|
45
|
+
:link="customLink.href"
|
|
46
|
+
:main="customLink.label"
|
|
47
|
+
:icon="customLink.icon || 'link-external'"
|
|
48
|
+
/>
|
|
30
49
|
</PaneContentScroll>
|
|
31
50
|
</template>
|
|
@@ -23,7 +23,7 @@ watch(navState!.value[props.navItem.id]!, onStateUpdate);
|
|
|
23
23
|
|
|
24
24
|
<template>
|
|
25
25
|
<div>
|
|
26
|
-
<
|
|
26
|
+
<EruditLink
|
|
27
27
|
:to="`/group/${navItem.id}`"
|
|
28
28
|
:class="[
|
|
29
29
|
$style.header,
|
|
@@ -38,7 +38,7 @@ watch(navState!.value[props.navItem.id]!, onStateUpdate);
|
|
|
38
38
|
v-if="navItem.flags"
|
|
39
39
|
:flags="navItem.flags"
|
|
40
40
|
/>
|
|
41
|
-
</
|
|
41
|
+
</EruditLink>
|
|
42
42
|
<FNavItem v-for="childItem of navItem.children" :navItem="childItem" />
|
|
43
43
|
</div>
|
|
44
44
|
</template>
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
import type { MyIconName } from '#my-icons';
|
|
3
3
|
|
|
4
4
|
defineProps<{ link?: string; icon: MyIconName; active?: boolean }>();
|
|
5
|
-
|
|
6
|
-
const Link = defineNuxtLink({ prefetch: false });
|
|
7
5
|
</script>
|
|
8
6
|
|
|
9
7
|
<template>
|
|
10
|
-
<
|
|
8
|
+
<EruditLink
|
|
11
9
|
:to="link"
|
|
10
|
+
:prefetch="false"
|
|
12
11
|
:class="[
|
|
13
12
|
$style.asideMinorTopLink,
|
|
14
13
|
!link && $style.noLink,
|
|
@@ -16,7 +15,7 @@ const Link = defineNuxtLink({ prefetch: false });
|
|
|
16
15
|
]"
|
|
17
16
|
>
|
|
18
17
|
<MyIcon :name="icon" />
|
|
19
|
-
</
|
|
18
|
+
</EruditLink>
|
|
20
19
|
</template>
|
|
21
20
|
|
|
22
21
|
<style lang="scss" module>
|
|
@@ -8,23 +8,23 @@ import BookContribution from './BookContribution.vue';
|
|
|
8
8
|
const contributorData = injectAsideData<AsideMinorContributor>();
|
|
9
9
|
const phrase = await usePhrases('contribution');
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const bookIdContributions = (() => {
|
|
12
12
|
const grouped: Record<string, typeof contributorData.value.contributions> =
|
|
13
13
|
{};
|
|
14
14
|
|
|
15
15
|
contributorData.value.contributions.forEach((contribution) => {
|
|
16
|
-
const
|
|
17
|
-
if (!grouped[
|
|
18
|
-
grouped[
|
|
16
|
+
const bookId = contribution.bookId || '';
|
|
17
|
+
if (!grouped[bookId]) {
|
|
18
|
+
grouped[bookId] = [];
|
|
19
19
|
}
|
|
20
|
-
grouped[
|
|
20
|
+
grouped[bookId].push(contribution);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
return grouped;
|
|
24
24
|
})();
|
|
25
25
|
|
|
26
|
-
const topLevelContributions =
|
|
27
|
-
delete
|
|
26
|
+
const topLevelContributions = bookIdContributions[''];
|
|
27
|
+
delete bookIdContributions[''];
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
<template>
|
|
@@ -43,8 +43,8 @@ delete bookContributions[''];
|
|
|
43
43
|
/>
|
|
44
44
|
</template>
|
|
45
45
|
<BookContribution
|
|
46
|
-
v-for="(contributions,
|
|
47
|
-
:bookTitle
|
|
46
|
+
v-for="(contributions, bookId) in bookIdContributions"
|
|
47
|
+
:bookTitle="contributions[0]!.bookTitle!"
|
|
48
48
|
:contributions
|
|
49
49
|
/>
|
|
50
50
|
</TreeContainer>
|
|
@@ -9,7 +9,9 @@ import TopicContributors from './TopicContributors.vue';
|
|
|
9
9
|
const topicData = injectAsideData<AsideMinorTopic>();
|
|
10
10
|
const contributePaneVisible = ref(false);
|
|
11
11
|
|
|
12
|
-
watch(topicData, () =>
|
|
12
|
+
watch(topicData, () => {
|
|
13
|
+
contributePaneVisible.value = false;
|
|
14
|
+
});
|
|
13
15
|
</script>
|
|
14
16
|
|
|
15
17
|
<template>
|
|
@@ -44,11 +44,16 @@ onMounted(() => {
|
|
|
44
44
|
<section :class="$style.topicContributors" @click="listPaneVisible = true">
|
|
45
45
|
<div :class="$style.showcase">
|
|
46
46
|
<template v-if="showcase">
|
|
47
|
-
<
|
|
47
|
+
<Avatar
|
|
48
48
|
v-for="contributor of showcase"
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
icon="user"
|
|
50
|
+
:src="
|
|
51
|
+
contributor.avatar
|
|
52
|
+
? `/contributors/${contributor.avatar}`
|
|
53
|
+
: undefined
|
|
54
|
+
"
|
|
51
55
|
:class="$style.avatar"
|
|
56
|
+
:color="stringColor(contributor.contributorId)"
|
|
52
57
|
/>
|
|
53
58
|
</template>
|
|
54
59
|
<template v-else>
|
|
@@ -112,6 +117,7 @@ onMounted(() => {
|
|
|
112
117
|
|
|
113
118
|
.avatar,
|
|
114
119
|
.avatarReplacer {
|
|
120
|
+
--avatarSize: #{$showcaseAvatarSize};
|
|
115
121
|
width: #{$showcaseAvatarSize};
|
|
116
122
|
height: #{$showcaseAvatarSize};
|
|
117
123
|
}
|
|
@@ -10,7 +10,7 @@ const phrase = await usePhrases('article', 'summary', 'practice');
|
|
|
10
10
|
const topicData = injectAsideData<AsideMinorTopic>();
|
|
11
11
|
|
|
12
12
|
const currentTopicPart = computed(() => {
|
|
13
|
-
return topicData.value.part
|
|
13
|
+
return topicData.value.part;
|
|
14
14
|
});
|
|
15
15
|
</script>
|
|
16
16
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { headingName } from '@erudit-js/bitran-elements/heading/shared';
|
|
3
3
|
|
|
4
4
|
import type { TocItem } from '@erudit/shared/bitran/toc';
|
|
5
|
-
import { topicLocation } from '@app/scripts/aside/minor/topic';
|
|
6
5
|
import { injectAsideData } from '@app/scripts/aside/minor/state';
|
|
7
6
|
import type { AsideMinorTopic } from '@shared/aside/minor';
|
|
8
7
|
|
|
@@ -21,9 +20,9 @@ interface RuntimeTocItem extends TocItem {
|
|
|
21
20
|
type RuntimeToc = RuntimeTocItem[];
|
|
22
21
|
|
|
23
22
|
const topicData = injectAsideData<AsideMinorTopic>();
|
|
24
|
-
const phrase = await usePhrases('empty_toc');
|
|
25
23
|
const runtimeToc = ref<RuntimeToc>([]);
|
|
26
24
|
const tocStateKey = ref(0);
|
|
25
|
+
const phrase = await usePhrases('empty_toc');
|
|
27
26
|
|
|
28
27
|
watch(topicData, setupRuntimeToc);
|
|
29
28
|
setupRuntimeToc();
|
|
@@ -165,18 +164,18 @@ function intersectionTrigger(entries: IntersectionObserverEntry[]): void {
|
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (!topicData.value.part || !topicLocation.value) return;
|
|
175
|
-
|
|
167
|
+
watch(
|
|
168
|
+
() => topicData.value.part,
|
|
169
|
+
() => {
|
|
170
|
+
disableLiveToc();
|
|
171
|
+
nextTick().then(() => {
|
|
176
172
|
enableLiveToc();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
onMounted(() => {
|
|
178
|
+
enableLiveToc();
|
|
180
179
|
});
|
|
181
180
|
|
|
182
181
|
onUnmounted(() => {
|
|
@@ -5,28 +5,15 @@ const props = defineProps<{ tocItem: TocItem; active: boolean }>();
|
|
|
5
5
|
const productName = props.tocItem.productName;
|
|
6
6
|
const productIcon = await useBitranElementIcon(productName);
|
|
7
7
|
const productPhrase = await useBitranElementLanguage(productName);
|
|
8
|
-
|
|
9
8
|
const pretty = useFormatText();
|
|
10
|
-
|
|
11
|
-
function tocItemClick(event: MouseEvent): void {
|
|
12
|
-
navigateTo({
|
|
13
|
-
query: {
|
|
14
|
-
element: props.tocItem.id,
|
|
15
|
-
},
|
|
16
|
-
//hash: props.tocItem.id, // Apply page hash as requested in the comment
|
|
17
|
-
});
|
|
18
|
-
event.preventDefault();
|
|
19
|
-
event.stopPropagation();
|
|
20
|
-
}
|
|
21
9
|
</script>
|
|
22
10
|
|
|
23
11
|
<template>
|
|
24
12
|
<TreeItem
|
|
25
13
|
:label="pretty(tocItem.title || productPhrase('_element_title'))"
|
|
26
14
|
:level="tocItem.level"
|
|
27
|
-
:link="
|
|
15
|
+
:link="`#${tocItem.id}`"
|
|
28
16
|
:svg="productIcon"
|
|
29
17
|
:active
|
|
30
|
-
@click="tocItemClick"
|
|
31
18
|
/>
|
|
32
19
|
</template>
|
|
@@ -5,7 +5,6 @@ import { setEruditBitranRuntime } from '@erudit-js/cog/schema';
|
|
|
5
5
|
import eruditConfig from '#erudit/config';
|
|
6
6
|
import type { RawBitranContent } from '@shared/bitran/content';
|
|
7
7
|
import RenderWrapper from './RenderWrapper.vue';
|
|
8
|
-
import { HeadingNode } from '@erudit-js/bitran-elements/heading/shared';
|
|
9
8
|
|
|
10
9
|
const props = defineProps<{
|
|
11
10
|
rawContent: RawBitranContent;
|