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.
Files changed (91) hide show
  1. package/app/app.vue +2 -0
  2. package/app/assets/icons/cameo-add.svg +3 -0
  3. package/app/assets/icons/diamond.svg +3 -0
  4. package/app/components/Avatar.vue +118 -0
  5. package/app/components/EruditLink.vue +17 -0
  6. package/app/components/SiteMain.vue +4 -4
  7. package/app/components/ads/Ads.vue +1 -3
  8. package/app/components/ads/AdsProviderYandex.vue +59 -22
  9. package/app/components/aside/AsideListItem.vue +21 -4
  10. package/app/components/aside/AsideMinor.vue +1 -1
  11. package/app/components/aside/major/SiteInfo.vue +4 -4
  12. package/app/components/aside/major/panes/Pages.vue +20 -1
  13. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +2 -2
  14. package/app/components/aside/minor/AsideMinorTopLink.vue +3 -4
  15. package/app/components/aside/minor/contributor/AsideMinorContributor.vue +9 -9
  16. package/app/components/aside/minor/topic/AsideMinorTopic.vue +3 -1
  17. package/app/components/aside/minor/topic/TopicContributors.vue +9 -3
  18. package/app/components/aside/minor/topic/TopicNav.vue +1 -1
  19. package/app/components/aside/minor/topic/TopicToc.vue +12 -13
  20. package/app/components/aside/minor/topic/TopicTocItem.vue +1 -14
  21. package/app/components/bitran/BitranContent.vue +0 -1
  22. package/app/components/contributor/ContributorListItem.vue +13 -5
  23. package/app/components/main/MainActionButton.vue +51 -0
  24. package/app/components/main/MainBitranContent.vue +11 -3
  25. package/app/components/main/MainBreadcrumb.vue +2 -6
  26. package/app/components/main/MainSection.vue +58 -0
  27. package/app/components/main/cameo/MainCameo.vue +135 -0
  28. package/app/components/main/cameo/MainCameoData.vue +232 -0
  29. package/app/components/main/content/ContentPopovers.vue +1 -1
  30. package/app/components/main/topic/MainTopic.vue +13 -18
  31. package/app/components/main/topic/TopicPartSwitch.vue +7 -12
  32. package/app/components/preview/PreviewFooterAction.vue +1 -1
  33. package/app/components/sponsor/SponsorTier1.vue +89 -0
  34. package/app/components/sponsor/SponsorTier2.vue +109 -0
  35. package/app/components/tree/TreeItem.vue +8 -4
  36. package/app/composables/asset.ts +12 -0
  37. package/app/composables/contentData.ts +1 -1
  38. package/app/composables/head.ts +24 -0
  39. package/app/composables/majorPane.ts +1 -0
  40. package/app/composables/url.ts +17 -7
  41. package/app/pages/contributor/[contributorId].vue +9 -6
  42. package/app/pages/contributors.vue +73 -72
  43. package/app/pages/group/[...groupId].vue +4 -3
  44. package/app/pages/sponsors.vue +95 -0
  45. package/app/plugins/prerender.server.ts +14 -2
  46. package/app/scripts/og.ts +2 -1
  47. package/app/scripts/preview/data/pageLink.ts +4 -3
  48. package/app/scripts/preview/data/unique.ts +6 -6
  49. package/const.ts +0 -1
  50. package/globals/cameo.ts +5 -0
  51. package/globals/register.ts +5 -0
  52. package/globals/sponsor.ts +17 -0
  53. package/languages/en.ts +8 -3
  54. package/languages/ru.ts +8 -3
  55. package/module/imports.ts +13 -6
  56. package/nuxt.config.ts +2 -7
  57. package/package.json +4 -4
  58. package/server/api/cameo/data/[cameoId].ts +42 -0
  59. package/server/api/cameo/ids.ts +5 -0
  60. package/server/api/prerender/assets/cameo.ts +14 -0
  61. package/server/api/prerender/assets/contributor.ts +12 -0
  62. package/server/api/prerender/assets/sponsor.ts +13 -0
  63. package/server/api/prerender/cameos.ts +12 -0
  64. package/server/api/{prerender.ts → prerender/language.ts} +3 -13
  65. package/server/api/sponsor/cameo/data/[sponsorId].ts +51 -0
  66. package/server/api/sponsor/cameo/ids.ts +5 -0
  67. package/server/api/sponsor/count.ts +5 -0
  68. package/server/api/sponsor/list.ts +36 -0
  69. package/server/plugin/build/process.ts +2 -0
  70. package/server/plugin/build/rebuild.ts +2 -0
  71. package/server/plugin/global.ts +2 -0
  72. package/server/plugin/repository/cameo.ts +16 -0
  73. package/server/plugin/repository/contributor.ts +35 -4
  74. package/server/plugin/sponsor/build.ts +82 -0
  75. package/server/plugin/sponsor/index.ts +5 -0
  76. package/server/plugin/sponsor/repository.ts +56 -0
  77. package/server/routes/asset/[...assetPath].ts +34 -0
  78. package/server/routes/robots.txt.ts +9 -0
  79. package/server/routes/sitemap.xml.ts +103 -0
  80. package/shared/asset.ts +0 -5
  81. package/shared/contributor.ts +1 -0
  82. package/shared/link.ts +4 -4
  83. package/shared/types/language.ts +6 -1
  84. package/test/utils/url.test.ts +99 -0
  85. package/utils/ext.ts +41 -0
  86. package/utils/url.ts +23 -0
  87. package/app/components/contributor/ContributorAvatar.vue +0 -45
  88. package/app/components/main/content/ContentSection.vue +0 -45
  89. package/app/public/user.svg +0 -10
  90. package/app/scripts/aside/minor/topic.ts +0 -3
  91. package/utils/slash.ts +0 -11
package/app/app.vue CHANGED
@@ -145,6 +145,8 @@ if (import.meta.client) {
145
145
  </script>
146
146
 
147
147
  <template>
148
+ <NuxtLoadingIndicator color="var(--brand)" />
149
+
148
150
  <SiteAside :type="AsideType.Major"><AsideMajor /></SiteAside>
149
151
  <SiteAside :type="AsideType.Minor"><AsideMinor /></SiteAside>
150
152
 
@@ -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
- --_pMainBase: var(--gapBig);
15
- --_pMainX: var(--_pMainBase);
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
- --_pMainBase: var(--_bitran_asideWidth);
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
- updateAds();
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
- useHead({
15
- script: [
16
- {
17
- key: '__darkMagicYandexCb',
18
- innerHTML: 'window.yaContextCb=window.yaContextCb||[]',
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
- const uid = useId();
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
- onMounted(() => {
31
- const theme = useTheme();
38
+ // Load script for the first time
39
+ window.__yandexScriptLoading = true;
40
+ window.yaContextCb = window.yaContextCb || [];
32
41
 
33
- window?.yaContextCb?.push(() => {
34
- Ya?.Context?.AdvManager?.render({
35
- blockId: props.data.blockId,
36
- renderTo: uid,
37
- darkTheme: theme.binaryTheme.value === 'dark',
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 { MyIconName } from '#my-icons';
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?: MyIconName;
7
+ icon?: string;
7
8
  main?: string;
8
9
  active?: boolean;
9
10
  secondary?: string;
10
11
  }>();
11
12
 
12
- const vnode = h(props.link ? defineNuxtLink({}) : 'div');
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
- <MyIcon v-if="icon" :name="icon" />
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/slash';
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
- <NuxtLink v-if="siteInfo.logotype" to="/" :class="$style.logo">
32
+ <EruditLink v-if="siteInfo.logotype" to="/" :class="$style.logo">
33
33
  <img :src="baseUrlPath(siteInfo.logotype)" :alt="siteInfo.title" />
34
- </NuxtLink>
34
+ </EruditLink>
35
35
  <div :class="[$style.textInfo, !siteInfo.logotype && $style.noLogo]">
36
36
  <h1 :class="$style.title">
37
- <NuxtLink to="/">{{
37
+ <EruditLink to="/">{{
38
38
  siteInfo.title || phrase.site_info_title
39
- }}</NuxtLink>
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
- <NuxtLink
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
- </NuxtLink>
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
- <Link
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
- </Link>
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 bookContributions = (() => {
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 bookTitle = contribution.bookTitle || '';
17
- if (!grouped[bookTitle]) {
18
- grouped[bookTitle] = [];
16
+ const bookId = contribution.bookId || '';
17
+ if (!grouped[bookId]) {
18
+ grouped[bookId] = [];
19
19
  }
20
- grouped[bookTitle].push(contribution);
20
+ grouped[bookId].push(contribution);
21
21
  });
22
22
 
23
23
  return grouped;
24
24
  })();
25
25
 
26
- const topLevelContributions = bookContributions[''];
27
- delete bookContributions[''];
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, bookTitle) in bookContributions"
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, () => (contributePaneVisible.value = false));
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
- <ContributorAvatar
47
+ <Avatar
48
48
  v-for="contributor of showcase"
49
- :contributorId="contributor.contributorId"
50
- :avatar="contributor.avatar"
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 as TopicPart;
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
- onMounted(() => {
169
- watch(
170
- [topicData, topicLocation],
171
- () => {
172
- disableLiveToc();
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
- { immediate: true },
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="`?element=${tocItem.id}`"
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;