summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkNote.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components/MkNote.vue')
-rw-r--r--packages/frontend/src/components/MkNote.vue237
1 files changed, 108 insertions, 129 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 8a3b4cef48..9a667c3118 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -1,13 +1,13 @@
<!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div
- v-if="!hardMuted && !muted"
+ v-if="!hardMuted && muted === false"
v-show="!isDeleted"
- ref="el"
+ ref="rootEl"
v-hotkey="keymap"
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
:tabindex="!isDeleted ? '-1' : undefined"
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</span>
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
- <span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+ <span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
</div>
</div>
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
- <div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
+ <div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
@@ -74,18 +74,18 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
- <div v-else>
- <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+ <div v-else-if="translation">
+ <b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
</div>
</div>
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
</div>
- <div v-if="appearNote.files.length > 0">
+ <div v-if="appearNote.files && appearNote.files.length > 0">
<MkMediaList :mediaList="appearNote.files" @click.stop/>
</div>
- <MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll" @click.stop/>
+ <MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click.stop @click="collapsed = false">
@@ -145,7 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
<i class="ph-paperclip ph-bold ph-lg"></i>
</button>
- <button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
+ <button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()">
<i class="ph-dots-three ph-bold ph-lg"></i>
</button>
</footer>
@@ -153,7 +153,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</article>
</div>
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
- <I18n :src="i18n.ts.userSaysSomething" tag="small">
+ <I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
+ <template #name>
+ <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
+ <MkUserName :user="appearNote.user"/>
+ </MkA>
+ </template>
+ </I18n>
+ <I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
<MkUserName :user="appearNote.user"/>
@@ -171,7 +178,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
@@ -190,6 +197,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import * as os from '@/os.js';
import * as sound from '@/scripts/sound.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore, noteViewInterruptors } from '@/store.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
@@ -207,7 +215,8 @@ import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { shouldCollapsed } from '@/scripts/collapsed.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -227,6 +236,7 @@ const emit = defineEmits<{
const router = useRouter();
+const inTimeline = inject<boolean>('inTimeline', false);
const inChannel = inject('inChannel', null);
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
@@ -245,7 +255,7 @@ if (noteViewInterruptors.length > 0) {
let result: Misskey.entities.Note | null = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
try {
- result = await interruptor.handler(result);
+ result = await interruptor.handler(result!) as Misskey.entities.Note | null;
if (result === null) {
isDeleted.value = true;
return;
@@ -254,7 +264,7 @@ if (noteViewInterruptors.length > 0) {
console.error(err);
}
}
- note.value = result;
+ note.value = result as Misskey.entities.Note;
});
}
@@ -262,11 +272,11 @@ const isRenote = (
note.value.renote != null &&
note.value.text == null &&
note.value.cw == null &&
- note.value.fileIds.length === 0 &&
+ note.value.fileIds && note.value.fileIds.length === 0 &&
note.value.poll == null
);
-const el = shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const menuVersionsButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
@@ -276,50 +286,61 @@ const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW);
-const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text).filter(u => u !== renoteUrl && u !== renoteUri) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
-const collapsed = defaultStore.state.expandLongNote && appearNote.value.cw == null ? false : ref(appearNote.value.cw == null && isLong);
+const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
const renoted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
-const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
+const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));
-const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null)));
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
+const renoteCollapsed = ref(
+ defaultStore.state.collapseRenotes && isRenote && (
+ ($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
+ (appearNote.value.myReaction != null)
+ )
+);
const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
-function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean {
+/* Overload FunctionにLintが対応していないのでコメントアウト
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
+*/
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
if (mutedWords == null) return false;
- if (checkWordMute(note, $i, mutedWords)) return true;
- if (note.reply && checkWordMute(note.reply, $i, mutedWords)) return true;
- if (note.renote && checkWordMute(note.renote, $i, mutedWords)) return true;
+ if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
+ if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
+ if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+
+ if (checkOnly) return false;
+
+ if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
return false;
}
const keymap = {
'r': () => reply(true),
'e|a|plus': () => react(true),
- 'q': () => renoteButton.value.renote(true),
+ 'q': () => renote(appearNote.value.visibility),
'up|k|shift+tab': focusBefore,
'down|j|tab': focusAfter,
'esc': blur,
- 'm|o': () => menu(true),
+ 'm|o': () => showMenu(true),
's': () => showContent.value !== showContent.value,
};
provide('react', (reaction: string) => {
- os.api('notes/reactions/create', {
+ misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@@ -331,7 +352,7 @@ if (props.mock) {
}, { deep: true });
} else {
useNoteCapture({
- rootEl: el,
+ rootEl: rootEl,
note: appearNote,
pureNote: note,
isDeletedRef: isDeleted,
@@ -340,7 +361,7 @@ if (props.mock) {
if (!props.mock) {
useTooltip(renoteButton, async (showing) => {
- const renotes = await os.api('notes/renotes', {
+ const renotes = await misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
limit: 11,
});
@@ -358,7 +379,7 @@ if (!props.mock) {
});
useTooltip(quoteButton, async (showing) => {
- const renotes = await os.api('notes/renotes', {
+ const renotes = await misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
limit: 11,
quote: true,
@@ -377,7 +398,7 @@ if (!props.mock) {
});
if ($i) {
- os.api('notes/renotes', {
+ misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
userId: $i.id,
limit: 1,
@@ -387,54 +408,15 @@ if (!props.mock) {
}
}
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
-
-// defaultStore.state.visibilityがstringなためstringも受け付けている
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
- if (a === 'specified' || b === 'specified') return 'specified';
- if (a === 'followers' || b === 'followers') return 'followers';
- if (a === 'home' || b === 'home') return 'home';
- // if (a === 'public' || b === 'public')
- return 'public';
-}
-
function boostVisibility() {
- os.popupMenu([
- {
- type: 'button',
- icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
- text: i18n.ts._visibility['public'],
- action: () => {
- renote('public');
- },
- },
- {
- type: 'button',
- icon: 'ph-house ph-bold ph-lg',
- text: i18n.ts._visibility['home'],
- action: () => {
- renote('home');
- },
- },
- {
- type: 'button',
- icon: 'ph-lock ph-bold ph-lg',
- text: i18n.ts._visibility['followers'],
- action: () => {
- renote('followers');
- },
- },
- {
- type: 'button',
- icon: 'ph-planet ph-bold ph-lg',
- text: i18n.ts._timelines.local,
- action: () => {
- renote('local');
- },
- }], renoteButton.value);
+ if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+ renote(defaultStore.state.visibilityOnBoost);
+ } else {
+ os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+ }
}
-function renote(visibility: Visibility | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
pleaseLogin();
showMovedDialog();
@@ -448,7 +430,7 @@ function renote(visibility: Visibility | 'local') {
}
if (!props.mock) {
- os.api('notes/create', {
+ misskeyApi('notes/create', {
renoteId: appearNote.value.id,
channelId: appearNote.value.channelId,
}).then(() => {
@@ -456,7 +438,7 @@ function renote(visibility: Visibility | 'local') {
renoted.value = true;
});
}
- } else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) {
+ } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
const el = renoteButton.value as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
@@ -465,18 +447,10 @@ function renote(visibility: Visibility | 'local') {
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
- const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
- const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
-
- let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility);
- if (appearNote.value.channel?.isSensitive) {
- noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home');
- }
-
if (!props.mock) {
- os.api('notes/create', {
- localOnly: visibility === 'local' ? true : localOnlySetting,
- visibility: noteVisibility,
+ misskeyApi('notes/create', {
+ localOnly: localOnly,
+ visibility: visibility,
renoteId: appearNote.value.id,
}).then(() => {
os.toast(i18n.ts.renoted);
@@ -498,9 +472,9 @@ function quote() {
renote: appearNote.value,
channel: appearNote.value.channel,
}).then(() => {
- os.api('notes/renotes', {
+ misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
- userId: $i.id,
+ userId: $i?.id,
limit: 1,
quote: true,
}).then((res) => {
@@ -520,9 +494,9 @@ function quote() {
os.post({
renote: appearNote.value,
}).then(() => {
- os.api('notes/renotes', {
+ misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
- userId: $i.id,
+ userId: $i?.id,
limit: 1,
quote: true,
}).then((res) => {
@@ -550,7 +524,7 @@ function reply(viaKeyboard = false): void {
reply: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
- }, () => {
+ }).then(() => {
focus();
});
}
@@ -558,10 +532,11 @@ function reply(viaKeyboard = false): void {
function like(): void {
pleaseLogin();
showMovedDialog();
+ sound.playMisskeySfx('reaction');
if (props.mock) {
return;
}
- os.api('notes/like', {
+ misskeyApi('notes/like', {
noteId: appearNote.value.id,
override: defaultLike.value,
});
@@ -578,17 +553,17 @@ function react(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
if (appearNote.value.reactionAcceptance === 'likeOnly') {
- sound.play('reaction');
+ sound.playMisskeySfx('reaction');
if (props.mock) {
return;
}
- os.api('notes/like', {
+ misskeyApi('notes/like', {
noteId: appearNote.value.id,
override: defaultLike.value,
});
- const el = reactButton.value as HTMLElement | null | undefined;
+ const el = reactButton.value;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
@@ -597,15 +572,15 @@ function react(viaKeyboard = false): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value, reaction => {
- sound.play('reaction');
+ reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ sound.playMisskeySfx('reaction');
if (props.mock) {
emit('reaction', reaction);
return;
}
- os.api('notes/reactions/create', {
+ misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@@ -618,8 +593,8 @@ function react(viaKeyboard = false): void {
}
}
-function undoReact(note): void {
- const oldReaction = note.myReaction;
+function undoReact(targetNote: Misskey.entities.Note): void {
+ const oldReaction = targetNote.myReaction;
if (!oldReaction) return;
if (props.mock) {
@@ -627,8 +602,8 @@ function undoReact(note): void {
return;
}
- os.api('notes/reactions/delete', {
- noteId: note.id,
+ misskeyApi('notes/reactions/delete', {
+ noteId: targetNote.id,
});
}
@@ -636,7 +611,7 @@ function undoRenote(note) : void {
if (props.mock) {
return;
}
- os.api('notes/unrenote', {
+ misskeyApi('notes/unrenote', {
noteId: note.id,
});
os.toast(i18n.ts.rmboost);
@@ -656,32 +631,34 @@ function onContextmenu(ev: MouseEvent): void {
return;
}
- const isLink = (el: HTMLElement) => {
+ const isLink = (el: HTMLElement): boolean => {
if (el.tagName === 'A') return true;
// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
if (el.tagName === 'AUDIO') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
+ return false;
};
- if (isLink(ev.target)) return;
- if (window.getSelection().toString() !== '') return;
+
+ if (ev.target && isLink(ev.target as HTMLElement)) return;
+ if (window.getSelection()?.toString() !== '') return;
if (defaultStore.state.useReactionPickerForContextMenu) {
ev.preventDefault();
react();
} else {
- const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+ const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
os.contextMenu(menu, ev).then(focus).finally(cleanup);
}
}
-function menu(viaKeyboard = false): void {
+function showMenu(viaKeyboard = false): void {
if (props.mock) {
return;
}
- const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+ const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
os.popupMenu(menu, menuButton.value, {
viaKeyboard,
}).then(focus).finally(cleanup);
@@ -713,7 +690,7 @@ function showRenoteMenu(viaKeyboard = false): void {
icon: 'ph-trash ph-bold ph-lg',
danger: true,
action: () => {
- os.api('notes/delete', {
+ misskeyApi('notes/delete', {
noteId: note.value.id,
});
isDeleted.value = true;
@@ -735,7 +712,7 @@ function showRenoteMenu(viaKeyboard = false): void {
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
{ type: 'divider' },
getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
- $i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
+ ($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined,
], renoteTime.value, {
viaKeyboard: viaKeyboard,
});
@@ -755,23 +732,23 @@ function animatedMFM() {
}
function focus() {
- el.value.focus();
+ rootEl.value?.focus();
}
function blur() {
- el.value.blur();
+ rootEl.value?.blur();
}
function focusBefore() {
- focusPrev(el.value);
+ focusPrev(rootEl.value ?? null);
}
function focusAfter() {
- focusNext(el.value);
+ focusNext(rootEl.value ?? null);
}
function readPromo() {
- os.api('promo/read', {
+ misskeyApi('promo/read', {
noteId: appearNote.value.id,
});
isDeleted.value = true;
@@ -825,12 +802,13 @@ function emitUpdReaction(emoji: string, delta: number) {
}
.footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
position: relative;
z-index: 1;
margin-top: 0.4em;
- width: max-content;
- min-width: min-content;
- max-width: fit-content;
+ max-width: 400px;
}
&:hover > .article > .main > .footer > .footerButton {
@@ -986,8 +964,8 @@ function emitUpdReaction(emoji: string, delta: number) {
flex-shrink: 0;
display: block !important;
margin: 0 14px 0 0;
- width: 58px;
- height: 58px;
+ width: var(--avatar);
+ height: var(--avatar);
position: sticky !important;
top: calc(22px + var(--stickyTop, 0px));
left: 0;
@@ -1249,5 +1227,6 @@ function emitUpdReaction(emoji: string, delta: number) {
.clickToOpen {
cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
}
</style>