wip
This commit is contained in:
parent
2d5c1fca68
commit
8ad49637c5
|
@ -4,8 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="muted && appearNote" :class="$style.muted" @click="muted = false">
|
||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<template #name>
|
||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||
<MkUserName :user="appearNote.user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div
|
||||
v-if="!muted"
|
||||
v-else-if="note && appearNote"
|
||||
v-show="!isDeleted"
|
||||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
|
@ -126,19 +135,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else :class="$style.muted" @click="muted = false">
|
||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<template #name>
|
||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||
<MkUserName :user="appearNote.user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent } from 'vue';
|
||||
import { computed, inject, onUnmounted, ref, shallowRef, Ref, defineAsyncComponent, watch, onActivated, onDeactivated } from 'vue';
|
||||
import * as mfm from 'mfm-js';
|
||||
import * as misskey from 'misskey-js';
|
||||
import MkNoteSub from '@/components/MkNoteSub.vue';
|
||||
|
@ -162,7 +162,7 @@ import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
|
|||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu';
|
||||
import { useNoteCapture } from '@/scripts/use-note-capture';
|
||||
import { noteManager } from '@/scripts/entity-manager';
|
||||
import { deepClone } from '@/scripts/clone';
|
||||
import { useTooltip } from '@/scripts/use-tooltip';
|
||||
import { claimAchievement } from '@/scripts/achievements';
|
||||
|
@ -173,32 +173,51 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog';
|
|||
import { shouldCollapsed } from '@/scripts/collapsed';
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
note: { id: string };
|
||||
pinned?: boolean;
|
||||
}>();
|
||||
|
||||
const inChannel = inject('inChannel', null);
|
||||
const currentClip = inject<Ref<misskey.entities.Clip> | null>('currentClip', null);
|
||||
|
||||
let note = $ref(deepClone(props.note));
|
||||
|
||||
const cachedNote = noteManager.get(props.note.id);
|
||||
const overridingNote = shallowRef<Partial<misskey.entities.Note>>({});
|
||||
// plugin
|
||||
if (noteViewInterruptors.length > 0) {
|
||||
onMounted(async () => {
|
||||
let result = deepClone(note);
|
||||
for (const interruptor of noteViewInterruptors) {
|
||||
result = await interruptor.handler(result);
|
||||
}
|
||||
note = result;
|
||||
});
|
||||
}
|
||||
watch(cachedNote, async () => {
|
||||
if (cachedNote.value == null) {
|
||||
isDeleted.value = true;
|
||||
overridingNote.value = {};
|
||||
return;
|
||||
}
|
||||
if (noteViewInterruptors.length > 0) {
|
||||
overridingNote.value = {};
|
||||
return;
|
||||
}
|
||||
|
||||
const isRenote = (
|
||||
let result = deepClone(cachedNote.value);
|
||||
for (const interruptor of noteViewInterruptors) {
|
||||
result = await interruptor.handler(result) as misskey.entities.Note;
|
||||
}
|
||||
overridingNote.value = result;
|
||||
});
|
||||
const note = $computed<misskey.entities.Note | null>(() => {
|
||||
if (cachedNote.value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...cachedNote.value,
|
||||
...overridingNote.value
|
||||
};
|
||||
});
|
||||
|
||||
const isRenote = computed(() => (
|
||||
note != null &&
|
||||
note.renote != null &&
|
||||
note.text == null &&
|
||||
note.fileIds.length === 0 &&
|
||||
note.fileIds?.length === 0 &&
|
||||
note.poll == null
|
||||
);
|
||||
));
|
||||
|
||||
const el = shallowRef<HTMLElement>();
|
||||
const menuButton = shallowRef<HTMLElement>();
|
||||
|
@ -206,19 +225,19 @@ const renoteButton = shallowRef<HTMLElement>();
|
|||
const renoteTime = shallowRef<HTMLElement>();
|
||||
const reactButton = shallowRef<HTMLElement>();
|
||||
const clipButton = shallowRef<HTMLElement>();
|
||||
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
|
||||
const isMyRenote = $i && ($i.id === note.userId);
|
||||
let appearNote = $computed(() => isRenote.value ? note?.renote as misskey.entities.Note : note);
|
||||
const isMyRenote = $i && ($i.id === note?.userId);
|
||||
const showContent = ref(false);
|
||||
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
|
||||
const isLong = shouldCollapsed(appearNote);
|
||||
const collapsed = ref(appearNote.cw == null && isLong);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
|
||||
const urls = appearNote?.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
|
||||
const isLong = appearNote ? shouldCollapsed(appearNote) : false;
|
||||
const collapsed = ref(appearNote?.cw == null && isLong);
|
||||
const isDeleted = ref(note === null);
|
||||
const muted = ref(appearNote ? checkWordMute(appearNote, $i, defaultStore.state.mutedWords) : false);
|
||||
const translation = ref<any>(null);
|
||||
const translating = ref(false);
|
||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
|
||||
let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
|
||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && !!appearNote?.user.instance);
|
||||
const canRenote = computed(() => (!!appearNote && !!$i) && (['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id));
|
||||
let renoteCollapsed = $ref(note && appearNote && defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
|
||||
|
||||
const keymap = {
|
||||
'r': () => reply(true),
|
||||
|
@ -231,13 +250,9 @@ const keymap = {
|
|||
's': () => showContent.value !== showContent.value,
|
||||
};
|
||||
|
||||
useNoteCapture({
|
||||
rootEl: el,
|
||||
note: $$(appearNote),
|
||||
isDeletedRef: isDeleted,
|
||||
});
|
||||
|
||||
useTooltip(renoteButton, async (showing) => {
|
||||
if (!appearNote) return;
|
||||
|
||||
const renotes = await os.api('notes/renotes', {
|
||||
noteId: appearNote.id,
|
||||
limit: 11,
|
||||
|
@ -267,6 +282,8 @@ function smallerVisibility(a: Visibility | string, b: Visibility | string): Visi
|
|||
}
|
||||
|
||||
function renote(viaKeyboard = false) {
|
||||
if (!appearNote || !canRenote.value) return;
|
||||
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
|
||||
|
@ -277,6 +294,8 @@ function renote(viaKeyboard = false) {
|
|||
text: i18n.ts.inChannelRenote,
|
||||
icon: 'ti ti-repeat',
|
||||
action: () => {
|
||||
if (!appearNote) return;
|
||||
|
||||
const el = renoteButton.value as HTMLElement | null | undefined;
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
@ -296,6 +315,7 @@ function renote(viaKeyboard = false) {
|
|||
text: i18n.ts.inChannelQuote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
if (!appearNote) return;
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
channel: appearNote.channel,
|
||||
|
@ -308,6 +328,8 @@ function renote(viaKeyboard = false) {
|
|||
text: i18n.ts.renote,
|
||||
icon: 'ti ti-repeat',
|
||||
action: () => {
|
||||
if (!appearNote) return;
|
||||
|
||||
const el = renoteButton.value as HTMLElement | null | undefined;
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
@ -331,6 +353,7 @@ function renote(viaKeyboard = false) {
|
|||
text: i18n.ts.quote,
|
||||
icon: 'ti ti-quote',
|
||||
action: () => {
|
||||
if (!appearNote) return;
|
||||
os.post({
|
||||
renote: appearNote,
|
||||
});
|
||||
|
@ -342,18 +365,19 @@ function renote(viaKeyboard = false) {
|
|||
});
|
||||
}
|
||||
|
||||
function reply(viaKeyboard = false): void {
|
||||
async function reply(viaKeyboard = false): void {
|
||||
if (!appearNote) return;
|
||||
pleaseLogin();
|
||||
os.post({
|
||||
await os.post({
|
||||
reply: appearNote,
|
||||
channel: appearNote.channel,
|
||||
animation: !viaKeyboard,
|
||||
}, () => {
|
||||
focus();
|
||||
});
|
||||
focus();
|
||||
}
|
||||
|
||||
function react(viaKeyboard = false): void {
|
||||
if (!appearNote) return;
|
||||
pleaseLogin();
|
||||
showMovedDialog();
|
||||
if (appearNote.reactionAcceptance === 'likeOnly') {
|
||||
|
@ -369,8 +393,10 @@ function react(viaKeyboard = false): void {
|
|||
os.popup(MkRippleEffect, { x, y }, {}, 'end');
|
||||
}
|
||||
} else {
|
||||
if (!reactButton.value) return;
|
||||
blur();
|
||||
reactionPicker.show(reactButton.value, reaction => {
|
||||
if (!appearNote) return;
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: appearNote.id,
|
||||
reaction: reaction,
|
||||
|
@ -408,17 +434,20 @@ function onContextmenu(ev: MouseEvent): void {
|
|||
ev.preventDefault();
|
||||
react();
|
||||
} else {
|
||||
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), ev).then(focus);
|
||||
if (!note) return;
|
||||
os.contextMenu(getNoteMenu({ note: note, translating, translation, isDeleted, currentClip: currentClip?.value }), ev).then(focus);
|
||||
}
|
||||
}
|
||||
|
||||
function menu(viaKeyboard = false): void {
|
||||
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), menuButton.value, {
|
||||
if (!note) return;
|
||||
os.popupMenu(getNoteMenu({ note: note, translating, translation, isDeleted, currentClip: currentClip?.value }), menuButton.value, {
|
||||
viaKeyboard,
|
||||
}).then(focus);
|
||||
}
|
||||
|
||||
async function clip() {
|
||||
if (!note) return;
|
||||
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
|
@ -441,22 +470,23 @@ function showRenoteMenu(viaKeyboard = false): void {
|
|||
}
|
||||
|
||||
function focus() {
|
||||
el.value.focus();
|
||||
el.value?.focus();
|
||||
}
|
||||
|
||||
function blur() {
|
||||
el.value.blur();
|
||||
el.value?.blur();
|
||||
}
|
||||
|
||||
function focusBefore() {
|
||||
focusPrev(el.value);
|
||||
focusPrev(el.value!);
|
||||
}
|
||||
|
||||
function focusAfter() {
|
||||
focusNext(el.value);
|
||||
focusNext(el.value!);
|
||||
}
|
||||
|
||||
function readPromo() {
|
||||
if (!appearNote) return;
|
||||
os.api('promo/read', {
|
||||
noteId: appearNote.id,
|
||||
});
|
||||
|
@ -464,10 +494,35 @@ function readPromo() {
|
|||
}
|
||||
|
||||
function showReactions(): void {
|
||||
if (!appearNote) return;
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), {
|
||||
noteId: appearNote.id,
|
||||
}, {}, 'closed');
|
||||
}
|
||||
|
||||
const unuse = ref<() => void>();
|
||||
unuse.value = noteManager.useNote(props.note.id, true).unuse;
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unuse.value) {
|
||||
unuse.value();
|
||||
unuse.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (!unuse.value) {
|
||||
unuse.value = noteManager.useNote(props.note.id, true).unuse;
|
||||
}
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
if (unuse.value) {
|
||||
unuse.value();
|
||||
unuse.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo, tamaina and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Note, UserLite } from "misskey-js/built/entities";
|
||||
import { Ref, ref, ComputedRef, computed } from "vue";
|
||||
import { api } from "./api";
|
||||
import { useStream } from '@/stream';
|
||||
import { Stream } from "misskey-js";
|
||||
import { $i } from "@/account";
|
||||
|
||||
export class EntitiyManager<T extends { id: string }> {
|
||||
private entities: Map<T['id'], Ref<T>>;
|
||||
|
@ -27,20 +35,53 @@ export class EntitiyManager<T extends { id: string }> {
|
|||
export const userLiteManager = new EntitiyManager<UserLite>();
|
||||
|
||||
type OmittedNote = Omit<Note, 'user' | 'renote' | 'reply'>;
|
||||
type InternalCachedNote = Ref<OmittedNote>;
|
||||
type CachedNoteSource = Ref<OmittedNote | null>;
|
||||
type CachedNote = ComputedRef<Note | null>;
|
||||
|
||||
/**
|
||||
* ノートのキャッシュを管理する
|
||||
* 基本的な使い方:
|
||||
* 1. setでノートのデータをセットする
|
||||
* 2. useNoteでデータを取得+監視
|
||||
*/
|
||||
export class NoteManager {
|
||||
private notes: Map<Note['id'], InternalCachedNote>;
|
||||
/**
|
||||
* ノートのソースとなるRef
|
||||
* user, renote, replyを持たない
|
||||
* nullは削除済みであることを表す
|
||||
*/
|
||||
private notesSource: Map<Note['id'], CachedNoteSource>;
|
||||
|
||||
/**
|
||||
* ソースからuser, renote, replyを取得したComputedRef
|
||||
* nullは削除済みであることを表す
|
||||
*/
|
||||
private notesComputed: Map<Note['id'], CachedNote>;
|
||||
private updatedAt: Map<Note['id'], number>;
|
||||
private captureing: Map<Note['id'], number>;
|
||||
private connection: Stream | null;
|
||||
|
||||
constructor() {
|
||||
this.notes = new Map();
|
||||
this.notesSource = new Map();
|
||||
this.notesComputed = new Map();
|
||||
this.updatedAt = new Map();
|
||||
this.captureing = new Map();
|
||||
this.connection = $i ? useStream() : null;
|
||||
this.connection?.on('noteUpdated', this.onStreamNoteUpdated);
|
||||
this.connection?.on('_connected_', () => {
|
||||
// 再接続時に再キャプチャ
|
||||
for (const [id, captureingNumber] of Array.from(this.captureing)) {
|
||||
if (captureingNumber === 0) {
|
||||
this.captureing.delete(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.connection?.send('s', { id });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public set(_note: Note): ComputedRef<Note> {
|
||||
public set(_note: Note): CachedNote {
|
||||
const note: Note = { ..._note };
|
||||
userLiteManager.set(note.user);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
@ -50,45 +91,199 @@ export class NoteManager {
|
|||
delete note.renote;
|
||||
if (note.reply) this.set(note.reply);
|
||||
delete note.reply;
|
||||
const cached = this.notes.get(note.id);
|
||||
const cached = this.notesSource.get(note.id);
|
||||
if (cached) {
|
||||
cached.value = note;
|
||||
} else {
|
||||
this.notes.set(note.id, ref(note));
|
||||
this.notesSource.set(note.id, ref(note));
|
||||
}
|
||||
this.updatedAt.set(note.id, Date.now());
|
||||
return this.get(note.id)!;
|
||||
}
|
||||
|
||||
public get(id: string): ComputedRef<Note> | undefined {
|
||||
const note: InternalCachedNote | undefined = this.notes.get(id);
|
||||
if (!note) return undefined;
|
||||
public get(id: string): CachedNote {
|
||||
if (!this.notesComputed.has(id)) {
|
||||
const note = this.notesSource.get(id);
|
||||
|
||||
return computed<Note>(() => {
|
||||
const user = userLiteManager.get(note.value.userId)!;
|
||||
const renote = note.value.renoteId ? this.get(note.value.renoteId) : undefined;
|
||||
const reply = note.value.replyId ? this.get(note.value.replyId) : undefined;
|
||||
this.notesComputed.set(id, computed<Note | null>(() => {
|
||||
if (!note || !note.value) return null;
|
||||
|
||||
return {
|
||||
...note.value,
|
||||
user: user.value,
|
||||
renote: renote?.value,
|
||||
reply: reply?.value,
|
||||
};
|
||||
});
|
||||
const user = userLiteManager.get(note.value.userId)!;
|
||||
|
||||
const renote = note.value.renoteId ? this.get(note.value.renoteId) : undefined;
|
||||
// renoteが削除されている場合はCASCADE削除されるためnullを返す
|
||||
if (renote && !renote.value) return null;
|
||||
|
||||
const reply = note.value.replyId ? this.get(note.value.replyId) : undefined;
|
||||
if (reply && !reply.value) return null;
|
||||
|
||||
return {
|
||||
...note.value,
|
||||
user: user.value,
|
||||
renote: renote?.value ?? undefined,
|
||||
reply: reply?.value ?? undefined,
|
||||
};
|
||||
}));
|
||||
}
|
||||
return this.notesComputed.get(id)!;
|
||||
}
|
||||
|
||||
public async fetch(id: string, force = false): Promise<ComputedRef<Note>> {
|
||||
public async fetch(id: string, force = false): Promise<CachedNote> {
|
||||
if (!force) {
|
||||
const updatedAt = this.updatedAt.get(id);
|
||||
if (updatedAt && Date.now() - updatedAt < 1000 * 30) {
|
||||
const cachedNote = this.get(id);
|
||||
if (cachedNote) {
|
||||
return cachedNote as ComputedRef<Note>;
|
||||
return cachedNote;
|
||||
}
|
||||
}
|
||||
}
|
||||
const fetchedNote = await api('notes/show', { noteId: id });
|
||||
return this.set(fetchedNote);
|
||||
return api('notes/show', { noteId: id })
|
||||
.then(fetchedNote => this.set(fetchedNote))
|
||||
.catch(() => {
|
||||
// エラーが発生した場合はとりあえず削除されたものとして扱う
|
||||
const cached = this.notesSource.get(id);
|
||||
if (cached) {
|
||||
cached.value = null;
|
||||
} else {
|
||||
this.notesSource.set(id, ref(null));
|
||||
}
|
||||
// updateAtはしない
|
||||
return this.get(id)!;
|
||||
});
|
||||
}
|
||||
|
||||
private onStreamNoteUpdated(noteData: any): void {
|
||||
const { type, id, body } = noteData;
|
||||
|
||||
const note = this.notesSource.get(id);
|
||||
|
||||
if (!note || !note.value) {
|
||||
this.connection?.send('un', { id });
|
||||
this.captureing.delete(id);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'reacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
if (body.emoji && !(body.emoji.name in note.value.reactionEmojis)) {
|
||||
note.value.reactionEmojis[body.emoji.name] = body.emoji.url;
|
||||
}
|
||||
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (note.value.reactions || {})[reaction] || 0;
|
||||
|
||||
note.value.reactions[reaction] = currentCount + 1;
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
note.value.myReaction = reaction;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unreacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (note.value.reactions || {})[reaction] || 0;
|
||||
|
||||
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
|
||||
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
note.value.myReaction = undefined;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pollVoted': {
|
||||
const choice = body.choice;
|
||||
|
||||
const choices = [...note.value.poll!.choices];
|
||||
choices[choice] = {
|
||||
...choices[choice],
|
||||
votes: choices[choice].votes + 1,
|
||||
...($i && (body.userId === $i.id) ? {
|
||||
isVoted: true,
|
||||
} : {}),
|
||||
};
|
||||
|
||||
note.value.poll!.choices = choices;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'deleted': {
|
||||
note.value = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private capture(id: string, markRead = true): void {
|
||||
if (!this.notesSource.has(id)) return;
|
||||
|
||||
const captureingNumber = this.captureing.get(id);
|
||||
if (typeof captureingNumber === 'number' && captureingNumber > 0) {
|
||||
this.captureing.set(id, captureingNumber + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connection) {
|
||||
// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
|
||||
this.connection.send(markRead ? 'sr' : 's', { id });
|
||||
}
|
||||
|
||||
this.captureing.set(id, 1);
|
||||
}
|
||||
|
||||
private decapture(id: string): void {
|
||||
if (!this.notesSource.has(id)) return;
|
||||
|
||||
const captureingNumber = this.captureing.get(id);
|
||||
if (typeof captureingNumber === 'number' && captureingNumber > 1) {
|
||||
this.captureing.set(id, captureingNumber - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connection) {
|
||||
this.connection.send('un', { id });
|
||||
}
|
||||
|
||||
this.captureing.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* ノートを取得・監視
|
||||
* キャプチャが要らなくなったら必ずunuseすること
|
||||
* @param id note id
|
||||
* @returns { note, unuse } note: CachedNote | Promise<CachedNote>, unuse: () => void
|
||||
*/
|
||||
public useNote(id: string, shoudFetch: true): { note: Promise<CachedNote>, unuse: () => void };
|
||||
public useNote(id: string, shoudFetch = false) {
|
||||
const note = (!this.notesSource.has(id) || shoudFetch) ? this.fetch(id) : this.get(id)!;
|
||||
let using = false;
|
||||
const CapturePromise = Promise.resolve(note)
|
||||
.then(() => {
|
||||
this.capture(id);
|
||||
using = true;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
|
||||
const unuse = () => {
|
||||
CapturePromise.then(() => {
|
||||
if (!using) return;
|
||||
this.decapture(id);
|
||||
using = false;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
note,
|
||||
unuse,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const noteManager = new NoteManager();
|
||||
|
|
|
@ -93,7 +93,6 @@ export async function getNoteClipMenu(props: {
|
|||
|
||||
export function getNoteMenu(props: {
|
||||
note: misskey.entities.Note;
|
||||
menuButton: Ref<HTMLElement>;
|
||||
translation: Ref<any>;
|
||||
translating: Ref<boolean>;
|
||||
isDeleted: Ref<boolean>;
|
||||
|
|
Loading…
Reference in New Issue