diff options
| author | Khsmty <me@khsmty.com> | 2023-02-25 09:01:21 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-25 09:01:21 +0900 |
| commit | 64be363adca6385104e4af268e2e57a06c4d83a9 (patch) | |
| tree | 99dc43380b92b6729a1e1789e62df40f24cbc1cb /packages/frontend/src | |
| parent | Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop (diff) | |
| download | misskey-64be363adca6385104e4af268e2e57a06c4d83a9.tar.gz misskey-64be363adca6385104e4af268e2e57a06c4d83a9.tar.bz2 misskey-64be363adca6385104e4af268e2e57a06c4d83a9.zip | |
feat: 2つの検索画面の統合 (#9949) (#10038)
* feat: 検索画面の UI を統一
* fix: エラーの修正
* add: changelog
---------
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/init.ts | 6 | ||||
| -rw-r--r-- | packages/frontend/src/navbar.ts | 3 | ||||
| -rw-r--r-- | packages/frontend/src/pages/explore.vue | 35 | ||||
| -rw-r--r-- | packages/frontend/src/pages/search.vue | 128 | ||||
| -rw-r--r-- | packages/frontend/src/router.ts | 2 | ||||
| -rw-r--r-- | packages/frontend/src/scripts/search.ts | 63 | ||||
| -rw-r--r-- | packages/frontend/src/ui/classic.header.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/ui/classic.sidebar.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/ui/visitor/a.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/ui/visitor/b.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/ui/visitor/header.vue | 6 |
11 files changed, 136 insertions, 125 deletions
diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 8c657295f9..0a626b36c6 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -36,7 +36,6 @@ import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; import { defaultStore, ColdDeviceStorage } from '@/store'; import { fetchInstance, instance } from '@/instance'; import { makeHotkey } from '@/scripts/hotkey'; -import { search } from '@/scripts/search'; import { deviceKind } from '@/scripts/device-kind'; import { initializeSw } from '@/scripts/initialize-sw'; import { reloadChannel } from '@/scripts/unison-reload'; @@ -47,6 +46,7 @@ import { deckStore } from './ui/deck/deck-store'; import { miLocalStorage } from './local-storage'; import { claimAchievement, claimedAchievements } from './scripts/achievements'; import { fetchCustomEmojis } from './custom-emojis'; +import { mainRouter } from './router'; console.info(`Misskey v${version}`); @@ -352,7 +352,9 @@ const hotkeys = { 'd': (): void => { defaultStore.set('darkMode', !defaultStore.state.darkMode); }, - 's': search, + 's': (): void => { + mainRouter.push('/search'); + } }; if ($i) { diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 95bf6e8181..48bece05fa 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -1,7 +1,6 @@ import { computed, reactive } from 'vue'; import { $i } from './account'; import { miLocalStorage } from './local-storage'; -import { search } from '@/scripts/search'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { ui } from '@/config'; @@ -42,7 +41,7 @@ export const navbarItemDef = reactive({ search: { title: i18n.ts.search, icon: 'ti ti-search', - action: () => search(), + to: '/search', }, lists: { title: i18n.ts.lists, diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue index 0ed0a7ebc2..2131188dde 100644 --- a/packages/frontend/src/pages/explore.vue +++ b/packages/frontend/src/pages/explore.vue @@ -11,23 +11,6 @@ <div v-else-if="tab === 'roles'"> <XRoles/> </div> - <div v-else-if="tab === 'search'"> - <MkSpacer :content-max="1200"> - <div> - <MkInput v-model="searchQuery" :debounce="true" type="search"> - <template #prefix><i class="ti ti-search"></i></template> - <template #label>{{ i18n.ts.searchUser }}</template> - </MkInput> - <MkRadios v-model="searchOrigin"> - <option value="combined">{{ i18n.ts.all }}</option> - <option value="local">{{ i18n.ts.local }}</option> - <option value="remote">{{ i18n.ts.remote }}</option> - </MkRadios> - </div> - - <MkUserList v-if="searchQuery" ref="searchEl" class="_margin" :pagination="searchPagination"/> - </MkSpacer> - </div> </div> </MkStickyContainer> </template> @@ -38,11 +21,8 @@ import XFeatured from './explore.featured.vue'; import XUsers from './explore.users.vue'; import XRoles from './explore.roles.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkRadios from '@/components/MkRadios.vue'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; -import MkUserList from '@/components/MkUserList.vue'; const props = withDefaults(defineProps<{ tag?: string; @@ -53,22 +33,11 @@ const props = withDefaults(defineProps<{ let tab = $ref(props.initialTab); let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>(); -let searchQuery = $ref(null); -let searchOrigin = $ref('combined'); watch(() => props.tag, () => { if (tagsEl) tagsEl.toggleContent(props.tag == null); }); -const searchPagination = { - endpoint: 'users/search' as const, - limit: 10, - params: computed(() => (searchQuery && searchQuery !== '') ? { - query: searchQuery, - origin: searchOrigin, - } : null), -}; - const headerActions = $computed(() => []); const headerTabs = $computed(() => [{ @@ -83,10 +52,6 @@ const headerTabs = $computed(() => [{ key: 'roles', icon: 'ti ti-badges', title: i18n.ts.roles, -}, { - key: 'search', - icon: 'ti ti-search', - title: i18n.ts.search, }]); definePageMetadata(computed(() => ({ diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index e52c97b350..d32bdcd78e 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -2,18 +2,41 @@ <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="800"> - <MkNotes ref="notes" :pagination="pagination"/> + <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"/> + </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"/> + </div> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { computed } from 'vue'; +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 { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import * as os from '@/os'; -import { useRouter } from '@/router'; +import { useRouter, mainRouter } from '@/router'; import { $i } from '@/account'; const router = useRouter(); @@ -21,14 +44,63 @@ const router = useRouter(); const props = defineProps<{ query: string; channel?: string; + type?: string; + origin?: string; }>(); -const query = props.query; +let searchQuery = $ref(''); +let searchType = $ref('note'); +let searchOrigin = $ref('combined'); + +onMounted(() => { + searchQuery = props.query ?? ''; + searchType = props.type ?? 'note'; + searchOrigin = props.origin ?? 'combined'; + + if (searchQuery) { + search(); + } +}); + +const search = async () => { + const query = searchQuery.toString().trim(); + + if (query == null || query === '') return; + + if (query.startsWith('@') && !query.includes(' ')) { + mainRouter.push(`/${query}`); + return; + } + + 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 ($i != null) { - if (query.startsWith('https://') || (query.startsWith('@') && !query.includes(' '))) { + if (query.startsWith('https://')) { const promise = os.api('ap/show', { - uri: props.query, + uri: query, }); os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); @@ -36,28 +108,58 @@ if ($i != null) { const res = await promise; if (res.type === 'User') { - router.replace(`/@${res.object.username}@${res.object.host}`); + mainRouter.push(`/@${res.object.username}@${res.object.host}`); } else if (res.type === 'Note') { - router.replace(`/notes/${res.object.id}`); + mainRouter.push(`/notes/${res.object.id}`); } + + return; } -} -const pagination = { + if ($i != null) { + if (query.startsWith('https://') || (query.startsWith('@') && !query.includes(' '))) { + 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.replace(`/@${res.object.username}@${res.object.host}`); + } else if (res.type === 'Note') { + router.replace(`/notes/${res.object.id}`); + } + } + } + + 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: props.query, + query: searchQuery, channelId: props.channel, })), }; +const userPagination = { + endpoint: 'users/search' as const, + limit: 10, + params: computed(() => ({ + query: searchQuery, + origin: searchOrigin, + })), +}; const headerActions = $computed(() => []); const headerTabs = $computed(() => []); definePageMetadata(computed(() => ({ - title: i18n.t('searchWith', { q: props.query }), + title: searchQuery ? i18n.t('searchWith', { q: searchQuery }) : i18n.ts.search, icon: 'ti ti-search', }))); </script> diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 9521e01910..70576688b1 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -213,6 +213,8 @@ export const routes = [{ query: { q: 'query', channel: 'channel', + type: 'type', + origin: 'origin', }, }, { path: '/authorize-follow', diff --git a/packages/frontend/src/scripts/search.ts b/packages/frontend/src/scripts/search.ts deleted file mode 100644 index 69f1586b77..0000000000 --- a/packages/frontend/src/scripts/search.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { mainRouter } from '@/router'; - -export async function search() { - const { canceled, result: query } = await os.inputText({ - title: i18n.ts.search, - }); - if (canceled || query == null || query === '') return; - - const q = query.trim(); - - if (q.startsWith('@') && !q.includes(' ')) { - mainRouter.push(`/${q}`); - return; - } - - if (q.startsWith('#')) { - mainRouter.push(`/tags/${encodeURIComponent(q.substr(1))}`); - return; - } - - // like 2018/03/12 - if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) { - const date = new Date(q.replace(/-/g, '/')); - - // 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは - // 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので - // 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の - // 結果になってしまい、2018/03/12 のコンテンツは含まれない) - if (q.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 (q.startsWith('https://')) { - const promise = os.api('ap/show', { - uri: q, - }); - - 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; - } - - mainRouter.push(`/search?q=${encodeURIComponent(q)}`); -} diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue index 34ddfa1d32..3dfb371d32 100644 --- a/packages/frontend/src/ui/classic.header.vue +++ b/packages/frontend/src/ui/classic.header.vue @@ -45,11 +45,11 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import { openInstanceMenu } from './_common_/common'; import { host } from '@/config'; -import { search } from '@/scripts/search'; import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; import { openAccountMenu } from '@/account'; import MkButton from '@/components/MkButton.vue'; +import { mainRouter } from '@/router'; export default defineComponent({ components: { @@ -103,7 +103,7 @@ export default defineComponent({ }, search() { - search(); + mainRouter.push('/search'); }, more(ev) { diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue index a11c2ba10e..6fff233ac5 100644 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ b/packages/frontend/src/ui/classic.sidebar.vue @@ -44,12 +44,12 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import { openInstanceMenu } from './_common_/common'; import { host } from '@/config'; -import { search } from '@/scripts/search'; import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; import { openAccountMenu } from '@/account'; import MkButton from '@/components/MkButton.vue'; import { StickySidebar } from '@/scripts/sticky-sidebar'; +import { mainRouter } from '@/router'; //import MisskeyLogo from '@assets/client/misskey.svg'; export default defineComponent({ @@ -120,7 +120,7 @@ export default defineComponent({ }, search() { - search(); + mainRouter.push('/search'); }, more(ev) { diff --git a/packages/frontend/src/ui/visitor/a.vue b/packages/frontend/src/ui/visitor/a.vue index 9494b1b705..023b7fdb94 100644 --- a/packages/frontend/src/ui/visitor/a.vue +++ b/packages/frontend/src/ui/visitor/a.vue @@ -40,7 +40,6 @@ import { defineComponent } from 'vue'; import XHeader from './header.vue'; import { host, instanceName } from '@/config'; -import { search } from '@/scripts/search'; import * as os from '@/os'; import MkButton from '@/components/MkButton.vue'; import { ColdDeviceStorage } from '@/store'; @@ -77,7 +76,9 @@ export default defineComponent({ if (ColdDeviceStorage.get('syncDeviceDarkMode')) return; this.$store.set('darkMode', !this.$store.state.darkMode); }, - 's': search, + 's': () => { + mainRouter.push('/search'); + }, 'h|/': this.help, }; }, diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue index 163f038b43..e2168768e8 100644 --- a/packages/frontend/src/ui/visitor/b.vue +++ b/packages/frontend/src/ui/visitor/b.vue @@ -58,7 +58,6 @@ import { ComputedRef, onMounted, provide } from 'vue'; import XHeader from './header.vue'; import XKanban from './kanban.vue'; import { host, instanceName } from '@/config'; -import { search } from '@/scripts/search'; import * as os from '@/os'; import { instance } from '@/instance'; import XSigninDialog from '@/components/MkSigninDialog.vue'; @@ -97,7 +96,9 @@ const keymap = $computed(() => { if (ColdDeviceStorage.get('syncDeviceDarkMode')) return; defaultStore.set('darkMode', !defaultStore.state.darkMode); }, - 's': search, + 's': () => { + mainRouter.push('/search'); + }, }; }); diff --git a/packages/frontend/src/ui/visitor/header.vue b/packages/frontend/src/ui/visitor/header.vue index 2647d0e62a..aaa7e77e90 100644 --- a/packages/frontend/src/ui/visitor/header.vue +++ b/packages/frontend/src/ui/visitor/header.vue @@ -27,7 +27,7 @@ import XSigninDialog from '@/components/MkSigninDialog.vue'; import XSignupDialog from '@/components/MkSignupDialog.vue'; import * as os from '@/os'; import { instance } from '@/instance'; -import { search } from '@/scripts/search'; +import { mainRouter } from '@/router'; export default defineComponent({ data() { @@ -55,7 +55,9 @@ export default defineComponent({ }, {}, 'closed'); }, - search, + search() { + mainRouter.push('/search'); + }, }, }); </script> |