diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c44c4f8b83..31d4b6dd432e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ - Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正 ### Client +- Feat: ユーザーページから「このユーザーのノートを検索」できるように (#14128) +- Feat: 検索ページはクエリを受け付けるようになりました (#14128) +- Enhance: 検索ページのUI改善 (#14128) - Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善 - Enhance: 非ログイン時に他サーバーに遷移するアクションを追加 - Enhance: 非ログイン時のハイライトTLのデザインを改善 @@ -44,6 +47,7 @@ - Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに - Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに - Enhance: `default.yml`内の`url`, `db.db`, `db.user`, `db.pass`を環境変数から読み込めるように +- Enhance: エンドポイント`api/meta`にプロパティ`noteSearchableScope`が増え、`string`値`local`または`global`を返却します - Fix: チャート生成時にinstance.suspensionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正 - Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006) - Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036) diff --git a/locales/index.d.ts b/locales/index.d.ts index 694ee53a1f0e..5a9eb01460e7 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -256,6 +256,10 @@ export interface Locale extends ILocale { * ユーザーを検索 */ "searchUser": string; + /** + * ユーザーのノートを検索 + */ + "searchThisUsersNotes": string; /** * 返信 */ @@ -792,6 +796,10 @@ export interface Locale extends ILocale { * ホスト */ "host": string; + /** + * 自分を選択 + */ + "selectSelf": string; /** * ユーザーを選択 */ @@ -4480,6 +4488,10 @@ export interface Locale extends ILocale { * ユーザー指定 */ "specifyUser": string; + /** + * ホスト指定 + */ + "specifyHost": string; /** * プレビューできません */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index bb3999f0e3b1..b1e6aaa680b2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -60,6 +60,7 @@ copyFileId: "ファイルIDをコピー" copyFolderId: "フォルダーIDをコピー" copyProfileUrl: "プロフィールURLをコピー" searchUser: "ユーザーを検索" +searchThisUsersNotes: "ユーザーのノートを検索" reply: "返信" loadMore: "もっと見る" showMore: "もっと見る" @@ -194,6 +195,7 @@ followConfirm: "{name}をフォローしますか?" proxyAccount: "プロキシアカウント" proxyAccountDescription: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。" host: "ホスト" +selectSelf: "自分を選択" selectUser: "ユーザーを選択" recipient: "宛先" annotation: "注釈" @@ -1116,6 +1118,7 @@ preventAiLearning: "生成AIによる学習を拒否" preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。" options: "オプション" specifyUser: "ユーザー指定" +specifyHost: "ホスト指定" failedToPreviewUrl: "プレビューできません" update: "更新" rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール" diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 09641ce48552..bcfd8ae41c1c 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -128,6 +128,7 @@ export class MetaEntityService { mediaProxy: this.config.mediaProxy, enableUrlPreview: instance.urlPreviewEnabled, + noteSearchableScope: this.config.meilisearch == null || this.config.meilisearch.scope === 'local' ? 'local' : 'global', }; return packed; diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index e7bc6356e5e3..3bcf9cac92d2 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -247,6 +247,12 @@ export const packedMetaLiteSchema = { optional: false, nullable: false, ref: 'RolePolicies', }, + noteSearchableScope: { + type: 'string', + enum: ['local', 'global'], + optional: false, nullable: false, + default: 'local', + }, }, } as const; diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 9d789a34ff90..01f10466095f 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -154,7 +154,7 @@ export function federationInstance(): entities.FederationInstance { }; } -export function userDetailed(id = 'someuserid', username = 'miskist', host = 'misskey-hub.net', name = 'Misskey User'): entities.UserDetailed { +export function userDetailed(id = 'someuserid', username = 'miskist', host:entities.UserDetailed['host'] = 'misskey-hub.net', name:entities.UserDetailed['name'] = 'Misskey User'): entities.UserDetailed { return { id, username, diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 7b6c86447edc..b94dfc53e390 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -405,6 +405,7 @@ function toStories(component: string): Promise { glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInviteCode.vue'), + glob('src/pages/search.vue'), glob('src/pages/user/home.vue'), ]); const components = globs.flat(); diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue index 549438f61b35..705c93f770c3 100644 --- a/packages/frontend/src/components/MkRadios.vue +++ b/packages/frontend/src/components/MkRadios.vue @@ -29,6 +29,9 @@ export default defineComponent({ // なぜかFragmentになることがあるため if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[]; + // vnodeのうちv-if=falseなものを除外する(trueになるものはoptionなど他typeになる) + options = options.filter(vnode => !(typeof vnode.type === 'symbol' && vnode.type.description === 'v-cmt' && vnode.children === 'v-if')); + return () => h('div', { class: 'novjtcto', }, [ @@ -40,6 +43,7 @@ export default defineComponent({ }, options.map(option => h(MkRadio, { key: option.key as string, value: option.props?.value, + disabled: option.props?.disabled, modelValue: value.value, 'onUpdate:modelValue': _v => value.value = _v, }, () => option.children)), diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue index d68bbaeeca2b..05dc77b94b49 100644 --- a/packages/frontend/src/pages/search.note.vue +++ b/packages/frontend/src/pages/search.note.vue @@ -9,26 +9,35 @@ SPDX-License-Identifier: AGPL-3.0-only - - + +
- {{ i18n.ts.localOnly }} + + + + + + + + + - - -
-
@{{ user.username }}
-
- {{ i18n.ts.selectUser }} - {{ i18n.ts.remove }} + + +
+
+ +
{{ i18n.ts.selectSelf }}
+
{{ i18n.ts.selectUser }}
+
- +
{{ i18n.ts.search }}
@@ -42,38 +51,98 @@ SPDX-License-Identifier: AGPL-3.0-only + diff --git a/packages/frontend/src/pages/search.stories.impl.ts b/packages/frontend/src/pages/search.stories.impl.ts new file mode 100644 index 000000000000..0110a7ab8ed1 --- /dev/null +++ b/packages/frontend/src/pages/search.stories.impl.ts @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { StoryObj } from '@storybook/vue3'; +import { HttpResponse, http } from 'msw'; +import search_ from './search.vue'; +import { userDetailed } from '@/../.storybook/fakes.js'; +import { commonHandlers } from '@/../.storybook/mocks.js'; + +const localUser = userDetailed('someuserid', 'miskist', null, 'Local Misskey User'); + +export const Default = { + render(args) { + return { + components: { + search_, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + ignoreNotesSearchAvailable: true, + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/users/show', () => { + return HttpResponse.json(userDetailed()); + }), + http.post('/api/users/search', () => { + return HttpResponse.json([userDetailed(), localUser]); + }), + ], + }, + }, +} satisfies StoryObj; + +export const NoteSearchDisabled = { + ...Default, + args: {}, +} satisfies StoryObj; + +export const WithUsernameLocal = { + ...Default, + + args: { + ...Default.args, + username: localUser.username, + host: localUser.host, + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/users/show', () => { + return HttpResponse.json(localUser); + }), + http.post('/api/users/search', () => { + return HttpResponse.json([userDetailed(), localUser]); + }), + ], + }, + }, +} satisfies StoryObj; + +export const WithUserType = { + ...Default, + args: { + type: 'user', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue index b9c2704bc722..85d869d9cba7 100644 --- a/packages/frontend/src/pages/search.user.vue +++ b/packages/frontend/src/pages/search.user.vue @@ -25,7 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only