erudit 3.0.0-dev.18 → 3.0.0-dev.19
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/graduation.svg +3 -0
- package/app/components/aside/AsideMinor.vue +41 -17
- package/app/components/aside/major/panes/Pages.vue +11 -14
- package/app/components/aside/minor/{Contribute.vue → AsideMinorContribute.vue} +35 -5
- package/app/components/aside/minor/content/AsideMinorContent.vue +7 -10
- package/app/components/aside/minor/contributor/AsideMinorContributor.vue +78 -0
- package/app/components/aside/minor/contributor/BookContribution.vue +64 -0
- package/app/components/aside/minor/topic/AsideMinorTopic.vue +1 -4
- package/app/components/aside/minor/topic/TopicContributors.vue +3 -3
- package/app/components/aside/minor/topic/TopicNav.vue +6 -6
- package/app/components/aside/minor/topic/TopicToc.vue +1 -2
- package/app/components/bitran/BitranContent.vue +15 -16
- package/app/components/contributor/ContributorAvatar.vue +3 -3
- package/app/components/main/MainBitranContent.vue +41 -0
- package/app/components/main/{utils/Breadcrumb.vue → MainBreadcrumb.vue} +12 -19
- package/app/components/main/MainDescription.vue +24 -0
- package/app/components/main/{utils/ContentTitle.vue → MainTitle.vue} +11 -2
- package/app/components/main/content/ContentBreadcrumb.vue +28 -0
- package/app/components/main/{utils → content}/ContentSection.vue +2 -2
- package/app/components/main/topic/MainTopic.vue +15 -20
- package/app/components/main/topic/TopicPartSwitch.vue +9 -3
- package/app/components/preview/display/Unique.vue +2 -11
- package/app/composables/adsAllowed.ts +1 -1
- package/app/composables/majorPane.ts +3 -2
- package/app/composables/phrases.ts +21 -9
- package/app/pages/book/[...bookId].vue +6 -11
- package/app/pages/contributor/[contributorId].vue +225 -0
- package/app/pages/contributors.vue +183 -0
- package/app/pages/group/[...groupId].vue +11 -19
- package/app/scripts/preview/data/unique.ts +18 -21
- package/languages/en.ts +12 -3
- package/languages/ru.ts +12 -3
- package/package.json +5 -5
- package/server/api/aside/minor/book/[...bookId].ts +18 -0
- package/server/api/aside/minor/contributor/[contributorId].ts +18 -0
- package/server/api/aside/minor/group/[...groupId].ts +18 -0
- package/server/api/aside/minor/news.ts +2 -2
- package/server/api/aside/minor/topic.ts +36 -0
- package/server/api/bitran/content/[...location].ts +4 -1
- package/server/api/contributor/list.ts +44 -0
- package/server/api/contributor/page/[contributorId].ts +14 -0
- package/server/api/preview/unique/[...location].ts +10 -23
- package/server/plugin/bitran/content.ts +34 -19
- package/server/plugin/bitran/location.ts +6 -2
- package/server/plugin/build/jobs/contributors.ts +3 -0
- package/server/plugin/db/entities/Contributor.ts +9 -0
- package/server/plugin/repository/asideMinor.ts +51 -0
- package/server/plugin/repository/book.ts +16 -0
- package/server/plugin/repository/contributor.ts +90 -0
- package/shared/aside/minor.ts +32 -28
- package/shared/bitran/content.ts +9 -0
- package/shared/breadcrumb.ts +7 -0
- package/shared/contributor.ts +28 -0
- package/shared/types/language.ts +8 -3
- package/app/components/aside/minor/AsideMinorContributor.vue +0 -5
- package/app/components/main/utils/ContentDescription.vue +0 -19
- package/app/composables/bitranContent.ts +0 -96
- package/app/pages/members.vue +0 -5
- package/server/api/aside/minor/path.ts +0 -82
- package/shared/bitran/stringContent.ts +0 -6
- /package/app/components/main/{utils → content}/ContentDecoration.vue +0 -0
- /package/app/components/main/{utils → content}/ContentPopover.vue +0 -0
- /package/app/components/main/{utils → content}/ContentPopovers.vue +0 -0
- /package/app/components/main/{utils → content}/ContentReferences.vue +0 -0
- /package/app/components/main/{utils → content}/reference/ReferenceGroup.vue +0 -0
- /package/app/components/main/{utils → content}/reference/ReferenceItem.vue +0 -0
- /package/app/components/main/{utils → content}/reference/ReferenceSource.vue +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
defineProps<{ description: string; html?: boolean }>();
|
|
3
|
+
const pretty = useFormatText();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<section
|
|
8
|
+
v-if="html"
|
|
9
|
+
:class="$style.contentDescription"
|
|
10
|
+
v-html="description"
|
|
11
|
+
></section>
|
|
12
|
+
<section v-else :class="$style.contentDescription">
|
|
13
|
+
{{ pretty(description) }}
|
|
14
|
+
</section>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<style lang="scss" module>
|
|
18
|
+
.contentDescription {
|
|
19
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
20
|
+
font-weight: 500;
|
|
21
|
+
font-size: 1.1em;
|
|
22
|
+
color: var(--text);
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
@@ -6,12 +6,18 @@ defineProps<{ icon: string; title: string; hint?: string }>();
|
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<section :class="$style.contentTitle">
|
|
9
|
-
<MyIcon
|
|
9
|
+
<MyIcon
|
|
10
|
+
v-if="isMyIcon(icon)"
|
|
11
|
+
:name="icon as any"
|
|
12
|
+
:title="hint"
|
|
13
|
+
:class="{ [$style.hasHint]: !!hint }"
|
|
14
|
+
/>
|
|
10
15
|
<MyRuntimeIcon
|
|
11
16
|
v-else
|
|
12
17
|
name="content-title-icon"
|
|
13
18
|
:svg="icon"
|
|
14
19
|
:title="hint"
|
|
20
|
+
:class="{ [$style.hasHint]: !!hint }"
|
|
15
21
|
/>
|
|
16
22
|
<h1>{{ title }}</h1>
|
|
17
23
|
</section>
|
|
@@ -24,7 +30,7 @@ defineProps<{ icon: string; title: string; hint?: string }>();
|
|
|
24
30
|
display: flex;
|
|
25
31
|
align-items: center;
|
|
26
32
|
gap: var(--gap);
|
|
27
|
-
padding:
|
|
33
|
+
padding: var(--_pMainY) var(--_pMainX);
|
|
28
34
|
|
|
29
35
|
[my-icon] {
|
|
30
36
|
flex-shrink: 0;
|
|
@@ -32,6 +38,9 @@ defineProps<{ icon: string; title: string; hint?: string }>();
|
|
|
32
38
|
color: var(--textMuted);
|
|
33
39
|
position: relative;
|
|
34
40
|
top: 1px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.hasHint {
|
|
35
44
|
cursor: help;
|
|
36
45
|
}
|
|
37
46
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { MyIconName } from '#my-icons';
|
|
3
|
+
import type { BreadcrumbItem } from '@shared/breadcrumb';
|
|
4
|
+
import type { Context } from '@shared/content/context';
|
|
5
|
+
|
|
6
|
+
defineProps<{ context: Context }>();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<MainBreadcrumb
|
|
11
|
+
v-if="context?.length > 1"
|
|
12
|
+
:items="
|
|
13
|
+
context.reduce((acc, item, i, arr) => {
|
|
14
|
+
if (item.hidden || i === arr.length - 1) {
|
|
15
|
+
return acc;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
acc.push({
|
|
19
|
+
title: item.title,
|
|
20
|
+
icon: item.icon as MyIconName,
|
|
21
|
+
link: item.href,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return acc;
|
|
25
|
+
}, [] as BreadcrumbItem[])
|
|
26
|
+
"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
2
|
+
import { type BitranLocation, type TopicPart } from '@erudit-js/cog/schema';
|
|
3
3
|
|
|
4
4
|
import eruditConfig from '#erudit/config';
|
|
5
5
|
|
|
6
|
-
import { type ContentTopicData } from '@
|
|
6
|
+
import { type ContentTopicData } from '@shared/content/data/type/topic';
|
|
7
|
+
import { TOPIC_PART_ICON } from '@shared/icons';
|
|
7
8
|
import { topicLocation } from '@app/scripts/aside/minor/topic';
|
|
8
9
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import ContentBreadcrumb from '@app/components/main/content/ContentBreadcrumb.vue';
|
|
11
|
+
import ContentDecoration from '@app/components/main/content/ContentDecoration.vue';
|
|
12
|
+
import ContentPopovers from '@app/components/main/content/ContentPopovers.vue';
|
|
13
|
+
import ContentReferences from '@app/components/main/content/ContentReferences.vue';
|
|
14
|
+
import ContentSection from '@app/components/main/content/ContentSection.vue';
|
|
12
15
|
import TopicPartSwitch from './TopicPartSwitch.vue';
|
|
13
|
-
import Breadcrumb from '../utils/Breadcrumb.vue';
|
|
14
|
-
import ContentPopovers from '../utils/ContentPopovers.vue';
|
|
15
|
-
import ContentReferences from '../utils/ContentReferences.vue';
|
|
16
|
-
import ContentSection from '../utils/ContentSection.vue';
|
|
17
|
-
import { TOPIC_PART_ICON } from '@erudit/shared/icons';
|
|
18
16
|
|
|
19
|
-
const location = useBitranLocation()
|
|
17
|
+
const location = useBitranLocation() as Ref<BitranLocation>;
|
|
20
18
|
const topicPart = computed(() => location.value?.type as TopicPart);
|
|
21
19
|
|
|
22
20
|
const topicData = await useContentData<ContentTopicData>();
|
|
@@ -24,8 +22,6 @@ await useContentPage(topicData);
|
|
|
24
22
|
|
|
25
23
|
const phrase = await usePhrases('article', 'summary', 'practice');
|
|
26
24
|
|
|
27
|
-
const content = await useBitranContent(location);
|
|
28
|
-
|
|
29
25
|
onMounted(() => {
|
|
30
26
|
watchEffect(() => {
|
|
31
27
|
// Telling live toc that content is mounted
|
|
@@ -40,12 +36,9 @@ onMounted(() => {
|
|
|
40
36
|
:decoration="topicData.generic.decoration"
|
|
41
37
|
/>
|
|
42
38
|
|
|
43
|
-
<
|
|
44
|
-
v-if="topicData.generic.context?.length > 1"
|
|
45
|
-
:context="topicData.generic.context"
|
|
46
|
-
/>
|
|
39
|
+
<ContentBreadcrumb :context="topicData.generic.context" />
|
|
47
40
|
|
|
48
|
-
<
|
|
41
|
+
<MainTitle
|
|
49
42
|
:title="
|
|
50
43
|
topicData.generic?.title ||
|
|
51
44
|
topicData.generic.contentId.split('/').pop()!
|
|
@@ -54,7 +47,7 @@ onMounted(() => {
|
|
|
54
47
|
:hint="phrase[location!.type as TopicPart]"
|
|
55
48
|
/>
|
|
56
49
|
|
|
57
|
-
<
|
|
50
|
+
<MainDescription
|
|
58
51
|
v-if="topicData.generic?.description"
|
|
59
52
|
:description="topicData.generic?.description"
|
|
60
53
|
/>
|
|
@@ -68,7 +61,9 @@ onMounted(() => {
|
|
|
68
61
|
|
|
69
62
|
<div style="clear: both"></div>
|
|
70
63
|
|
|
71
|
-
<
|
|
64
|
+
<ContentSection>
|
|
65
|
+
<MainBitranContent :location />
|
|
66
|
+
</ContentSection>
|
|
72
67
|
|
|
73
68
|
<ContentSection v-if="topicData.generic.references">
|
|
74
69
|
<ContentReferences :references="topicData.generic.references" />
|
|
@@ -33,17 +33,23 @@ const Link = defineNuxtLink({ prefetch: false });
|
|
|
33
33
|
.topicPartSwitch {
|
|
34
34
|
--height: 50px;
|
|
35
35
|
|
|
36
|
+
position: relative;
|
|
37
|
+
top: 65px;
|
|
38
|
+
|
|
36
39
|
display: flex;
|
|
37
40
|
align-items: end;
|
|
38
41
|
justify-content: center;
|
|
39
42
|
gap: var(--gapBig);
|
|
40
43
|
margin: var(--_pMainY) 0;
|
|
41
|
-
margin-top:
|
|
44
|
+
margin-top: -30px;
|
|
42
45
|
|
|
43
46
|
width: 100%;
|
|
44
47
|
height: var(--height);
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
border-bottom: 2px solid transparent;
|
|
49
|
+
|
|
50
|
+
@include bp.below('mobile') {
|
|
51
|
+
top: 56px;
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
.partButton {
|
|
@@ -5,9 +5,6 @@ import type { PreviewDataUnique } from '@app/scripts/preview/data/unique';
|
|
|
5
5
|
import type { PreviewDisplayProps } from '@app/scripts/preview/display';
|
|
6
6
|
|
|
7
7
|
const { data } = defineProps<PreviewDisplayProps<PreviewDataUnique>>();
|
|
8
|
-
|
|
9
|
-
const bitranTranspiler = await useBitranTranspiler();
|
|
10
|
-
const root = await bitranTranspiler.parser.parse(data.bitran.content.biCode);
|
|
11
8
|
</script>
|
|
12
9
|
|
|
13
10
|
<template>
|
|
@@ -15,16 +12,10 @@ const root = await bitranTranspiler.parser.parse(data.bitran.content.biCode);
|
|
|
15
12
|
<div
|
|
16
13
|
:class="[
|
|
17
14
|
$style.bitranPreviewContent,
|
|
18
|
-
data.
|
|
15
|
+
data.elementName === headingName && $style.heading,
|
|
19
16
|
]"
|
|
20
17
|
>
|
|
21
|
-
<BitranContent
|
|
22
|
-
:content="{
|
|
23
|
-
root,
|
|
24
|
-
renderDataStorage: data.bitran.content.renderDataStorage,
|
|
25
|
-
}"
|
|
26
|
-
:context="data.bitran.context"
|
|
27
|
-
/>
|
|
18
|
+
<BitranContent :rawContent="data.rawBitranContent" />
|
|
28
19
|
</div>
|
|
29
20
|
</PreviewDisplay>
|
|
30
21
|
</template>
|
|
@@ -44,8 +44,9 @@ export function useMajorPane() {
|
|
|
44
44
|
const route = useRoute();
|
|
45
45
|
|
|
46
46
|
const activePane = useState<MajorPaneKey>('major-pane', () => {
|
|
47
|
-
switch (
|
|
48
|
-
case '/
|
|
47
|
+
switch (true) {
|
|
48
|
+
case route.path.startsWith('/contributors'):
|
|
49
|
+
case route.path.startsWith('/contributor/'):
|
|
49
50
|
return 'pages';
|
|
50
51
|
default:
|
|
51
52
|
return 'index';
|
|
@@ -9,7 +9,7 @@ interface LanguagePayload {
|
|
|
9
9
|
strFunctions: Record<string, string>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
let functions: Record<string, Function>;
|
|
13
13
|
const functionPhrases: Record<string, Function> = {};
|
|
14
14
|
|
|
15
15
|
const phraseApiRoute = (phraseId: EruditPhraseId) =>
|
|
@@ -42,9 +42,14 @@ export function usePhrases<T extends EruditPhraseId[]>(
|
|
|
42
42
|
|
|
43
43
|
if (strPhrase.startsWith('~!~FUNC~!~')) {
|
|
44
44
|
const strFunction = strPhrase.replace('~!~FUNC~!~', '');
|
|
45
|
-
functionPhrases[phraseId] ||= new Function(
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
functionPhrases[phraseId] ||= new Function(
|
|
46
|
+
'funcs',
|
|
47
|
+
`
|
|
48
|
+
with(funcs) {
|
|
49
|
+
${strFunction}
|
|
50
|
+
}
|
|
51
|
+
`,
|
|
52
|
+
)(functions);
|
|
48
53
|
phraseCaller[phraseId] = functionPhrases[phraseId];
|
|
49
54
|
} else {
|
|
50
55
|
phraseCaller[phraseId] = strPhrase;
|
|
@@ -56,10 +61,17 @@ export function usePhrases<T extends EruditPhraseId[]>(
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
async function prepareFunctions(payload: LanguagePayload) {
|
|
59
|
-
|
|
64
|
+
payload.strFunctions ||= await $fetch(functionsApiRoute, {
|
|
65
|
+
responseType: 'json',
|
|
66
|
+
});
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
functions ||= (() => {
|
|
69
|
+
const _functions: Record<string, Function> = {};
|
|
70
|
+
for (const [funcName, strFunc] of Object.entries(
|
|
71
|
+
payload.strFunctions,
|
|
72
|
+
)) {
|
|
73
|
+
_functions[funcName] = new Function(strFunc)();
|
|
74
|
+
}
|
|
75
|
+
return _functions;
|
|
76
|
+
})();
|
|
65
77
|
}
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
import type { ContentBookData } from '@erudit/shared/content/data/type/book';
|
|
3
3
|
import type { MyIconName } from '#my-icons';
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import ContentDescription from '@app/components/main/utils/ContentDescription.vue';
|
|
9
|
-
import ContentPopovers from '@app/components/main/utils/ContentPopovers.vue';
|
|
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';
|
|
10
8
|
|
|
11
9
|
const bookData = await useContentData<ContentBookData>();
|
|
12
10
|
await useContentPage(bookData);
|
|
@@ -20,12 +18,9 @@ const phrase = await usePhrases('book');
|
|
|
20
18
|
:decoration="bookData.generic.decoration"
|
|
21
19
|
/>
|
|
22
20
|
|
|
23
|
-
<
|
|
24
|
-
v-if="bookData.generic.context?.length > 1"
|
|
25
|
-
:context="bookData.generic.context"
|
|
26
|
-
/>
|
|
21
|
+
<ContentBreadcrumb :context="bookData.generic.context" />
|
|
27
22
|
|
|
28
|
-
<
|
|
23
|
+
<MainTitle
|
|
29
24
|
:title="
|
|
30
25
|
bookData.generic?.title ||
|
|
31
26
|
bookData.generic.contentId.split('/').pop()!
|
|
@@ -34,7 +29,7 @@ const phrase = await usePhrases('book');
|
|
|
34
29
|
:hint="phrase.book"
|
|
35
30
|
/>
|
|
36
31
|
|
|
37
|
-
<
|
|
32
|
+
<MainDescription
|
|
38
33
|
v-if="bookData.generic?.description"
|
|
39
34
|
:description="bookData.generic?.description"
|
|
40
35
|
/>
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import eruditConfig from '#erudit/config';
|
|
3
|
+
import { type PageContributor } from '@shared/contributor';
|
|
4
|
+
import ContentSection from '@app/components/main/content/ContentSection.vue';
|
|
5
|
+
|
|
6
|
+
const route = useRoute();
|
|
7
|
+
const nuxtApp = useNuxtApp();
|
|
8
|
+
const contributor = shallowRef<PageContributor>(null as any);
|
|
9
|
+
const contributorColor = ref<string>('');
|
|
10
|
+
const resolved = computed(() => {
|
|
11
|
+
const title = (() => {
|
|
12
|
+
return contributor.value.displayName || contributor.value.contributorId;
|
|
13
|
+
})();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
title,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let requestCounter = 0;
|
|
21
|
+
|
|
22
|
+
function getPayloadCache() {
|
|
23
|
+
const payloadKey = 'contributor';
|
|
24
|
+
return (nuxtApp.static.data[payloadKey] ||= nuxtApp.payload.data[
|
|
25
|
+
payloadKey
|
|
26
|
+
] ||=
|
|
27
|
+
{});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function fetchContributorData(contributorId: string, requestId: number) {
|
|
31
|
+
const payloadCache = getPayloadCache();
|
|
32
|
+
|
|
33
|
+
if (!payloadCache[contributorId]) {
|
|
34
|
+
try {
|
|
35
|
+
const data = await $fetch(`/api/contributor/page/${contributorId}`);
|
|
36
|
+
|
|
37
|
+
if (requestId === requestCounter) {
|
|
38
|
+
payloadCache[contributorId] = data;
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(
|
|
42
|
+
`Error fetching contributor ${contributorId}:`,
|
|
43
|
+
error,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
contributor.value = payloadCache[contributorId];
|
|
49
|
+
contributorColor.value = stringColor(contributor.value.contributorId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await fetchContributorData(
|
|
53
|
+
route.params.contributorId as string,
|
|
54
|
+
++requestCounter,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const phrase = await usePhrases(
|
|
58
|
+
'contributors',
|
|
59
|
+
'contributor',
|
|
60
|
+
'contributor_description',
|
|
61
|
+
'editor',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
useHead({
|
|
65
|
+
title:
|
|
66
|
+
resolved.value.title +
|
|
67
|
+
' | ' +
|
|
68
|
+
(contributor.value.isEditor ? phrase.editor : phrase.contributor) +
|
|
69
|
+
' - ' +
|
|
70
|
+
(eruditConfig.seo?.title || eruditConfig.site?.title),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
useSeoMeta({
|
|
74
|
+
ogTitle:
|
|
75
|
+
resolved.value.title +
|
|
76
|
+
' | ' +
|
|
77
|
+
(contributor.value.isEditor ? phrase.editor : phrase.contributor) +
|
|
78
|
+
' - ' +
|
|
79
|
+
(eruditConfig.seo?.title || eruditConfig.site?.title),
|
|
80
|
+
description: phrase.contributor_description(resolved.value.title),
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<MainBreadcrumb
|
|
86
|
+
:items="[
|
|
87
|
+
{
|
|
88
|
+
title: phrase.contributors,
|
|
89
|
+
icon: 'users',
|
|
90
|
+
link: '/contributors',
|
|
91
|
+
},
|
|
92
|
+
]"
|
|
93
|
+
/>
|
|
94
|
+
<header
|
|
95
|
+
:class="$style.header"
|
|
96
|
+
:style="{ ['--contributorColor']: contributorColor }"
|
|
97
|
+
>
|
|
98
|
+
<div style="position: relative">
|
|
99
|
+
<ContributorAvatar
|
|
100
|
+
:class="$style.avatar"
|
|
101
|
+
:contributorId="contributor.contributorId"
|
|
102
|
+
:avatar="contributor.avatar"
|
|
103
|
+
/>
|
|
104
|
+
<MyIcon
|
|
105
|
+
v-if="contributor.isEditor"
|
|
106
|
+
name="graduation"
|
|
107
|
+
:class="$style.editorIcon"
|
|
108
|
+
:title="phrase.editor"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
<h1 :class="$style.name">
|
|
112
|
+
{{ resolved.title }}
|
|
113
|
+
</h1>
|
|
114
|
+
<div v-if="contributor.slogan" :class="$style.slogan">
|
|
115
|
+
{{ contributor.slogan }}
|
|
116
|
+
</div>
|
|
117
|
+
<div v-if="contributor.links" :class="$style.links">
|
|
118
|
+
<div v-for="(link, label) of contributor.links">
|
|
119
|
+
<a
|
|
120
|
+
:href="link"
|
|
121
|
+
target="_blank"
|
|
122
|
+
rel="noopener noreferrer"
|
|
123
|
+
:class="$style.link"
|
|
124
|
+
>
|
|
125
|
+
{{ label }}
|
|
126
|
+
</a>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</header>
|
|
130
|
+
<ContentSection v-if="contributor.hasDescription">
|
|
131
|
+
<MainBitranContent
|
|
132
|
+
:location="{ type: 'contributor', path: contributor.contributorId }"
|
|
133
|
+
/>
|
|
134
|
+
</ContentSection>
|
|
135
|
+
</template>
|
|
136
|
+
|
|
137
|
+
<style lang="scss" module>
|
|
138
|
+
@use '$/def/bp';
|
|
139
|
+
|
|
140
|
+
.header {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: var(--gap);
|
|
145
|
+
padding-top: 20px;
|
|
146
|
+
padding-left: var(--gapBig);
|
|
147
|
+
padding-right: var(--gapBig);
|
|
148
|
+
|
|
149
|
+
.avatar {
|
|
150
|
+
--_avatarSize: 110px;
|
|
151
|
+
border: 2px solid var(--bgMain);
|
|
152
|
+
outline: 2px solid var(--contributorColor);
|
|
153
|
+
box-shadow: 0 0 100px 100px
|
|
154
|
+
color-mix(in srgb, var(--contributorColor), transparent 90%);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.editorIcon {
|
|
158
|
+
position: absolute;
|
|
159
|
+
right: 50%;
|
|
160
|
+
transform: translate(50%, -50%);
|
|
161
|
+
color: color-mix(in srgb, var(--text), var(--contributorColor) 50%);
|
|
162
|
+
font-size: 16px;
|
|
163
|
+
background: var(--bgMain);
|
|
164
|
+
padding: 4px;
|
|
165
|
+
border-radius: 50%;
|
|
166
|
+
outline: 2px solid var(--contributorColor);
|
|
167
|
+
cursor: help;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.name,
|
|
171
|
+
.slogan,
|
|
172
|
+
.links {
|
|
173
|
+
text-align: center;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.name {
|
|
177
|
+
padding-top: 6px;
|
|
178
|
+
line-height: 1.2;
|
|
179
|
+
font-size: 2em;
|
|
180
|
+
color: color-mix(in srgb, var(--textDeep), var(--contributorColor) 15%);
|
|
181
|
+
text-shadow: 2px 2px
|
|
182
|
+
color-mix(in srgb, var(--contributorColor), transparent 80%);
|
|
183
|
+
|
|
184
|
+
@include bp.below('mobile') {
|
|
185
|
+
font-size: 1.8em;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.slogan {
|
|
190
|
+
font-size: 1.2em;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
padding-bottom: 8px;
|
|
193
|
+
color: var(--textMuted);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.links {
|
|
197
|
+
display: flex;
|
|
198
|
+
gap: var(--gap);
|
|
199
|
+
justify-content: center;
|
|
200
|
+
flex-wrap: wrap;
|
|
201
|
+
|
|
202
|
+
.link {
|
|
203
|
+
line-height: 2;
|
|
204
|
+
font-size: 1em;
|
|
205
|
+
font-weight: 600;
|
|
206
|
+
color: color-mix(in srgb, var(--text), var(--contributorColor) 20%);
|
|
207
|
+
border: 1.5px solid
|
|
208
|
+
color-mix(in srgb, var(--contributorColor), transparent 50%);
|
|
209
|
+
border-radius: 3px;
|
|
210
|
+
padding: 4px 10px;
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
|
|
213
|
+
@include transition(background);
|
|
214
|
+
|
|
215
|
+
&:hover {
|
|
216
|
+
background: color-mix(
|
|
217
|
+
in srgb,
|
|
218
|
+
var(--contributorColor),
|
|
219
|
+
transparent 90%
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
</style>
|