Compare commits
6 Commits
796b4ebc0f
...
b05f2bb834
Author | SHA1 | Date |
---|---|---|
tamaina | b05f2bb834 | |
tamaina | 33c3583115 | |
tamaina | 7fc2309822 | |
tamaina | d92fe0803c | |
tamaina | 7e7aece2f5 | |
tamaina | 4e29da828f |
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue