From 453574644c9d8fd19b3f11adc8b24250c00ea268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Sun, 26 Mar 2023 09:25:51 +0000 Subject: [PATCH 01/11] fix: add workaround for cat ears on iOS Safari --- packages/frontend/src/components/global/MkPageHeader.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 4d968db6a..013dc2fd6 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -8,7 +8,9 @@ <template v-if="metadata"> <div v-if="!hideTitle" :class="$style.titleContainer" @click="top"> - <MkAvatar v-if="metadata.avatar" :class="$style.titleAvatar" :user="metadata.avatar" indicator/> + <div v-if="metadata.avatar" :class="$style.titleAvatarContainer"> + <MkAvatar :class="$style.titleAvatar" :user="metadata.avatar" indicator/> + </div> <i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i> <div :class="$style.title"> @@ -249,6 +251,11 @@ onUnmounted(() => { margin-left: 24px; } +.titleAvatarContainer { + overflow: hidden; + padding: 8px 0; +} + .titleAvatar { $size: 32px; display: inline-block; From 07cfab04890a46159e78ec0199634f98ff9f6ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Sun, 26 Mar 2023 09:36:46 +0000 Subject: [PATCH 02/11] fix: add tight state to cat ears for workarounds --- .../frontend/src/components/global/MkAvatar.vue | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 814ab53d2..ed104cb0f 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -86,6 +86,18 @@ watch(() => props.user.avatarBlurhash, () => { to { transform: rotate(-37.6deg) skew(-30deg); } } +@keyframes eartightleft { + from { transform: rotate(37.6deg) skew(30deg); } + 50% { transform: rotate(37.4deg) skew(30deg); } + to { transform: rotate(37.6deg) skew(30deg); } +} + +@keyframes eartightright { + from { transform: rotate(-37.6deg) skew(-30deg); } + 50% { transform: rotate(-37.4deg) skew(-30deg); } + to { transform: rotate(-37.6deg) skew(-30deg); } +} + .root { position: relative; display: inline-block; @@ -184,6 +196,7 @@ watch(() => props.user.avatarBlurhash, () => { > .earLeft { transform: rotate(37.5deg) skew(30deg); + animation: eartightleft 6s infinite; &, &::after { border-radius: 0 75% 75%; @@ -205,6 +218,7 @@ watch(() => props.user.avatarBlurhash, () => { > .earRight { transform: rotate(-37.5deg) skew(-30deg); + animation: eartightright 6s infinite; &, &::after { border-radius: 75% 0 75% 75%; From 4d3f0be419a972a9233e187458ff45f13baf1dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Mon, 27 Mar 2023 05:16:39 +0000 Subject: [PATCH 03/11] chore: respect the animation preference --- .../frontend/src/components/global/MkAvatar.vue | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index ed104cb0f..14075cd7b 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -1,5 +1,5 @@ <template> -<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> +<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> <img :class="$style.inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/> <div v-if="user.isCat" :class="[$style.ears, { [$style.mask]: useBlurEffect }]"> @@ -27,6 +27,7 @@ import { acct, userPage } from '@/filters/user'; import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; import { defaultStore } from '@/store'; +const animation = $ref(defaultStore.state.animation); const squareAvatars = $ref(defaultStore.state.squareAvatars); const useBlurEffect = $ref(defaultStore.state.useBlurEffect); @@ -156,6 +157,14 @@ watch(() => props.user.avatarBlurhash, () => { mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><filter id="a"><feGaussianBlur in="SourceGraphic" stdDeviation="1"/></filter><circle cx="16" cy="16" r="15" filter="url(%23a)"/></svg>') exclude center / 50% 50%, linear-gradient(#fff, #fff); // polyfill of `image(#fff)` + + > .earLeft { + animation: eartightright 6s infinite; + } + + > .earRight { + animation: eartightleft 6s infinite; + } } > .earLeft, @@ -196,7 +205,6 @@ watch(() => props.user.avatarBlurhash, () => { > .earLeft { transform: rotate(37.5deg) skew(30deg); - animation: eartightleft 6s infinite; &, &::after { border-radius: 0 75% 75%; @@ -218,7 +226,6 @@ watch(() => props.user.avatarBlurhash, () => { > .earRight { transform: rotate(-37.5deg) skew(-30deg); - animation: eartightright 6s infinite; &, &::after { border-radius: 75% 0 75% 75%; @@ -239,7 +246,7 @@ watch(() => props.user.avatarBlurhash, () => { } } - &:hover { + &.animation:hover { > .ears { > .earLeft { animation: earwiggleleft 1s infinite; From 8963e36aa200ac9f663265315223a07544a17965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Mon, 27 Mar 2023 05:25:32 +0000 Subject: [PATCH 04/11] docs: note change logs --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8fa82ad2..fdb88c017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,13 @@ - ### Client -- +- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更 + - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります + - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように + - 猫耳の色がアバター上部のピクセルから決定されます(無効化時はアバター全体の平均色) + - 左耳は上からおよそ 10%, 左からおよそ 20% の位置で決定します + - 右耳は上からおよそ 10%, 左からおよそ 80% の位置で決定します + - 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります ### Server - From 49d4c538fee8a44796b26cd987b953e2cc770c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Wed, 29 Mar 2023 23:37:43 +0900 Subject: [PATCH 05/11] fix: typo --- packages/frontend/src/components/global/MkAvatar.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 14075cd7b..9a21941c8 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -159,11 +159,11 @@ watch(() => props.user.avatarBlurhash, () => { linear-gradient(#fff, #fff); // polyfill of `image(#fff)` > .earLeft { - animation: eartightright 6s infinite; + animation: eartightleft 6s infinite; } > .earRight { - animation: eartightleft 6s infinite; + animation: eartightright 6s infinite; } } From 14977ed2b703aaeafb26fd811035ed8fa7166ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Fri, 31 Mar 2023 07:47:03 +0000 Subject: [PATCH 06/11] fix: the avatar in the title bar is clipped --- .../src/components/global/MkPageHeader.vue | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 31f23486d..710edd797 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -252,17 +252,18 @@ onUnmounted(() => { } .titleAvatarContainer { - overflow: hidden; - padding: 8px 0; + $size: 32px; + contain: strict; + overflow: clip; + width: $size; + height: $size; + padding: 8px; + flex-shrink: 0; } .titleAvatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: bottom; - margin: 0 8px; + width: 100%; + height: 100%; pointer-events: none; } From 4f9f625e6574990dfcd736c7a7d059af8fef7234 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 3 Apr 2023 11:49:58 +0900 Subject: [PATCH 07/11] perf(backend): cache timeline of a channel to redis --- packages/backend/src/core/IdService.ts | 15 +++++++++- .../backend/src/core/NoteCreateService.ts | 8 +++++ packages/backend/src/misc/id/aid.ts | 5 ++++ .../server/api/endpoints/channels/timeline.ts | 29 +++++++++++++++++-- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 31c0819e5..94084ad84 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ulid } from 'ulid'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import { genAid } from '@/misc/id/aid.js'; +import { genAid, parseAid } from '@/misc/id/aid.js'; import { genMeid } from '@/misc/id/meid.js'; import { genMeidg } from '@/misc/id/meidg.js'; import { genObjectId } from '@/misc/id/object-id.js'; @@ -32,4 +32,17 @@ export class IdService { default: throw new Error('unrecognized id generation method'); } } + + @bindThis + public parse(id: string): { date: Date; } { + switch (this.method) { + case 'aid': return parseAid(id); + // TODO + //case 'meid': + //case 'meidg': + //case 'ulid': + //case 'objectid': + default: throw new Error('unrecognized id generation method'); + } + } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 7d0805376..93fab9d17 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -1,6 +1,7 @@ import { setImmediate } from 'node:timers/promises'; import * as mfm from 'mfm-js'; import { In, DataSource } from 'typeorm'; +import Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; @@ -150,6 +151,9 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -321,6 +325,10 @@ export class NoteCreateService implements OnApplicationShutdown { const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); + if (data.channel) { + this.redisClient.xadd(`channelTimeline:${data.channel.id}`, 'MAXLEN', '~', '1000', `${this.idService.parse(note.id).date.getTime()}-*`, 'note', note.id); + } + setImmediate('post created', { signal: this.#shutdownController.signal }).then( () => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!), () => { /* aborted, ignore this */ }, diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index 19c8546f9..93a9929aa 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -23,3 +23,8 @@ export function genAid(date: Date): string { counter++; return getTime(t) + getNoise(); } + +export function parseAid(id: string): { date: Date; } { + const time = parseInt(id.slice(0, 8), 36) + TIME2000; + return { date: new Date(time) }; +} diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index cdaa40013..eef343d13 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,10 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ChannelsRepository, NotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -48,12 +50,16 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, + private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, private activeUsersChart: ActiveUsersChart, @@ -67,9 +73,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchChannel); } + const noteIdsRes = await this.redisClient.xrevrange( + `channelTimeline:${channel.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+', + '-', + 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1 + + if (noteIdsRes.length === 0) { + return []; + } + + const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId); + + if (noteIds.length === 0) { + return []; + } + //#region Construct query - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere('note.channelId = :channelId', { channelId: channel.id }) + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') @@ -90,7 +112,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.getMany(); + timeline.sort((a, b) => a.id > b.id ? -1 : 1); if (me) this.activeUsersChart.read(me); From c032dd1214df6f7640e0ee2a39d25796e622a1b2 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 3 Apr 2023 11:50:17 +0900 Subject: [PATCH 08/11] fix(frontend): tweak MkPagination behaviouyr --- .../frontend/src/components/MkPagination.vue | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 867d43257..cd8af560e 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -163,21 +163,22 @@ async function init(): Promise<void> { const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; await os.api(props.pagination.endpoint, { ...params, - limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1, + limit: props.pagination.limit ?? 10, }).then(res => { for (let i = 0; i < res.length; i++) { const item = res[i]; if (i === 3) item._shouldInsertAd_ = true; } - if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) { - res.pop(); + + if (res.length === 0 || props.pagination.noPaging) { + items.value = res; + more.value = false; + } else { if (props.pagination.reversed) moreFetching.value = true; items.value = res; more.value = true; - } else { - items.value = res; - more.value = false; } + offset.value = res.length; error.value = false; fetching.value = false; @@ -198,7 +199,7 @@ const fetchMore = async (): Promise<void> => { const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; await os.api(props.pagination.endpoint, { ...params, - limit: SECOND_FETCH_LIMIT + 1, + limit: SECOND_FETCH_LIMIT, ...(props.pagination.offsetMode ? { offset: offset.value, } : { @@ -227,20 +228,7 @@ const fetchMore = async (): Promise<void> => { }); }; - if (res.length > SECOND_FETCH_LIMIT) { - res.pop(); - - if (props.pagination.reversed) { - reverseConcat(res).then(() => { - more.value = true; - moreFetching.value = false; - }); - } else { - items.value = items.value.concat(res); - more.value = true; - moreFetching.value = false; - } - } else { + if (res.length === 0) { if (props.pagination.reversed) { reverseConcat(res).then(() => { more.value = false; @@ -251,6 +239,17 @@ const fetchMore = async (): Promise<void> => { more.value = false; moreFetching.value = false; } + } else { + if (props.pagination.reversed) { + reverseConcat(res).then(() => { + more.value = true; + moreFetching.value = false; + }); + } else { + items.value = items.value.concat(res); + more.value = true; + moreFetching.value = false; + } } offset.value += res.length; }, err => { @@ -264,20 +263,19 @@ const fetchMoreAhead = async (): Promise<void> => { const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; await os.api(props.pagination.endpoint, { ...params, - limit: SECOND_FETCH_LIMIT + 1, + limit: SECOND_FETCH_LIMIT, ...(props.pagination.offsetMode ? { offset: offset.value, } : { sinceId: items.value[items.value.length - 1].id, }), }).then(res => { - if (res.length > SECOND_FETCH_LIMIT) { - res.pop(); - items.value = items.value.concat(res); - more.value = true; - } else { + if (res.length === 0) { items.value = items.value.concat(res); more.value = false; + } else { + items.value = items.value.concat(res); + more.value = true; } offset.value += res.length; moreFetching.value = false; From b53d6c7f8ca1a712eab44967e8d05a0cc7bcc034 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 3 Apr 2023 12:11:16 +0900 Subject: [PATCH 09/11] perf(backend): store notes of an antenna to redis instead of postgresql Resolve #10169 --- CHANGELOG.md | 2 + .../migration/1680491187535-cleanup.js | 10 +++ packages/backend/src/core/AntennaService.ts | 61 +++---------------- .../backend/src/core/NoteCreateService.ts | 6 +- packages/backend/src/core/NoteReadService.ts | 43 +------------ .../src/core/entities/AntennaEntityService.ts | 9 +-- .../src/core/entities/UserEntityService.ts | 8 +-- packages/backend/src/di-symbols.ts | 1 - .../backend/src/models/RepositoryModule.ts | 10 +-- .../src/models/entities/AntennaNote.ts | 43 ------------- packages/backend/src/models/index.ts | 3 - packages/backend/src/postgres.ts | 2 - .../queue/processors/CleanProcessorService.ts | 5 +- .../server/api/endpoints/antennas/notes.ts | 40 ++++++++---- 14 files changed, 64 insertions(+), 179 deletions(-) create mode 100644 packages/backend/migration/1680491187535-cleanup.js delete mode 100644 packages/backend/src/models/entities/AntennaNote.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 711d9db62..5f3501735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ### General - チャンネルをお気に入りに登録できるように - チャンネルにノートをピン留めできるように +- アンテナのタイムライン取得時のパフォーマンスを向上 +- チャンネルのタイムライン取得時のパフォーマンスを向上 ### Client - 検索ページでURLを入力した際に照会したときと同等の挙動をするように diff --git a/packages/backend/migration/1680491187535-cleanup.js b/packages/backend/migration/1680491187535-cleanup.js new file mode 100644 index 000000000..1e609ca06 --- /dev/null +++ b/packages/backend/migration/1680491187535-cleanup.js @@ -0,0 +1,10 @@ +export class cleanup1680491187535 { + name = 'cleanup1680491187535' + + async up(queryRunner) { + await queryRunner.query(`DROP TABLE "antenna_note" `); + } + + async down(queryRunner) { + } +} diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index aaa26a832..4bd3f39af 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -12,7 +12,7 @@ import { PushNotificationService } from '@/core/PushNotificationService.js'; import * as Acct from '@/misc/acct.js'; import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { MutingsRepository, NotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { StreamMessages } from '@/server/api/stream/types.js'; @@ -24,6 +24,9 @@ export class AntennaService implements OnApplicationShutdown { private antennas: Antenna[]; constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.redisSubscriber) private redisSubscriber: Redis.Redis, @@ -33,9 +36,6 @@ export class AntennaService implements OnApplicationShutdown { @Inject(DI.notesRepository) private notesRepository: NotesRepository, - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, - @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, @@ -92,54 +92,13 @@ export class AntennaService implements OnApplicationShutdown { @bindThis public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise<void> { - // 通知しない設定になっているか、自分自身の投稿なら既読にする - const read = !antenna.notify || (antenna.userId === noteUser.id); - - this.antennaNotesRepository.insert({ - id: this.idService.genId(), - antennaId: antenna.id, - noteId: note.id, - read: read, - }); - + this.redisClient.xadd( + `antennaTimeline:${antenna.id}`, + 'MAXLEN', '~', '200', + `${this.idService.parse(note.id).date.getTime()}-*`, + 'note', note.id); + this.globalEventService.publishAntennaStream(antenna.id, 'note', note); - - if (!read) { - const mutings = await this.mutingsRepository.find({ - where: { - muterId: antenna.userId, - }, - select: ['muteeId'], - }); - - // Copy - const _note: Note = { - ...note, - }; - - if (note.replyId != null) { - _note.reply = await this.notesRepository.findOneByOrFail({ id: note.replyId }); - } - if (note.renoteId != null) { - _note.renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); - } - - if (isUserRelated(_note, new Set<string>(mutings.map(x => x.muteeId)))) { - return; - } - - // 2秒経っても既読にならなかったら通知 - setTimeout(async () => { - const unread = await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false }); - if (unread) { - this.globalEventService.publishMainStream(antenna.userId, 'unreadAntenna', antenna); - this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', { - antenna: { id: antenna.id, name: antenna.name }, - note: await this.noteEntityService.pack(note), - }); - } - }, 2000); - } } // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 93fab9d17..7af709943 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -326,7 +326,11 @@ export class NoteCreateService implements OnApplicationShutdown { const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); if (data.channel) { - this.redisClient.xadd(`channelTimeline:${data.channel.id}`, 'MAXLEN', '~', '1000', `${this.idService.parse(note.id).date.getTime()}-*`, 'note', note.id); + this.redisClient.xadd( + `channelTimeline:${data.channel.id}`, + 'MAXLEN', '~', '1000', + `${this.idService.parse(note.id).date.getTime()}-*`, + 'note', note.id); } setImmediate('post created', { signal: this.#shutdownController.signal }).then( diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 22d72815e..1bf0eb918 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -8,7 +8,7 @@ import type { Packed } from '@/misc/json-schema.js'; import type { Note } from '@/models/entities/Note.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js'; +import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository } from '@/models/index.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from './NotificationService.js'; @@ -38,9 +38,6 @@ export class NoteReadService implements OnApplicationShutdown { @Inject(DI.channelFollowingsRepository) private channelFollowingsRepository: ChannelFollowingsRepository, - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, - private userEntityService: UserEntityService, private idService: IdService, private globalEventService: GlobalEventService, @@ -121,7 +118,6 @@ export class NoteReadService implements OnApplicationShutdown { const readMentions: (Note | Packed<'Note'>)[] = []; const readSpecifiedNotes: (Note | Packed<'Note'>)[] = []; const readChannelNotes: (Note | Packed<'Note'>)[] = []; - const readAntennaNotes: (Note | Packed<'Note'>)[] = []; for (const note of notes) { if (note.mentions && note.mentions.includes(userId)) { @@ -133,14 +129,6 @@ export class NoteReadService implements OnApplicationShutdown { if (note.channelId && followingChannels.has(note.channelId)) { readChannelNotes.push(note); } - - if (note.user != null) { // たぶんnullになることは無いはずだけど一応 - for (const antenna of myAntennas) { - if (await this.antennaService.checkHitAntenna(antenna, note, note.user)) { - readAntennaNotes.push(note); - } - } - } } if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) { @@ -186,35 +174,6 @@ export class NoteReadService implements OnApplicationShutdown { noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]), }); } - - if (readAntennaNotes.length > 0) { - await this.antennaNotesRepository.update({ - antennaId: In(myAntennas.map(a => a.id)), - noteId: In(readAntennaNotes.map(n => n.id)), - }, { - read: true, - }); - - // TODO: まとめてクエリしたい - for (const antenna of myAntennas) { - const count = await this.antennaNotesRepository.countBy({ - antennaId: antenna.id, - read: false, - }); - - if (count === 0) { - this.globalEventService.publishMainStream(userId, 'readAntenna', antenna); - this.pushNotificationService.pushNotification(userId, 'readAntenna', { antennaId: antenna.id }); - } - } - - this.userEntityService.getHasUnreadAntenna(userId).then(unread => { - if (!unread) { - this.globalEventService.publishMainStream(userId, 'readAllAntennas'); - this.pushNotificationService.pushNotification(userId, 'readAllAntennas', undefined); - } - }); - } } onApplicationShutdown(signal?: string | undefined): void { diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index e02daefd6..328511f5d 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { AntennaNotesRepository, AntennasRepository } from '@/models/index.js'; +import type { AntennasRepository } from '@/models/index.js'; import type { Packed } from '@/misc/json-schema.js'; import type { Antenna } from '@/models/entities/Antenna.js'; import { bindThis } from '@/decorators.js'; @@ -10,9 +10,6 @@ export class AntennaEntityService { constructor( @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, - - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, ) { } @@ -22,8 +19,6 @@ export class AntennaEntityService { ): Promise<Packed<'Antenna'>> { const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src }); - const hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null; - return { id: antenna.id, createdAt: antenna.createdAt.toISOString(), @@ -38,7 +33,7 @@ export class AntennaEntityService { withReplies: antenna.withReplies, withFile: antenna.withFile, isActive: antenna.isActive, - hasUnreadNote, + hasUnreadNote: false, // TODO }; } } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index b693883e0..61fd6f2f6 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -12,7 +12,7 @@ import { KVCache } from '@/misc/cache.js'; import type { Instance } from '@/models/entities/Instance.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; -import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -108,9 +108,6 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.announcementsRepository) private announcementsRepository: AnnouncementsRepository, - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, - @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, @@ -223,6 +220,7 @@ export class UserEntityService implements OnModuleInit { @bindThis public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> { + /* const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); const unread = myAntennas.length > 0 ? await this.antennaNotesRepository.findOneBy({ @@ -231,6 +229,8 @@ export class UserEntityService implements OnModuleInit { }) : null; return unread != null; + */ + return false; // TODO } @bindThis diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 4f475a03a..f2ab6cb86 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -54,7 +54,6 @@ export const DI = { clipNotesRepository: Symbol('clipNotesRepository'), clipFavoritesRepository: Symbol('clipFavoritesRepository'), antennasRepository: Symbol('antennasRepository'), - antennaNotesRepository: Symbol('antennaNotesRepository'), promoNotesRepository: Symbol('promoNotesRepository'), promoReadsRepository: Symbol('promoReadsRepository'), relaysRepository: Symbol('relaysRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index da7faf9ff..b74ee3689 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -298,12 +298,6 @@ const $antennasRepository: Provider = { inject: [DI.db], }; -const $antennaNotesRepository: Provider = { - provide: DI.antennaNotesRepository, - useFactory: (db: DataSource) => db.getRepository(AntennaNote), - inject: [DI.db], -}; - const $promoNotesRepository: Provider = { provide: DI.promoNotesRepository, useFactory: (db: DataSource) => db.getRepository(PromoNote), @@ -453,7 +447,6 @@ const $roleAssignmentsRepository: Provider = { $clipNotesRepository, $clipFavoritesRepository, $antennasRepository, - $antennaNotesRepository, $promoNotesRepository, $promoReadsRepository, $relaysRepository, @@ -521,7 +514,6 @@ const $roleAssignmentsRepository: Provider = { $clipNotesRepository, $clipFavoritesRepository, $antennasRepository, - $antennaNotesRepository, $promoNotesRepository, $promoReadsRepository, $relaysRepository, diff --git a/packages/backend/src/models/entities/AntennaNote.ts b/packages/backend/src/models/entities/AntennaNote.ts deleted file mode 100644 index 5524a8936..000000000 --- a/packages/backend/src/models/entities/AntennaNote.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm'; -import { id } from '../id.js'; -import { Note } from './Note.js'; -import { Antenna } from './Antenna.js'; - -@Entity() -@Index(['noteId', 'antennaId'], { unique: true }) -export class AntennaNote { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column({ - ...id(), - comment: 'The note ID.', - }) - public noteId: Note['id']; - - @ManyToOne(type => Note, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public note: Note | null; - - @Index() - @Column({ - ...id(), - comment: 'The antenna ID.', - }) - public antennaId: Antenna['id']; - - @ManyToOne(type => Antenna, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public antenna: Antenna | null; - - @Index() - @Column('boolean', { - default: false, - }) - public read: boolean; -} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 79bd014ce..c4c9717ed 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -4,7 +4,6 @@ import { Ad } from '@/models/entities/Ad.js'; import { Announcement } from '@/models/entities/Announcement.js'; import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js'; import { Antenna } from '@/models/entities/Antenna.js'; -import { AntennaNote } from '@/models/entities/AntennaNote.js'; import { App } from '@/models/entities/App.js'; import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'; import { AuthSession } from '@/models/entities/AuthSession.js'; @@ -73,7 +72,6 @@ export { Announcement, AnnouncementRead, Antenna, - AntennaNote, App, AttestationChallenge, AuthSession, @@ -141,7 +139,6 @@ export type AdsRepository = Repository<Ad>; export type AnnouncementsRepository = Repository<Announcement>; export type AnnouncementReadsRepository = Repository<AnnouncementRead>; export type AntennasRepository = Repository<Antenna>; -export type AntennaNotesRepository = Repository<AntennaNote>; export type AppsRepository = Repository<App>; export type AttestationChallengesRepository = Repository<AttestationChallenge>; export type AuthSessionsRepository = Repository<AuthSession>; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index cbe3814a2..024aa114f 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -12,7 +12,6 @@ import { Ad } from '@/models/entities/Ad.js'; import { Announcement } from '@/models/entities/Announcement.js'; import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js'; import { Antenna } from '@/models/entities/Antenna.js'; -import { AntennaNote } from '@/models/entities/AntennaNote.js'; import { App } from '@/models/entities/App.js'; import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'; import { AuthSession } from '@/models/entities/AuthSession.js'; @@ -168,7 +167,6 @@ export const entities = [ ClipNote, ClipFavorite, Antenna, - AntennaNote, PromoNote, PromoRead, Relay, diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 9534454fd..3feb86f86 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AntennaNotesRepository, AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js'; +import type { AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; @@ -29,9 +29,6 @@ export class CleanProcessorService { @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, - @Inject(DI.roleAssignmentsRepository) private roleAssignmentsRepository: RoleAssignmentsRepository, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 039ba1115..364f9d9c0 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,10 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { NotesRepository, AntennaNotesRepository, AntennasRepository } from '@/models/index.js'; +import type { NotesRepository, AntennasRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteReadService } from '@/core/NoteReadService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { IdService } from '@/core/IdService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -50,15 +52,16 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, - @Inject(DI.antennaNotesRepository) - private antennaNotesRepository: AntennaNotesRepository, - + private idService: IdService, private noteEntityService: NoteEntityService, private queryService: QueryService, private noteReadService: NoteReadService, @@ -73,9 +76,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.noSuchAntenna); } - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .innerJoin(this.antennaNotesRepository.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id') + const noteIdsRes = await this.redisClient.xrevrange( + `antennaTimeline:${antenna.id}`, + ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+', + '-', + 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1 + + if (noteIdsRes.length === 0) { + return []; + } + + const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId); + + if (noteIds.length === 0) { + return []; + } + + const query = this.notesRepository.createQueryBuilder('note') + .where('note.id IN (:...noteIds)', { noteIds: noteIds }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') @@ -86,16 +104,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') - .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id }); + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQuery(query, me); this.queryService.generateBlockedUserQuery(query, me); - const notes = await query - .take(ps.limit) - .getMany(); + const notes = await query.getMany(); + notes.sort((a, b) => a.id > b.id ? -1 : 1); if (notes.length > 0) { this.noteReadService.read(me.id, notes); From 92ddebb3fd46da4e75a91f9afb221088744f222a Mon Sep 17 00:00:00 2001 From: nexryai <61890205+nexryai@users.noreply.github.com> Date: Mon, 3 Apr 2023 12:47:30 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=E3=82=AA=E3=83=96=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=82=B9=E3=83=88=E3=83=AC=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=81=AE=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=82=92=E5=88=86?= =?UTF-8?q?=E3=81=8B=E3=82=8A=E3=82=84=E3=81=99=E3=81=8F=E3=81=99=E3=82=8B?= =?UTF-8?q?=20(#10456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: Improved object storage configuration page * Update ja-JP.yml * Update CHANGELOG --- CHANGELOG.md | 1 + locales/ja-JP.yml | 1 + packages/frontend/src/pages/admin/object-storage.vue | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 711d9db62..0b33c767e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Client - 検索ページでURLを入力した際に照会したときと同等の挙動をするように - ノートのリアクションを大きく表示するオプションを追加 +- オブジェクトストレージの設定画面を分かりやすく ### Server - diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a9c54810a..a4f1d802c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -506,6 +506,7 @@ objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフに objectStorageUseProxy: "Proxyを利用する" objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください" objectStorageSetPublicRead: "アップロード時に'public-read'を設定する" +s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。" serverLogs: "サーバーログ" deleteAll: "全て削除" showFixedPostForm: "タイムライン上部に投稿フォームを表示する" diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue index cbe38b2d8..704b27c17 100644 --- a/packages/frontend/src/pages/admin/object-storage.vue +++ b/packages/frontend/src/pages/admin/object-storage.vue @@ -7,7 +7,7 @@ <MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch> <template v-if="useObjectStorage"> - <MkInput v-model="objectStorageBaseUrl"> + <MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'"> <template #label>{{ i18n.ts.objectStorageBaseUrl }}</template> <template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template> </MkInput> @@ -22,8 +22,9 @@ <template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template> </MkInput> - <MkInput v-model="objectStorageEndpoint"> + <MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'"> <template #label>{{ i18n.ts.objectStorageEndpoint }}</template> + <template #prefix>https://</template> <template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template> </MkInput> @@ -60,6 +61,7 @@ <MkSwitch v-model="objectStorageS3ForcePathStyle"> <template #label>s3ForcePathStyle</template> + <template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template> </MkSwitch> </template> </div> From 32149f51228aea0c5de42a8242b1c56c509db1f3 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Mon, 3 Apr 2023 14:11:08 +0900 Subject: [PATCH 11/11] Update CHANGELOG.md --- CHANGELOG.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8612220c8..5cee783ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,7 @@ - ### Client -- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更 - - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります - - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように - - 猫耳の色がアバター上部のピクセルから決定されます(無効化時はアバター全体の平均色) - - 左耳は上からおよそ 10%, 左からおよそ 20% の位置で決定します - - 右耳は上からおよそ 10%, 左からおよそ 80% の位置で決定します - - 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります +- ### Server - @@ -23,16 +17,23 @@ ### General - チャンネルをお気に入りに登録できるように - チャンネルにノートをピン留めできるように -- アンテナのタイムライン取得時のパフォーマンスを向上 -- チャンネルのタイムライン取得時のパフォーマンスを向上 ### Client - 検索ページでURLを入力した際に照会したときと同等の挙動をするように - ノートのリアクションを大きく表示するオプションを追加 - オブジェクトストレージの設定画面を分かりやすく +- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更 + - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります + - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように + - 猫耳の色がアバター上部のピクセルから決定されます(無効化時はアバター全体の平均色) + - 左耳は上からおよそ 10%, 左からおよそ 20% の位置で決定します + - 右耳は上からおよそ 10%, 左からおよそ 80% の位置で決定します + - 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります ### Server -- +- ノート作成時のパフォーマンスを向上 +- アンテナのタイムライン取得時のパフォーマンスを向上 +- チャンネルのタイムライン取得時のパフォーマンスを向上 ## 13.10.3