diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-03 20:26:11 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-03 20:26:11 +0900 |
| commit | 6277a5545c746fac15ee6b4fe58de2e354ed7fda (patch) | |
| tree | 700b0d162795f03d644a15ba2375628d66f95d92 /packages/frontend/src/pages | |
| parent | Update about-misskey.vue (diff) | |
| download | misskey-6277a5545c746fac15ee6b4fe58de2e354ed7fda.tar.gz misskey-6277a5545c746fac15ee6b4fe58de2e354ed7fda.tar.bz2 misskey-6277a5545c746fac15ee6b4fe58de2e354ed7fda.zip | |
feat: improve tl performance (#11946)
* wip
* wip
* wip
* wip
* wip
* wip
* Update NoteCreateService.ts
* wip
* wip
* wip
* wip
* Update NoteCreateService.ts
* wip
* Update NoteCreateService.ts
* wip
* Update user-notes.ts
* wip
* wip
* wip
* Update NoteCreateService.ts
* wip
* Update timeline.ts
* Update timeline.ts
* Update timeline.ts
* Update timeline.ts
* Update timeline.ts
* wip
* Update timelines.ts
* Update timelines.ts
* Update timelines.ts
* wip
* wip
* wip
* Update timelines.ts
* Update misskey-js.api.md
* Update timelines.ts
* Update timelines.ts
* wip
* wip
* wip
* Update timelines.ts
* wip
* Update timelines.ts
* wip
* test
* Update activitypub.ts
* refactor: UserListJoining -> UserListMembership
* Update NoteCreateService.ts
* wip
Diffstat (limited to 'packages/frontend/src/pages')
| -rw-r--r-- | packages/frontend/src/pages/my-lists/list.vue | 94 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/word-mute.vue | 52 | ||||
| -rw-r--r-- | packages/frontend/src/pages/timeline.vue | 9 | ||||
| -rw-r--r-- | packages/frontend/src/pages/user/home.vue | 6 | ||||
| -rw-r--r-- | packages/frontend/src/pages/user/index.files.vue (renamed from packages/frontend/src/pages/user/index.photos.vue) | 32 |
5 files changed, 83 insertions, 110 deletions
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index df13998f6b..b600f99fbc 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -29,16 +29,22 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_s"> <MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton> - <div v-for="user in users" :key="user.id" :class="$style.userItem"> - <MkA :class="$style.userItemBody" :to="`${userPage(user)}`"> - <MkUserCardMini :user="user"/> - </MkA> - <button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button> - </div> - <MkButton v-if="!fetching && queueUserIds.length !== 0" v-appear="enableInfiniteScroll ? fetchMoreUsers : null" :class="$style.more" :style="{ cursor: 'pointer' }" primary rounded @click="fetchMoreUsers"> - {{ i18n.ts.loadMore }} - </MkButton> - <MkLoading v-if="fetching" class="loading"/> + + <MkPagination ref="paginationEl" :pagination="membershipsPagination"> + <template #default="{ items }"> + <div class="_gaps_s"> + <div v-for="item in items" :key="item.id"> + <div :class="$style.userItem"> + <MkA :class="$style.userItemBody" :to="`${userPage(item.user)}`"> + <MkUserCardMini :user="item.user"/> + </MkA> + <button class="_button" :class="$style.menu" @click="showMembershipMenu(item, $event)"><i class="ti ti-dots"></i></button> + <button class="_button" :class="$style.remove" @click="removeUser(item, $event)"><i class="ti ti-x"></i></button> + </div> + </div> + </div> + </template> + </MkPagination> </div> </MkFolder> </div> @@ -59,9 +65,11 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; -import { userListsCache } from '@/cache'; +import { userListsCache } from '@/cache.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; +import MkPagination, { Paging } from '@/components/MkPagination.vue'; + const { enableInfiniteScroll, } = defaultStore.reactiveState; @@ -70,40 +78,25 @@ const props = defineProps<{ listId: string; }>(); -const FETCH_USERS_LIMIT = 20; - +const paginationEl = ref<InstanceType<typeof MkPagination>>(); let list = $ref<Misskey.entities.UserList | null>(null); -let users = $ref<Misskey.entities.UserLite[]>([]); -let queueUserIds = $ref<string[]>([]); -let fetching = $ref(true); const isPublic = ref(false); const name = ref(''); +const membershipsPagination = { + endpoint: 'users/lists/get-memberships' as const, + limit: 30, + params: computed(() => ({ + listId: props.listId, + })), +}; function fetchList() { - fetching = true; os.api('users/lists/show', { listId: props.listId, }).then(_list => { list = _list; name.value = list.name; isPublic.value = list.isPublic; - queueUserIds = list.userIds; - - return fetchMoreUsers(); - }); -} - -function fetchMoreUsers() { - if (!list) return; - if (fetching && users.length !== 0) return; // fetchingがtrueならやめるが、usersが空なら続行 - fetching = true; - os.api('users/show', { - userIds: queueUserIds.slice(0, FETCH_USERS_LIMIT), - }).then(_users => { - users = users.concat(_users); - queueUserIds = queueUserIds.slice(FETCH_USERS_LIMIT); - }).finally(() => { - fetching = false; }); } @@ -114,12 +107,12 @@ function addUser() { listId: list.id, userId: user.id, }).then(() => { - users.push(user); + paginationEl.value.reload(); }); }); } -async function removeUser(user, ev) { +async function removeUser(item, ev) { os.popupMenu([{ text: i18n.ts.remove, icon: 'ti ti-x', @@ -128,9 +121,28 @@ async function removeUser(user, ev) { if (!list) return; os.api('users/lists/pull', { listId: list.id, - userId: user.id, + userId: item.userId, + }).then(() => { + paginationEl.value.removeItem(item.id); + }); + }, + }], ev.currentTarget ?? ev.target); +} + +async function showMembershipMenu(item, ev) { + os.popupMenu([{ + text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline, + icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages', + action: async () => { + os.api('users/lists/update-membership', { + listId: list.id, + userId: item.userId, + withReplies: !item.withReplies, }).then(() => { - users = users.filter(x => x.id !== user.id); + paginationEl.value.updateItem(item.id, (old) => ({ + ...old, + withReplies: !item.withReplies, + })); }); }, }], ev.currentTarget ?? ev.target); @@ -202,6 +214,12 @@ definePageMetadata(computed(() => list ? { align-self: center; } +.menu { + width: 32px; + height: 32px; + align-self: center; +} + .more { margin-left: auto; margin-right: auto; diff --git a/packages/frontend/src/pages/settings/word-mute.vue b/packages/frontend/src/pages/settings/word-mute.vue index 1fefbdc92b..4e698698fe 100644 --- a/packages/frontend/src/pages/settings/word-mute.vue +++ b/packages/frontend/src/pages/settings/word-mute.vue @@ -5,29 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps_m"> - <MkTab v-model="tab"> - <option value="soft">{{ i18n.ts._wordMute.soft }}</option> - <option value="hard">{{ i18n.ts._wordMute.hard }}</option> - </MkTab> <div> - <div v-show="tab === 'soft'" class="_gaps_m"> - <MkInfo>{{ i18n.ts._wordMute.softDescription }}</MkInfo> - <MkTextarea v-model="softMutedWords"> - <span>{{ i18n.ts._wordMute.muteWords }}</span> - <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> - </MkTextarea> - </div> - <div v-show="tab === 'hard'" class="_gaps_m"> - <MkInfo>{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo> - <MkTextarea v-model="hardMutedWords"> - <span>{{ i18n.ts._wordMute.muteWords }}</span> - <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> - </MkTextarea> - <MkKeyValue v-if="hardWordMutedNotesCount != null"> - <template #key>{{ i18n.ts._wordMute.mutedNotes }}</template> - <template #value>{{ number(hardWordMutedNotesCount) }}</template> - </MkKeyValue> - </div> + <MkTextarea v-model="mutedWords"> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> + </MkTextarea> </div> <MkButton primary inline :disabled="!changed" @click="save()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> </div> @@ -56,25 +38,15 @@ const render = (mutedWords) => mutedWords.map(x => { }).join('\n'); const tab = ref('soft'); -const softMutedWords = ref(render(defaultStore.state.mutedWords)); -const hardMutedWords = ref(render($i!.mutedWords)); -const hardWordMutedNotesCount = ref(null); +const mutedWords = ref(render($i!.mutedWords)); const changed = ref(false); -os.api('i/get-word-muted-notes-count', {}).then(response => { - hardWordMutedNotesCount.value = response?.count; -}); - -watch(softMutedWords, () => { - changed.value = true; -}); - -watch(hardMutedWords, () => { +watch(mutedWords, () => { changed.value = true; }); async function save() { - const parseMutes = (mutes, tab) => { + const parseMutes = (mutes) => { // split into lines, remove empty lines and unnecessary whitespace let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== ''); @@ -92,7 +64,7 @@ async function save() { os.alert({ type: 'error', title: i18n.ts.regexpError, - text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + '\n' + err.toString(), + text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(), }); // re-throw error so these invalid settings are not saved throw err; @@ -105,18 +77,16 @@ async function save() { return lines; }; - let softMutes, hardMutes; + let parsed; try { - softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft); - hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard); + parsed = parseMutes(mutedWords.value); } catch (err) { // already displayed error message in parseMutes return; } - defaultStore.set('mutedWords', softMutes); await os.api('i/update', { - mutedWords: hardMutes, + mutedWords: parsed, }); changed.value = false; diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 5bad689aee..b8deb77952 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -15,11 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.tl"> <MkTimeline ref="tlComponent" - :key="src + withRenotes + withReplies + onlyFiles" + :key="src + withRenotes + onlyFiles" :src="src.split(':')[0]" :list="src.split(':')[1]" :withRenotes="withRenotes" - :withReplies="withReplies" :onlyFiles="onlyFiles" :sound="true" @queue="queueUpdated" @@ -62,7 +61,6 @@ let queue = $ref(0); let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global'); const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) }); const withRenotes = $ref(true); -const withReplies = $ref(false); const onlyFiles = $ref(false); watch($$(src), () => queue = 0); @@ -146,11 +144,6 @@ const headerActions = $computed(() => [{ ref: $$(withRenotes), }, { type: 'switch', - text: i18n.ts.withReplies, - icon: 'ti ti-arrow-back-up', - ref: $$(withReplies), - }, { - type: 'switch', text: i18n.ts.fileAttachedOnly, icon: 'ti ti-photo', ref: $$(onlyFiles), diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 385c81a97f..71eec0aa26 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -128,14 +128,14 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo> <template v-if="narrow"> - <XPhotos :key="user.id" :user="user"/> + <XFiles :key="user.id" :user="user"/> <XActivity :key="user.id" :user="user"/> </template> <MkNotes v-if="!disableNotes" :class="$style.tl" :noGap="true" :pagination="pagination"/> </div> </div> <div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;"> - <XPhotos :key="user.id" :user="user"/> + <XFiles :key="user.id" :user="user"/> <XActivity :key="user.id" :user="user"/> </div> </div> @@ -182,7 +182,7 @@ function calcAge(birthdate: string): number { return yearDiff; } -const XPhotos = defineAsyncComponent(() => import('./index.photos.vue')); +const XFiles = defineAsyncComponent(() => import('./index.files.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/pages/user/index.photos.vue b/packages/frontend/src/pages/user/index.files.vue index b6cae9f131..205da5071d 100644 --- a/packages/frontend/src/pages/user/index.photos.vue +++ b/packages/frontend/src/pages/user/index.files.vue @@ -6,20 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkContainer :max-height="300" :foldable="true"> <template #icon><i class="ti ti-photo"></i></template> - <template #header>{{ i18n.ts.images }}</template> + <template #header>{{ i18n.ts.files }}</template> <div :class="$style.root"> <MkLoading v-if="fetching"/> - <div v-if="!fetching && images.length > 0" :class="$style.stream"> + <div v-if="!fetching && files.length > 0" :class="$style.stream"> <MkA - v-for="image in images" - :key="image.note.id + image.file.id" + v-for="file in files" + :key="file.note.id + file.file.id" :class="$style.img" - :to="notePage(image.note)" + :to="notePage(file.note)" > - <ImgWithBlurhash :hash="image.file.blurhash" :src="thumbnail(image.file)" :title="image.file.name"/> + <!-- TODO: 画像以外のファイルに対応 --> + <ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/> </MkA> </div> - <p v-if="!fetching && images.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p> + <p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p> </div> </MkContainer> </template> @@ -40,7 +41,7 @@ const props = defineProps<{ }>(); let fetching = $ref(true); -let images = $ref<{ +let files = $ref<{ note: Misskey.entities.Note; file: Misskey.entities.DriveFile; }[]>([]); @@ -52,24 +53,15 @@ function thumbnail(image: Misskey.entities.DriveFile): string { } onMounted(() => { - const image = [ - 'image/jpeg', - 'image/webp', - 'image/avif', - 'image/png', - 'image/gif', - 'image/apng', - 'image/vnd.mozilla.apng', - ]; os.api('users/notes', { userId: props.user.id, - fileType: image, + withFiles: true, excludeNsfw: defaultStore.state.nsfw !== 'ignore', - limit: 10, + limit: 15, }).then(notes => { for (const note of notes) { for (const file of note.files) { - images.push({ + files.push({ note, file, }); |