diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-05-11 18:10:34 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-05-11 18:10:34 +0900 |
| commit | 8b352e4e56a484b7a72143ae57b80f1fe8a5c685 (patch) | |
| tree | 75cf509a7c6b9744b0eaa0a62430eec4e9ec3a41 | |
| parent | enhance(backend): ノートのハッシュタグもMeilisearchに突っ込む... (diff) | |
| download | sharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.tar.gz sharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.tar.bz2 sharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.zip | |
feat(frontend): ユーザー指定ノート検索
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | locales/ja-JP.yml | 2 | ||||
| -rw-r--r-- | packages/frontend/src/pages/search.note.vue | 98 | ||||
| -rw-r--r-- | packages/frontend/src/pages/search.user.vue | 77 | ||||
| -rw-r--r-- | packages/frontend/src/pages/search.vue | 113 |
5 files changed, 187 insertions, 104 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1533dd32bd..93fbec7994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - 投稿したコンテンツのAIによる学習を軽減するオプションを追加 ### Client +- ユーザーを指定してのノート検索が可能に - Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 - Fix: カラーバーがリプライには表示されないのを修正 - Fix: チャンネル内の検索ボックスが挙動不審な問題を修正 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 66ecae8e64..6908748c36 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1041,6 +1041,8 @@ initialAccountSetting: "初期設定" youFollowing: "フォロー中" preventAiLearning: "生成AIによる学習を拒否" preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。" +options: "オプション" +specifyUser: "ユーザー指定" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue new file mode 100644 index 0000000000..d9b44d15f5 --- /dev/null +++ b/packages/frontend/src/pages/search.note.vue @@ -0,0 +1,98 @@ +<template> +<div class="_gaps"> + <div class="_gaps"> + <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> + <template #prefix><i class="ti ti-search"></i></template> + </MkInput> + <MkFolder> + <template #label>{{ i18n.ts.options }}</template> + + <MkFolder> + <template #label>{{ i18n.ts.specifyUser }}</template> + <template v-if="user" #suffix>@{{ user.username }}</template> + + <div style="text-align: center;" class="_gaps"> + <div v-if="user">@{{ user.username }}</div> + <div> + <MkButton v-if="user == null" primary rounded inline @click="selectUser">{{ i18n.ts.selectUser }}</MkButton> + <MkButton v-else danger rounded inline @click="user = null">{{ i18n.ts.remove }}</MkButton> + </div> + </div> + </MkFolder> + </MkFolder> + <div> + <MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton> + </div> + </div> + + <MkFoldableSection v-if="notePagination"> + <template #header>{{ i18n.ts.searchResult }}</template> + <MkNotes :key="key" :pagination="notePagination"/> + </MkFoldableSection> +</div> +</template> + +<script lang="ts" setup> +import { computed, onMounted } from 'vue'; +import MkNotes from '@/components/MkNotes.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkRadios from '@/components/MkRadios.vue'; +import MkButton from '@/components/MkButton.vue'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; +import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import MkInfo from '@/components/MkInfo.vue'; +import { useRouter } from '@/router'; +import MkFolder from '@/components/MkFolder.vue'; + +const router = useRouter(); + +let key = $ref(0); +let searchQuery = $ref(''); +let searchOrigin = $ref('combined'); +let notePagination = $ref(); +let user = $ref(null); + +function selectUser() { + os.selectUser().then(_user => { + user = _user; + }); +} + +async function search() { + const query = searchQuery.toString().trim(); + + if (query == null || query === '') return; + + if (query.startsWith('https://')) { + const promise = os.api('ap/show', { + uri: query, + }); + + os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); + + const res = await promise; + + if (res.type === 'User') { + router.push(`/@${res.object.username}@${res.object.host}`); + } else if (res.type === 'Note') { + router.push(`/notes/${res.object.id}`); + } + + return; + } + + notePagination = { + endpoint: 'notes/search', + limit: 10, + params: { + query: searchQuery, + userId: user ? user.id : null, + }, + }; + + key++; +} +</script> diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue new file mode 100644 index 0000000000..23a8978fd1 --- /dev/null +++ b/packages/frontend/src/pages/search.user.vue @@ -0,0 +1,77 @@ +<template> +<div class="_gaps"> + <div class="_gaps"> + <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> + <template #prefix><i class="ti ti-search"></i></template> + </MkInput> + <MkRadios v-model="searchOrigin" @update:model-value="search()"> + <option value="combined">{{ i18n.ts.all }}</option> + <option value="local">{{ i18n.ts.local }}</option> + <option value="remote">{{ i18n.ts.remote }}</option> + </MkRadios> + <MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton> + </div> + + <MkFoldableSection v-if="userPagination"> + <template #header>{{ i18n.ts.searchResult }}</template> + <MkUserList :key="key" :pagination="userPagination"/> + </MkFoldableSection> +</div> +</template> + +<script lang="ts" setup> +import { computed, defineAsyncComponent, onMounted } from 'vue'; +import MkUserList from '@/components/MkUserList.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkRadios from '@/components/MkRadios.vue'; +import MkButton from '@/components/MkButton.vue'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; +import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import MkInfo from '@/components/MkInfo.vue'; +import { useRouter } from '@/router'; + +const router = useRouter(); + +let key = $ref(''); +let searchQuery = $ref(''); +let searchOrigin = $ref('combined'); +let userPagination = $ref(); + +async function search() { + const query = searchQuery.toString().trim(); + + if (query == null || query === '') return; + + if (query.startsWith('https://')) { + const promise = os.api('ap/show', { + uri: query, + }); + + os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); + + const res = await promise; + + if (res.type === 'User') { + router.push(`/@${res.object.username}@${res.object.host}`); + } else if (res.type === 'Note') { + router.push(`/notes/${res.object.id}`); + } + + return; + } + + userPagination = { + endpoint: 'users/search', + limit: 10, + params: { + query: searchQuery, + origin: searchOrigin, + }, + }; + + key = query; +} +</script> diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 5523d5cf4d..9f3d8da560 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -1,133 +1,38 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer v-if="tab === 'note'" :content-max="800"> - <div v-if="notesSearchAvailable" class="_gaps"> - <div class="_gaps"> - <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> - <template #prefix><i class="ti ti-search"></i></template> - </MkInput> - <MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton> - </div> - <MkFoldableSection v-if="notePagination"> - <template #header>{{ i18n.ts.searchResult }}</template> - <MkNotes :key="key" :pagination="notePagination"/> - </MkFoldableSection> + <MkSpacer v-if="tab === 'note'" :content-max="800"> + <div v-if="notesSearchAvailable"> + <XNote/> </div> <div v-else> <MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo> </div> </MkSpacer> - <MkSpacer v-else-if="tab === 'user'" :content-max="800"> - <div class="_gaps"> - <div class="_gaps"> - <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> - <template #prefix><i class="ti ti-search"></i></template> - </MkInput> - <MkRadios v-model="searchOrigin" @update:model-value="search()"> - <option value="combined">{{ i18n.ts.all }}</option> - <option value="local">{{ i18n.ts.local }}</option> - <option value="remote">{{ i18n.ts.remote }}</option> - </MkRadios> - <MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton> - </div> - <MkFoldableSection v-if="userPagination"> - <template #header>{{ i18n.ts.searchResult }}</template> - <MkUserList :key="key" :pagination="userPagination"/> - </MkFoldableSection> - </div> + <MkSpacer v-else-if="tab === 'user'" :content-max="800"> + <XUser/> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { computed, onMounted } from 'vue'; -import MkNotes from '@/components/MkNotes.vue'; -import MkUserList from '@/components/MkUserList.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkRadios from '@/components/MkRadios.vue'; -import MkButton from '@/components/MkButton.vue'; +import { computed, defineAsyncComponent, onMounted } from 'vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import * as os from '@/os'; -import MkFoldableSection from '@/components/MkFoldableSection.vue'; import { $i } from '@/account'; import { instance } from '@/instance'; import MkInfo from '@/components/MkInfo.vue'; -import { useRouter } from '@/router'; - -const router = useRouter(); -const props = defineProps<{ - query: string; - channel?: string; - type?: string; - origin?: string; -}>(); +const XNote = defineAsyncComponent(() => import('./search.note.vue')); +const XUser = defineAsyncComponent(() => import('./search.user.vue')); -let key = $ref(''); let tab = $ref('note'); -let searchQuery = $ref(''); -let searchOrigin = $ref('combined'); -let notePagination = $ref(); -let userPagination = $ref(); const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes)); -onMounted(() => { - tab = props.type ?? 'note'; - searchQuery = props.query ?? ''; - searchOrigin = props.origin ?? 'combined'; -}); - -async function search() { - const query = searchQuery.toString().trim(); - - if (query == null || query === '') return; - - if (query.startsWith('https://')) { - const promise = os.api('ap/show', { - uri: query, - }); - - os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); - - const res = await promise; - - if (res.type === 'User') { - router.push(`/@${res.object.username}@${res.object.host}`); - } else if (res.type === 'Note') { - router.push(`/notes/${res.object.id}`); - } - - return; - } - - if (tab === 'note') { - notePagination = { - endpoint: 'notes/search', - limit: 10, - params: { - query: searchQuery, - channelId: props.channel, - }, - }; - } else if (tab === 'user') { - userPagination = { - endpoint: 'users/search', - limit: 10, - params: { - query: searchQuery, - origin: searchOrigin, - }, - }; - } - - key = query; -} - const headerActions = $computed(() => []); const headerTabs = $computed(() => [{ @@ -141,7 +46,7 @@ const headerTabs = $computed(() => [{ }]); definePageMetadata(computed(() => ({ - title: searchQuery ? i18n.t('searchWith', { q: searchQuery }) : i18n.ts.search, + title: i18n.ts.search, icon: 'ti ti-search', }))); </script> |