From 925cc06aea4aea0bed4624df82bc84c6ca4a98a0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 16 Mar 2023 11:56:20 +0900 Subject: [PATCH] enhance(client): tweak search page --- locales/ja-JP.yml | 1 + packages/frontend/src/navbar.ts | 8 + packages/frontend/src/pages/notifications.vue | 2 + packages/frontend/src/pages/search.vue | 174 ++++++++---------- packages/frontend/src/scripts/lookup.ts | 41 +++++ 5 files changed, 129 insertions(+), 97 deletions(-) create mode 100644 packages/frontend/src/scripts/lookup.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index feb09b9054..a3a1d2cd24 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -973,6 +973,7 @@ rolesAssignedToMe: "自分ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸãƒãƒ¼ãƒ«" resetPasswordConfirm: "パスワードリセットã—ã¾ã™ã‹ï¼Ÿ" sensitiveWords: "センシティブワード" sensitiveWordsDescription: "è¨å®šã—ãŸãƒ¯ãƒ¼ãƒ‰ãŒå«ã¾ã‚Œã‚‹ãƒŽãƒ¼ãƒˆã®å…¬é–‹ç¯„囲をホームã«ã—ã¾ã™ã€‚改行ã§åŒºåˆ‡ã£ã¦è¤‡æ•°è¨å®šã§ãã¾ã™ã€‚" +notesSearchNotAvailable: "ノート検索ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。" _achievements: earnedAt: "ç²å¾—日時" diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 032d68862d..0e2f787d50 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -2,6 +2,7 @@ import { computed, reactive } from 'vue'; import { $i } from './account'; import { miLocalStorage } from './local-storage'; import { openInstanceMenu } from './ui/_common_/common'; +import { lookup } from './scripts/lookup'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { ui } from '@/config'; @@ -44,6 +45,13 @@ export const navbarItemDef = reactive({ icon: 'ti ti-search', to: '/search', }, + lookup: { + title: i18n.ts.lookup, + icon: 'ti ti-world-search', + action: (ev) => { + lookup(); + }, + }, lists: { title: i18n.ts.lists, icon: 'ti ti-list', diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index da64a4c1e0..a5c7cdaa71 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -75,9 +75,11 @@ const headerActions = $computed(() => [tab === 'all' ? { const headerTabs = $computed(() => [{ key: 'all', title: i18n.ts.all, + icon: 'ti ti-point', }, { key: 'unread', title: i18n.ts.unread, + icon: 'ti ti-loader', }, { key: 'mentions', title: i18n.ts.mentions, diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 7e81cd2c0d..cc6f8cc0cc 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -1,26 +1,42 @@ <template> <MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> - <MkInput v-model="searchQuery" :large="true" :autofocus="true" :debounce="true" type="search" style="margin-bottom: var(--margin);" @update:model-value="search()"> - <template #prefix><i class="ti ti-search"></i></template> - </MkInput> - <MkTab v-model="searchType" style="margin-bottom: var(--margin);" @update:model-value="search()"> - <option value="note">{{ i18n.ts.note }}</option> - <option value="user">{{ i18n.ts.user }}</option> - </MkTab> - - <div v-if="searchType === 'note'"> - <MkNotes v-if="searchQuery" ref="notes" :pagination="notePagination"/> + <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> </div> <div v-else> - <MkRadios v-model="searchOrigin" style="margin-bottom: var(--margin);" @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> - - <MkUserList v-if="searchQuery" ref="users" :pagination="userPagination"/> + <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> </MkStickyContainer> @@ -31,14 +47,15 @@ import { computed, onMounted } from 'vue'; import MkNotes from '@/components/MkNotes.vue'; import MkUserList from '@/components/MkUserList.vue'; import MkInput from '@/components/MkInput.vue'; -import MkTab from '@/components/MkTab.vue'; import MkRadios from '@/components/MkRadios.vue'; +import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import * as os from '@/os'; -import { useRouter, mainRouter } from '@/router'; - -const router = useRouter(); +import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import MkInfo from '@/components/MkInfo.vue'; const props = defineProps<{ query: string; @@ -47,97 +64,60 @@ const props = defineProps<{ origin?: string; }>(); +let key = $ref(''); +let tab = $ref('note'); let searchQuery = $ref(''); -let searchType = $ref('note'); 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 ?? ''; - searchType = props.type ?? 'note'; searchOrigin = props.origin ?? 'combined'; - - if (searchQuery) { - search(); - } }); -const search = async () => { +async function search() { const query = searchQuery.toString().trim(); if (query == null || query === '') return; - if (query.startsWith('@') && !query.includes(' ')) { - mainRouter.push(`/${query}`); - 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, + }, + }; } - if (query.startsWith('#')) { - mainRouter.push(`/tags/${encodeURIComponent(query.substr(1))}`); - return; - } - - // like 2018/03/12 - if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(query.replace(/-/g, '/'))) { - const date = new Date(query.replace(/-/g, '/')); - - // 日付ã—ã‹æŒ‡å®šã•ã‚Œã¦ãªã„å ´åˆã€ä¾‹ãˆã° 2018/03/12 ãªã‚‰ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ - // 2018/03/12 ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を「å«ã‚€ã€çµæžœã«ãªã‚‹ã“ã¨ã‚’期待ã™ã‚‹ã¯ãšãªã®ã§ - // 23時間59分進ã‚ã‚‹(ãã®ã¾ã¾ã 㨠2018/03/12 00:00:00 「ã¾ã§ã€ã® - // çµæžœã«ãªã£ã¦ã—ã¾ã„ã€2018/03/12 ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã¯å«ã¾ã‚Œãªã„) - if (query.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) { - date.setHours(23, 59, 59, 999); - } - - // TODO - //v.$root.$emit('warp', date); - os.alert({ - icon: 'ti ti-history', - iconOnly: true, autoClose: true, - }); - 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') { - mainRouter.push(`/@${res.object.username}@${res.object.host}`); - } else if (res.type === 'Note') { - mainRouter.push(`/notes/${res.object.id}`); - } - - return; - } - - window.history.replaceState('', '', `/search?q=${encodeURIComponent(query)}&type=${searchType}${searchType === 'user' ? `&origin=${searchOrigin}` : ''}`); -}; - -const notePagination = { - endpoint: 'notes/search' as const, - limit: 10, - params: computed(() => ({ - query: searchQuery, - channelId: props.channel, - })), -}; -const userPagination = { - endpoint: 'users/search' as const, - limit: 10, - params: computed(() => ({ - query: searchQuery, - origin: searchOrigin, - })), -}; + key = query; +} const headerActions = $computed(() => []); -const headerTabs = $computed(() => []); +const headerTabs = $computed(() => [{ + key: 'note', + title: i18n.ts.notes, + icon: 'ti ti-pencil', +}, { + key: 'user', + title: i18n.ts.users, + icon: 'ti ti-users', +}]); definePageMetadata(computed(() => ({ title: searchQuery ? i18n.t('searchWith', { q: searchQuery }) : i18n.ts.search, diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts new file mode 100644 index 0000000000..ce5b03fc38 --- /dev/null +++ b/packages/frontend/src/scripts/lookup.ts @@ -0,0 +1,41 @@ +import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { mainRouter } from '@/router'; +import { Router } from '@/nirax'; + +export async function lookup(router?: Router) { + const _router = router ?? mainRouter; + + const { canceled, result: query } = await os.inputText({ + title: i18n.ts.lookup, + }); + if (canceled) return; + + if (query.startsWith('@') && !query.includes(' ')) { + _router.push(`/${query}`); + return; + } + + if (query.startsWith('#')) { + _router.push(`/tags/${encodeURIComponent(query.substr(1))}`); + 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; + } +} -- GitLab