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.
- package/app/assets/icons/cameo-add.svg +3 -3
- package/app/assets/icons/diamond.svg +2 -2
- package/app/assets/icons/files.svg +5 -0
- package/app/assets/icons/list-squared.svg +3 -0
- package/app/assets/icons/rocket.svg +1 -0
- package/app/components/GroupLikePage.vue +70 -0
- package/app/components/QuickLinks.vue +99 -0
- package/app/components/aside/major/SiteInfo.vue +2 -2
- package/app/components/aside/major/panes/nav/NavBook.vue +1 -1
- package/app/components/aside/major/panes/other/ItemTheme.vue +25 -30
- package/app/components/index/IndexAvatars.vue +143 -0
- package/app/components/main/MainActionButton.vue +3 -1
- package/app/components/main/MainBitranContent.vue +13 -0
- package/app/components/main/MainDescription.vue +6 -0
- package/app/components/main/MainSectionTitle.vue +50 -0
- package/app/components/main/MainSourceUsages.vue +119 -0
- package/app/components/main/MainSourcesUsage.vue +60 -0
- package/app/components/main/MainToc.vue +135 -0
- package/app/components/main/content/ContentPopovers.vue +9 -2
- package/app/components/main/content/ContentReferences.vue +1 -27
- package/app/components/main/content/reference/ReferenceGroup.vue +10 -9
- package/app/components/main/content/reference/ReferenceSource.vue +1 -0
- package/app/components/main/topic/MainTopic.vue +5 -8
- package/app/components/main/topic/MainTopicQuickLinks.vue +24 -0
- package/app/components/stats/Stats.vue +21 -0
- package/app/components/stats/StatsGroupLike.vue +24 -0
- package/app/components/stats/StatsItem.vue +33 -0
- package/app/components/stats/StatsItemElement.vue +13 -0
- package/app/composables/bitranLocation.ts +2 -3
- package/app/composables/contentPage.ts +7 -6
- package/app/composables/theme.ts +26 -5
- package/app/pages/book/[...bookId].vue +6 -31
- package/app/pages/group/[...groupId].vue +5 -41
- package/app/pages/index.vue +189 -16
- package/app/pages/sponsors.vue +2 -2
- package/app/scripts/preview/data/pageLink.ts +4 -3
- package/app/scripts/preview/data/unique.ts +6 -6
- package/languages/en.ts +7 -1
- package/languages/ru.ts +10 -3
- package/package.json +4 -4
- package/server/api/content/data.ts +33 -11
- package/server/api/index/data.ts +46 -0
- package/server/plugin/bitran/content.ts +0 -14
- package/server/plugin/bitran/location.ts +3 -6
- package/server/plugin/build/jobs/content/parse.ts +95 -1
- package/server/plugin/build/jobs/content/type/group.ts +0 -21
- package/server/plugin/content/context.ts +3 -6
- package/server/plugin/db/entities/Group.ts +0 -3
- package/server/plugin/db/entities/QuickLink.ts +19 -0
- package/server/plugin/db/entities/Stat.ts +13 -0
- package/server/plugin/db/setup.ts +4 -0
- package/server/plugin/repository/content.ts +3 -3
- package/server/plugin/repository/contentToc.ts +90 -0
- package/server/plugin/repository/elementStats.ts +80 -0
- package/server/plugin/repository/link.ts +20 -0
- package/server/plugin/repository/quickLink.ts +36 -0
- package/server/plugin/repository/readLink.ts +17 -0
- package/server/plugin/repository/reference.ts +78 -0
- package/server/plugin/repository/topicCount.ts +19 -0
- package/shared/content/data/base.ts +2 -2
- package/shared/content/data/groupLike.ts +11 -0
- package/shared/content/data/type/book.ts +3 -1
- package/shared/content/data/type/group.ts +3 -1
- package/shared/content/data/type/topic.ts +3 -1
- package/shared/content/reference.ts +18 -0
- package/shared/content/toc.ts +35 -0
- package/shared/indexData.ts +10 -0
- package/shared/link.ts +3 -2
- package/shared/quickLink.ts +7 -0
- package/shared/stat.ts +23 -0
- package/shared/types/language.ts +6 -1
- 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
|
-
<
|
|
36
|
+
<div :class="$style.title">
|
|
37
37
|
<EruditLink to="/">{{
|
|
38
38
|
siteInfo.title || phrase.site_info_title
|
|
39
39
|
}}</EruditLink>
|
|
40
|
-
</
|
|
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?.
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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>
|