merge upstream

This commit is contained in:
Hazelnoot 2025-03-25 16:14:53 -04:00
commit d8908ef2d8
1065 changed files with 32953 additions and 20092 deletions

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-show="!isDeleted"
ref="rootEl"
v-hotkey="keymap"
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]"
:class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]"
:tabindex="isDeleted ? '-1' : '0'"
>
<div v-if="appearNote.reply && inReplyToCollapsed" :class="$style.collapsedInReplyTo">
@ -86,12 +86,13 @@ SPDX-License-Identifier: AGPL-3.0-only
:enableEmojiMenuReaction="true"
:isAnim="allowAnim"
:isBlock="true"
class="_selectable"
/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
<Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
<Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
</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>
@ -159,9 +160,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ph-smiley ph-bold ph-lg"></i>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
<i class="ti ti-paperclip"></i>
</button>
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
@ -206,14 +207,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
import { computed, inject, onMounted, ref, useTemplateRef, watch, provide } from 'vue';
import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
import { shouldCollapsed } from '@@/js/collapsed.js';
import { host } from '@@/js/config.js';
import type { Ref } from 'vue';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import type { MenuItem } from '@/types/menu.js';
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import type { Keymap } from '@/utility/hotkey.js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
@ -226,35 +230,36 @@ import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
import MkButton from '@/components/MkButton.vue';
import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { pleaseLogin } from '@/utility/please-login.js';
import { checkWordMute } from '@/utility/check-word-mute.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import number from '@/filters/number.js';
import * as os from '@/os.js';
import * as sound from '@/scripts/sound.js';
import { misskeyApi, misskeyApiGet } 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';
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
import { $i } from '@/account.js';
import * as sound from '@/utility/sound.js';
import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import { reactionPicker } from '@/utility/reaction-picker.js';
import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js';
import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
import { useNoteCapture } from '@/scripts/use-note-capture.js';
import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { getNoteSummary } from '@/scripts/get-note-summary.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
import { useNoteCapture } from '@/use/use-note-capture.js';
import { deepClone } from '@/utility/clone.js';
import { useTooltip } from '@/use/use-tooltip.js';
import { claimAchievement } from '@/utility/achievements.js';
import { getNoteSummary } from '@/utility/get-note-summary.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
import { useRouter } from '@/router/supplier.js';
import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/scripts/boost-quote.js';
import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js';
import { isEnabledUrlPreview } from '@/instance.js';
import { type Keymap } from '@/scripts/hotkey.js';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
import { focusPrev, focusNext } from '@/utility/focus.js';
import { getAppearNote } from '@/utility/get-appear-note.js';
import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@ -265,7 +270,7 @@ const props = withDefaults(defineProps<{
mock: false,
});
provide('mock', props.mock);
provide(DI.mock, props.mock);
const emit = defineEmits<{
(ev: 'reaction', emoji: string): void;
@ -289,6 +294,7 @@ function noteclick(id: string) {
}
// plugin
const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result: Misskey.entities.Note | null = deepClone(note.value);
@ -309,17 +315,17 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const menuVersionsButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const quoteButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const likeButton = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
const renoteButton = useTemplateRef('renoteButton');
const renoteTime = useTemplateRef('renoteTime');
const reactButton = useTemplateRef('reactButton');
const clipButton = useTemplateRef('clipButton');
const menuVersionsButton = useTemplateRef('menuVersionsButton');
const quoteButton = useTemplateRef('quoteButton');
const likeButton = useTemplateRef('likeButton');
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const galleryEl = useTemplateRef('galleryEl');
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);
@ -330,13 +336,13 @@ 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, true));
const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
const showSoftWordMutedWord = computed(() => prefer.s.showSoftWordMutedWord);
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 showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.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 && (
prefer.s.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)
),
@ -401,7 +407,7 @@ const keymap = {
},
'c': () => {
if (renoteCollapsed.value) return;
if (!defaultStore.state.showClipButtonInNoteFooter) return;
if (!prefer.s.showClipButtonInNoteFooter) return;
clip();
},
'o': () => {
@ -699,7 +705,7 @@ function react(viaKeyboard = false): void {
override: defaultLike.value,
});
const el = reactButton.value;
if (el) {
if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@ -709,7 +715,16 @@ function react(viaKeyboard = false): void {
}
} else {
blur();
reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
if (prefer.s.confirmOnReact) {
const confirm = await os.confirm({
type: 'question',
text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
});
if (confirm.canceled) return;
}
sound.playMisskeySfx('reaction');
if (props.mock) {
@ -781,7 +796,7 @@ function onContextmenu(ev: MouseEvent): void {
if (ev.target && isLink(ev.target as HTMLElement)) return;
if (window.getSelection()?.toString() !== '') return;
if (defaultStore.state.useReactionPickerForContextMenu) {
if (prefer.s.useReactionPickerForContextMenu) {
ev.preventDefault();
react();
} else {