diff --git a/locales/en-US.yml b/locales/en-US.yml index 0b44c50ff..fc1a8646c 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1131,6 +1131,7 @@ verifiedLink: "Verified link" openInMainColumn: "Open in main column" searchNotLoggedIn_1: "You have to be authenticated in order to use full text search." searchNotLoggedIn_2: "However, you can search using hashtags, and search users." +searchEmptyQuery: "Please enter a serch term." _sensitiveMediaDetection: description: "Reduces the effort of server moderation through automatically recognizing diff --git a/packages/client/src/components/MkDialog.vue b/packages/client/src/components/MkDialog.vue index 189426c36..3b4d56c8a 100644 --- a/packages/client/src/components/MkDialog.vue +++ b/packages/client/src/components/MkDialog.vue @@ -98,15 +98,6 @@ " /> - import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue"; -import * as Acct from "iceshrimp-js/built/acct"; import MkModal from "@/components/MkModal.vue"; import MkButton from "@/components/MkButton.vue"; import MkInput from "@/components/form/input.vue"; import MkTextarea from "@/components/form/textarea.vue"; import MkSelect from "@/components/form/select.vue"; -import * as os from "@/os"; import { i18n } from "@/i18n"; -import XSearchFilterDialog from "@/components/MkSearchFilterDialog.vue"; interface Input { type: HTMLInputElement["type"]; @@ -347,179 +335,6 @@ function onInputKeydown(evt: KeyboardEvent) { } } -function formatDateToYYYYMMDD(date) { - const year = date.getFullYear(); - const month = ("0" + (date.getMonth() + 1)).slice(-2); - const day = ("0" + (date.getDate() + 1)).slice(-2); - return `${year}-${month}-${day}`; -} - -function appendSearchFilter(filter: string, trailingSpace: boolean = true) { - if (typeof inputValue.value !== "string") inputValue.value = ""; - if (inputValue.value.length > 0 && inputValue.value.at(inputValue.value.length - 1) !== " ") inputValue.value += " "; - inputValue.value += filter; - if (trailingSpace) inputValue.value += " "; -} - -async function openSearchFilters(ev) { - await os.popupMenu( - [ - { - icon: "ph-user ph-bold ph-lg", - text: i18n.ts._filters.fromUser, - action: () => { - os.selectUser().then((user) => { - appendSearchFilter(`from:${Acct.toString(user)}`); - }); - }, - }, - { - icon: "ph-at ph-bold ph-lg", - text: i18n.ts._filters.mentioning, - action: () => { - os.selectUser().then((user) => { - appendSearchFilter(`mention:${Acct.toString(user)}`); - }); - }, - }, - { - icon: "ph-arrow-u-up-left ph-bold ph-lg", - text: i18n.ts._filters.replyTo, - action: () => { - os.selectUser().then((user) => { - appendSearchFilter(`reply:${Acct.toString(user)}`); - }); - }, - }, - null, - { - icon: "ph-eye ph-bold ph-lg", - text: i18n.ts._filters.followingOnly, - action: () => { - appendSearchFilter("filter:following"); - }, - }, - { - icon: "ph-users-three ph-bold ph-lg", - text: i18n.ts._filters.followersOnly, - action: () => { - appendSearchFilter("filter:followers"); - }, - }, - { - icon: "ph-link ph-bold ph-lg", - text: i18n.ts._filters.fromDomain, - action: () => { - appendSearchFilter("instance:", false); - }, - }, - null, - { - type: "parent", - text: i18n.ts._filters.withFile, - icon: "ph-paperclip ph-bold ph-lg", - children: [ - { - text: i18n.ts.image, - icon: "ph-image-square ph-bold ph-lg", - action: () => { - appendSearchFilter("has:image"); - }, - }, - { - text: i18n.ts.video, - icon: "ph-video-camera ph-bold ph-lg", - action: () => { - appendSearchFilter("has:video"); - }, - }, - { - text: i18n.ts.audio, - icon: "ph-music-note ph-bold ph-lg", - action: () => { - appendSearchFilter("has:audio"); - }, - }, - { - text: i18n.ts.file, - icon: "ph-file ph-bold ph-lg", - action: () => { - appendSearchFilter("has:file"); - }, - }, - ], - }, - null, - { - icon: "ph-calendar-blank ph-bold ph-lg", - text: i18n.ts._filters.notesBefore, - action: () => { - os.inputDate({ - title: i18n.ts._filters.notesBefore, - }).then((res) => { - if (res.canceled) return; - appendSearchFilter("before:" + formatDateToYYYYMMDD(res.result)); - }); - }, - }, - { - icon: "ph-calendar-blank ph-bold ph-lg", - text: i18n.ts._filters.notesAfter, - action: () => { - os.inputDate({ - title: i18n.ts._filters.notesAfter, - }).then((res) => { - if (res.canceled) return; - appendSearchFilter("after:" + formatDateToYYYYMMDD(res.result)); - }); - }, - }, - null, - { - icon: "ph-arrow-u-up-left ph-bold ph-lg", - text: i18n.ts._filters.excludeReplies, - action: () => { - appendSearchFilter("-filter:replies"); - }, - }, - { - icon: "ph-repeat ph-bold ph-lg", - text: i18n.ts._filters.excludeRenotes, - action: () => { - appendSearchFilter("-filter:renotes"); - }, - }, - null, - { - icon: "ph-text-aa ph-bold ph-lg", - text: i18n.ts._filters.caseSensitive, - action: () => { - appendSearchFilter("case:sensitive"); - }, - }, - { - icon: "ph-brackets-angle ph-bold ph-lg", - text: i18n.ts._filters.matchWords, - action: () => { - appendSearchFilter("match:words"); - }, - }, - null, - { - icon: "ph-question ph-bold ph-lg", - text: i18n.ts._filters._dialog.learnMore, - action: () => { - os.popup(XSearchFilterDialog, {}, {}, "closed"); - }, - }, - ], - ev.target, - { noReturnFocus: true }, - ); - inputEl.value!.focus(); - inputEl.value!.selectRange((inputValue.value as string).length, (inputValue.value as string).length); // cursor at end -} - onMounted(() => { document.addEventListener("keydown", onKeydown); }); diff --git a/packages/client/src/components/MkSearch.vue b/packages/client/src/components/MkSearch.vue new file mode 100644 index 000000000..85448ff46 --- /dev/null +++ b/packages/client/src/components/MkSearch.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 29c8a4c19..b244002a7 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -43,7 +43,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"; @@ -428,7 +427,6 @@ function checkForSplash() { d: (): void => { defaultStore.set("darkMode", !defaultStore.state.darkMode); }, - s: search, }; if ($i) { diff --git a/packages/client/src/navbar.ts b/packages/client/src/navbar.ts index 3e8723cd1..da6077cef 100644 --- a/packages/client/src/navbar.ts +++ b/packages/client/src/navbar.ts @@ -1,6 +1,5 @@ import { computed, ref, reactive } from "vue"; import { $i } from "./account"; -import { search } from "@/scripts/search"; import * as os from "@/os"; import { i18n } from "@/i18n"; import { ui } from "@/config"; @@ -53,7 +52,7 @@ export const navbarItemDef = reactive({ search: { title: "search", icon: "ph-magnifying-glass ph-bold ph-lg", - action: () => search(), + to: "/search", }, lists: { title: "lists", diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index 24043fbb1..e287776e8 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -8,6 +8,7 @@ :display-back-button="true" /> + - + + @@ -70,11 +103,19 @@ import { $i } from "@/account"; import "swiper/scss"; import "swiper/scss/virtual"; import {instance} from "@/instance"; +import MkSearch from "@/components/MkSearch.vue"; +import { mainRouter } from "@/router.js"; +import * as os from "@/os.js"; -const props = defineProps<{ - query: string; - channel?: string; -}>(); +const props = withDefaults( + defineProps<{ + query: string; + channel?: string; + }>(), + { + query: "" + } +); const notesPagination = { endpoint: "notes/search" as const, @@ -134,8 +175,42 @@ onMounted(() => { definePageMetadata( computed(() => ({ - title: i18n.t("searchWith", { q: props.query }), + title: i18n.ts.search, icon: "ph-magnifying-glass ph-bold ph-lg", })), ); + +async function search(query: string) { + const q = query.trim(); + + if (q.startsWith("@") && !q.includes(" ")) { + mainRouter.push(`/${q}`); + return; + } + + if (q.startsWith("#")) { + mainRouter.push(`/tags/${encodeURIComponent(q.slice(1))}`); + 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/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts deleted file mode 100644 index 0e23a412d..000000000 --- a/packages/client/src/scripts/search.ts +++ /dev/null @@ -1,64 +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({ - type: "search", - title: i18n.ts.search, - placeholder: i18n.ts.searchPlaceholder, - }); - 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.slice(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({ - type: "waiting", - }); - 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/client/src/ui/visitor/a.vue b/packages/client/src/ui/visitor/a.vue index a755b3789..21797c4e2 100644 --- a/packages/client/src/ui/visitor/a.vue +++ b/packages/client/src/ui/visitor/a.vue @@ -77,7 +77,6 @@ import { defineAsyncComponent, defineComponent } from "vue"; import XHeader from "./header.vue"; import { host, instanceName } from "@/config"; -import { search } from "@/scripts/search"; import * as os from "@/os"; import MkPagination from "@/components/MkPagination.vue"; import MkButton from "@/components/MkButton.vue"; @@ -118,7 +117,6 @@ export default defineComponent({ if (ColdDeviceStorage.get("syncDeviceDarkMode")) return; this.$store.set("darkMode", !this.$store.state.darkMode); }, - s: search, "h|/": this.help, }; }, @@ -168,7 +166,7 @@ export default defineComponent({ help() { // TODO(thatonecalculator): popup with keybinds // window.open('https://misskey-hub.net/docs/keyboard-shortcut.md', '_blank'); - console.log("d = dark/light mode, s = search, p = post :3"); + console.log("d = dark/light mode, p = post :3"); }, }, }); diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue index 5c3ccd694..3a34bff4c 100644 --- a/packages/client/src/ui/visitor/b.vue +++ b/packages/client/src/ui/visitor/b.vue @@ -53,10 +53,10 @@ >{{ i18n.ts.gallery }} - +
+