Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	package.json
#	packages/frontend/src/components/MkNoteDetailed.vue
#	packages/frontend/src/components/global/MkCustomEmoji.vue
#	packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
#	packages/frontend/src/pages/settings/general.vue
#	packages/frontend/src/pages/user/home.vue
#	packages/frontend/src/scripts/nyaize.ts
This commit is contained in:
mattyatea 2023-11-05 21:25:11 +09:00
commit da40b4df17
44 changed files with 516 additions and 48 deletions

View file

@ -61,19 +61,28 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
<p v-if="appearNote.cw != null" :class="$style.cw">
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
</p>
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
<Mfm
v-if="appearNote.text"
:parsedNodes="parsed"
:text="appearNote.text"
:author="appearNote.user"
:nyaize="'account'"
:emojiUrls="appearNote.emojis"
:enableEmojiMenu="true"
:enableEmojiMenuReaction="true"
/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else>
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
</div>
@ -252,6 +261,13 @@ const keymap = {
's': () => showContent.value !== showContent.value,
};
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: reaction,
});
});
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);

View file

@ -67,19 +67,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</header>
<div :class="$style.noteContent">
<p v-if="appearNote.cw != null" :class="$style.cw">
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'" :i="$i"/>
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="appearNote"/>
</p>
<div v-show="appearNote.cw == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="appearNote.text" :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
<Mfm
v-if="appearNote.text"
:parsedNodes="parsed"
:text="appearNote.text"
:author="appearNote.user"
:nyaize="'account'"
:emojiUrls="appearNote.emojis"
:enableEmojiMenu="true"
:enableEmojiMenuReaction="true"
/>
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else>
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :i="$i" :emojiUrls="appearNote.emojis"/>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
<div v-if="appearNote.files.length > 0">
@ -178,7 +187,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import {computed, inject, onMounted, ref, shallowRef, watch} from 'vue';
import {computed, inject, onMounted, provide, ref, shallowRef, watch} from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue';
@ -303,6 +312,13 @@ const keymap = {
's': () => showContent.value !== showContent.value,
};
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: reaction,
});
});
let tab = $ref('replies');
let reactionTabType = $ref(null);

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i" :emojiUrls="note.emojis"/>
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :emojiUrls="note.emojis"/>
<MkCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent">

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
<p v-if="note.cw != null" :class="$style.cw">
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :i="$i"/>
<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'"/>
<MkCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent">

View file

@ -32,8 +32,8 @@ const SCROLL_STOP = 10;
const MAX_PULL_DISTANCE = Infinity;
const FIRE_THRESHOLD = 230;
const RELEASE_TRANSITION_DURATION = 200;
const PULL_BRAKE_BASE = 2;
const PULL_BRAKE_FACTOR = 200;
const PULL_BRAKE_BASE = 1.5;
const PULL_BRAKE_FACTOR = 170;
let isPullStart = $ref(false);
let isPullEnd = $ref(false);

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emojiUrls="note.emojis"/>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :emojiUrls="note.emojis"/>
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<details v-if="note.files.length > 0">

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._postNote.description1 }}</div>
<MkPostForm :class="$style.exampleRoot" :mock="true"/>
<MkPostForm :class="$style.exampleRoot" :mock="true" :autofocus="false"/>
<MkFormSection>
<template #label>{{ i18n.ts.visibility }}</template>
<div class="_gaps">

View file

@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPostForm
:class="$style.exampleRoot"
:mock="true"
:autofocus="false"
:initialNote="exampleNote"
@fileChangeSensitive="doSucceeded"
></MkPostForm>

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
<div :class="$style.description">
<div v-if="user.description" :class="$style.mfm">
<Mfm :text="user.description" :author="user" :i="$i"/>
<Mfm :text="user.description" :author="user"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>

View file

@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.username"><MkAcct :user="user"/></div>
</div>
<div :class="$style.description">
<Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user" :i="$i"/>
<Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user"/>
<div v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</div>
</div>
<div :class="$style.status">

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.description">
<div v-if="user.description" :class="$style.mfm">
<Mfm :text="user.description" :author="user" :i="$i"/>
<Mfm :text="user.description" :author="user"/>
</div>
<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>

View file

@ -18,10 +18,10 @@ interface Props {
const contentSymbol = Symbol();
const observer = new ResizeObserver((entries) => {
const results: {
container: HTMLSpanElement;
transform: string;
}[] = [];
const results: {
container: HTMLSpanElement;
transform: string;
}[] = [];
for (const entry of entries) {
const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement;
const props: Required<Props> = content[contentSymbol];

View file

@ -5,14 +5,27 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<span v-if="errored || isDraft">:{{ customEmojiName }}:</span>
<img v-else :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/>
<img
v-else
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
:src="url"
:alt="alt"
:title="alt"
decoding="async"
@error="errored = true"
@load="errored = false"
@click="onClick"
/>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, inject } from 'vue';
import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js';
import { defaultStore } from '@/store.js';
import { customEmojisMap } from '@/custom-emojis.js';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
name: string;
@ -21,8 +34,12 @@ const props = defineProps<{
host?: string | null;
url?: string;
useOriginalSize?: boolean;
menu?: boolean;
menuReaction?: boolean;
}>();
const react = inject<((name: string) => void) | null>('react', null);
const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', ''));
const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
const isDraft = computed(() => customEmojisMap.get(customEmojiName.value)?.draft ?? false);
@ -63,6 +80,28 @@ const url = computed(() => {
const alt = computed(() => `:${customEmojiName.value}:`);
let errored = $ref(url.value == null);
function onClick(ev: MouseEvent) {
if (props.menu) {
os.popupMenu([{
type: 'label',
text: `:${props.name}:`,
}, {
text: i18n.ts.copy,
icon: 'ti ti-copy',
action: () => {
copyToClipboard(`:${props.name}:`);
os.success();
},
}, ...(props.menuReaction && react ? [{
text: i18n.ts.doReaction,
icon: 'ti ti-plus',
action: () => {
react(`:${props.name}:`);
},
}] : [])], ev.currentTarget ?? ev.target);
}
}
</script>
<style lang="scss" module>

View file

@ -4,21 +4,28 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle"/>
<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle">{{ props.emoji }}</span>
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ props.emoji }}</span>
<span v-else>{{ emoji }}</span>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, inject } from 'vue';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
import { defaultStore } from '@/store.js';
import { getEmojiName } from '@/scripts/emojilist.js';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
emoji: string;
menu?: boolean;
menuReaction?: boolean;
}>();
const react = inject<((name: string) => void) | null>('react', null);
const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native');
@ -31,6 +38,28 @@ function computeTitle(event: PointerEvent): void {
const title = getEmojiName(props.emoji as string) ?? props.emoji as string;
(event.target as HTMLElement).title = title;
}
function onClick(ev: MouseEvent) {
if (props.menu) {
os.popupMenu([{
type: 'label',
text: props.emoji,
}, {
text: i18n.ts.copy,
icon: 'ti ti-copy',
action: () => {
copyToClipboard(props.emoji);
os.success();
},
}, ...(props.menuReaction && react ? [{
text: i18n.ts.doReaction,
icon: 'ti ti-plus',
action: () => {
react(props.emoji);
},
}] : [])], ev.currentTarget ?? ev.target);
}
}
</script>
<style lang="scss" module>

View file

@ -100,6 +100,8 @@ type MfmProps = {
nyaize: boolean | 'account';
uhoize: boolean | 'account';
parsedNodes?: mfm.MfmNode[] | null;
enableEmojiMenu?: boolean;
enableEmojiMenuReaction?: boolean;
};
// eslint-disable-next-line import/no-default-export
@ -528,6 +530,8 @@ export default function(props: MfmProps) {
normal: props.plain,
host: null,
useOriginalSize: scale >= 2.5,
menu: props.enableEmojiMenu,
menuReaction: props.enableEmojiMenuReaction,
})];
} else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@ -551,6 +555,8 @@ export default function(props: MfmProps) {
return [h(MkEmoji, {
key: Math.random(),
emoji: token.props.emoji,
menu: props.enableEmojiMenu,
menuReaction: props.enableEmojiMenuReaction,
})];
}

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<Mfm :text="block.text" :isNote="false" :i="$i"/>
<Mfm :text="block.text" :isNote="false"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
</div>
</template>

View file

@ -48,6 +48,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="announcement.forExistingUsers" :helpText="i18n.ts._announcement.forExistingUsersDescription">
{{ i18n.ts._announcement.forExistingUsers }}
</MkSwitch>
<MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription">
{{ i18n.ts._announcement.silence }}
</MkSwitch>
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
{{ i18n.ts._announcement.needConfirmationToRead }}
</MkSwitch>
@ -97,6 +100,7 @@ function add() {
icon: 'info',
display: 'normal',
forExistingUsers: false,
silence: false,
needConfirmationToRead: false,
});
}

View file

@ -132,7 +132,7 @@ const menuDef = $computed(() => [{
}, {
icon: 'ti ti-sparkles',
text: i18n.ts.avatarDecorations,
to: '/avatar-decorations',
to: '/admin/avatar-decorations',
active: currentPage?.route.name === 'avatarDecorations',
}, {
icon: 'ti ti-whirl',

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
<div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div>
<div :class="$style.header">
<span v-if="$i && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
<span style="margin-right: 0.5em;">
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/>
</div>
</div>
<div v-if="tab !== 'past' && $i && !announcement.isRead" :class="$style.footer">
<div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer">
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
</div>
</section>

View file

@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.bannerFade"></div>
</div>
<div v-if="channel.description" :class="$style.description">
<Mfm :text="channel.description" :isNote="false" :i="$i"/>
<Mfm :text="channel.description" :isNote="false"/>
</div>
</div>

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="clip" class="_gaps">
<div class="_panel">
<div v-if="clip.description" :class="$style.description">
<Mfm :text="clip.description" :isNote="false" :i="$i"/>
<Mfm :text="clip.description" :isNote="false"/>
</div>
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
<MkButton v-else v-tooltip="i18n.ts.favorite" asLike rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>

View file

@ -29,7 +29,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
<MkFolder>
<template #label>{{ i18n.ts.pinnedList }}</template>
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
@ -67,7 +66,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
<MkRadios v-model="reactionsDisplaySize">
<template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
<option value="small">{{ i18n.ts.small }}</option>
@ -174,6 +172,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m">
<div class="_gaps_s">
<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>

View file

@ -77,6 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSection>
<div class="_gaps_s">
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
<MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
<MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
</div>
@ -103,6 +104,7 @@ import FormSection from '@/components/form/section.vue';
const reportError = computed(defaultStore.makeGetterSetter('reportError'));
const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
const devMode = computed(defaultStore.makeGetterSetter('devMode'));
const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
function onChangeInjectFeaturedNote(v) {
os.api('i/update', {

View file

@ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
@click="openDecoration(avatarDecoration)"
>
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="2 / 3">{{ avatarDecoration.name }}</MkCondensedLine></div>
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decoration="{ url: avatarDecoration.url }" forceShowDecoration/>
<i v-if="avatarDecoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => avatarDecoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.avatarDecorationLock" class="ti ti-lock"></i>
</div>

View file

@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div class="description">
<MkOmit>
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" :i="$i"/>
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user"/>
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
</MkOmit>
</div>
@ -112,7 +112,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<Mfm :text="field.name" :plain="true" :colored="false"/>
</dt>
<dd class="value">
<Mfm :text="field.value" :author="user" :i="$i" :colored="false"/>
<Mfm :text="field.value" :author="user" :colored="false"/>
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink"
class="ti ti-circle-check" :class="$style.verifiedLink"></i>
</dd>

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_panel" :class="$style.content">
<div>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
</div>
<div v-if="note.files.length > 0" :class="$style.richcontent">