Compare commits

...

6 Commits

Author SHA1 Message Date
tamaina b05f2bb834 fix 2023-07-28 16:38:00 +00:00
tamaina 33c3583115 Merge branch 'pag-back' into noman 2023-07-28 15:58:11 +00:00
tamaina 7fc2309822 fix comment 2023-07-28 15:58:00 +00:00
tamaina d92fe0803c fix 2023-07-28 15:57:34 +00:00
tamaina 7e7aece2f5 ✌️ 2023-07-28 15:54:06 +00:00
tamaina 4e29da828f wip 2023-07-28 15:25:50 +00:00
4 changed files with 112 additions and 25 deletions

View File

@ -238,7 +238,16 @@ const translation = ref<any>(null);
const translating = ref(false); const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && !!appearNote?.user.instance); 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)); 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))); let renoteCollapsed = $ref(
note &&
appearNote &&
defaultStore.state.collapseRenotes &&
isRenote.value &&
(
($i && ($i.id === note.userId || $i.id === appearNote.userId)) ||
(appearNote.myReaction != null)
)
);
const keymap = { const keymap = {
'r': () => reply(true), 'r': () => reply(true),
@ -518,12 +527,14 @@ onActivated(() => {
}); });
onDeactivated(() => { onDeactivated(() => {
if (unuse.value) { //
unuse.value(); setTimeout(() => {
unuse.value = undefined; if (unuse.value) {
} unuse.value();
unuse.value = undefined;
}
}, 1000);
}); });
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -87,6 +87,11 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints>
offsetMode?: boolean; offsetMode?: boolean;
pageEl?: HTMLElement; pageEl?: HTMLElement;
/**
* 変換関数
*/
transform?: (source: Omit<MisskeyEntity, '_shouldInsertAd_'>[]) => MisskeyEntity[];
}; };
type MisskeyEntityMap = Map<string, MisskeyEntity>; type MisskeyEntityMap = Map<string, MisskeyEntity>;
@ -169,6 +174,11 @@ const visibility = useDocumentVisibility();
const isPausingUpdateByExecutingQueue = ref(false); const isPausingUpdateByExecutingQueue = ref(false);
const denyMoveTransition = ref(false); const denyMoveTransition = ref(false);
/**
* 変換関数
*/
const transform = computed(() => props.pagination.transform ?? ((source: MisskeyEntity[]) => source));
//#region scrolling //#region scrolling
const checkFn = props.pagination.reversed ? isBottomVisible : isTopVisible; const checkFn = props.pagination.reversed ? isBottomVisible : isTopVisible;
const checkTop = (tolerance?: number) => { const checkTop = (tolerance?: number) => {
@ -298,7 +308,9 @@ async function init(): Promise<void> {
await os.api(props.pagination.endpoint, { await os.api(props.pagination.endpoint, {
...params, ...params,
limit: props.pagination.limit ?? 10, limit: props.pagination.limit ?? 10,
}).then(res => { }).then(_res => {
const res = transform.value(_res);
for (let i = 0; i < res.length; i++) { for (let i = 0; i < res.length; i++) {
const item = res[i]; const item = res[i];
if (i === 3) item._shouldInsertAd_ = true; if (i === 3) item._shouldInsertAd_ = true;
@ -378,7 +390,9 @@ const fetchMore = async (): Promise<void> => {
} : { } : {
untilId: Array.from(items.value.keys()).at(-1), untilId: Array.from(items.value.keys()).at(-1),
}), }),
}).then(res => { }).then(_res => {
const res = transform.value(_res);
for (let i = 0; i < res.length; i++) { for (let i = 0; i < res.length; i++) {
const item = res[i]; const item = res[i];
if (i === 10) item._shouldInsertAd_ = true; if (i === 10) item._shouldInsertAd_ = true;
@ -425,15 +439,15 @@ const fetchMoreAhead = async (): Promise<void> => {
} : { } : {
sinceId: Array.from(items.value.keys()).at(-1), sinceId: Array.from(items.value.keys()).at(-1),
}), }),
}).then(res => { }).then(_res => {
if (res.length === 0) { if (_res.length === 0) {
items.value = concatMapWithArray(items.value, res);
more.value = false; more.value = false;
} else { } else {
const res = transform.value(_res);
items.value = concatMapWithArray(items.value, res); items.value = concatMapWithArray(items.value, res);
more.value = true; more.value = true;
} }
offset.value += res.length; offset.value += _res.length;
moreFetching.value = false; moreFetching.value = false;
}, err => { }, err => {
moreFetching.value = false; moreFetching.value = false;
@ -484,9 +498,12 @@ watch([active, visibility], () => {
/** /**
* 最新のものとして1つだけアイテムを追加する * 最新のものとして1つだけアイテムを追加する
* ストリーミングから降ってきたアイテムはこれで追加する * ストリーミングから降ってきたアイテムはこれで追加する
* @param item アイテム * @param item アイテムtransform前
*/ */
const prepend = (item: MisskeyEntity): void => { const prepend = (_item: MisskeyEntity): void => {
const item = transform.value([_item])[0];
if (!item) return;
if (items.value.size === 0) { if (items.value.size === 0) {
items.value.set(item.id, item); items.value.set(item.id, item);
fetching.value = false; fetching.value = false;
@ -540,7 +557,10 @@ function concatItems(oldItems: MisskeyEntity[]) {
} }
async function executeQueue() { async function executeQueue() {
if (queue.value.size === 0) return; //
//
// if (queue.value.size === 0) return;
if (isPausingUpdateByExecutingQueue.value) return; if (isPausingUpdateByExecutingQueue.value) return;
if (timelineBackTopBehavior.value === 'newest') { if (timelineBackTopBehavior.value === 'newest') {
// Safari // Safari
@ -570,8 +590,12 @@ function prependQueue(newItem: MisskeyEntity) {
/* /*
* アイテムを末尾に追加する使うの * アイテムを末尾に追加する使うの
* @param item アイテムtransform前
*/ */
const appendItem = (item: MisskeyEntity): void => { const appendItem = (_item: MisskeyEntity): void => {
const item = transform.value([_item])[0];
if (!item) return;
items.value.set(item.id, item); items.value.set(item.id, item);
}; };

View File

@ -26,6 +26,9 @@ import { $i } from '@/account';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { noteManager } from '@/scripts/entity-manager';
import { Note } from 'misskey-js/built/entities';
import { MisskeyEntity } from '@/types/date-separated-list';
const props = defineProps<{ const props = defineProps<{
src: string; src: string;
@ -62,6 +65,16 @@ const prepend = note => {
} }
}; };
const transform = (notes: Note[]): MisskeyEntity[] => {
return notes.map(note => {
noteManager.set(note);
return {
id: note.id,
createdAt: note.createdAt,
};
});
};
let endpoint; let endpoint;
let query; let query;
let connection; let connection;
@ -165,6 +178,7 @@ const pagination = {
endpoint: endpoint, endpoint: endpoint,
limit: 10, limit: 10,
params: query, params: query,
transform: transform,
}; };
onUnmounted(() => { onUnmounted(() => {

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Note, UserLite } from "misskey-js/built/entities"; import { Note, UserLite, DriveFile } from "misskey-js/built/entities";
import { Ref, ref, ComputedRef, computed } from "vue"; import { Ref, ref, ComputedRef, computed } from "vue";
import { api } from "./api"; import { api } from "./api";
import { useStream } from '@/stream'; import { useStream } from '@/stream';
@ -13,7 +13,9 @@ import { $i } from "@/account";
export class EntitiyManager<T extends { id: string }> { export class EntitiyManager<T extends { id: string }> {
private entities: Map<T['id'], Ref<T>>; private entities: Map<T['id'], Ref<T>>;
constructor() { constructor(
public key: string,
) {
this.entities = new Map(); this.entities = new Map();
} }
@ -32,7 +34,8 @@ export class EntitiyManager<T extends { id: string }> {
} }
} }
export const userLiteManager = new EntitiyManager<UserLite>(); export const userLiteManager = new EntitiyManager<UserLite>('userLite');
export const driveFileManager = new EntitiyManager<DriveFile>('driveFile');
type OmittedNote = Omit<Note, 'user' | 'renote' | 'reply'>; type OmittedNote = Omit<Note, 'user' | 'renote' | 'reply'>;
type CachedNoteSource = Ref<OmittedNote | null>; type CachedNoteSource = Ref<OmittedNote | null>;
@ -53,8 +56,9 @@ export class NoteManager {
private notesSource: Map<Note['id'], CachedNoteSource>; private notesSource: Map<Note['id'], CachedNoteSource>;
/** /**
* user, renote, replyを取得したComputedRef * user, renote, replyを取得したComputedRefのキャッシュを保持しておく
* nullは削除済みであることを表す * nullは削除済みであることを表す
* 0
*/ */
private notesComputed: Map<Note['id'], CachedNote>; private notesComputed: Map<Note['id'], CachedNote>;
private updatedAt: Map<Note['id'], number>; private updatedAt: Map<Note['id'], number>;
@ -67,7 +71,7 @@ export class NoteManager {
this.updatedAt = new Map(); this.updatedAt = new Map();
this.captureing = new Map(); this.captureing = new Map();
this.connection = $i ? useStream() : null; this.connection = $i ? useStream() : null;
this.connection?.on('noteUpdated', this.onStreamNoteUpdated); this.connection?.on('noteUpdated', noteData => this.onStreamNoteUpdated(noteData));
this.connection?.on('_connected_', () => { this.connection?.on('_connected_', () => {
// 再接続時に再キャプチャ // 再接続時に再キャプチャ
for (const [id, captureingNumber] of Array.from(this.captureing)) { for (const [id, captureingNumber] of Array.from(this.captureing)) {
@ -81,16 +85,29 @@ export class NoteManager {
}); });
} }
public set(_note: Note): CachedNote { public set(_note: Note): void {
const note: Note = { ..._note }; const note: Note = { ..._note };
userLiteManager.set(note.user); userLiteManager.set(note.user);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
delete note.user; delete note.user;
if (note.fileIds.length > 0) {
for (const file of note.files) {
driveFileManager.set(file);
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
delete note.files;
if (note.renote) this.set(note.renote); if (note.renote) this.set(note.renote);
delete note.renote; delete note.renote;
if (note.reply) this.set(note.reply); if (note.reply) this.set(note.reply);
delete note.reply; delete note.reply;
const cached = this.notesSource.get(note.id); const cached = this.notesSource.get(note.id);
if (cached) { if (cached) {
cached.value = note; cached.value = note;
@ -98,7 +115,6 @@ export class NoteManager {
this.notesSource.set(note.id, ref(note)); this.notesSource.set(note.id, ref(note));
} }
this.updatedAt.set(note.id, Date.now()); this.updatedAt.set(note.id, Date.now());
return this.get(note.id)!;
} }
public get(id: string): CachedNote { public get(id: string): CachedNote {
@ -117,11 +133,14 @@ export class NoteManager {
const reply = note.value.replyId ? this.get(note.value.replyId) : undefined; const reply = note.value.replyId ? this.get(note.value.replyId) : undefined;
if (reply && !reply.value) return null; if (reply && !reply.value) return null;
const files = note.value.fileIds.map(id => driveFileManager.get(id)?.value);
return { return {
...note.value, ...note.value,
user: user.value, user: user.value,
renote: renote?.value ?? undefined, renote: renote?.value ?? undefined,
reply: reply?.value ?? undefined, reply: reply?.value ?? undefined,
files: files.filter(file => file) as DriveFile[],
}; };
})); }));
} }
@ -131,7 +150,8 @@ export class NoteManager {
public async fetch(id: string, force = false): Promise<CachedNote> { public async fetch(id: string, force = false): Promise<CachedNote> {
if (!force) { if (!force) {
const updatedAt = this.updatedAt.get(id); const updatedAt = this.updatedAt.get(id);
if (updatedAt && Date.now() - updatedAt < 1000 * 30) { // 2分以上経過していない場合はキャッシュを返す
if (updatedAt && Date.now() - updatedAt < 1000 * 120) {
const cachedNote = this.get(id); const cachedNote = this.get(id);
if (cachedNote) { if (cachedNote) {
return cachedNote; return cachedNote;
@ -139,7 +159,10 @@ export class NoteManager {
} }
} }
return api('notes/show', { noteId: id }) return api('notes/show', { noteId: id })
.then(fetchedNote => this.set(fetchedNote)) .then(fetchedNote => {
this.set(fetchedNote);
return this.get(id)!;
})
.catch(() => { .catch(() => {
// エラーが発生した場合はとりあえず削除されたものとして扱う // エラーが発生した場合はとりあえず削除されたものとして扱う
const cached = this.notesSource.get(id); const cached = this.notesSource.get(id);
@ -161,6 +184,8 @@ export class NoteManager {
if (!note || !note.value) { if (!note || !note.value) {
this.connection?.send('un', { id }); this.connection?.send('un', { id });
this.captureing.delete(id); this.captureing.delete(id);
this.notesComputed.delete(id);
this.updatedAt.delete(id);
return; return;
} }
@ -219,6 +244,8 @@ export class NoteManager {
break; break;
} }
} }
this.updatedAt.set(id, Date.now());
} }
private capture(id: string, markRead = true): void { private capture(id: string, markRead = true): void {
@ -252,6 +279,9 @@ export class NoteManager {
} }
this.captureing.delete(id); this.captureing.delete(id);
// キャプチャが終わったらcomputedキャッシュも消してしまう
this.notesComputed.delete(id);
} }
/** /**
@ -287,3 +317,11 @@ export class NoteManager {
} }
export const noteManager = new NoteManager(); export const noteManager = new NoteManager();
if (_DEV_) {
console.log('entity manager initialized', {
noteManager,
userLiteManager,
driveFileManager,
});
}