Merge branch 'develop' into upstream/2025.5.0
This commit is contained in:
commit
886160bdec
52 changed files with 1519 additions and 630 deletions
|
|
@ -72,20 +72,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-show="mergedCW == 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="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
:isBlock="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<div>
|
||||
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
class="_selectable"
|
||||
/>
|
||||
</div>
|
||||
<SkNoteTranslation :note="note" :translation="translation" :translating="translating"></SkNoteTranslation>
|
||||
<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="!prefer.s.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>
|
||||
|
|
@ -305,7 +306,7 @@ const galleryEl = useTemplateRef('galleryEl');
|
|||
const isMyRenote = $i && ($i.id === note.value.userId);
|
||||
const showContent = ref(prefer.s.uncollapseCW);
|
||||
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
||||
const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
|
||||
const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
|
||||
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
||||
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
||||
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
||||
|
|
|
|||
|
|
@ -89,21 +89,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</p>
|
||||
<div v-show="mergedCW == 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="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
:isBlock="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<div>
|
||||
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
class="_selectable"
|
||||
/>
|
||||
</div>
|
||||
<SkNoteTranslation :note="note" :translation="translation" :translating="translating"></SkNoteTranslation>
|
||||
<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="!prefer.s.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>
|
||||
|
|
@ -414,6 +414,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
|||
const tab = ref(props.initialTab);
|
||||
const reactionTabType = ref<string | null>(null);
|
||||
|
||||
// Auto-select the first page of reactions
|
||||
watch(appearNote, n => {
|
||||
reactionTabType.value ??= Object.keys(n.reactions)[0] ?? null;
|
||||
}, { immediate: true });
|
||||
|
||||
const renotesPagination = computed<Paging>(() => ({
|
||||
endpoint: 'notes/renotes',
|
||||
limit: 10,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ import { prefer } from '@/preferences.js';
|
|||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||
import { instance, policies } from '@/instance';
|
||||
import { getAppearNote } from '@/utility/get-appear-note';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
|
@ -128,7 +129,9 @@ const props = withDefaults(defineProps<{
|
|||
onDeleteCallback: undefined,
|
||||
});
|
||||
|
||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
||||
const appearNote = computed(() => getAppearNote(props.note));
|
||||
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||
|
||||
const el = shallowRef<HTMLElement>();
|
||||
const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
|
||||
|
|
@ -144,19 +147,11 @@ const likeButton = shallowRef<HTMLElement>();
|
|||
|
||||
const renoteTooltip = computeRenoteTooltip(renoted);
|
||||
|
||||
const appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
||||
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
||||
const replies = ref<Misskey.entities.Note[]>([]);
|
||||
|
||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||
|
||||
const isRenote = (
|
||||
props.note.renote != null &&
|
||||
props.note.text == null &&
|
||||
props.note.fileIds && props.note.fileIds.length === 0 &&
|
||||
props.note.poll == null
|
||||
);
|
||||
|
||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||
type: 'lookup',
|
||||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||
|
|
@ -206,8 +201,8 @@ async function reply(viaKeyboard = false): Promise<void> {
|
|||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||
showMovedDialog();
|
||||
await os.post({
|
||||
reply: props.note,
|
||||
channel: props.note.channel ?? undefined,
|
||||
reply: appearNote.value,
|
||||
channel: appearNote.value.channel ?? undefined,
|
||||
animation: !viaKeyboard,
|
||||
});
|
||||
focus();
|
||||
|
|
@ -217,9 +212,9 @@ function react(): void {
|
|||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||
showMovedDialog();
|
||||
sound.playMisskeySfx('reaction');
|
||||
if (props.note.reactionAcceptance === 'likeOnly') {
|
||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||
misskeyApi('notes/like', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
override: defaultLike.value,
|
||||
});
|
||||
const el = reactButton.value as HTMLElement | null | undefined;
|
||||
|
|
@ -233,12 +228,12 @@ function react(): void {
|
|||
}
|
||||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
|
||||
reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
|
||||
misskeyApi('notes/reactions/create', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
reaction: reaction,
|
||||
});
|
||||
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
||||
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||
claimAchievement('reactWithoutRead');
|
||||
}
|
||||
}, () => {
|
||||
|
|
@ -252,7 +247,7 @@ function like(): void {
|
|||
showMovedDialog();
|
||||
sound.playMisskeySfx('reaction');
|
||||
misskeyApi('notes/like', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
override: defaultLike.value,
|
||||
});
|
||||
const el = likeButton.value as HTMLElement | null | undefined;
|
||||
|
|
@ -361,7 +356,7 @@ function quote() {
|
|||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
limit: 1,
|
||||
quote: true,
|
||||
|
|
@ -383,12 +378,12 @@ function quote() {
|
|||
}
|
||||
|
||||
function menu(): void {
|
||||
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
||||
const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
|
||||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
async function translate() {
|
||||
|
|
@ -397,7 +392,7 @@ async function translate() {
|
|||
|
||||
if (props.detail) {
|
||||
misskeyApi('notes/children', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
limit: prefer.s.numberOfReplies,
|
||||
showQuotes: false,
|
||||
}).then(res => {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="{ [$style.clickToOpen]: prefer.s.clickToOpen }" @click.stop="prefer.s.clickToOpen ? noteclick(note.id) : undefined">
|
||||
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span>
|
||||
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" @click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm v-if="note.text" :text="note.text" :isBlock="true" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
|
||||
<div>
|
||||
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" @click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
|
||||
<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
|
||||
</div>
|
||||
<MkButton v-if="!allowAnim && animated && !hideFiles" :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="!prefer.s.animatedMfm && allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
|
||||
<SkNoteTranslation :note="note" :translation="translation" :translating="translating"></SkNoteTranslation>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div>{{ email }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div :class="$style.label">Reason</div>
|
||||
<div :class="$style.label">{{ i18n.ts.signupReason }}</div>
|
||||
<div>{{ reason }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ const galleryEl = useTemplateRef('galleryEl');
|
|||
const isMyRenote = $i && ($i.id === note.value.userId);
|
||||
const showContent = ref(prefer.s.uncollapseCW);
|
||||
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
||||
const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
|
||||
const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
|
||||
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
||||
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
||||
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:isBlock="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<SkNoteTranslation :note="note" :translation="translation" :translating="translating"></SkNoteTranslation>
|
||||
<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="!prefer.s.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>
|
||||
|
|
@ -420,6 +419,11 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
|||
const tab = ref(props.initialTab);
|
||||
const reactionTabType = ref<string | null>(null);
|
||||
|
||||
// Auto-select the first page of reactions
|
||||
watch(appearNote, n => {
|
||||
reactionTabType.value ??= Object.keys(n.reactions)[0] ?? null;
|
||||
}, { immediate: true });
|
||||
|
||||
const renotesPagination = computed<Paging>(() => ({
|
||||
endpoint: 'notes/renotes',
|
||||
limit: 10,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ import { prefer } from '@/preferences.js';
|
|||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||
import { instance, policies } from '@/instance';
|
||||
import { getAppearNote } from '@/utility/get-appear-note';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
|
@ -141,7 +142,9 @@ const props = withDefaults(defineProps<{
|
|||
onDeleteCallback: undefined,
|
||||
});
|
||||
|
||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
||||
const appearNote = computed(() => getAppearNote(props.note));
|
||||
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||
const hideLine = computed(() => props.detail);
|
||||
|
||||
const el = shallowRef<HTMLElement>();
|
||||
|
|
@ -158,19 +161,11 @@ const likeButton = shallowRef<HTMLElement>();
|
|||
|
||||
const renoteTooltip = computeRenoteTooltip(renoted);
|
||||
|
||||
let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
||||
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
||||
const replies = ref<Misskey.entities.Note[]>([]);
|
||||
|
||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||
|
||||
const isRenote = (
|
||||
props.note.renote != null &&
|
||||
props.note.text == null &&
|
||||
props.note.fileIds && props.note.fileIds.length === 0 &&
|
||||
props.note.poll == null
|
||||
);
|
||||
|
||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||
type: 'lookup',
|
||||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||
|
|
@ -220,8 +215,8 @@ async function reply(viaKeyboard = false): Promise<void> {
|
|||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||
showMovedDialog();
|
||||
await os.post({
|
||||
reply: props.note,
|
||||
channel: props.note.channel ?? undefined,
|
||||
reply: appearNote.value,
|
||||
channel: appearNote.value.channel ?? undefined,
|
||||
animation: !viaKeyboard,
|
||||
});
|
||||
focus();
|
||||
|
|
@ -231,9 +226,9 @@ function react(): void {
|
|||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||
showMovedDialog();
|
||||
sound.playMisskeySfx('reaction');
|
||||
if (props.note.reactionAcceptance === 'likeOnly') {
|
||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||
misskeyApi('notes/like', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
override: defaultLike.value,
|
||||
});
|
||||
const el = reactButton.value as HTMLElement | null | undefined;
|
||||
|
|
@ -247,12 +242,12 @@ function react(): void {
|
|||
}
|
||||
} else {
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
|
||||
reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
|
||||
misskeyApi('notes/reactions/create', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
reaction: reaction,
|
||||
});
|
||||
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
||||
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||
claimAchievement('reactWithoutRead');
|
||||
}
|
||||
}, () => {
|
||||
|
|
@ -266,7 +261,7 @@ function like(): void {
|
|||
showMovedDialog();
|
||||
sound.playMisskeySfx('reaction');
|
||||
misskeyApi('notes/like', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
override: defaultLike.value,
|
||||
});
|
||||
const el = likeButton.value as HTMLElement | null | undefined;
|
||||
|
|
@ -375,7 +370,7 @@ function quote() {
|
|||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
limit: 1,
|
||||
quote: true,
|
||||
|
|
@ -397,12 +392,12 @@ function quote() {
|
|||
}
|
||||
|
||||
function menu(): void {
|
||||
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
||||
const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
|
||||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
async function translate() {
|
||||
|
|
@ -411,7 +406,7 @@ async function translate() {
|
|||
|
||||
if (props.detail) {
|
||||
misskeyApi('notes/children', {
|
||||
noteId: props.note.id,
|
||||
noteId: appearNote.value.id,
|
||||
limit: prefer.s.numberOfReplies,
|
||||
showQuotes: false,
|
||||
}).then(res => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue