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

# Conflicts:
#	locales/en-US.yml
#	locales/index.d.ts
#	locales/ja-JP.yml
#	package.json
#	packages/backend/src/server/api/endpoints/notes/create.ts
#	packages/frontend/src/components/MkDrive.file.vue
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/components/MkNoteHeader.vue
#	packages/frontend/src/components/MkPostForm.vue
#	packages/frontend/src/components/MkReactionsViewer.reaction.vue
#	packages/frontend/src/components/MkUserSetupDialog.vue
#	packages/frontend/src/pages/timeline.tutorial.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/misskey-js/etc/misskey-js.api.md
This commit is contained in:
mattyatea 2023-11-04 01:24:44 +09:00
commit cb1005811d
106 changed files with 2036 additions and 963 deletions

View file

@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<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 preview/>
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<div :class="$style.main">
<MkNoteHeader :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
</div>
<MkReactionsViewer :note="appearNote" :maxNumber="16">
<MkReactionsViewer :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
<template #more>
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
</template>
@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import {computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent} from 'vue';
import {computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent, watch, provide } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue';
@ -169,7 +169,7 @@ import {reactionPicker} from '@/scripts/reaction-picker.js';
import {extractUrlFromMfm} from '@/scripts/extract-url-from-mfm.js';
import {$i} from '@/account.js';
import {i18n} from '@/i18n.js';
import {getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu} from '@/scripts/get-note-menu.js';
import {getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu} from '@/scripts/get-note-menu.js';
import {useNoteCapture} from '@/scripts/use-note-capture.js';
import {deepClone} from '@/scripts/clone.js';
import {useTooltip} from '@/scripts/use-tooltip.js';
@ -180,9 +180,19 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
import {showMovedDialog} from '@/scripts/show-moved-dialog.js';
import {shouldCollapsed} from '@/scripts/collapsed.js';
const props = defineProps<{
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
pinned?: boolean;
mock?: boolean;
}>(), {
mock: false,
});
provide('mock', props.mock);
const emit = defineEmits<{
(ev: 'reaction', emoji: string): void;
(ev: 'removeReaction', emoji: string): void;
}>();
const inChannel = inject('inChannel', null);
@ -242,126 +252,54 @@ const keymap = {
's': () => showContent.value !== showContent.value,
};
useNoteCapture({
rootEl: el,
note: $$(appearNote),
pureNote: $$(note),
isDeletedRef: isDeleted,
});
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
noteId: appearNote.id,
limit: 11,
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);
}, { deep: true });
} else {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
pureNote: $$(note),
isDeletedRef: isDeleted,
});
}
const users = renotes.map(x => x.user);
if (!props.mock) {
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
noteId: appearNote.id,
limit: 11,
});
if (users.length < 1) return;
const users = renotes.map(x => x.user);
os.popup(MkUsersTooltip, {
showing,
users,
count: appearNote.renoteCount,
targetElement: renoteButton.value,
}, {}, 'closed');
});
if (users.length < 1) return;
type Visibility = 'public' | 'home' | 'followers' | 'specified';
// defaultStore.state.visibilitystringstring
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';
os.popup(MkUsersTooltip, {
showing,
users,
count: appearNote.renoteCount,
targetElement: renoteButton.value,
}, {}, 'closed');
});
}
function renote(viaKeyboard = false) {
pleaseLogin();
showMovedDialog();
let items = [] as MenuItem[];
if (appearNote.channel) {
items = items.concat([{
text: i18n.ts.inChannelRenote,
icon: 'ti ti-repeat',
action: () => {
const el = renoteButton.value as HTMLElement | null | undefined;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, {x, y}, {}, 'end');
}
os.api('notes/create', {
renoteId: appearNote.id,
channelId: appearNote.channelId,
}).then(() => {
os.toast(i18n.ts.renoted);
});
},
}, {
text: i18n.ts.inChannelQuote,
icon: 'ti ti-quote',
action: () => {
os.post({
renote: appearNote,
channel: appearNote.channel,
});
},
}, null]);
}
items = items.concat([{
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
const el = renoteButton.value as HTMLElement | null | undefined;
if (el) {
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 configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
let visibility = appearNote.visibility;
visibility = smallerVisibility(visibility, configuredVisibility);
if (appearNote.channel?.isSensitive) {
visibility = smallerVisibility(visibility, 'home');
}
os.api('notes/create', {
localOnly,
visibility,
renoteId: appearNote.id,
}).then(() => {
os.toast(i18n.ts.renoted);
});
},
}, {
text: i18n.ts.quote,
icon: 'ti ti-quote',
action: () => {
os.post({
renote: appearNote,
});
},
}]);
os.popupMenu(items, renoteButton.value, {
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
os.popupMenu(menu, renoteButton.value, {
viaKeyboard,
});
}).then(focus);
}
function reply(viaKeyboard = false): void {
pleaseLogin();
if (props.mock) {
return;
}
os.post({
reply: appearNote,
channel: appearNote.channel,
@ -375,6 +313,10 @@ function react(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
if (appearNote.reactionAcceptance === 'likeOnly') {
if (props.mock) {
return;
}
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: '❤️',
@ -389,6 +331,11 @@ function react(viaKeyboard = false): void {
} else {
blur();
reactionPicker.show(reactButton.value, reaction => {
if (props.mock) {
emit('reaction', reaction);
return;
}
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: reaction,
@ -405,12 +352,22 @@ function react(viaKeyboard = false): void {
function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
if (props.mock) {
emit('removeReaction', oldReaction);
return;
}
os.api('notes/reactions/delete', {
noteId: note.id,
});
}
function onContextmenu(ev: MouseEvent): void {
if (props.mock) {
return;
}
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
// Audio
@ -439,6 +396,10 @@ function onContextmenu(ev: MouseEvent): void {
}
function menu(viaKeyboard = false): void {
if (props.mock) {
return;
}
const {menu, cleanup} = getNoteMenu({
note: note,
translating,
@ -453,6 +414,10 @@ function menu(viaKeyboard = false): void {
}
async function clip() {
if (props.mock) {
return;
}
os.popupMenu(await getNoteClipMenu({
note: note,
isDeleted,
@ -461,6 +426,10 @@ async function clip() {
}
function showRenoteMenu(viaKeyboard = false): void {
if (props.mock) {
return;
}
function getUnrenote(): MenuItem {
return {
text: i18n.ts.unrenote,
@ -518,6 +487,14 @@ function readPromo() {
});
isDeleted.value = true;
}
function emitUpdReaction(emoji: string, delta: number) {
if (delta < 0) {
emit('removeReaction', emoji);
} else if (delta > 0) {
emit('reaction', emoji);
}
}
</script>
<style lang="scss" module>