From dbc24ce58767774c38e58424e8d5dc544e80a224 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 3 May 2023 16:38:52 +0900 Subject: [PATCH 001/105] Update about-misskey.vue --- packages/frontend/src/pages/about-misskey.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index dbbe4c4686..101c737f9c 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -141,6 +141,9 @@ const patronsWithIcon = [{ }, { name: 'ぺんぎん', icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg', +}, { + name: 'かみらえっと', + icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg', }]; const patrons = [ From 2c606028b37062174bd99841d2486ed77d12f66f Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 08:05:04 +0900 Subject: [PATCH 002/105] :art --- packages/frontend/src/components/global/MkPageHeader.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 710edd797a..b91d378b17 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -156,7 +156,7 @@ onUnmounted(() => { } &.thin { - --height: 42px; + --height: 40px; > .buttons { > .button { From 53498991bbf7ce0b4dbcae55ac86893d45517a17 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 08:05:33 +0900 Subject: [PATCH 003/105] Update about-misskey.vue --- packages/frontend/src/pages/about-misskey.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 101c737f9c..e592c629ce 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -237,6 +237,7 @@ const patrons = [ 'けそ', 'ずも', 'binvinyl', + '渡志郎', ]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); From 2cfed3395e50712c73248512fe7723394a299517 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?= Date: Fri, 5 May 2023 08:16:55 +0900 Subject: [PATCH 004/105] feat: condense acct (#10753) * feat: condense acct * fix: watch parent element size --------- Co-authored-by: syuilo --- .../components/global/MkAcct.stories.impl.ts | 16 ++++++ .../frontend/src/components/global/MkAcct.vue | 5 +- .../global/MkCondensedLine.stories.impl.ts | 39 +++++++++++++ .../src/components/global/MkCondensedLine.vue | 56 +++++++++++++++++++ packages/frontend/src/components/index.ts | 3 + 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkCondensedLine.vue diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts index d5e3fc3568..68202bb705 100644 --- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts @@ -41,3 +41,19 @@ export const Detail = { detail: true, }, } satisfies StoryObj; +export const Long = { + ...Default, + args: { + ...Default.args, + user: { + ...userDetailed(), + username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc', + host: 'nostr.example', + }, + }, + decorators: [ + () => ({ + template: '
', + }), + ], +} satisfies StoryObj; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 2b9f892fc6..8a93a5adf7 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,13 +1,14 @@ + + + + diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 63e8fc225c..4ef8111da9 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -5,6 +5,7 @@ import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; import MkEmoji from './global/MkEmoji.vue'; +import MkCondensedLine from './global/MkCondensedLine.vue'; import MkCustomEmoji from './global/MkCustomEmoji.vue'; import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; @@ -33,6 +34,7 @@ export const components = { MkAcct: MkAcct, MkAvatar: MkAvatar, MkEmoji: MkEmoji, + MkCondensedLine: MkCondensedLine, MkCustomEmoji: MkCustomEmoji, MkUserName: MkUserName, MkEllipsis: MkEllipsis, @@ -55,6 +57,7 @@ declare module '@vue/runtime-core' { MkAcct: typeof MkAcct; MkAvatar: typeof MkAvatar; MkEmoji: typeof MkEmoji; + MkCondensedLine: typeof MkCondensedLine; MkCustomEmoji: typeof MkCustomEmoji; MkUserName: typeof MkUserName; MkEllipsis: typeof MkEllipsis; From febb9f388c3068f23fc806dac1a5b5580af22bfc Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 08:34:05 +0900 Subject: [PATCH 005/105] enhance(frontend): make MkCondensedLine experimental --- .../frontend/src/components/global/MkAcct.vue | 6 ++- .../frontend/src/pages/settings/other.vue | 42 ++++++++++++++++--- packages/frontend/src/store.ts | 4 ++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 8a93a5adf7..3b0715a23c 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,8 +1,12 @@ diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 05394b9187..dd80dd737b 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -11,21 +11,50 @@ -->
- {{ i18n.ts.accountInfo }} + + + + +
+ + + + + + + + + + + {{ i18n.ts.statistics }} +
+
+ {{ i18n.ts.registry }} - {{ i18n.ts.closeAccount }} + + + + + +
+ {{ i18n.ts._accountDelete.mayTakeTime }} + {{ i18n.ts._accountDelete.sendEmail }} + {{ i18n.ts._accountDelete.requestAccountDelete }} + {{ i18n.ts._accountDelete.inProgress }} +
+
+ + + + + +
+ + + +
+
- - - - - -
- - - -
-
@@ -34,9 +63,12 @@ import { computed, watch } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import FormLink from '@/components/form/link.vue'; import MkFolder from '@/components/MkFolder.vue'; +import FormInfo from '@/components/MkInfo.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; +import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; -import { $i } from '@/account'; +import { signout, $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import { unisonReload } from '@/scripts/unison-reload'; @@ -52,6 +84,32 @@ function onChangeInjectFeaturedNote(v) { }); } +async function deleteAccount() { + { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (canceled) return; + } + + const { canceled, result: password } = await os.inputText({ + title: i18n.ts.password, + type: 'password', + }); + if (canceled) return; + + await os.apiWithDialog('i/delete-account', { + password: password, + }); + + await os.alert({ + title: i18n.ts._accountDelete.started, + }); + + await signout(); +} + async function reloadAsk() { const { canceled } = await os.confirm({ type: 'info', diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index fa19682f32..e46c1eeb77 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -164,7 +164,7 @@ export const routes = [{ }, { path: '/migration', name: 'migration', - component: page(() => import('./pages/settings/migration.vue')) + component: page(() => import('./pages/settings/migration.vue')), }, { path: '/custom-css', name: 'general', @@ -174,13 +174,9 @@ export const routes = [{ name: 'profile', component: page(() => import('./pages/settings/accounts.vue')), }, { - path: '/account-info', + path: '/account-stats', name: 'other', - component: page(() => import('./pages/settings/account-info.vue')), - }, { - path: '/delete-account', - name: 'other', - component: page(() => import('./pages/settings/delete-account.vue')), + component: page(() => import('./pages/settings/account-stats.vue')), }, { path: '/other', name: 'other', From 8dab46470eb501e2ed4be12d0f6010210614441d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=83=BC=E3=81=B3=E3=82=93?= Date: Fri, 5 May 2023 08:48:14 +0900 Subject: [PATCH 008/105] =?UTF-8?q?fix=20#10666=20=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E6=A4=9C=E7=B4=A2=E3=81=A7=E3=81=99?= =?UTF-8?q?=E3=81=B9=E3=81=A6=E3=81=AE=E3=83=81=E3=83=A3=E3=83=B3=E3=83=8D?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E5=8F=96=E5=BE=97/=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=8C=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B=20(#10667)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update CHANGELOG.md * fix : able to search all channels * add chennel/search test * update Changelog --------- Co-authored-by: tamaina Co-authored-by: syuilo Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com> Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com> Co-authored-by: Kagami Sascha Rosylight Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com> Co-authored-by: xianon Co-authored-by: kabo2468 <28654659+kabo2468@users.noreply.github.com> Co-authored-by: YS <47836716+yszkst@users.noreply.github.com> Co-authored-by: Khsmty Co-authored-by: Soni L Co-authored-by: mei23 Co-authored-by: daima3629 <52790780+daima3629@users.noreply.github.com> Co-authored-by: Windymelt <1113940+windymelt@users.noreply.github.com> Co-authored-by: Ebise Lutica <7106976+EbiseLutica@users.noreply.github.com> --- CHANGELOG.md | 2 + .../server/api/endpoints/channels/search.ts | 16 ++-- packages/backend/test/e2e/endpoints.ts | 94 +++++++++++++++++++ packages/frontend/src/pages/channels.vue | 2 +- 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 204f7227dc..893258fbe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Fix: フォローリクエストの通知が残る問題を修正 ### Client +- チャンネル検索ですべてのチャンネルの取得/表示ができるように - 通知の表示をカスタマイズできるように - コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように * 絵文字ピッカーから入力可能になります @@ -46,6 +47,7 @@ - ドライブのファイル一覧から直接ノートを作成できるように ### Server +- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更 - 環境変数MISSKEY_CONFIG_YMLで設定ファイルをdefault.ymlから変更可能に - Fix: 他のサーバーの情報が取得できないことがある問題を修正 - Fix: エクスポートデータの拡張子がunknownになる問題を修正 diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts index a954ba224c..900723ff8a 100644 --- a/packages/backend/src/server/api/endpoints/channels/search.ts +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -48,13 +48,15 @@ export default class extends Endpoint { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId); - if (ps.type === 'nameAndDescription') { - query.andWhere(new Brackets(qb => { qb - .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) - .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); - })); - } else { - query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + if (ps.query !== '') { + if (ps.type === 'nameAndDescription') { + query.andWhere(new Brackets(qb => { qb + .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) + .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + })); + } else { + query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + } } const channels = await query diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 6898435084..f885209b7f 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -403,6 +403,100 @@ describe('Endpoints', () => { }); }); + describe('channels/search', () => { + test('空白検索で一覧を取得できる', async () => { + await api('/channels/create', { + name: 'aaa', + description: 'bbb', + }, bob); + await api('/channels/create', { + name: 'ccc1', + description: 'ddd1', + }, bob); + await api('/channels/create', { + name: 'ccc2', + description: 'ddd2', + }, bob); + + const res = await api('/channels/search', { + query: '', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 3); + }); + test('名前のみの検索で名前を検索できる', async () => { + const res = await api('/channels/search', { + query: 'aaa', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'aaa'); + }); + test('名前のみの検索で名前を複数検索できる', async () => { + const res = await api('/channels/search', { + query: 'ccc', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + test('名前のみの検索で説明は検索できない', async () => { + const res = await api('/channels/search', { + query: 'bbb', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 0); + }); + test('名前と説明の検索で名前を検索できる', async () => { + const res = await api('/channels/search', { + query: 'ccc1', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'ccc1'); + }); + test('名前と説明での検索で説明を検索できる', async () => { + const res = await api('/channels/search', { + query: 'ddd1', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'ccc1'); + }); + test('名前と説明の検索で名前を複数検索できる', async () => { + const res = await api('/channels/search', { + query: 'ccc', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + test('名前と説明での検索で説明を複数検索できる', async () => { + const res = await api('/channels/search', { + query: 'ddd', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + }); + describe('drive', () => { test('ドライブ情報を取得できる', async () => { await uploadFile(alice, { diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index 70e7705d1d..e670cdd864 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -96,7 +96,7 @@ const ownedPagination = { async function search() { const query = searchQuery.toString().trim(); - if (query == null || query === '') return; + if (query == null) return; const type = searchType.toString().trim(); From 5f62cefe313a9e045cd718ea175cac150c257eff Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 08:50:25 +0900 Subject: [PATCH 009/105] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 893258fbe9..f3adee8f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,16 +35,16 @@ ### Client - チャンネル検索ですべてのチャンネルの取得/表示ができるように - 通知の表示をカスタマイズできるように +- ドライブのファイル一覧から直接ノートを作成できるように +- ノートメニューからRenoteしたユーザーの一覧を見れるように - コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように * 絵文字ピッカーから入力可能になります - データセーバーモードを追加 * 画像が全て隠れた状態で表示されるようになります - 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように - 新しい実績を追加 -- Renoteしたユーザーの一覧を見れるように - Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正 - Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正 -- ドライブのファイル一覧から直接ノートを作成できるように ### Server - channel/searchのqueryが空の場合に全てのチャンネルを返すように変更 From 5c08f2b93b4a9f5bac0718d5b202b83314f4cb7c Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 08:52:14 +0900 Subject: [PATCH 010/105] feat: Introduce Meilisearch (#10755) * wip * wip * Update SearchService.ts * Update SearchService.ts * wip * wip * Update SearchService.ts * Update CHANGELOG.md * wip * Update SearchService.ts * Update docker-compose.yml.example --- .config/docker_example.yml | 14 +- .config/example.yml | 12 +- .devcontainer/devcontainer.yml | 14 +- .dockerignore | 1 - .gitignore | 2 +- CHANGELOG.md | 1 + chart/files/default.yml | 12 +- docker-compose.yml.example | 14 +- packages/backend/package.json | 1 + packages/backend/src/GlobalModule.ts | 20 ++- packages/backend/src/config.ts | 10 +- packages/backend/src/core/CoreModule.ts | 7 + .../backend/src/core/NoteCreateService.ts | 16 +- packages/backend/src/core/SearchService.ts | 166 ++++++++++++++++++ packages/backend/src/di-symbols.ts | 1 + .../backend/src/server/api/endpoints/meta.ts | 5 - .../src/server/api/endpoints/notes/search.ts | 37 ++-- pnpm-lock.yaml | 15 +- 18 files changed, 257 insertions(+), 91 deletions(-) create mode 100644 packages/backend/src/core/SearchService.ts diff --git a/.config/docker_example.yml b/.config/docker_example.yml index af0a90dc95..6946954ce5 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -95,15 +95,13 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 8111b1992e..5861176677 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -95,15 +95,13 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: +#meilisearch: # host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# port: 7700 +# apiKey: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 2af306e3da..e1b89c25bd 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -95,15 +95,13 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.dockerignore b/.dockerignore index 151ede038e..1de0c7982b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,7 +8,6 @@ build/ built/ db/ docker-compose.yml -elasticsearch/ node_modules/ packages/*/node_modules redis/ diff --git a/.gitignore b/.gitignore index fbe2245502..537232d37f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,7 @@ built /data /.cache-loader /db -/elasticsearch +/meili_data npm-debug.log *.pem run.bat diff --git a/CHANGELOG.md b/CHANGELOG.md index f3adee8f52..72a9355565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Node.js 18.6.0以上が必要になりました ### General +- Meilisearchを全文検索に使用できるようになりました - 新規登録前に簡潔なルールをユーザーに表示できる、サーバールール機能を追加 - ユーザーへの自分用メモ機能 * ユーザーに対して、自分だけが見られるメモを追加できるようになりました。 diff --git a/chart/files/default.yml b/chart/files/default.yml index 1888669245..f50c38d57e 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -116,15 +116,13 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: +#meilisearch: # host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# port: 7700 +# apiKey: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/docker-compose.yml.example b/docker-compose.yml.example index b0c4a914d5..a0061c5c20 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -7,7 +7,7 @@ services: links: - db - redis -# - es +# - meilisearch depends_on: db: condition: service_healthy @@ -48,16 +48,18 @@ services: interval: 5s retries: 20 -# es: +# meilisearch: # restart: always -# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2 +# image: getmeili/meilisearch:v1.1.1 # environment: -# - "ES_JAVA_OPTS=-Xms512m -Xmx512m" -# - "TAKE_FILE_OWNERSHIP=111" +# - MEILI_NO_ANALYTICS=true +# - MEILI_ENV=production +# env_file: +# - .config/meilisearch.env # networks: # - internal_network # volumes: -# - ./elasticsearch:/usr/share/elasticsearch/data +# - ./meili_data:/meili_data networks: internal_network: diff --git a/packages/backend/package.json b/packages/backend/package.json index 9b20c121eb..08557d415e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -91,6 +91,7 @@ "jsdom": "21.1.1", "json5": "2.2.3", "jsonld": "8.1.1", + "meilisearch": "0.32.3", "jsrsasign": "10.8.6", "mfm-js": "0.23.3", "mime-types": "2.1.35", diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 4574429c43..2f4862285d 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -2,6 +2,7 @@ import { setTimeout } from 'node:timers/promises'; import { Global, Inject, Module } from '@nestjs/common'; import * as Redis from 'ioredis'; import { DataSource } from 'typeorm'; +import { MeiliSearch } from 'meilisearch'; import { DI } from './di-symbols.js'; import { loadConfig } from './config.js'; import { createPostgresDataSource } from './postgres.js'; @@ -22,6 +23,21 @@ const $db: Provider = { inject: [DI.config], }; +const $meilisearch: Provider = { + provide: DI.meilisearch, + useFactory: (config) => { + if (config.meilisearch) { + return new MeiliSearch({ + host: `http://${config.meilisearch.host}:${config.meilisearch.port}`, + apiKey: config.meilisearch.apiKey, + }); + } else { + return null; + } + }, + inject: [DI.config], +}; + const $redis: Provider = { provide: DI.redis, useFactory: (config) => { @@ -73,8 +89,8 @@ const $redisForSub: Provider = { @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $redis, $redisForPub, $redisForSub], - exports: [$config, $db, $redis, $redisForPub, $redisForSub, RepositoryModule], + providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub], + exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 4499475ee9..7354268a4d 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -57,13 +57,10 @@ export type Source = { db?: number; prefix?: string; }; - elasticsearch: { + meilisearch?: { host: string; - port: number; - ssl?: boolean; - user?: string; - pass?: string; - index?: string; + port: string; + apiKey: string; }; proxy?: string; @@ -139,6 +136,7 @@ const path = process.env.MISSKEY_CONFIG_YML : process.env.NODE_ENV === 'test' ? resolve(dir, 'test.yml') : resolve(dir, 'default.yml'); + export function loadConfig() { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json'); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 8775536e4a..d3a1b1b024 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -50,6 +50,7 @@ import { WebhookService } from './WebhookService.js'; import { ProxyAccountService } from './ProxyAccountService.js'; import { UtilityService } from './UtilityService.js'; import { FileInfoService } from './FileInfoService.js'; +import { SearchService } from './SearchService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -171,6 +172,8 @@ const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', u const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; +const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; + const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; const $NotesChart: Provider = { provide: 'NotesChart', useExisting: NotesChart }; @@ -295,6 +298,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebhookService, UtilityService, FileInfoService, + SearchService, ChartLoggerService, FederationChart, NotesChart, @@ -413,6 +417,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebhookService, $UtilityService, $FileInfoService, + $SearchService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -532,6 +537,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebhookService, UtilityService, FileInfoService, + SearchService, FederationChart, NotesChart, UsersChart, @@ -649,6 +655,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebhookService, $UtilityService, $FileInfoService, + $SearchService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 50081f831b..364976e4a7 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -46,6 +46,7 @@ import { bindThis } from '@/decorators.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; +import { SearchService } from '@/core/SearchService.js'; const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); @@ -198,6 +199,7 @@ export class NoteCreateService implements OnApplicationShutdown { private apRendererService: ApRendererService, private roleService: RoleService, private metaService: MetaService, + private searchService: SearchService, private notesChart: NotesChart, private perUserNotesChart: PerUserNotesChart, private activeUsersChart: ActiveUsersChart, @@ -728,17 +730,9 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private index(note: Note) { - if (note.text == null || this.config.elasticsearch == null) return; - /* - es!.index({ - index: this.config.elasticsearch.index ?? 'misskey_note', - id: note.id.toString(), - body: { - text: normalizeForSearch(note.text), - userId: note.userId, - userHost: note.userHost, - }, - });*/ + if (note.text == null && note.cw == null) return; + + this.searchService.indexNote(note); } @bindThis diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts new file mode 100644 index 0000000000..67332581f7 --- /dev/null +++ b/packages/backend/src/core/SearchService.ts @@ -0,0 +1,166 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; +import { Note } from '@/models/entities/Note.js'; +import { User } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import { QueryService } from '@/core/QueryService.js'; +import { IdService } from '@/core/IdService.js'; +import type { Index, MeiliSearch } from 'meilisearch'; + +type K = string; +type V = string | number | boolean; +type Q = + { op: '=', k: K, v: V } | + { op: '!=', k: K, v: V } | + { op: '>', k: K, v: number } | + { op: '<', k: K, v: number } | + { op: '>=', k: K, v: number } | + { op: '<=', k: K, v: number } | + { op: 'and', qs: Q[] } | + { op: 'or', qs: Q[] } | + { op: 'not', q: Q }; + +function compileValue(value: V): string { + if (typeof value === 'string') { + return `'${value}'`; // TODO: escape + } else if (typeof value === 'number') { + return value.toString(); + } else if (typeof value === 'boolean') { + return value.toString(); + } + throw new Error('unrecognized value'); +} + +function compileQuery(q: Q): string { + switch (q.op) { + case '=': return `(${q.k} = ${compileValue(q.v)})`; + case '!=': return `(${q.k} != ${compileValue(q.v)})`; + case '>': return `(${q.k} > ${compileValue(q.v)})`; + case '<': return `(${q.k} < ${compileValue(q.v)})`; + case '>=': return `(${q.k} >= ${compileValue(q.v)})`; + case '<=': return `(${q.k} <= ${compileValue(q.v)})`; + case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`; + case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`; + case 'not': return `(NOT ${compileQuery(q.q)})`; + default: throw new Error('unrecognized query operator'); + } +} + +@Injectable() +export class SearchService { + private meilisearchNoteIndex: Index | null = null; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.meilisearch) + private meilisearch: MeiliSearch | null, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private queryService: QueryService, + private idService: IdService, + ) { + if (meilisearch) { + this.meilisearchNoteIndex = meilisearch.index('notes'); + this.meilisearchNoteIndex.updateSettings({ + searchableAttributes: [ + 'text', + 'cw', + ], + sortableAttributes: [ + 'createdAt', + ], + filterableAttributes: [ + 'createdAt', + 'userId', + 'userHost', + 'channelId', + ], + typoTolerance: { + enabled: false, + }, + pagination: { + maxTotalHits: 10000, + }, + }); + } + } + + @bindThis + public async indexNote(note: Note): Promise { + if (this.meilisearch) { + this.meilisearchNoteIndex!.addDocuments([{ + id: note.id, + createdAt: note.createdAt.getTime(), + userId: note.userId, + userHost: note.userHost, + channelId: note.channelId, + cw: note.cw, + text: note.text, + }], { + primaryKey: 'id', + }); + } + } + + @bindThis + public async searchNote(q: string, me: User | null, opts: { + userId?: Note['userId'] | null; + channelId?: Note['channelId'] | null; + }, pagination: { + untilId?: Note['id']; + sinceId?: Note['id']; + limit?: number; + }): Promise { + if (this.meilisearch) { + const filter: Q = { + op: 'and', + qs: [], + }; + if (pagination.untilId) filter.qs.push({ op: '<', k: 'createdAt', v: this.idService.parse(pagination.untilId).date.getTime() }); + if (pagination.sinceId) filter.qs.push({ op: '>', k: 'createdAt', v: this.idService.parse(pagination.sinceId).date.getTime() }); + if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId }); + if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId }); + const res = await this.meilisearchNoteIndex!.search(q, { + sort: ['createdAt:desc'], + matchingStrategy: 'all', + attributesToRetrieve: ['id', 'createdAt'], + filter: compileQuery(filter), + limit: pagination.limit, + }); + if (res.hits.length === 0) return []; + return await this.notesRepository.findBy({ + id: In(res.hits.map(x => x.id)), + }); + } else { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId); + + if (opts.userId) { + query.andWhere('note.userId = :userId', { userId: opts.userId }); + } else if (opts.channelId) { + query.andWhere('note.channelId = :channelId', { channelId: opts.channelId }); + } + + query + .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + return await query.take(pagination.limit).getMany(); + } + } +} diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 190d8d65c2..c06c7a7159 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -1,6 +1,7 @@ export const DI = { config: Symbol('config'), db: Symbol('db'), + meilisearch: Symbol('meilisearch'), redis: Symbol('redis'), redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index a5cb3fa7ee..584ea07c3b 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -201,10 +201,6 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - elasticsearch: { - type: 'boolean', - optional: false, nullable: false, - }, hcaptcha: { type: 'boolean', optional: false, nullable: false, @@ -331,7 +327,6 @@ export default class extends Endpoint { response.features = { registration: !instance.disableRegistration, emailRequiredForSignup: instance.emailRequiredForSignup, - elasticsearch: this.config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, turnstile: instance.enableTurnstile, diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index fb5abd917f..990ba526d9 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,11 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { QueryService } from '@/core/QueryService.js'; +import { SearchService } from '@/core/SearchService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; @@ -61,11 +60,8 @@ export default class extends Endpoint { @Inject(DI.config) private config: Config, - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - private noteEntityService: NoteEntityService, - private queryService: QueryService, + private searchService: SearchService, private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { @@ -74,27 +70,14 @@ export default class extends Endpoint { throw new ApiError(meta.errors.unavailable); } - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); - - if (ps.userId) { - query.andWhere('note.userId = :userId', { userId: ps.userId }); - } else if (ps.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); - } - - query - .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); - - this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); - - const notes = await query.take(ps.limit).getMany(); + const notes = await this.searchService.searchNote(ps.query, me, { + userId: ps.userId, + channelId: ps.channelId, + }, { + untilId: ps.untilId, + sinceId: ps.sinceId, + limit: ps.limit, + }); return await this.noteEntityService.packMany(notes, me); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0010581416..31f6b919d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,6 +229,9 @@ importers: jsrsasign: specifier: 10.8.6 version: 10.8.6 + meilisearch: + specifier: 0.32.3 + version: 0.32.3 mfm-js: specifier: 0.23.3 version: 0.23.3 @@ -9582,7 +9585,6 @@ packages: node-fetch: 2.6.7 transitivePeerDependencies: - encoding - dev: true /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -14496,6 +14498,14 @@ packages: engines: {node: '>= 0.6'} dev: true + /meilisearch@0.32.3: + resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==} + dependencies: + cross-fetch: 3.1.5 + transitivePeerDependencies: + - encoding + dev: false + /memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} dependencies: @@ -14657,6 +14667,7 @@ packages: /minimist@1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: false /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -19700,7 +19711,7 @@ packages: axios: 0.27.2(debug@4.3.4) joi: 17.7.0 lodash: 4.17.21 - minimist: 1.2.7 + minimist: 1.2.8 rxjs: 7.8.1 transitivePeerDependencies: - debug From b45bc3fd5daa1e609031224a9f2647eb26d9db16 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 5 May 2023 10:05:33 +0900 Subject: [PATCH 011/105] feat(frontend): in channel search --- CHANGELOG.md | 1 + packages/frontend/src/pages/channel.vue | 33 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a9355565..3a2d637144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Fix: フォローリクエストの通知が残る問題を修正 ### Client +- チャンネル内検索ができるように - チャンネル検索ですべてのチャンネルの取得/表示ができるように - 通知の表示をカスタマイズできるように - ドライブのファイル一覧から直接ノートを作成できるように diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 437c1fae31..30e18c32ba 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -36,6 +36,17 @@
+
+
+
+ + + + {{ i18n.ts.search }} +
+ +
+
@@ -34,6 +35,8 @@ import bytes from '@/filters/bytes'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import * as os from '@/os'; +import { iAmModerator } from '@/account'; const props = defineProps<{ image: misskey.entities.DriveFile; @@ -57,6 +60,17 @@ watch(() => props.image, () => { deep: true, immediate: true, }); + +function showMenu(ev: MouseEvent) { + os.popupMenu([...(iAmModerator ? [{ + text: i18n.ts.markAsSensitive, + icon: 'ti ti-eye-off', + action: () => { + os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); + }, + }] : [])], ev.currentTarget ?? ev.target); +} + diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 51eb426e97..3571ca84d9 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -8,7 +8,7 @@ @@ -29,8 +29,8 @@ const props = withDefaults(defineProps<{ }); - diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts index 1308dfff9a..2d95455730 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts +++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts @@ -3,7 +3,7 @@ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; import { onBeforeUnmount } from 'vue'; -import MkSignupServerRules from './MkSignupDialog,rules.vue'; +import MkSignupServerRules from './MkSignupDialog.rules.vue'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; export const Empty = { diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts new file mode 100644 index 0000000000..7d5a65f41a --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { commonHandlers } from '../../.storybook/mocks'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_Follow, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + rest.post('/api/pinned-users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + ], + }, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue new file mode 100644 index 0000000000..b89e3e4c9d --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts new file mode 100644 index 0000000000..f4930aa26b --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_Profile, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue new file mode 100644 index 0000000000..373e2cf8dc --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts new file mode 100644 index 0000000000..7413f4884b --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_User, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue new file mode 100644 index 0000000000..d66f34f165 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts new file mode 100644 index 0000000000..55790602d5 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { commonHandlers } from '../../.storybook/mocks'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog from './MkUserSetupDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + rest.post('/api/pinned-users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + ], + }, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue new file mode 100644 index 0000000000..58afe09b61 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 91a009e8f8..13fbd08c56 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -343,6 +343,10 @@ if ($i) { // only add post shortcuts if logged in hotkeys['p|n'] = post; + if (defaultStore.state.accountSetupWizard !== -1) { + popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed'); + } + if ($i.isDeleted) { alert({ type: 'warning', diff --git a/packages/frontend/src/pages/timeline.tutorial.vue b/packages/frontend/src/pages/timeline.tutorial.vue index 0d0c932a5c..32228d28f4 100644 --- a/packages/frontend/src/pages/timeline.tutorial.vue +++ b/packages/frontend/src/pages/timeline.tutorial.vue @@ -1,7 +1,7 @@
{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}
- {{ i18n.ts.close }} + {{ i18n.ts.close }} @@ -91,6 +91,7 @@ import { instance } from '@/instance'; import { host } from '@/config'; import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; import { defaultStore } from '@/store'; +import * as os from '@/os'; const emit = defineEmits<{ (ev: 'closed'): void; @@ -104,7 +105,15 @@ watch(page, () => { defaultStore.set('accountSetupWizard', page.value); }); -function close() { +async function close(skip: boolean) { + if (skip) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts._initialAccountSetting.skipAreYouSure, + }); + if (canceled) return; + } + dialog.value.close(); defaultStore.set('accountSetupWizard', -1); } diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index eb67803240..49e7bb4008 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -345,8 +345,11 @@ if ($i) { if (defaultStore.state.accountSetupWizard !== -1) { // このウィザードが実装される前に登録したユーザーには表示させないため + // TODO: そのうち消す if (Date.now() - new Date($i.createdAt).getTime() < 1000 * 60 * 60 * 24) { popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed'); + } else { + defaultStore.set('accountSetupWizard', -1); } } From 1a96425768b743cf6a7f93b7813c082152497461 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?= Date: Mon, 8 May 2023 17:51:52 +0900 Subject: [PATCH 069/105] =?UTF-8?q?chore:=20=E7=8C=AB=E8=80=B3=E3=81=AE?= =?UTF-8?q?=E5=85=88=E7=AB=AF=E3=81=A3=E3=81=A6=E6=9C=AC=E6=9D=A5=E5=B0=91?= =?UTF-8?q?=E3=81=97=E4=B8=B8=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E3=82=82=E3=81=AE=E3=81=AA=E3=81=AE=E3=81=A7=E3=81=AF=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=A0=E3=82=8D=E3=81=86=E3=81=8B=20(#10800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: syuilo --- 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 8497b8443b..ad36dcabe4 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -222,7 +222,7 @@ watch(() => props.user.avatarBlurhash, () => { transform: rotate(37.5deg) skew(30deg); &, &::after { - border-radius: 0 75% 75%; + border-radius: 25% 75% 75%; } > .layer { @@ -251,7 +251,7 @@ watch(() => props.user.avatarBlurhash, () => { transform: rotate(-37.5deg) skew(-30deg); &, &::after { - border-radius: 75% 0 75% 75%; + border-radius: 75% 25% 75% 75%; } > .layer { From 41e9aa6f9b03107518224e2ebde8889c64408204 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 May 2023 18:23:35 +0900 Subject: [PATCH 070/105] =?UTF-8?q?fix(frontend):=20=E3=82=BB=E3=83=B3?= =?UTF-8?q?=E3=82=B7=E3=83=86=E3=82=A3=E3=83=96=E8=A8=AD=E5=AE=9A=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=9F=E7=94=BB=E5=83=8F=E3=82=92=E9=96=8B=E3=81=8F?= =?UTF-8?q?=E3=81=A8=E3=81=8D=E4=B8=80=E7=9E=AC=E3=83=AC=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=82=A6=E3=83=88=E3=81=8C=E5=B4=A9=E3=82=8C=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #10801 --- .../frontend/src/components/MkMediaList.vue | 60 +------------------ .../frontend/src/pages/settings/general.vue | 3 +- 2 files changed, 4 insertions(+), 59 deletions(-) diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index d28c78fe5c..e456ff3eec 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -7,7 +7,6 @@ :class="[ $style.medias, count <= 4 ? $style['n' + count] : $style.nMany, - $style[`n1${defaultStore.reactiveState.mediaListWithOneImageAppearance.value}`] ]" >
- {{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }} - {{ cancelText ?? i18n.ts.cancel }} + {{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }} + {{ cancelText ?? i18n.ts.cancel }}
{{ action.text }} diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index f983f77750..1c942cfd0d 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -6,7 +6,7 @@ - +
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index a9d117e073..c5e75276f0 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -3,6 +3,7 @@ ref="dialog" :width="500" :height="550" + data-cy-user-setup @close="close(true)" @closed="emit('closed')" > @@ -23,7 +24,7 @@
{{ i18n.ts._initialAccountSetting.accountCreated }}
{{ i18n.ts._initialAccountSetting.letsFillYourProfile }}
- {{ i18n.ts._initialAccountSetting.profileSetting }} + {{ i18n.ts._initialAccountSetting.profileSetting }}
@@ -32,7 +33,7 @@
- {{ i18n.ts.continue }} + {{ i18n.ts.continue }}
@@ -40,7 +41,7 @@
- {{ i18n.ts.continue }} + {{ i18n.ts.continue }}
@@ -52,7 +53,7 @@
{{ i18n.ts.pushNotification }}
{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}
- {{ i18n.ts.continue }} + {{ i18n.ts.continue }} @@ -70,7 +71,7 @@
{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}
- {{ i18n.ts.close }} + {{ i18n.ts.close }} From b16d7cc6c4b8d670e35e0b32e1527149e2c575ab Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 08:09:16 +0900 Subject: [PATCH 081/105] =?UTF-8?q?chore(frontend):=20=E3=82=88=E3=82=8A?= =?UTF-8?q?=E6=9F=94=E8=BB=9F=E3=81=AA=E6=96=87=E8=A8=80=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 1 + packages/frontend/src/components/MkUserSetupDialog.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b886cc7bfd..8bbf9459f5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1041,6 +1041,7 @@ youFollowing: "フォロー中" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" + letsStartAccountSetup: "アカウントの初期設定を行いましょう。" letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。" profileSetting: "プロフィール設定" theseSettingsCanEditLater: "これらの設定は後から変更できます。" diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index c5e75276f0..096b88c309 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -23,7 +23,7 @@
{{ i18n.ts._initialAccountSetting.accountCreated }}
-
{{ i18n.ts._initialAccountSetting.letsFillYourProfile }}
+
{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}
{{ i18n.ts._initialAccountSetting.profileSetting }}
From 5c54e12099035a5dad080de5ed8ad4ec4e541158 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 08:32:25 +0900 Subject: [PATCH 082/105] =?UTF-8?q?fix(test):=20=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=96=E3=82=AD=E3=83=A5=E3=83=BC=E3=82=92=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=8C=E7=B5=82=E3=82=8F=E3=81=A3=E3=81=9F=E3=82=89?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #10802 ? --- packages/backend/src/boot/common.ts | 2 ++ packages/backend/test/e2e/move.ts | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index 279a1fe59d..45ded5495c 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -34,4 +34,6 @@ export async function jobQueue() { jobQueue.get(QueueProcessorService).start(); jobQueue.get(ChartManagementService).start(); + + return jobQueue; } diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index 4dd5cbb9dd..7d6c646090 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -10,6 +10,7 @@ import type { INestApplicationContext } from '@nestjs/common'; describe('Account Move', () => { let app: INestApplicationContext; + let jq: INestApplicationContext; let url: URL; let root: any; @@ -24,7 +25,7 @@ describe('Account Move', () => { beforeAll(async () => { app = await startServer(); - await jobQueue(); + jq = await jobQueue(); const config = loadConfig(); url = new URL(config.url); const connection = await initTestDb(false); @@ -39,7 +40,7 @@ describe('Account Move', () => { }, 1000 * 60 * 2); afterAll(async () => { - await app.close(); + await Promise.all([app.close(), jq.close()]); }); describe('Create Alias', () => { From aa28ddf762f88ca355c62edcbfa54e19a2b93c29 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 08:33:57 +0900 Subject: [PATCH 083/105] fix(frontend): fix e2e --- .../frontend/src/components/MkUserSetupDialog.Profile.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 373e2cf8dc..adb8d43349 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -12,11 +12,11 @@ - + - + From 7feca2a60a4bf6e302ea7f0c79927524e7d0817d Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 08:48:42 +0900 Subject: [PATCH 084/105] fix(frontend): fix e2e --- cypress/e2e/basic.cy.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index e5e3a466ac..73f6e7a0f0 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -181,6 +181,30 @@ describe('After user signed in', () => { cy.get('[data-cy-user-setup-continue]').click(); }); +}); + +describe('After user setup', () => { + beforeEach(() => { + cy.resetState(); + + // インスタンス初期セットアップ + cy.registerUser('admin', 'pass', true); + + // ユーザー作成 + cy.registerUser('alice', 'alice1234'); + + cy.login('alice', 'alice1234'); + + // アカウント初期設定ウィザード + cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click(); + cy.get('[data-cy-modal-dialog-ok]').click(); + }); + + afterEach(() => { + // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。 + // waitを入れることでそれを防止できる + cy.wait(1000); + }); it('note', () => { cy.get('[data-cy-open-post-form]').should('be.visible'); From c8343b2750310bb3f48cc8ef2e4c50c9c7f98b83 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 09:11:44 +0900 Subject: [PATCH 085/105] [ci skip] New Crowdin updates (#10803) * New translations ja-JP.yml (Norwegian) * New translations ja-JP.yml (English) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Norwegian) * New translations ja-JP.yml (Korean) * New translations ja-JP.yml (Korean) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Japanese, Kansai) --- locales/de-DE.yml | 23 +++++++++++++++++++++++ locales/en-US.yml | 23 +++++++++++++++++++++++ locales/ja-KS.yml | 41 +++++++++++++++++++++++++++++++++++++---- locales/ko-KR.yml | 14 +++++++++++++- locales/no-NO.yml | 28 ++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 5 deletions(-) diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 89a61d42d8..d678fadd4a 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1036,7 +1036,20 @@ channelArchiveConfirmTitle: "{name} wirklich archivieren?" channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden." thisChannelArchived: "Dieser Kanal wurde archiviert." displayOfNote: "Anzeige von Notizen" +initialAccountSetting: "Kontoeinrichtung" youFollowing: "Gefolgt" +_initialAccountSetting: + accountCreated: "Dein Konto wurde erfolgreich erstellt!" + letsFillYourProfile: "Lass uns zuerst dein Profil einrichten." + profileSetting: "Profileinstellungen" + theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern." + youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei." + followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen." + pushNotificationDescription: "Durch die Aktivierung von Push-Benachrichtigungen kannst du von {name} Benachrichtigungen direkt auf dein Gerät erhalten." + initialAccountSettingCompleted: "Kontoeinrichtung abgeschlossen!" + haveFun: "Viel Spaß mit {name}!" + ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest." + skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?" _serverRules: description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." _accountMigration: @@ -1586,6 +1599,16 @@ _time: minute: "Minute(n)" hour: "Stunde(n)" day: "Tag(en)" +_timelineTutorial: + title: "Wie du Misskey verwendest" + step1_1: "Dieser Bildschirm ist die \"Chronik\". Hier werden alle \"Notizen\" von {name} angezeigt." + step1_2: "Es gibt einige verschiedene Chroniken. Beispielsweise werden in der \"Startseite\" alle Notizen von Nutzern, denen du folgst, angezeigt, und in der \"Lokalen Chronik\" werden Notizen aller Nutzer auf {name} angezeigt." + step2_1: "Lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon drückst." + step2_2: "Stell dich den anderen vor oder schreibe einfach \"Hallo {name}!\", wenn du darauf keine Lust hast oder dir nichts einfällt." + step3_1: "Fertig mit dem Senden deiner ersten Notiz?" + step3_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht." + step4_1: "Notizen können zusätzlich mit \"Reaktionen\" ausgestattet werden." + step4_2: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol einer Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest." _2fa: alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert." registerTOTP: "Authentifizierungs-App registrieren" diff --git a/locales/en-US.yml b/locales/en-US.yml index f5533d03a1..ea91bcc0e5 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1036,7 +1036,20 @@ channelArchiveConfirmTitle: "Really archive {name}?" channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore." thisChannelArchived: "This channel has been archived." displayOfNote: "Note display" +initialAccountSetting: "Profile configuration" youFollowing: "Followed" +_initialAccountSetting: + accountCreated: "Your account was successfully created!" + letsFillYourProfile: "First, let's set up your profile." + profileSetting: "Profile settings" + theseSettingsCanEditLater: "You can always change these settings later." + youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later." + followUsers: "Try following some users that interest you to build up your timeline." + pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device." + initialAccountSettingCompleted: "Profile configuration complete!" + haveFun: "Enjoy {name}!" + ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}." + skipAreYouSure: "Really skip profile configuration?" _serverRules: description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended." _accountMigration: @@ -1586,6 +1599,16 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" +_timelineTutorial: + title: "How to use Misskey" + step1_1: "This is the \"timeline\". All \"notes\" submitted on {name} will be chronologically displayed here." + step1_2: "There are a few different timelines. For example, the \"Home timeline\" will contain notes of users you follow, and the \"Local timeline\" will contain notes from all users of {name}." + step2_1: "Let's try posting a note next. You can do so by pressing the button with a pencil icon." + step2_2: "How about writing a self-introduction, or just \"Hello {name}!\" if you don't feel like it?" + step3_1: "Finished posting your first note?" + step3_2: "Your first note should now be displayed on your timeline." + step4_1: "You can also attach \"Reactions\" to notes." + step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with." _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index ee1c0f1138..512cce1452 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -26,7 +26,7 @@ otherSettings: "ほかの設定" openInWindow: "ウィンドウで開くで" profile: "プロフィール" timeline: "タイムライン" -noAccountDescription: "自己紹介はあらへん" +noAccountDescription: "自己紹介食ってもた" login: "ログイン" loggingIn: "ログインしよるで" logout: "ログアウト" @@ -38,9 +38,9 @@ addUser: "ユーザーを追加や" favorite: "お気に入り" favorites: "お気に入り" unfavorite: "やっぱ気に入らん" -favorited: "お気に入りに登録したで" +favorited: "お気に入りに入れたで" alreadyFavorited: "もうお気に入りに入れとるがな。" -cantFavorite: "アカン、お気に入り登録できへんかったで。" +cantFavorite: "アカン、お気に入りに入れれんかったわ。" pin: "ピン留めしとく" unpin: "やっぱピン留めせん" copyContent: "内容をコピー" @@ -462,7 +462,7 @@ uiLanguage: "UIの表示言語" aboutX: "{x}について" emojiStyle: "絵文字のスタイル" native: "ネイティブ" -disableDrawer: "メニューをドロワーで表示せぇへん" +disableDrawer: "メニューをドロワーで表示せえへん" showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで" noHistory: "履歴はないわ。" signinHistory: "ログイン履歴" @@ -560,6 +560,7 @@ accountDeletedDescription: "このアカウントは削除されとるで。" menu: "メニュー" divider: "分割線" addItem: "項目を追加" +rearrange: "並び替え" relays: "リレー" addRelay: "リレーの追加" inboxUrl: "inboxのURL" @@ -1028,11 +1029,32 @@ pleaseConfirmBelowBeforeSignup: "このサーバーに登録する前に、下 pleaseAgreeAllToContinue: "続けるんやったら、全ての「せやな」にチェック入れてる必要があるで。" continue: "続けるで" preservedUsernames: "予約ユーザー名" +preservedUsernamesDescription: "予約しとくユーザー名を行ごとに挙げるで。ここで指定されたユーザー名はアカウント作るときに使えへんくなるけど、管理者は例外や。あと、もうあるアカウントも例外やな。" +createNoteFromTheFile: "このファイル使うてノート作るで" +archive: "アーカイブ" +channelArchiveConfirmTitle: "{name}をアーカイブしてええか?" +channelArchiveConfirmDescription: "アーカイブしたら、チャンネル一覧とか検索結果からなくなるし、新しく書き込みもできへんなるで。" +thisChannelArchived: "このチャンネル、アーカイブされとるで。" +displayOfNote: "ノートの表示" +initialAccountSetting: "初期設定" youFollowing: "フォロー中やで" +_initialAccountSetting: + accountCreated: "アカウント作り終わったで。" + letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。" + profileSetting: "プロフィール設定" + theseSettingsCanEditLater: "この設定はあとから変えれるで。" + youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。" + followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。" + pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。" + initialAccountSettingCompleted: "初期設定が終わったで。" + haveFun: "{name}、楽しんでな~" + ifYouNeedLearnMore: "{name}(Misskey)の使い方とかをよー知りたいんやったら{link}をみてな。" + skipAreYouSure: "初期設定飛ばすか?" _serverRules: description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに引っ越す" + moveFromSub: "別のアカウントへエイリアスを作る" moveFromLabel: "引っ越し元のアカウント:" moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したかったら、ここでエイリアスを作っとく必要があるで。必ずお引っ越しを実行する前に作っとかなあかんで!引っ越し元のアカウントをこんな風に入力してくれへんか?:@person@instance.com" moveTo: "このアカウントをさらのアカウントに引っ越すで" @@ -1324,6 +1346,7 @@ _role: canInvite: "サーバー招待コードの発行" canManageCustomEmojis: "カスタム絵文字の管理" driveCapacity: "ドライブ容量" + alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける" pinMax: "ノートのピン留めの最大数" antennaMax: "アンテナの作成可能数" wordMuteMax: "ワードミュートの最大文字数" @@ -1575,6 +1598,16 @@ _time: minute: "分" hour: "時間" day: "日" +_timelineTutorial: + title: "Misskeyってなんや?" + step1_1: "これは「タイムライン」や。{name}に投稿された「ノート」が順番に表示されるで。" + step1_2: "タイムラインには何個か種類があってな、例えば「ホームタイムライン」だったらあんたのフォローしてる人のノート、「ローカルタイムライン」には{name}全部のノートが流れてくるで。" + step2_1: "試しに、何かノートを投稿してみ。画面の鉛筆マークのボタンでフォームが開くで。" + step2_2: "最初のノートは、自己紹介とか「{name}始めてみたんや」とかがええと思うで。" + step3_1: "投稿できた?" + step3_2: "あんたのノートがタイムラインに出てきたら成功や。" + step4_1: "ノートには、「リアクション」を付けれるで。" + step4_2: "ツッコむんやったら、ノートの「+」マークを押して、好きな絵文字を選ぶで。" _2fa: alreadyRegistered: "もう設定終わっとるわ。" registerTOTP: "認証アプリの設定はじめる" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 0f2b9a740a..4a2fbe2a80 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2,7 +2,7 @@ _lang_: "한국어" headlineMisskey: "노트로 연결되는 네트워크" introMisskey: "환영합니다! Misskey는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n'노트'를 작성해서 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n'리액션' 기능으로 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀" -poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼Misskey를 사용한 서버 중 하나입니다." +poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼 Misskey를 사용한 서버 가운데 하나입니다." monthAndDay: "{month}월 {day}일" search: "검색" notifications: "알림" @@ -1036,7 +1036,19 @@ channelArchiveConfirmTitle: "{name} 을(를) 아카이브하시겠습니까?" channelArchiveConfirmDescription: "아카이브한 채널은 채널 목록과 검색 결과에 표시되지 않으며, 채널에 새로운 노트를 작성할 수 없게 됩니다." thisChannelArchived: "이 채널은 아카이브되었습니다." displayOfNote: "노트 표시" +initialAccountSetting: "초기 설정" youFollowing: "팔로잉" +_initialAccountSetting: + accountCreated: "계정 생성이 완료되었습니다!" + letsFillYourProfile: "우선 나의 프로필을 설정해 보아요." + profileSetting: "프로필 설정" + theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다." + youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 맛게 조절할 수 있습니다. 꼭 확인해 보세요!" + followUsers: "관심사가 맞는 유저를 팔로우하여 타임라인을 가꾸어 봅시다." + pushNotificationDescription: "푸시 알림을 활성화하면 {name}의 알림을 나의 기기에서 받아볼 수 있게 됩니다." + initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!" + haveFun: "{name}와 함께 즐거운 시간 보내세요!" + ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요." _serverRules: description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다." _accountMigration: diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 883b17c78f..f7f0d442d4 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -274,10 +274,16 @@ letsLookAtTimeline: "La oss se på tidslinje" cannotBeChangedLater: "Du kan ikke endre senere." likeOnly: "Bare liker" retryAllQueuesConfirmTitle: "Vil du prøve igjen akkurat nå?" +video: "Video" +videos: "Videoer" continue: "Fortsett" youFollowing: "Følger" +_initialAccountSetting: + theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: _types: + _notes100000: + flavor: "Du har jammen mye å si." _noteFavorited1: title: "Stjernekikker" _myNoteFavorited1: @@ -290,13 +296,28 @@ _achievements: title: "For mange venner" _followers10: title: "Følg meg!" + _followers100: + title: "Populær" + _postedAtLateNight: + flavor: "Det er på tide å gå til sengs." + _driveFolderCircularReference: + title: "Rundskrivreferanse" _reactWithoutRead: title: "Leste du det virkelig?" _clickedClickHere: title: "Klikk her" description: "Du har klikket her" + _justPlainLucky: + title: "Rett og slett heldig" + _setNameToSyuilo: + description: "Du har satt navnet ditt til \"syuilo\"" _loggedInOnBirthday: title: "Gratulerer med dagen" + _loggedInOnNewYearsDay: + title: "Godt nytt år" + _brainDiver: + title: "Brain Diver" + flavor: "Misskey-Misskey La-Tu-Ma" _role: options: "Alternativ" _priority: @@ -318,6 +339,7 @@ _registry: key: "Nøkkel" keys: "Nøkler" _aboutMisskey: + about: "Misskey er programvare med åpen kildekode som har blitt utviklet av syuilo siden 2014." translation: "Oversett Misskey" _instanceTicker: none: "Ikke vis" @@ -355,6 +377,8 @@ _time: minute: "Minutter" hour: "Timer" day: "Dager" +_timelineTutorial: + title: "Hvordan bruke Misskey" _2fa: renewTOTPCancel: "Avbryt" _weekday: @@ -374,14 +398,18 @@ _widgets: clock: "Klokke" photos: "Bilder" button: "Knapp" + aiscriptApp: "AiScript App" userList: "Brukerliste" _userList: chooseList: "Velg liste" _cw: show: "Vis mer" _poll: + noOnlyOneChoice: "Trenger minst to valger." + choiceN: "Valg {n}" noMore: "Du kan ikke legge til flere." deadlineTime: "Timer" + votesCount: "{n} stemmer" vote: "Stem" showResult: "Vis resultatet" voted: "Stemt" From e382f74bb38c5d78828c4ade2621dd96315c8da6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 09:17:17 +0900 Subject: [PATCH 086/105] [ci skip] 13.12.0 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 336b56ff53..003e907c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - --> -## 13.x.x (unreleased) +## 13.12.0 ### NOTE - Node.js 18.6.0以上が必要になりました diff --git a/package.json b/package.json index 1358b6f42b..5b1d21b815 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.12.0-beta.6", + "version": "13.12.0", "codename": "nasubi", "repository": { "type": "git", From 80619260c11a44ad50619aaed2578eb5dc0260e9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 May 2023 13:57:43 +0900 Subject: [PATCH 087/105] =?UTF-8?q?fix(frontend):=20=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E9=81=B8=E6=8A=9E=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #10809 --- CHANGELOG.md | 12 ++++++++++++ packages/frontend/src/components/MkModalWindow.vue | 7 +++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 003e907c5f..83caa66a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ - --> + +## 13.x.x (unreleased) + +### General +- + +### Client +- Fix: ユーザー選択ダイアログが表示されない問題を修正 + +### Server +- + ## 13.12.0 ### NOTE diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index 1c942cfd0d..ad7dc4da11 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -1,6 +1,6 @@