Merge upstream
This commit is contained in:
commit
ad42eccfa4
24 changed files with 529 additions and 20 deletions
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@click="(ev: MouseEvent) => warningExternalWebsite(ev, url)"
|
||||
>
|
||||
<slot></slot>
|
||||
<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i>
|
||||
<i v-if="target === '_blank' && !hideIcon" class="ti ti-external-link" :class="$style.icon"></i>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
|
@ -34,7 +34,9 @@ const props = withDefaults(defineProps<{
|
|||
url: string;
|
||||
rel?: null | string;
|
||||
navigationBehavior?: MkABehavior;
|
||||
hideIcon?: boolean;
|
||||
}>(), {
|
||||
hideIcon: false,
|
||||
});
|
||||
|
||||
const self = props.url.startsWith(local);
|
||||
|
|
|
@ -109,6 +109,8 @@ export const ROLE_POLICIES = [
|
|||
'rateLimitFactor',
|
||||
'avatarDecorationLimit',
|
||||
'canUseAccountRemoval',
|
||||
'mutualLinkSectionLimit',
|
||||
'mutualLinkLimit',
|
||||
] as const;
|
||||
|
||||
// なんか動かない
|
||||
|
|
|
@ -66,6 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton v-if="user?.host == null" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
||||
<MkButton inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||
<MkButton inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||
<MkButton inline danger @click="unsetUserMutualLink"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserMutualLink }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
|
@ -292,7 +293,7 @@ function createFetcher() {
|
|||
|
||||
watch(moderationNote, async () => {
|
||||
await misskeyApi('admin/update-user-note', {
|
||||
userId: user.value.id, text: moderationNote.value
|
||||
userId: user.value.id, text: moderationNote.value,
|
||||
}).then(refreshUser);
|
||||
});
|
||||
});
|
||||
|
@ -304,7 +305,7 @@ function refreshUser() {
|
|||
|
||||
async function updateRemoteUser() {
|
||||
await os.apiWithDialog('federation/update-remote-user', {
|
||||
userId: user.value.id
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
|
@ -335,7 +336,7 @@ async function toggleSuspend(v) {
|
|||
suspended.value = !v;
|
||||
} else {
|
||||
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', {
|
||||
userId: user.value.id
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +349,7 @@ async function unsetUserAvatar() {
|
|||
if (confirm.canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/unset-user-avatar', {
|
||||
userId: user.value.id
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
|
@ -360,7 +361,19 @@ async function unsetUserBanner() {
|
|||
if (confirm.canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/unset-user-banner', {
|
||||
userId: user.value.id
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
async function unsetUserMutualLink() {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.unsetUserMutualLinkConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/unset-user-mutual-banner', {
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
|
@ -378,7 +391,7 @@ async function deleteAllFiles() {
|
|||
|
||||
if (typed.result === user.value?.username) {
|
||||
await os.apiWithDialog('admin/drive/delete-all-files-of-a-user', {
|
||||
userId: user.value.id
|
||||
userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
} else {
|
||||
os.alert({
|
||||
|
@ -447,7 +460,7 @@ async function assignRole() {
|
|||
: null;
|
||||
|
||||
await os.apiWithDialog('admin/roles/assign', {
|
||||
roleId, userId: user.value.id, expiresAt
|
||||
roleId, userId: user.value.id, expiresAt,
|
||||
}).then(refreshUser);
|
||||
}
|
||||
|
||||
|
@ -458,7 +471,7 @@ async function unassignRole(role, ev) {
|
|||
danger: true,
|
||||
action: async () => {
|
||||
await os.apiWithDialog('admin/roles/unassign', {
|
||||
roleId: role.id, userId: user.value.id
|
||||
roleId: role.id, userId: user.value.id,
|
||||
}).then(refreshUser);
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
|
|
|
@ -775,6 +775,44 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkLimit, 'mutualLinkLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.mutualLinkLimit }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.mutualLinkLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.mutualLinkLimit.value }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.mutualLinkLimit)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.mutualLinkLimit.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="role.policies.mutualLinkLimit.value" :disabled="role.policies.mutualLinkLimit.useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
<MkRange v-model="role.policies.mutualLinkLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkSectionLimit, 'mutualLinkSectionLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.mutualLinkSectionLimit }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.mutualLinkSectionLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.mutualLinkSectionLimit.value }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.mutualLinkSectionLimit)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.mutualLinkSectionLimit.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="role.policies.mutualLinkSectionLimit.value" :disabled="role.policies.mutualLinkSectionLimit.useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
<MkRange v-model="role.policies.mutualLinkSectionLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
||||
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
||||
<template #suffix>
|
||||
|
|
|
@ -278,6 +278,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkSectionLimit, 'mutualLinkSectionLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.mutualLinkSectionLimit }}</template>
|
||||
<template #suffix>{{ policies.mutualLinkSectionLimit }}</template>
|
||||
<MkInput v-model="policies.mutualLinkSectionLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.mutualLinkLimit, 'mutualLinkLimit'])">
|
||||
<template #label>{{ i18n.ts._role._options.mutualLinkLimit }}</template>
|
||||
<template #suffix>{{ policies.mutualLinkLimit }}</template>
|
||||
<MkInput v-model="policies.mutualLinkLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canHideAds, 'canHideAds'])">
|
||||
<template #label>{{ i18n.ts._role._options.canHideAds }}</template>
|
||||
<template #suffix>{{ policies.canHideAds ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
|
|
|
@ -87,6 +87,79 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkFolder>
|
||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||
</FormSlot>
|
||||
<FormSlot>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-link"></i></template>
|
||||
<template #label>{{ i18n.ts._profile.mutualLinksEdit }}</template>
|
||||
|
||||
<div :class="$style.metadataRoot">
|
||||
<div :class="$style.metadataMargin">
|
||||
<MkButton inline style="margin-right: 8px;" :disabled="mutualLinkSections.length >= $i.policies.mutualLinkSectionLimit" @click="addMutualLinkSections"><i class="ti ti-plus"></i> {{ i18n.ts._profile.addMutualLinkSection }}</MkButton>
|
||||
<MkButton v-if="!mutualLinkSectionEditMode" inline danger style="margin-right: 8px;" @click="mutualLinkSectionEditMode = !mutualLinkSectionEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
<MkButton v-else inline style="margin-right: 8px;" @click="mutualLinkSectionEditMode = !mutualLinkSectionEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
|
||||
<MkButton inline primary @click="saveMutualLinks"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
|
||||
<Sortable
|
||||
v-model="mutualLinkSections"
|
||||
class="_gaps_s"
|
||||
itemKey="id"
|
||||
:animation="150"
|
||||
:handle="'.' + $style.dragItemHandle"
|
||||
@start="e => e.item.classList.add('active')"
|
||||
@end="e => e.item.classList.remove('active')"
|
||||
>
|
||||
<template #item="{element: sectionElement,index: sectionIndex}">
|
||||
<div :class="$style.mutualLinkSectionRoot">
|
||||
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLinkSection(sectionIndex)"><i class="ti ti-x"></i></button>
|
||||
<FormSlot :style="{flexGrow: 1}">
|
||||
<MkFolder>
|
||||
<template #label>{{ sectionElement.name || i18n.ts._profile.sectionNameNone }}</template>
|
||||
|
||||
<div :class="$style.metadataMargin">
|
||||
<MkInput v-model="sectionElement.name" :disabled="sectionElement.none" :placeholder="i18n.ts._profile.sectionName" :max="32"></MkInput>
|
||||
<MkSwitch v-model="sectionElement.none" @update:modelValue="()=>{sectionElement.name = null}">{{ i18n.ts._profile.sectionNameNoneDescription }}</MkSwitch>
|
||||
<MkButton inline style="margin-right: 8px;" :disabled="sectionElement.mutualLinks.length >= $i.policies.mutualLinkLimit" @click="addMutualLinks(sectionIndex)"><i class="ti ti-plus"></i> {{ i18n.ts._profile.addMutualLink }}</MkButton>
|
||||
</div>
|
||||
<Sortable
|
||||
v-model="sectionElement.mutualLinks"
|
||||
class="_gaps_s"
|
||||
itemKey="id"
|
||||
:animation="150"
|
||||
:handle="'.' + $style.dragItemHandle"
|
||||
@start="e => e.item.classList.add('active')"
|
||||
@end="e => e.item.classList.remove('active')"
|
||||
>
|
||||
<template #item="{element: linkElement,index: linkIndex}">
|
||||
<div :class="$style.mutualLinkRoot">
|
||||
<button v-if="!mutualLinkSectionEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
|
||||
<button v-if="mutualLinkSectionEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteMutualLink(sectionIndex,linkIndex)"><i class="ti ti-x"></i></button>
|
||||
|
||||
<div class="_gaps_s" :style="{flex: 1}">
|
||||
<MkInput v-model="linkElement.url" small>
|
||||
<template #label>{{ i18n.ts._profile.mutualLinksUrl }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="linkElement.description" small>
|
||||
<template #label>{{ i18n.ts._profile.mutualLinksDescriptionEdit }}</template>
|
||||
</MkInput>
|
||||
<span>{{ i18n.ts._profile.mutualLinksBanner }}</span>
|
||||
<img :class="$style.mutualLinkImg" :src="linkElement.imgSrc">
|
||||
<MkButton class="_button" @click="ev => changeMutualLinkFile(ev, sectionIndex, linkIndex)">{{ i18n.ts.selectFile }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Sortable>
|
||||
</MkFolder>
|
||||
</FormSlot>
|
||||
</div>
|
||||
</template>
|
||||
</Sortable>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<template #caption>{{ i18n.ts._profile.mutualLinksDescription }}</template>
|
||||
</FormSlot>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||
|
@ -109,7 +182,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch, defineAsyncComponent } from 'vue';
|
||||
import { computed, reactive, ref, watch, defineAsyncComponent, Ref } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
@ -128,11 +201,11 @@ import { defaultStore } from '@/store.js';
|
|||
import { globalEvents } from '@/events.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import * as Misskey from "misskey-js";
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
||||
|
||||
const profile = reactive({
|
||||
|
@ -151,8 +224,10 @@ watch(() => profile, () => {
|
|||
deep: true,
|
||||
});
|
||||
|
||||
const mutualLinkSections = ref($i.mutualLinkSections ?? []) as Ref<Misskey.entities.UserDetailed['mutualLinkSections']>;
|
||||
const fields = ref($i.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
|
||||
const fieldEditMode = ref(false);
|
||||
const mutualLinkSectionEditMode = ref(false);
|
||||
|
||||
function addField() {
|
||||
fields.value.push({
|
||||
|
@ -162,6 +237,22 @@ function addField() {
|
|||
});
|
||||
}
|
||||
|
||||
function addMutualLinks(index:number) {
|
||||
mutualLinkSections.value[index].mutualLinks.push({
|
||||
fileId: '',
|
||||
url: '',
|
||||
imgSrc: '',
|
||||
description: '',
|
||||
});
|
||||
}
|
||||
|
||||
function addMutualLinkSections() {
|
||||
mutualLinkSections.value.push({
|
||||
name: null,
|
||||
mutualLinks: [],
|
||||
});
|
||||
}
|
||||
|
||||
while (fields.value.length < 4) {
|
||||
addField();
|
||||
}
|
||||
|
@ -170,6 +261,14 @@ function deleteField(index: number) {
|
|||
fields.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function deleteMutualLinkSection(index: number) {
|
||||
mutualLinkSections.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function deleteMutualLink(sectionIndex:number, index: number) {
|
||||
mutualLinkSections.value[sectionIndex].mutualLinks.splice(index, 1);
|
||||
}
|
||||
|
||||
function saveFields() {
|
||||
os.apiWithDialog('i/update', {
|
||||
fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
|
||||
|
@ -177,6 +276,12 @@ function saveFields() {
|
|||
globalEvents.emit('requestClearPageCache');
|
||||
}
|
||||
|
||||
function saveMutualLinks() {
|
||||
os.apiWithDialog('i/update', {
|
||||
mutualLinkSections: mutualLinkSections.value,
|
||||
});
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('i/update', {
|
||||
// 空文字列をnullにしたいので??は使うな
|
||||
|
@ -203,6 +308,13 @@ function save() {
|
|||
}
|
||||
}
|
||||
|
||||
function changeMutualLinkFile(ev: MouseEvent, sectionIndex: number, linkIndex: number) {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.mutualLink).then(async (file) => {
|
||||
mutualLinkSections.value[sectionIndex].mutualLinks[linkIndex].imgSrc = file.url;
|
||||
mutualLinkSections.value[sectionIndex].mutualLinks[linkIndex].fileId = file.id;
|
||||
});
|
||||
}
|
||||
|
||||
function changeAvatar(ev) {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
|
||||
let originalOrCropped = file;
|
||||
|
@ -299,6 +411,36 @@ definePageMetadata(() => ({
|
|||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.mutualLinkRoot{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
padding-bottom: .75em;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
flex: 1;
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
.mutualLinkSectionRoot{
|
||||
display: flex;
|
||||
padding-bottom: .75em;
|
||||
align-items: center;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
overflow: clip;
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
|
||||
@container (max-width: 452px) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.metadataMargin {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
@ -350,4 +492,11 @@ definePageMetadata(() => ({
|
|||
.dragItemForm {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mutualLinkImg {
|
||||
max-width: 150px;
|
||||
max-height: 30px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -80,6 +80,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||
</MkOmit>
|
||||
</div>
|
||||
<MkContainer v-if="user?.mutualLinkSections?.length > 0" :showHeader="false" :max-height="200" class="fields" :style="{borderRadius: 0}">
|
||||
<div v-for="(section, index) in user?.mutualLinkSections" :key="index" :class="$style.mutualLinkSections">
|
||||
<span v-if="section.name">{{ section.name }}</span>
|
||||
<div :class="$style.mutualLinks">
|
||||
<div v-for="(mutualLink, i) in section.mutualLinks" :key="i">
|
||||
<MkLink :hideIcon="true" :url="mutualLink.url">
|
||||
<img :class="$style.mutualLinkImg" :src="mutualLink.imgSrc" :alt="mutualLink.description"/>
|
||||
</MkLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkContainer>
|
||||
<div class="fields system">
|
||||
<dl v-if="user.location" class="field">
|
||||
<dt class="name"><i class="ti ti-map-pin ti-fw"></i> {{ i18n.ts.location }}</dt>
|
||||
|
@ -143,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<MkA :to="userPage(user)">
|
||||
<b>{{ number(user.notesCount) }}</b>
|
||||
|
@ -213,6 +226,8 @@ import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
|||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkContainer from '@/components/MkContainer.vue';
|
||||
|
||||
function calcAge(birthdate: string): number {
|
||||
const date = new Date(birthdate);
|
||||
|
@ -792,4 +807,37 @@ onUnmounted(() => {
|
|||
color: rgb(255, 255, 255);
|
||||
background-color: rgb(54, 54, 54);
|
||||
}
|
||||
|
||||
.mutualLinkSections {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
background: var(--panel);
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
}
|
||||
|
||||
.mutualLinks {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding-top: 8px;
|
||||
@media (max-width: 500px) {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.mutualLink {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mutualLinkImg {
|
||||
max-width: 150px;
|
||||
max-height: 30px;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue