summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-05-11 18:10:34 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-05-11 18:10:34 +0900
commit8b352e4e56a484b7a72143ae57b80f1fe8a5c685 (patch)
tree75cf509a7c6b9744b0eaa0a62430eec4e9ec3a41
parentenhance(backend): ノートのハッシュタグもMeilisearchに突っ込む... (diff)
downloadsharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.tar.gz
sharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.tar.bz2
sharkey-8b352e4e56a484b7a72143ae57b80f1fe8a5c685.zip
feat(frontend): ユーザー指定ノート検索
-rw-r--r--CHANGELOG.md1
-rw-r--r--locales/ja-JP.yml2
-rw-r--r--packages/frontend/src/pages/search.note.vue98
-rw-r--r--packages/frontend/src/pages/search.user.vue77
-rw-r--r--packages/frontend/src/pages/search.vue113
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>