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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ContentSourceUsageSet } from '@shared/content/reference';
|
|
3
|
+
import ReferenceSource from '@app/components/main/content/reference/ReferenceSource.vue';
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
usageSet?: ContentSourceUsageSet;
|
|
7
|
+
}>();
|
|
8
|
+
|
|
9
|
+
const count = computed(() => {
|
|
10
|
+
if (!props.usageSet) {
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Object.keys(props.usageSet).length;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const phrase = await usePhrases('references', 'references_description');
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<MainSection v-if="usageSet && count">
|
|
22
|
+
<MainSectionTitle
|
|
23
|
+
icon="link-external"
|
|
24
|
+
:title="phrase.references"
|
|
25
|
+
:count
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<p :class="$style.description">{{ phrase.references_description }}</p>
|
|
29
|
+
|
|
30
|
+
<ul :class="$style.sources">
|
|
31
|
+
<li v-for="usageItem of usageSet">
|
|
32
|
+
<ReferenceSource :source="usageItem.source">
|
|
33
|
+
<template #after>
|
|
34
|
+
<MainSourceUsages :usages="usageItem.usages" />
|
|
35
|
+
</template>
|
|
36
|
+
</ReferenceSource>
|
|
37
|
+
</li>
|
|
38
|
+
</ul>
|
|
39
|
+
</MainSection>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style lang="scss" module>
|
|
43
|
+
.description {
|
|
44
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sources {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: var(--gap);
|
|
51
|
+
list-style: none;
|
|
52
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
53
|
+
margin: 0;
|
|
54
|
+
|
|
55
|
+
> li:not(:last-of-type) {
|
|
56
|
+
border-bottom: 1px solid var(--border);
|
|
57
|
+
padding-bottom: var(--gap);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { CONTENT_TYPE_ICON } from '@shared/icons';
|
|
3
|
+
import type { ContentToc, ContentTocItem } from '@shared/content/toc';
|
|
4
|
+
import QuickLinks from '../QuickLinks.vue';
|
|
5
|
+
|
|
6
|
+
defineProps<{ toc: ContentToc }>();
|
|
7
|
+
|
|
8
|
+
const pretty = useFormatText();
|
|
9
|
+
const phrase = await usePhrases('toc', 'topics');
|
|
10
|
+
|
|
11
|
+
function hasBottom(tocItem: ContentTocItem): boolean {
|
|
12
|
+
if (tocItem.type === 'topic') {
|
|
13
|
+
return tocItem.quickLinks.length > 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (tocItem.type === 'book' || tocItem.type === 'group') {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<MainSection>
|
|
26
|
+
<MainSectionTitle
|
|
27
|
+
icon="list-squared"
|
|
28
|
+
:title="phrase.toc"
|
|
29
|
+
:count="toc.length"
|
|
30
|
+
/>
|
|
31
|
+
<ol :class="$style.tocItems">
|
|
32
|
+
<li v-for="tocItem of toc" :class="$style.tocItem">
|
|
33
|
+
<EruditLink :to="tocItem.link" :class="$style.tocItemHeading">
|
|
34
|
+
<MyIcon
|
|
35
|
+
:name="CONTENT_TYPE_ICON[tocItem.type]"
|
|
36
|
+
:class="$style.tocItemIcon"
|
|
37
|
+
/>
|
|
38
|
+
<h3>
|
|
39
|
+
{{ pretty(tocItem.title) }}
|
|
40
|
+
</h3>
|
|
41
|
+
<MyIcon name="arrow-left" :class="$style.gotoArrow" />
|
|
42
|
+
</EruditLink>
|
|
43
|
+
<div
|
|
44
|
+
v-if="tocItem.description"
|
|
45
|
+
:class="$style.tocItemDescription"
|
|
46
|
+
>
|
|
47
|
+
{{ pretty(tocItem.description) }}
|
|
48
|
+
</div>
|
|
49
|
+
<div v-if="hasBottom(tocItem)" :class="$style.tocItemBottom">
|
|
50
|
+
<QuickLinks
|
|
51
|
+
v-if="
|
|
52
|
+
tocItem.type === 'topic' &&
|
|
53
|
+
tocItem.quickLinks.length
|
|
54
|
+
"
|
|
55
|
+
:links="tocItem.quickLinks"
|
|
56
|
+
/>
|
|
57
|
+
<Stats
|
|
58
|
+
v-if="
|
|
59
|
+
tocItem.type === 'book' || tocItem.type === 'group'
|
|
60
|
+
"
|
|
61
|
+
:stats="[
|
|
62
|
+
{
|
|
63
|
+
type: 'custom',
|
|
64
|
+
icon: 'files',
|
|
65
|
+
label: phrase.topics,
|
|
66
|
+
count: tocItem.topicCount,
|
|
67
|
+
},
|
|
68
|
+
...tocItem.stats,
|
|
69
|
+
]"
|
|
70
|
+
:class="$style.tocItemStats"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</li>
|
|
74
|
+
</ol>
|
|
75
|
+
</MainSection>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style lang="scss" module>
|
|
79
|
+
.tocItems {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: var(--gapBig);
|
|
83
|
+
list-style: none;
|
|
84
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
85
|
+
margin: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.tocItem {
|
|
89
|
+
background: light-dark(#f7f7f7, #282828);
|
|
90
|
+
border-radius: 5px;
|
|
91
|
+
border: 1px solid var(--border);
|
|
92
|
+
|
|
93
|
+
.tocItemHeading {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
padding: var(--gap);
|
|
97
|
+
color: inherit;
|
|
98
|
+
text-decoration: none;
|
|
99
|
+
|
|
100
|
+
.tocItemIcon {
|
|
101
|
+
color: var(--textMuted);
|
|
102
|
+
margin-right: calc(var(--gap) - 2px);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.gotoArrow {
|
|
106
|
+
color: var(--textDimmed);
|
|
107
|
+
transform: scaleX(-1);
|
|
108
|
+
opacity: 0;
|
|
109
|
+
@include transition(color, opacity, transform);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&:hover {
|
|
113
|
+
.gotoArrow {
|
|
114
|
+
color: var(--text);
|
|
115
|
+
opacity: 1;
|
|
116
|
+
transform: scaleX(-1) translateX(-12px);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.tocItemDescription {
|
|
122
|
+
padding: 0 var(--gap);
|
|
123
|
+
padding-bottom: var(--gap);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.tocItemBottom {
|
|
127
|
+
padding: var(--gap);
|
|
128
|
+
padding-top: 0;
|
|
129
|
+
|
|
130
|
+
.tocItemStats {
|
|
131
|
+
gap: var(--gap);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { ContentFlag } from '@erudit-js/cog/schema';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { ContentGeneric } from '@shared/content/data/base';
|
|
5
5
|
|
|
6
6
|
import ContentPopover from './ContentPopover.vue';
|
|
7
7
|
import type { PopoverData } from '@shared/popover';
|
|
8
8
|
import { type MyIconName } from '#my-icons';
|
|
9
9
|
|
|
10
10
|
const props = defineProps<{
|
|
11
|
-
generic:
|
|
11
|
+
generic: ContentGeneric /* TODO: customPopovers[] */;
|
|
12
12
|
}>();
|
|
13
13
|
|
|
14
14
|
const phrase = await usePhrases(
|
|
@@ -79,11 +79,18 @@ const hasPopovers = computed(() => {
|
|
|
79
79
|
</template>
|
|
80
80
|
|
|
81
81
|
<style lang="scss" module>
|
|
82
|
+
@use '$/def/bp';
|
|
83
|
+
|
|
82
84
|
.popovers {
|
|
83
85
|
display: flex;
|
|
84
86
|
flex-wrap: wrap;
|
|
85
87
|
gap: var(--gap);
|
|
86
88
|
padding: var(--_pMainY) var(--_pMainX);
|
|
89
|
+
|
|
90
|
+
@include bp.below('mobile') {
|
|
91
|
+
gap: var(--gapSmall);
|
|
92
|
+
justify-content: center;
|
|
93
|
+
}
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
.dependenciesList {
|
|
@@ -16,11 +16,7 @@ const phrase = await usePhrases('references', 'references_description');
|
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<template>
|
|
19
|
-
<
|
|
20
|
-
<MyIcon name="link-external" :class="$style.icon" />
|
|
21
|
-
<span :class="$style.title">{{ phrase.references }}</span>
|
|
22
|
-
<span :class="$style.count">{{ count }}</span>
|
|
23
|
-
</h2>
|
|
19
|
+
<MainSectionTitle icon="link-external" :title="phrase.references" :count />
|
|
24
20
|
|
|
25
21
|
<p :class="$style.description">{{ phrase.references_description }}</p>
|
|
26
22
|
|
|
@@ -30,28 +26,6 @@ const phrase = await usePhrases('references', 'references_description');
|
|
|
30
26
|
</template>
|
|
31
27
|
|
|
32
28
|
<style lang="scss" module>
|
|
33
|
-
.heading {
|
|
34
|
-
display: flex;
|
|
35
|
-
align-items: center;
|
|
36
|
-
gap: var(--gap);
|
|
37
|
-
padding: var(--_pMainY) var(--_pMainX);
|
|
38
|
-
|
|
39
|
-
.icon {
|
|
40
|
-
font-size: 0.9em;
|
|
41
|
-
color: var(--textMuted);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.count {
|
|
45
|
-
position: relative;
|
|
46
|
-
top: 1px;
|
|
47
|
-
font-weight: 550;
|
|
48
|
-
font-size: 0.6em;
|
|
49
|
-
background: color-mix(in srgb, var(--textDimmed), transparent 65%);
|
|
50
|
-
border-radius: 20px;
|
|
51
|
-
padding: 2px 10px;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
29
|
.description {
|
|
56
30
|
padding: var(--_pMainY) var(--_pMainX);
|
|
57
31
|
}
|
|
@@ -9,10 +9,16 @@ defineProps<{ group: ContentReferenceGroup }>();
|
|
|
9
9
|
|
|
10
10
|
<template>
|
|
11
11
|
<div :class="$style.group">
|
|
12
|
-
<ReferenceSource :source="group.source"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<ReferenceSource :source="group.source">
|
|
13
|
+
<template #after>
|
|
14
|
+
<div :class="$style.references">
|
|
15
|
+
<ReferenceItem
|
|
16
|
+
v-for="reference of group.references"
|
|
17
|
+
:reference
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
</ReferenceSource>
|
|
16
22
|
</div>
|
|
17
23
|
</template>
|
|
18
24
|
|
|
@@ -29,10 +35,5 @@ defineProps<{ group: ContentReferenceGroup }>();
|
|
|
29
35
|
display: flex;
|
|
30
36
|
flex-direction: column;
|
|
31
37
|
gap: var(--gap);
|
|
32
|
-
margin-left: 34px;
|
|
33
|
-
|
|
34
|
-
@include bp.below(mobile) {
|
|
35
|
-
margin-left: 0;
|
|
36
|
-
}
|
|
37
38
|
}
|
|
38
39
|
</style>
|
|
@@ -43,6 +43,11 @@ const phrase = await usePhrases('article', 'summary', 'practice');
|
|
|
43
43
|
:description="topicData.generic?.description"
|
|
44
44
|
/>
|
|
45
45
|
|
|
46
|
+
<MainTopicQuickLinks
|
|
47
|
+
v-if="topicData.quickLinks.length"
|
|
48
|
+
:links="topicData.quickLinks"
|
|
49
|
+
/>
|
|
50
|
+
|
|
46
51
|
<ContentPopovers :generic="topicData.generic" />
|
|
47
52
|
|
|
48
53
|
<MainCameo />
|
|
@@ -66,11 +71,3 @@ const phrase = await usePhrases('article', 'summary', 'practice');
|
|
|
66
71
|
<AdsBannerBottom />
|
|
67
72
|
</MainSection>
|
|
68
73
|
</template>
|
|
69
|
-
|
|
70
|
-
<style lang="scss" module>
|
|
71
|
-
.foo {
|
|
72
|
-
position: relative;
|
|
73
|
-
width: 100%;
|
|
74
|
-
height: 300px;
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { QuickLinks } from '@erudit/shared/quickLink';
|
|
3
|
+
|
|
4
|
+
defineProps<{ links: QuickLinks }>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<QuickLinks :links :class="$style.topicQuickLinks" />
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<style lang="scss" module>
|
|
12
|
+
@use '$/def/bp';
|
|
13
|
+
|
|
14
|
+
.topicQuickLinks {
|
|
15
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
16
|
+
--_erudit_quickLink_bg: var(--bgAside);
|
|
17
|
+
|
|
18
|
+
@include bp.below('mobile') {
|
|
19
|
+
> ul {
|
|
20
|
+
justify-content: center;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { Stats } from '@shared/stat';
|
|
3
|
+
|
|
4
|
+
defineProps<{ stats: Stats }>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<section :class="$style.stats">
|
|
9
|
+
<template v-for="stat of stats">
|
|
10
|
+
<StatsItem v-if="stat.type === 'custom'" v-bind:stat />
|
|
11
|
+
<StatsItemElement v-else v-bind:stat />
|
|
12
|
+
</template>
|
|
13
|
+
</section>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style lang="scss" module>
|
|
17
|
+
.stats {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { Stats } from '@shared/stat';
|
|
3
|
+
|
|
4
|
+
defineProps<{ stats: Stats }>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Stats :stats :class="$style.bookGroupStats" />
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<style lang="scss" module>
|
|
12
|
+
@use '$/def/bp';
|
|
13
|
+
|
|
14
|
+
.bookGroupStats {
|
|
15
|
+
gap: var(--gapBig);
|
|
16
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
17
|
+
font-size: 1.2em;
|
|
18
|
+
|
|
19
|
+
@include bp.below('mobile') {
|
|
20
|
+
justify-content: center;
|
|
21
|
+
gap: var(--gap);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { isMyIcon, type MyIconName } from '#my-icons';
|
|
3
|
+
import type { CustomStat } from '@shared/stat';
|
|
4
|
+
|
|
5
|
+
defineProps<{ stat: CustomStat }>();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div :class="$style.statsItem" :title="stat.label">
|
|
10
|
+
<MyIcon v-if="isMyIcon(stat.icon)" :name="stat.icon as MyIconName" />
|
|
11
|
+
<MyRuntimeIcon v-else name="stats-item-icon" :svg="stat.icon" />
|
|
12
|
+
<span :class="$style.statsItemCount">{{ stat.count }}</span>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style lang="scss" module>
|
|
17
|
+
.statsItem {
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: var(--gapSmall);
|
|
20
|
+
align-items: center;
|
|
21
|
+
color: var(--textMuted);
|
|
22
|
+
cursor: help;
|
|
23
|
+
|
|
24
|
+
.statsItemCount {
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[my-icon] {
|
|
29
|
+
font-size: 0.9em;
|
|
30
|
+
color: var(--textDimmed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ElementStat } from '@shared/stat';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ stat: ElementStat }>();
|
|
5
|
+
|
|
6
|
+
const icon = await useBitranElementIcon(props.stat.elementName);
|
|
7
|
+
const language = await useBitranElementLanguage(props.stat.elementName);
|
|
8
|
+
const label = language('_element_title');
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<StatsItem :stat="{ type: 'custom', icon, label, count: stat.count }" />
|
|
13
|
+
</template>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { locationFromPath, type BitranLocation } from '@erudit-js/cog/schema';
|
|
2
2
|
|
|
3
|
-
export function useBitranLocation(): ComputedRef<BitranLocation
|
|
3
|
+
export function useBitranLocation(): ComputedRef<BitranLocation> {
|
|
4
4
|
const route = useRoute();
|
|
5
|
-
|
|
6
|
-
return computed(() => locationFromPath(route.path));
|
|
5
|
+
return computed(() => locationFromPath(route.path)!);
|
|
7
6
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import eruditConfig from '#erudit/config';
|
|
2
2
|
|
|
3
|
+
import { normalizeText } from '@erudit/utils/normalize';
|
|
3
4
|
import { createOgImageTags, defaultOgImage } from '@app/scripts/og';
|
|
4
5
|
import type { ContentData } from '@shared/content/data';
|
|
5
6
|
|
|
@@ -100,18 +101,18 @@ export function useContentPage(contentData: Ref<ContentData>) {
|
|
|
100
101
|
contentData.value.generic?.description;
|
|
101
102
|
|
|
102
103
|
if (customDescription) {
|
|
103
|
-
seo.description.value = customDescription
|
|
104
|
-
.trim()
|
|
105
|
-
.replace(/\n/g, ' ');
|
|
104
|
+
seo.description.value = normalizeText(customDescription);
|
|
106
105
|
return;
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
if (contentRoute.value!.type === 'topic') {
|
|
110
109
|
const phraseFunc =
|
|
111
110
|
phrase[`seo_${contentRoute.value!.topicPart}_description`];
|
|
112
|
-
seo.description.value =
|
|
113
|
-
|
|
114
|
-
contentData.value.generic
|
|
111
|
+
seo.description.value = normalizeText(
|
|
112
|
+
phraseFunc(
|
|
113
|
+
contentData.value.generic?.seo?.title ||
|
|
114
|
+
contentData.value.generic.title!,
|
|
115
|
+
),
|
|
115
116
|
);
|
|
116
117
|
return;
|
|
117
118
|
}
|
package/app/composables/theme.ts
CHANGED
|
@@ -3,12 +3,32 @@ const themes = ['auto', 'light', 'dark'] as const;
|
|
|
3
3
|
export type Theme = (typeof themes)[number];
|
|
4
4
|
export type BinaryTheme = 'light' | 'dark';
|
|
5
5
|
|
|
6
|
+
const _theme = ref<Theme>();
|
|
7
|
+
|
|
6
8
|
export function useTheme() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
9
|
+
if (import.meta.server) {
|
|
10
|
+
throw new Error(`Calling 'useTheme' on server side is prohibited!`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setTheme(newTheme: Theme) {
|
|
14
|
+
localStorage.setItem('theme', newTheme);
|
|
15
|
+
_theme.value = newTheme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const localStorageTheme = localStorage.getItem('theme');
|
|
19
|
+
|
|
20
|
+
if (themes.includes(localStorageTheme as any)) {
|
|
21
|
+
setTheme(localStorageTheme as Theme);
|
|
22
|
+
} else {
|
|
23
|
+
console.warn(
|
|
24
|
+
`Failed to get correct theme value from Local Storage!\nRetrieved "${localStorageTheme}"!`,
|
|
25
|
+
);
|
|
26
|
+
setTheme('auto');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const theme = computed<Theme>(() => _theme.value!);
|
|
10
30
|
|
|
11
|
-
const binaryTheme = computed(() => {
|
|
31
|
+
const binaryTheme = computed<BinaryTheme>(() => {
|
|
12
32
|
return theme.value === 'auto'
|
|
13
33
|
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
14
34
|
? 'dark'
|
|
@@ -17,8 +37,9 @@ export function useTheme() {
|
|
|
17
37
|
});
|
|
18
38
|
|
|
19
39
|
const cycle = () => {
|
|
20
|
-
|
|
40
|
+
const newTheme =
|
|
21
41
|
themes[(themes.indexOf(theme.value) + 1) % themes.length]!;
|
|
42
|
+
setTheme(newTheme);
|
|
22
43
|
};
|
|
23
44
|
|
|
24
45
|
return {
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import type { ContentBookData } from '@
|
|
3
|
-
import type { MyIconName } from '#my-icons';
|
|
4
|
-
|
|
5
|
-
import ContentBreadcrumb from '@app/components/main/content/ContentBreadcrumb.vue';
|
|
6
|
-
import ContentDecoration from '@app/components/main/content/ContentDecoration.vue';
|
|
7
|
-
import ContentPopovers from '@app/components/main/content/ContentPopovers.vue';
|
|
2
|
+
import type { ContentBookData } from '@shared/content/data/type/book';
|
|
8
3
|
|
|
9
4
|
const bookData = await useContentData<ContentBookData>();
|
|
10
5
|
await useContentPage(bookData);
|
|
@@ -13,30 +8,10 @@ const phrase = await usePhrases('book');
|
|
|
13
8
|
</script>
|
|
14
9
|
|
|
15
10
|
<template>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
:
|
|
11
|
+
<GroupLikePage
|
|
12
|
+
icon="outline/book"
|
|
13
|
+
:contentLabel="phrase.book"
|
|
14
|
+
:generic="bookData.generic"
|
|
15
|
+
:groupLike="bookData.groupLike"
|
|
19
16
|
/>
|
|
20
|
-
|
|
21
|
-
<ContentBreadcrumb :context="bookData.generic.context" />
|
|
22
|
-
|
|
23
|
-
<MainTitle
|
|
24
|
-
:title="
|
|
25
|
-
bookData.generic?.title ||
|
|
26
|
-
bookData.generic.contentId.split('/').pop()!
|
|
27
|
-
"
|
|
28
|
-
:icon="<MyIconName>'outline/book'"
|
|
29
|
-
:hint="phrase.book"
|
|
30
|
-
/>
|
|
31
|
-
|
|
32
|
-
<MainDescription
|
|
33
|
-
v-if="bookData.generic?.description"
|
|
34
|
-
:description="bookData.generic?.description"
|
|
35
|
-
/>
|
|
36
|
-
|
|
37
|
-
<ContentPopovers :generic="bookData.generic" />
|
|
38
|
-
|
|
39
|
-
<!-- Counters, fancy "GO LEARN" button and etc. -->
|
|
40
|
-
|
|
41
|
-
<div style="clear: both"></div>
|
|
42
17
|
</template>
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { type BitranLocation } from '@erudit-js/cog/schema';
|
|
3
|
-
import eruditConfig from '#erudit/config';
|
|
4
|
-
|
|
5
2
|
import { type ContentGroupData } from '@shared/content/data/type/group';
|
|
6
|
-
import { locationIcon } from '@erudit/shared/icons';
|
|
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 location = useBitranLocation() as Ref<BitranLocation>;
|
|
13
3
|
|
|
14
4
|
const groupData = await useContentData<ContentGroupData>();
|
|
15
5
|
await useContentPage(groupData);
|
|
@@ -18,36 +8,10 @@ const phrase = await usePhrases('group');
|
|
|
18
8
|
</script>
|
|
19
9
|
|
|
20
10
|
<template>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
:
|
|
11
|
+
<GroupLikePage
|
|
12
|
+
icon="folder-open"
|
|
13
|
+
:contentLabel="phrase.group"
|
|
14
|
+
:generic="groupData.generic"
|
|
15
|
+
:groupLike="groupData.groupLike"
|
|
24
16
|
/>
|
|
25
|
-
|
|
26
|
-
<ContentBreadcrumb :context="groupData.generic.context" />
|
|
27
|
-
|
|
28
|
-
<MainTitle
|
|
29
|
-
:title="
|
|
30
|
-
groupData.generic?.title ||
|
|
31
|
-
groupData.generic.contentId.split('/').pop()!
|
|
32
|
-
"
|
|
33
|
-
:icon="locationIcon(location!)"
|
|
34
|
-
:hint="phrase.group"
|
|
35
|
-
/>
|
|
36
|
-
|
|
37
|
-
<MainDescription
|
|
38
|
-
v-if="groupData.generic?.description"
|
|
39
|
-
:description="groupData.generic?.description"
|
|
40
|
-
/>
|
|
41
|
-
|
|
42
|
-
<ContentPopovers :generic="groupData.generic" />
|
|
43
|
-
|
|
44
|
-
<MainCameo />
|
|
45
|
-
|
|
46
|
-
<div style="clear: both"></div>
|
|
47
|
-
|
|
48
|
-
<MainBitranContent :location />
|
|
49
|
-
|
|
50
|
-
<MainSection v-if="adsAllowed() && eruditConfig.ads?.bottom">
|
|
51
|
-
<AdsBannerBottom />
|
|
52
|
-
</MainSection>
|
|
53
17
|
</template>
|