diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 98f250c8b3..ff47170f5d 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="rootEl"
v-hotkey="keymap"
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
- :tabindex="!isDeleted ? '-1' : undefined"
+ :tabindex="isDeleted ? '-1' : '0'"
>
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+
@@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
class="_button"
:style="renoted ? 'color: var(--accent) !important;' : ''"
@click.stop
- @mousedown="renoted ? undoRenote(appearNote) : boostVisibility()"
+ @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility()"
>
{{ number(appearNote.renoteCount) }}
@@ -155,10 +155,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ number(appearNote.reactionCount) }}
-
+
-
+
@@ -204,8 +204,7 @@ import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkButton from '@/components/MkButton.vue';
-import { pleaseLogin } from '@/scripts/please-login.js';
-import { focusPrev, focusNext } from '@/scripts/focus.js';
+import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import number from '@/filters/number.js';
@@ -231,7 +230,10 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { shouldCollapsed } from '@/scripts/collapsed.js';
import { useRouter } from '@/router/supplier.js';
import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
+import { host } from '@/config.js';
import { isEnabledUrlPreview } from '@/instance.js';
+import { type Keymap } from '@/scripts/hotkey.js';
+import { focusPrev, focusNext } from '@/scripts/focus.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -302,7 +304,7 @@ const quoteButton = shallowRef();
const clipButton = shallowRef();
const likeButton = shallowRef();
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-
+const galleryEl = shallowRef>();
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) : null);
@@ -328,6 +330,11 @@ const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.
const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
+const pleaseLoginContext = computed(() => ({
+ type: 'lookup',
+ url: `https://${host}/notes/${appearNote.value.id}`,
+}));
+
/* Overload FunctionにLintが対応していないのでコメントアウト
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array | undefined | null, checkOnly: true): boolean;
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
@@ -348,15 +355,53 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array reply(true),
- 'e|a|plus': () => react(true),
- '(q)': () => { if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); },
- 'up|k|shift+tab': focusBefore,
- 'down|j|tab': focusAfter,
- 'esc': blur,
- 'm|o': () => showMenu(true),
- 's': () => showContent.value !== showContent.value,
-};
+ 'r': () => {
+ if (renoteCollapsed.value) return;
+ reply();
+ },
+ 'e|a|plus': () => {
+ if (renoteCollapsed.value) return;
+ react();
+ },
+ 'q': () => {
+ if (renoteCollapsed.value) return;
+ if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost);
+ },
+ 'm': () => {
+ if (renoteCollapsed.value) return;
+ showMenu();
+ },
+ 'c': () => {
+ if (renoteCollapsed.value) return;
+ if (!defaultStore.state.showClipButtonInNoteFooter) return;
+ clip();
+ },
+ 'o': () => {
+ if (renoteCollapsed.value) return;
+ galleryEl.value?.openGallery();
+ },
+ 'v|enter': () => {
+ if (renoteCollapsed.value) {
+ renoteCollapsed.value = false;
+ } else if (appearNote.value.cw != null) {
+ showContent.value = !showContent.value;
+ } else if (isLong) {
+ collapsed.value = !collapsed.value;
+ }
+ },
+ 'esc': {
+ allowRepeat: true,
+ callback: () => blur(),
+ },
+ 'up|k|shift+tab': {
+ allowRepeat: true,
+ callback: () => focusBefore(),
+ },
+ 'down|j|tab': {
+ allowRepeat: true,
+ callback: () => focusAfter(),
+ },
+} as const satisfies Keymap;
provide('react', (reaction: string) => {
misskeyApi('notes/reactions/create', {
@@ -389,12 +434,14 @@ if (!props.mock) {
if (users.length < 1) return;
- os.popup(MkUsersTooltip, {
+ const { dispose } = os.popup(MkUsersTooltip, {
showing,
users,
count: appearNote.value.renoteCount,
targetElement: renoteButton.value,
- }, {}, 'closed');
+ }, {
+ closed: () => dispose(),
+ });
});
useTooltip(quoteButton, async (showing) => {
@@ -438,13 +485,15 @@ if (!props.mock) {
if (users.length < 1) return;
- os.popup(MkReactionsViewerDetails, {
+ const { dispose } = os.popup(MkReactionsViewerDetails, {
showing,
reaction: '❤️',
users,
count: appearNote.value.reactionCount,
targetElement: reactButton.value!,
- }, {}, 'closed');
+ }, {
+ closed: () => dispose(),
+ });
});
}
}
@@ -460,7 +509,7 @@ function boostVisibility() {
}
function renote(visibility: Visibility, localOnly: boolean = false) {
- pleaseLogin();
+ pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();
renoting = true;
@@ -506,7 +555,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) {
}
function quote() {
- pleaseLogin();
+ pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();
if (props.mock) {
return;
@@ -560,22 +609,21 @@ function quote() {
}
}
-function reply(viaKeyboard = false): void {
- pleaseLogin();
+function reply(): void {
+ pleaseLogin(undefined, pleaseLoginContext.value);
if (props.mock) {
return;
}
os.post({
reply: appearNote.value,
channel: appearNote.value.channel,
- animation: !viaKeyboard,
}).then(() => {
focus();
});
}
function like(): void {
- pleaseLogin();
+ pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();
sound.playMisskeySfx('reaction');
if (props.mock) {
@@ -595,7 +643,7 @@ function like(): void {
}
function react(viaKeyboard = false): void {
- pleaseLogin();
+ pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();
if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.playMisskeySfx('reaction');
@@ -613,7 +661,9 @@ function react(viaKeyboard = false): void {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
- os.popup(MkRippleEffect, { x, y }, {}, 'end');
+ const { dispose } = os.popup(MkRippleEffect, { x, y }, {
+ end: () => dispose(),
+ });
}
} else {
blur();
@@ -706,15 +756,13 @@ function onContextmenu(ev: MouseEvent): void {
}
}
-function showMenu(viaKeyboard = false): void {
+function showMenu(): void {
if (props.mock) {
return;
}
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
- os.popupMenu(menu, menuButton.value, {
- viaKeyboard,
- }).then(focus).finally(cleanup);
+ os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
}
async function menuVersions(viaKeyboard = false): Promise {
@@ -724,7 +772,7 @@ async function menuVersions(viaKeyboard = false): Promise {
}).then(focus).finally(cleanup);
}
-async function clip() {
+async function clip(): Promise {
if (props.mock) {
return;
}
@@ -732,7 +780,7 @@ async function clip() {
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
}
-function showRenoteMenu(viaKeyboard = false): void {
+function showRenoteMenu(): void {
if (props.mock) {
return;
}
@@ -752,23 +800,19 @@ function showRenoteMenu(viaKeyboard = false): void {
}
if (isMyRenote) {
- pleaseLogin();
+ pleaseLogin(undefined, pleaseLoginContext.value);
os.popupMenu([
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
{ type: 'divider' },
getUnrenote(),
- ], renoteTime.value, {
- viaKeyboard: viaKeyboard,
- });
+ ], renoteTime.value);
} else {
os.popupMenu([
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
{ type: 'divider' },
getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined,
- ], renoteTime.value, {
- viaKeyboard: viaKeyboard,
- });
+ ], renoteTime.value);
}
}
@@ -793,11 +837,11 @@ function blur() {
}
function focusBefore() {
- focusPrev(rootEl.value ?? null);
+ focusPrev(rootEl.value);
}
function focusAfter() {
- focusNext(rootEl.value ?? null);
+ focusNext(rootEl.value);
}
function readPromo() {
@@ -835,7 +879,7 @@ function emitUpdReaction(emoji: string, delta: number) {
&:focus-visible {
outline: none;
- &:after {
+ &::after {
content: "";
pointer-events: none;
display: block;
@@ -848,7 +892,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin: auto;
width: calc(100% - 8px);
height: calc(100% - 8px);
- border: solid 1px var(--focus);
+ border: solid 2px var(--focus);
border-radius: var(--radius);
box-sizing: border-box;
}