erudit 3.0.0-dev.21 → 3.0.0-dev.23

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 (72) hide show
  1. package/app/assets/icons/cameo-add.svg +3 -3
  2. package/app/assets/icons/diamond.svg +2 -2
  3. package/app/assets/icons/files.svg +5 -0
  4. package/app/assets/icons/list-squared.svg +3 -0
  5. package/app/assets/icons/rocket.svg +1 -0
  6. package/app/components/GroupLikePage.vue +70 -0
  7. package/app/components/QuickLinks.vue +99 -0
  8. package/app/components/aside/major/SiteInfo.vue +2 -2
  9. package/app/components/aside/major/panes/nav/NavBook.vue +1 -1
  10. package/app/components/aside/major/panes/other/ItemTheme.vue +25 -30
  11. package/app/components/index/IndexAvatars.vue +143 -0
  12. package/app/components/main/MainActionButton.vue +3 -1
  13. package/app/components/main/MainBitranContent.vue +13 -0
  14. package/app/components/main/MainDescription.vue +6 -0
  15. package/app/components/main/MainSectionTitle.vue +50 -0
  16. package/app/components/main/MainSourceUsages.vue +119 -0
  17. package/app/components/main/MainSourcesUsage.vue +60 -0
  18. package/app/components/main/MainToc.vue +135 -0
  19. package/app/components/main/content/ContentPopovers.vue +9 -2
  20. package/app/components/main/content/ContentReferences.vue +1 -27
  21. package/app/components/main/content/reference/ReferenceGroup.vue +10 -9
  22. package/app/components/main/content/reference/ReferenceSource.vue +1 -0
  23. package/app/components/main/topic/MainTopic.vue +5 -8
  24. package/app/components/main/topic/MainTopicQuickLinks.vue +24 -0
  25. package/app/components/stats/Stats.vue +21 -0
  26. package/app/components/stats/StatsGroupLike.vue +24 -0
  27. package/app/components/stats/StatsItem.vue +33 -0
  28. package/app/components/stats/StatsItemElement.vue +13 -0
  29. package/app/composables/bitranLocation.ts +2 -3
  30. package/app/composables/contentPage.ts +7 -6
  31. package/app/composables/theme.ts +26 -5
  32. package/app/pages/book/[...bookId].vue +6 -31
  33. package/app/pages/group/[...groupId].vue +5 -41
  34. package/app/pages/index.vue +189 -16
  35. package/app/pages/sponsors.vue +2 -2
  36. package/app/scripts/preview/data/pageLink.ts +4 -3
  37. package/app/scripts/preview/data/unique.ts +6 -6
  38. package/languages/en.ts +7 -1
  39. package/languages/ru.ts +10 -3
  40. package/package.json +4 -4
  41. package/server/api/content/data.ts +33 -11
  42. package/server/api/index/data.ts +46 -0
  43. package/server/plugin/bitran/content.ts +0 -14
  44. package/server/plugin/bitran/location.ts +3 -6
  45. package/server/plugin/build/jobs/content/parse.ts +95 -1
  46. package/server/plugin/build/jobs/content/type/group.ts +0 -21
  47. package/server/plugin/content/context.ts +3 -6
  48. package/server/plugin/db/entities/Group.ts +0 -3
  49. package/server/plugin/db/entities/QuickLink.ts +19 -0
  50. package/server/plugin/db/entities/Stat.ts +13 -0
  51. package/server/plugin/db/setup.ts +4 -0
  52. package/server/plugin/repository/content.ts +3 -3
  53. package/server/plugin/repository/contentToc.ts +90 -0
  54. package/server/plugin/repository/elementStats.ts +80 -0
  55. package/server/plugin/repository/link.ts +20 -0
  56. package/server/plugin/repository/quickLink.ts +36 -0
  57. package/server/plugin/repository/readLink.ts +17 -0
  58. package/server/plugin/repository/reference.ts +78 -0
  59. package/server/plugin/repository/topicCount.ts +19 -0
  60. package/shared/content/data/base.ts +2 -2
  61. package/shared/content/data/groupLike.ts +11 -0
  62. package/shared/content/data/type/book.ts +3 -1
  63. package/shared/content/data/type/group.ts +3 -1
  64. package/shared/content/data/type/topic.ts +3 -1
  65. package/shared/content/reference.ts +18 -0
  66. package/shared/content/toc.ts +35 -0
  67. package/shared/indexData.ts +10 -0
  68. package/shared/link.ts +3 -2
  69. package/shared/quickLink.ts +7 -0
  70. package/shared/stat.ts +23 -0
  71. package/shared/types/language.ts +6 -1
  72. package/utils/normalize.ts +7 -0
@@ -1,3 +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>
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>
@@ -1,3 +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"/>
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
3
  </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
2
+ <path d="M18.29,23.19c-.15-.15-.32-.28-.48-.36l-7.7-.04c-.36,0-.72.12-1.13.4-.29.29-.4.79-.4,1.13,0,.4.14.78.4,1.03.33.33.7.49,1.13.49h7.06c.25,0,.47-.05.65-.14.15-.07.3-.18.44-.32.13-.08.31-.26.33-.59.07-.16.1-.32.1-.47,0-.22-.03-.41-.1-.57-.02-.2-.12-.38-.29-.55Z"/>
3
+ <path d="M9.03,18.41l-.1.1c-.23.35-.35.71-.35,1.07s.12.72.35,1.07l.1.1c.35.23.71.35,1.07.35h7.06c.25,0,.46-.05.65-.14l.39-.19.09-.07c.18-.18.28-.37.3-.58.07-.21.1-.39.1-.55s-.03-.34-.1-.55c-.02-.2-.12-.4-.3-.58l-.48-.26c-.18-.09-.4-.13-.64-.13h-7.06c-.36,0-.72.12-1.07.35Z"/>
4
+ <path d="M28.92,6.66L23.03.76l-.25-.3h-.15s-.16-.05-.28-.08c-.3-.09-.37-.11-.45-.11h-11.8c-.38,0-.73.08-1,.22-.37.15-.67.34-.9.56-.28.28-.5.58-.65.87l-.03.09c-.07.34-.1.69-.1,1.04v1.96h-2.05c-.38,0-.72.08-1,.22-.37.15-.67.34-.9.56-.3.3-.5.62-.56.9-.15.36-.22.73-.22,1.1v21.17c0,.37.07.74.2,1.05.08.32.28.64.58.94.23.23.53.42.87.55.31.15.66.23,1.03.23h16.53c.38,0,.72-.08,1.01-.22.37-.15.67-.34.9-.56.28-.28.5-.58.65-.87l.03-.09c.07-.34.1-.69.1-1.04v-1.96h2.05c.38,0,.73-.08,1-.22.37-.15.67-.34.9-.56.3-.3.5-.62.56-.9.15-.36.22-.74.22-1.1V7.49c0-.2-.3-.74-.4-.84ZM21.53,13.06v15.54H5.73V8.14h10.8l5.01,4.91ZM24.57,11.89l-.09-.1s-.07-.09-.11-.15c-.07-.1-.14-.2-.18-.24l-5.9-5.9-.25-.3h-.15c-.05-.01-.17-.05-.28-.08-.29-.09-.37-.11-.45-.11h-6.7v-1.6h10.8l5.01,5.01v15.54h-1.69v-12.06Z"/>
5
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
2
+ <path d="M14.2,7.1h10.7v3.6h-10.7v-3.6ZM14.2,14.2h10.7v3.6h-10.7v-3.6ZM14.2,21.3h10.7v3.6h-10.7v-3.6ZM7.1,7.1h3.6v3.6h-3.6v-3.6ZM7.1,14.2h3.6v3.6h-3.6v-3.6ZM7.1,21.3h3.6v3.6h-3.6v-3.6ZM30.4,0H1.6C.7,0,0,.7,0,1.6v28.8C0,31.1.7,32,1.6,32h28.8c.7,0,1.6-.9,1.6-1.6V1.6C32,.7,31.1,0,30.4,0M28.4,28.4H3.6V3.6h24.9v24.9Z"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m2.45 10.575l4.2-4.2q.35-.35.825-.5t.975-.05l1.3.275Q8.4 7.7 7.625 9t-1.5 3.15zm5.125 2.275q.575-1.8 1.563-3.4t2.387-3q2.2-2.2 5.025-3.287t5.275-.663q.425 2.45-.65 5.275T17.9 12.8q-1.375 1.375-3 2.388t-3.425 1.587zm6.9-3q.575.575 1.413.575T17.3 9.85t.575-1.412t-.575-1.413t-1.412-.575t-1.413.575t-.575 1.413t.575 1.412m-.7 12.025l-1.6-3.675q1.85-.725 3.163-1.5t2.912-2.125l.25 1.3q.1.5-.05.988t-.5.837zM4.05 16.05q.875-.875 2.125-.888t2.125.863t.875 2.125t-.875 2.125q-.625.625-2.087 1.075t-4.038.8q.35-2.575.8-4.025T4.05 16.05"/></svg>
@@ -0,0 +1,70 @@
1
+ <script lang="ts" setup>
2
+ import type { MyIconName } from '#my-icons';
3
+ import eruditConfig from '#erudit/config';
4
+
5
+ import type { ContentGeneric } from '@shared/content/data/base';
6
+ import type { ContentGroupLike } from '@shared/content/data/groupLike';
7
+
8
+ import ContentBreadcrumb from '@app/components/main/content/ContentBreadcrumb.vue';
9
+ import ContentDecoration from '@app/components/main/content/ContentDecoration.vue';
10
+ import ContentPopovers from '@app/components/main/content/ContentPopovers.vue';
11
+
12
+ const props = defineProps<{
13
+ icon: MyIconName;
14
+ contentLabel: string;
15
+ generic: ContentGeneric;
16
+ groupLike: ContentGroupLike;
17
+ }>();
18
+
19
+ const pageTitle = computed(() => {
20
+ return props.generic?.title || props.generic.contentId.split('/').pop()!;
21
+ });
22
+
23
+ const phrase = await usePhrases('start_learning', 'topics');
24
+ </script>
25
+
26
+ <template>
27
+ <ContentDecoration
28
+ v-if="generic.decoration"
29
+ :decoration="generic.decoration"
30
+ />
31
+
32
+ <ContentBreadcrumb :context="generic.context" />
33
+
34
+ <MainTitle :title="pageTitle" :icon :hint="contentLabel" />
35
+
36
+ <MainDescription
37
+ v-if="generic?.description"
38
+ :description="generic?.description"
39
+ />
40
+
41
+ <StatsGroupLike
42
+ :stats="[
43
+ {
44
+ type: 'custom',
45
+ icon: 'files',
46
+ label: phrase.topics,
47
+ count: groupLike.topicCount,
48
+ },
49
+ ...groupLike.elementStats,
50
+ ]"
51
+ />
52
+
53
+ <ContentPopovers :generic />
54
+
55
+ <MainActionButton
56
+ icon="rocket"
57
+ :label="phrase.start_learning"
58
+ :link="groupLike.readLink"
59
+ />
60
+
61
+ <div style="clear: both"></div>
62
+
63
+ <MainToc :toc="groupLike.contentToc" />
64
+
65
+ <MainSourcesUsage :usageSet="groupLike.sourceUsageSet" />
66
+
67
+ <MainSection v-if="adsAllowed() && eruditConfig.ads?.bottom">
68
+ <AdsBannerBottom />
69
+ </MainSection>
70
+ </template>
@@ -0,0 +1,99 @@
1
+ <script lang="ts" setup>
2
+ import type { QuickLinks } from '@shared/quickLink';
3
+
4
+ interface FrontQuickLink {
5
+ label: string;
6
+ link: string;
7
+ elementTitle: string;
8
+ elementIcon: string;
9
+ }
10
+
11
+ const props = defineProps<{ links: QuickLinks }>();
12
+ const pretty = useFormatText();
13
+
14
+ const frontQuickLinks: FrontQuickLink[] = [];
15
+ for (const link of props.links) {
16
+ const elementIcon = await useBitranElementIcon(link.elementName);
17
+ const elementLanguage = await useBitranElementLanguage(link.elementName);
18
+ frontQuickLinks.push({
19
+ label: link.label,
20
+ link: link.link,
21
+ elementTitle: elementLanguage('_element_title'),
22
+ elementIcon: elementIcon,
23
+ });
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <nav :class="$style.quickLinks">
29
+ <ul>
30
+ <li v-for="frontQuickLink of frontQuickLinks">
31
+ <EruditLink
32
+ :to="frontQuickLink.link"
33
+ :title="frontQuickLink.elementTitle"
34
+ :class="$style.quickLink"
35
+ >
36
+ <MyRuntimeIcon
37
+ name="quick-link-icon"
38
+ :svg="frontQuickLink.elementIcon"
39
+ :class="$style.quickLinkIcon"
40
+ />
41
+ <span :class="$style.quickLinkLabel">
42
+ {{ pretty(frontQuickLink.label) }}
43
+ </span>
44
+ </EruditLink>
45
+ </li>
46
+ </ul>
47
+ </nav>
48
+ </template>
49
+
50
+ <style lang="scss" module>
51
+ @use '$/def/bp';
52
+
53
+ .quickLinks {
54
+ --_erudit_quickLink_bg: var(--bgMain);
55
+
56
+ > ul {
57
+ list-style: none;
58
+ padding: 0;
59
+ margin: 0;
60
+
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+ gap: var(--gap);
64
+
65
+ @include bp.below('mobile') {
66
+ gap: var(--gapSmall);
67
+ }
68
+
69
+ .quickLink {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 8px;
73
+ color: var(--textMuted);
74
+ font-size: 0.85em;
75
+ text-decoration: none;
76
+ box-shadow: 0px 0px 0px 0px transparent;
77
+ background: var(--_erudit_quickLink_bg);
78
+
79
+ @include transition(color, border, box-shadow);
80
+
81
+ border: 1px solid var(--border);
82
+ border-radius: 5px;
83
+ padding: 3px 7px;
84
+
85
+ &:hover {
86
+ color: var(--brand);
87
+ border-color: var(--brand);
88
+ box-shadow: 0px 0px 0px 2px
89
+ color-mix(in srgb, var(--brand), transparent 70%);
90
+ }
91
+
92
+ [my-icon] {
93
+ flex-shrink: 0;
94
+ font-size: 0.9em;
95
+ }
96
+ }
97
+ }
98
+ }
99
+ </style>
@@ -33,11 +33,11 @@ const siteInfo = computed<SiteInfo>(() => {
33
33
  <img :src="baseUrlPath(siteInfo.logotype)" :alt="siteInfo.title" />
34
34
  </EruditLink>
35
35
  <div :class="[$style.textInfo, !siteInfo.logotype && $style.noLogo]">
36
- <h1 :class="$style.title">
36
+ <div :class="$style.title">
37
37
  <EruditLink to="/">{{
38
38
  siteInfo.title || phrase.site_info_title
39
39
  }}</EruditLink>
40
- </h1>
40
+ </div>
41
41
  <div v-if="siteInfo.slogan" :class="$style.description">
42
42
  {{ siteInfo.slogan }}
43
43
  </div>
@@ -56,7 +56,7 @@ const pharse = await usePhrases('to_index', 'about_book');
56
56
  <TreeItem
57
57
  icon="book-question"
58
58
  :label="pharse.about_book"
59
- :active="contentRoute?.contentId === book.id"
59
+ :active="contentRoute?.type === 'book'"
60
60
  :link="`/book/${book.id}`"
61
61
  />
62
62
  </section>
@@ -14,41 +14,36 @@ const themeOptions: Record<string, [MyIconName, string]> = {
14
14
  dark: ['moon', phrase.theme_dark],
15
15
  };
16
16
 
17
+ const clientMode = ref(false);
17
18
  const themeItem = ref(themeOptions.auto);
18
-
19
- let _cycle = () => {};
19
+ let cycle = () => {};
20
20
 
21
21
  onMounted(() => {
22
- const { cycle, theme } = useTheme();
23
-
24
- _cycle = cycle;
25
-
26
- watch(
27
- theme,
28
- () => {
29
- themeItem.value = (() => {
30
- switch (theme.value) {
31
- case 'auto':
32
- return themeOptions.auto;
33
- case 'light':
34
- return themeOptions.light;
35
- case 'dark':
36
- return themeOptions.dark;
37
- }
38
- })();
39
- },
40
- { immediate: true },
41
- );
22
+ const { cycle: _cycle, theme } = useTheme();
23
+
24
+ themeItem.value = themeOptions[theme.value];
25
+
26
+ cycle = () => {
27
+ _cycle();
28
+ themeItem.value = themeOptions[theme.value];
29
+ };
30
+
31
+ clientMode.value = true;
42
32
  });
43
33
  </script>
44
34
 
45
35
  <template>
46
- <ClientOnly>
47
- <AsideListItem
48
- @click="_cycle"
49
- :icon="themeItem![0]"
50
- :main="phrase.theme"
51
- :secondary="themeItem![1]"
52
- />
53
- </ClientOnly>
36
+ <AsideListItem
37
+ v-if="clientMode"
38
+ :icon="themeItem![0]"
39
+ :main="phrase.theme"
40
+ :secondary="themeItem![1]"
41
+ @click="cycle"
42
+ />
43
+ <AsideListItem
44
+ v-else
45
+ :icon="themeOptions.auto![0]"
46
+ :main="phrase.theme"
47
+ :secondary="themeOptions.auto![1]"
48
+ />
54
49
  </template>
@@ -0,0 +1,143 @@
1
+ <script lang="ts" setup>
2
+ import type { MyIconName } from '#my-icons';
3
+
4
+ const props = defineProps<{
5
+ label: string;
6
+ link: string;
7
+ max: number;
8
+ icon: MyIconName;
9
+ namesAvatars: [string, string?][];
10
+ }>();
11
+
12
+ const displayNamesAvatars = ref<[string, string?][]>([]);
13
+ const isLoaded = ref(false);
14
+
15
+ const remainingCount = computed(() => {
16
+ return Math.max(0, props.namesAvatars.length - props.max);
17
+ });
18
+
19
+ onMounted(() => {
20
+ const shuffled = [...props.namesAvatars];
21
+ for (let i = shuffled.length - 1; i > 0; i--) {
22
+ const j = Math.floor(Math.random() * (i + 1));
23
+ [shuffled[i], shuffled[j]] = [shuffled[j]!, shuffled[i]!];
24
+ }
25
+ displayNamesAvatars.value = shuffled.slice(0, props.max);
26
+ isLoaded.value = true;
27
+ });
28
+ </script>
29
+
30
+ <template>
31
+ <div :class="$style.indexAvatars">
32
+ <EruditLink :to="link" :class="$style.indexAvatarsLabel">{{
33
+ label
34
+ }}</EruditLink>
35
+ <ul>
36
+ <template v-if="isLoaded">
37
+ <li
38
+ v-for="nameAvatar of displayNamesAvatars"
39
+ :class="$style.indexAvatar"
40
+ >
41
+ <EruditLink :to="link">
42
+ <Avatar
43
+ :class="$style.indexAvatar"
44
+ :title="nameAvatar[0]"
45
+ :icon
46
+ :src="nameAvatar[1]"
47
+ :color="stringColor(nameAvatar[0])"
48
+ />
49
+ </EruditLink>
50
+ </li>
51
+ <li v-if="remainingCount > 0" :class="$style.remainingCounter">
52
+ <EruditLink :to="link" :class="$style.counterCircle">
53
+ +{{ remainingCount }}
54
+ </EruditLink>
55
+ </li>
56
+ </template>
57
+ <template v-else>
58
+ <li
59
+ v-for="i in Math.min(max, namesAvatars.length)"
60
+ :class="$style.indexAvatar"
61
+ >
62
+ <div :class="$style.avatarPlaceholder"></div>
63
+ </li>
64
+ <li v-if="remainingCount > 0" :class="$style.remainingCounter">
65
+ <div :class="$style.counterPlaceholder"></div>
66
+ </li>
67
+ </template>
68
+ </ul>
69
+ </div>
70
+ </template>
71
+
72
+ <style lang="scss" module>
73
+ .indexAvatars {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: var(--gap);
77
+ align-items: center;
78
+
79
+ &:hover {
80
+ .indexAvatarsLabel {
81
+ color: var(--text);
82
+ }
83
+ }
84
+
85
+ .indexAvatarsLabel {
86
+ font-size: 1.2em;
87
+ font-weight: 600;
88
+ color: var(--textMuted);
89
+ text-decoration: none;
90
+
91
+ @include transition(color);
92
+ }
93
+
94
+ > ul {
95
+ list-style: none;
96
+ padding: 0;
97
+ margin: 0;
98
+
99
+ display: flex;
100
+ gap: var(--gapSmall);
101
+
102
+ .indexAvatar {
103
+ --avatarSize: 50px;
104
+ }
105
+
106
+ .remainingCounter {
107
+ --avatarSize: 50px;
108
+ }
109
+ }
110
+ }
111
+
112
+ .avatarPlaceholder,
113
+ .counterPlaceholder {
114
+ width: var(--avatarSize);
115
+ height: var(--avatarSize);
116
+ border-radius: 50%;
117
+ background: var(--textDimmed);
118
+ animation: avatarPlaceholder 500ms infinite ease-in-out alternate;
119
+ }
120
+
121
+ .counterCircle {
122
+ width: var(--avatarSize);
123
+ height: var(--avatarSize);
124
+ border-radius: 50%;
125
+ border: 3px dashed var(--textDimmed);
126
+ color: var(--textMuted);
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ font-size: 0.95em;
131
+ font-weight: 600;
132
+ text-decoration: none;
133
+ }
134
+
135
+ @keyframes avatarPlaceholder {
136
+ from {
137
+ opacity: 0.625;
138
+ }
139
+ to {
140
+ opacity: 0.4;
141
+ }
142
+ }
143
+ </style>
@@ -13,7 +13,7 @@ defineProps<{
13
13
  <EruditLink
14
14
  :prefetch="false"
15
15
  :to="link"
16
- target="_blank"
16
+ :target="link.startsWith('/') ? undefined : '_blank'"
17
17
  :class="$style.actionButton"
18
18
  >
19
19
  <MyIcon :name="icon" :class="$style.actionButtonIcon" />
@@ -24,6 +24,8 @@ defineProps<{
24
24
 
25
25
  <style lang="scss" module>
26
26
  .actionButtonSection {
27
+ position: relative;
28
+ z-index: 10;
27
29
  display: flex;
28
30
  justify-content: center;
29
31
  align-items: center;
@@ -13,6 +13,7 @@ type MainBitranPayload = RawBitranContent & {
13
13
 
14
14
  const props = defineProps<{ location: BitranLocation }>();
15
15
 
16
+ const route = useRoute();
16
17
  const nuxtApp = useNuxtApp();
17
18
  const stringLocation = stringifyBitranLocation(props.location);
18
19
 
@@ -37,6 +38,18 @@ if (
37
38
  mainLocation: props.location,
38
39
  });
39
40
  }
41
+
42
+ onMounted(() => {
43
+ setTimeout(() => {
44
+ const hash = route.hash;
45
+ if (hash) {
46
+ const element = document.querySelector(hash.replace(':', '\\:'));
47
+ if (element) {
48
+ element.scrollIntoView({ behavior: 'instant' });
49
+ }
50
+ }
51
+ }, 100);
52
+ });
40
53
  </script>
41
54
 
42
55
  <template>
@@ -15,10 +15,16 @@ const pretty = useFormatText();
15
15
  </template>
16
16
 
17
17
  <style lang="scss" module>
18
+ @use '$/def/bp';
19
+
18
20
  .contentDescription {
19
21
  padding: var(--_pMainY) var(--_pMainX);
20
22
  font-weight: 500;
21
23
  font-size: 1.1em;
22
24
  color: var(--text);
25
+
26
+ @include bp.below('mobile') {
27
+ text-align: center;
28
+ }
23
29
  }
24
30
  </style>
@@ -0,0 +1,50 @@
1
+ <script lang="ts" setup>
2
+ import { isMyIcon, type MyIconName } from '#my-icons';
3
+
4
+ defineProps<{
5
+ icon: string;
6
+ title: string;
7
+ count?: number | string;
8
+ }>();
9
+
10
+ const pretty = useFormatText();
11
+ </script>
12
+
13
+ <template>
14
+ <h2 :class="$style.heading">
15
+ <MyIcon
16
+ v-if="isMyIcon(icon)"
17
+ :name="icon as MyIconName"
18
+ :class="$style.icon"
19
+ />
20
+ <MyRuntimeIcon v-else name="section-title-icon" :svg="icon" />
21
+ <span :class="$style.title">{{ pretty(title) }}</span>
22
+ <span v-if="count !== undefined" :class="$style.count">
23
+ {{ count }}
24
+ </span>
25
+ </h2>
26
+ </template>
27
+
28
+ <style lang="scss" module>
29
+ .heading {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: var(--gap);
33
+ padding: var(--_pMainY) var(--_pMainX);
34
+
35
+ .icon {
36
+ font-size: 0.9em;
37
+ color: var(--textMuted);
38
+ }
39
+
40
+ .count {
41
+ position: relative;
42
+ top: 1px;
43
+ font-weight: 550;
44
+ font-size: 0.6em;
45
+ background: color-mix(in srgb, var(--textDimmed), transparent 65%);
46
+ border-radius: 20px;
47
+ padding: 2px 10px;
48
+ }
49
+ }
50
+ </style>
@@ -0,0 +1,119 @@
1
+ <script lang="ts" setup>
2
+ import type { ContentSourceUsage } from '@shared/content/reference';
3
+
4
+ defineProps<{ usages: ContentSourceUsage[] }>();
5
+
6
+ const expanded = ref(false);
7
+ const pretty = useFormatText();
8
+ const phrase = await usePhrases('mentions');
9
+ </script>
10
+
11
+ <template>
12
+ <div :class="[expanded && $style.expanded]">
13
+ <div>
14
+ <button :class="$style.toggleUsages" @click="expanded = !expanded">
15
+ <span :class="$style.toggleUsageLabel">
16
+ {{
17
+ phrase.mentions(
18
+ usages.reduce((sum, usage) => sum + usage.count, 0),
19
+ )
20
+ }}
21
+ </span>
22
+ <MyIcon name="angle-right" :class="$style.toggleUsagesAngle" />
23
+ </button>
24
+ </div>
25
+ <ul :class="$style.mentions">
26
+ <li v-for="usageItem of usages" :class="$style.mention">
27
+ <MyIcon name="arrow-up-to-right" />
28
+ <EruditLink
29
+ :class="$style.mentionLabel"
30
+ :prefetch="false"
31
+ :to="usageItem.link"
32
+ >
33
+ {{ pretty(usageItem.title) }}
34
+ </EruditLink>
35
+ <span :class="$style.mentionCount"
36
+ >({{ usageItem.count }})</span
37
+ >
38
+ </li>
39
+ </ul>
40
+ </div>
41
+ </template>
42
+
43
+ <style lang="scss" module>
44
+ .toggleUsages {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 5px;
48
+ cursor: pointer;
49
+
50
+ &:hover {
51
+ .toggleUsageLabel {
52
+ text-decoration-color: var(--textDimmed);
53
+ }
54
+ }
55
+
56
+ .toggleUsageLabel {
57
+ color: var(--textMuted);
58
+ font-weight: 500;
59
+ text-decoration: underline;
60
+ text-decoration-color: transparent;
61
+ @include transition(text-decoration-color);
62
+ }
63
+
64
+ .toggleUsagesAngle {
65
+ color: var(--textDimmed);
66
+ @include transition(transform);
67
+
68
+ .expanded & {
69
+ transform: rotate(90deg);
70
+ }
71
+ }
72
+ }
73
+
74
+ .mentions {
75
+ height: 0;
76
+ overflow: hidden;
77
+ @include transition(height);
78
+
79
+ list-style: none;
80
+ padding: 0;
81
+ margin: 0;
82
+
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: var(--gapSmall);
86
+ color: var(--textMuted);
87
+ font-size: 0.95em;
88
+
89
+ .expanded & {
90
+ height: auto;
91
+ }
92
+
93
+ .mention {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: var(--gapSmall);
97
+
98
+ &:first-of-type {
99
+ margin-top: var(--gapSmall);
100
+ }
101
+
102
+ .mentionLabel {
103
+ color: var(--text);
104
+ font-weight: 500;
105
+ text-decoration-color: transparent;
106
+ @include transition(text-decoration-color);
107
+
108
+ &:hover {
109
+ text-decoration-color: var(--textDimmed);
110
+ }
111
+ }
112
+
113
+ .mentionCount {
114
+ color: var(--textDimmed);
115
+ font-weight: 500;
116
+ }
117
+ }
118
+ }
119
+ </style>