[client] Add proper search page

This commit is contained in:
Laura Hausmann 2023-11-19 00:22:46 +01:00
parent 855409332b
commit ee4b58dee8
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
10 changed files with 332 additions and 276 deletions

View file

@ -1131,6 +1131,7 @@ verifiedLink: "Verified link"
openInMainColumn: "Open in main column" openInMainColumn: "Open in main column"
searchNotLoggedIn_1: "You have to be authenticated in order to use full text search." 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." searchNotLoggedIn_2: "However, you can search using hashtags, and search users."
searchEmptyQuery: "Please enter a serch term."
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing

View file

@ -98,15 +98,6 @@
" "
/> />
</template> </template>
<template v-if="input.type === 'search'" #suffix>
<button
v-tooltip.noDelay="i18n.ts.filter"
class="_buttonIcon"
@click.stop="openSearchFilters"
>
<i class="ph-funnel ph-bold"></i>
</button>
</template>
</MkInput> </MkInput>
<MkTextarea <MkTextarea
v-if="input && input.type === 'paragraph'" v-if="input && input.type === 'paragraph'"
@ -200,15 +191,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue"; import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import * as Acct from "iceshrimp-js/built/acct";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
import MkTextarea from "@/components/form/textarea.vue"; import MkTextarea from "@/components/form/textarea.vue";
import MkSelect from "@/components/form/select.vue"; import MkSelect from "@/components/form/select.vue";
import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import XSearchFilterDialog from "@/components/MkSearchFilterDialog.vue";
interface Input { interface Input {
type: HTMLInputElement["type"]; 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(() => { onMounted(() => {
document.addEventListener("keydown", onKeydown); document.addEventListener("keydown", onKeydown);
}); });

View file

@ -0,0 +1,239 @@
<script setup lang="ts">
import { i18n } from "@/i18n.js";
import MkInput from "@/components/form/input.vue";
import * as os from "@/os.js";
import XSearchFilterDialog from "@/components/MkSearchFilterDialog.vue";
import { onActivated, onMounted, onUnmounted, ref, toRefs } from "vue";
import * as Acct from "iceshrimp-js/built/acct";
const props = defineProps<{
query: string;
}>();
const emit = defineEmits<{
(ev: "query", v: string): void;
}>();
const query = ref(props.query);
const input = ref<typeof MkInput>();
onActivated(() => {
query.value = props.query;
input.value!.focus();
});
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 (query.value.length > 0 && query.value.at(query.value.length - 1) !== " ") query.value += " ";
query.value += filter;
if (trailingSpace) query.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 },
);
input.value!.focus();
input.value!.selectRange((query.value as string).length, (query.value as string).length); // cursor at end
}
function onInputKeydown(evt: KeyboardEvent) {
if (evt.key === "Enter") {
evt.preventDefault();
evt.stopPropagation();
emit('query', query.value);
}
}
</script>
<template>
<MkInput
class="search"
ref="input"
v-model="query"
style="flex: 1"
type="text"
autofocus
:placeholder="i18n.ts.searchPlaceholder"
:spellcheck="false"
@keydown="onInputKeydown"
>
<template #prefix>
<div>
<i class="ph-magnifying-glass ph-bold"></i>
</div>
</template>
<template #suffix>
<button
v-tooltip.noDelay="i18n.ts.filter"
class="_buttonIcon"
@click.stop="openSearchFilters"
>
<i class="ph-funnel ph-bold"></i>
</button>
</template>
</MkInput>
</template>
<style scoped lang="scss">
.search {
margin-bottom: 16px;
}
</style>

View file

@ -43,7 +43,6 @@ import { $i, refreshAccount, login, updateAccount, signout } from "@/account";
import { defaultStore, ColdDeviceStorage } from "@/store"; import { defaultStore, ColdDeviceStorage } from "@/store";
import { fetchInstance, instance } from "@/instance"; import { fetchInstance, instance } from "@/instance";
import { makeHotkey } from "@/scripts/hotkey"; import { makeHotkey } from "@/scripts/hotkey";
import { search } from "@/scripts/search";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
import { initializeSw } from "@/scripts/initialize-sw"; import { initializeSw } from "@/scripts/initialize-sw";
import { reloadChannel } from "@/scripts/unison-reload"; import { reloadChannel } from "@/scripts/unison-reload";
@ -428,7 +427,6 @@ function checkForSplash() {
d: (): void => { d: (): void => {
defaultStore.set("darkMode", !defaultStore.state.darkMode); defaultStore.set("darkMode", !defaultStore.state.darkMode);
}, },
s: search,
}; };
if ($i) { if ($i) {

View file

@ -1,6 +1,5 @@
import { computed, ref, reactive } from "vue"; import { computed, ref, reactive } from "vue";
import { $i } from "./account"; import { $i } from "./account";
import { search } from "@/scripts/search";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { ui } from "@/config"; import { ui } from "@/config";
@ -53,7 +52,7 @@ export const navbarItemDef = reactive({
search: { search: {
title: "search", title: "search",
icon: "ph-magnifying-glass ph-bold ph-lg", icon: "ph-magnifying-glass ph-bold ph-lg",
action: () => search(), to: "/search",
}, },
lists: { lists: {
title: "lists", title: "lists",

View file

@ -8,6 +8,7 @@
:display-back-button="true" :display-back-button="true"
/></template> /></template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<MkSearch :query="query" @query="search"/>
<swiper <swiper
:round-lengths="true" :round-lengths="true"
:touch-angle="25" :touch-angle="25"
@ -26,7 +27,23 @@
> >
<swiper-slide> <swiper-slide>
<template v-if="$i"> <template v-if="$i">
<XNotes ref="notes" :pagination="notesPagination" /> <template v-if="query == null || query.trim().length < 1">
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div class="_fullinfo" ref="notes">
<img
:src="instance.images.info"
class="_ghost"
alt="Info"
/>
<div>
{{ i18n.ts.searchEmptyQuery }}
</div>
</div>
</transition>
</template>
<template v-else>
<XNotes ref="notes" :pagination="notesPagination" />
</template>
</template> </template>
<template v-else> <template v-else>
<transition :name="$store.state.animation ? 'zoom' : ''" appear> <transition :name="$store.state.animation ? 'zoom' : ''" appear>
@ -45,11 +62,27 @@
</template> </template>
</swiper-slide> </swiper-slide>
<swiper-slide> <swiper-slide>
<XUserList <template v-if="query == null || query.trim().length < 1">
ref="users" <transition :name="$store.state.animation ? 'zoom' : ''" appear>
class="_gap" <div class="_fullinfo" ref="notes">
:pagination="usersPagination" <img
/> :src="instance.images.info"
class="_ghost"
alt="Info"
/>
<div>
{{ i18n.ts.searchEmptyQuery }}
</div>
</div>
</transition>
</template>
<template v-else>
<XUserList
ref="users"
class="_gap"
:pagination="usersPagination"
/>
</template>
</swiper-slide> </swiper-slide>
</swiper> </swiper>
</MkSpacer> </MkSpacer>
@ -70,11 +103,19 @@ import { $i } from "@/account";
import "swiper/scss"; import "swiper/scss";
import "swiper/scss/virtual"; import "swiper/scss/virtual";
import {instance} from "@/instance"; import {instance} from "@/instance";
import MkSearch from "@/components/MkSearch.vue";
import { mainRouter } from "@/router.js";
import * as os from "@/os.js";
const props = defineProps<{ const props = withDefaults(
query: string; defineProps<{
channel?: string; query: string;
}>(); channel?: string;
}>(),
{
query: ""
}
);
const notesPagination = { const notesPagination = {
endpoint: "notes/search" as const, endpoint: "notes/search" as const,
@ -134,8 +175,42 @@ onMounted(() => {
definePageMetadata( definePageMetadata(
computed(() => ({ computed(() => ({
title: i18n.t("searchWith", { q: props.query }), title: i18n.ts.search,
icon: "ph-magnifying-glass ph-bold ph-lg", 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)}`);
}
</script> </script>

View file

@ -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)}`);
}

View file

@ -77,7 +77,6 @@
import { defineAsyncComponent, defineComponent } from "vue"; import { defineAsyncComponent, defineComponent } from "vue";
import XHeader from "./header.vue"; import XHeader from "./header.vue";
import { host, instanceName } from "@/config"; import { host, instanceName } from "@/config";
import { search } from "@/scripts/search";
import * as os from "@/os"; import * as os from "@/os";
import MkPagination from "@/components/MkPagination.vue"; import MkPagination from "@/components/MkPagination.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
@ -118,7 +117,6 @@ export default defineComponent({
if (ColdDeviceStorage.get("syncDeviceDarkMode")) return; if (ColdDeviceStorage.get("syncDeviceDarkMode")) return;
this.$store.set("darkMode", !this.$store.state.darkMode); this.$store.set("darkMode", !this.$store.state.darkMode);
}, },
s: search,
"h|/": this.help, "h|/": this.help,
}; };
}, },
@ -168,7 +166,7 @@ export default defineComponent({
help() { help() {
// TODO(thatonecalculator): popup with keybinds // TODO(thatonecalculator): popup with keybinds
// window.open('https://misskey-hub.net/docs/keyboard-shortcut.md', '_blank'); // 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");
}, },
}, },
}); });

View file

@ -53,10 +53,10 @@
><i class="ph-image-square ph-bold ph-lg icon"></i ><i class="ph-image-square ph-bold ph-lg icon"></i
>{{ i18n.ts.gallery }}</MkA >{{ i18n.ts.gallery }}</MkA
> >
<button class="_button link" active-class="active" @click="search()"> <MkA to="/search" class="link" active-class="active">
<i class="ph-magnifying-glass ph-bold ph-lg icon"></i <i class="ph-magnifying-glass ph-bold ph-lg icon"></i
><span>{{ i18n.ts.search }}</span> ><span>{{ i18n.ts.search }}</span>
</button> </MkA>
<div class="action"> <div class="action">
<button class="_buttonPrimary" @click="signup()"> <button class="_buttonPrimary" @click="signup()">
{{ i18n.ts.signup }} {{ i18n.ts.signup }}
@ -76,7 +76,6 @@ import { onMounted, provide } from "vue";
import XHeader from "./header.vue"; import XHeader from "./header.vue";
import XKanban from "./kanban.vue"; import XKanban from "./kanban.vue";
import { host, instanceName } from "@/config"; import { host, instanceName } from "@/config";
import { search } from "@/scripts/search";
import * as os from "@/os"; import * as os from "@/os";
import { instance } from "@/instance"; import { instance } from "@/instance";
import MkPagination from "@/components/MkPagination.vue"; import MkPagination from "@/components/MkPagination.vue";
@ -123,7 +122,6 @@ const keymap = $computed(() => {
if (ColdDeviceStorage.get("syncDeviceDarkMode")) return; if (ColdDeviceStorage.get("syncDeviceDarkMode")) return;
defaultStore.set("darkMode", !defaultStore.state.darkMode); defaultStore.set("darkMode", !defaultStore.state.darkMode);
}, },
s: search,
}; };
}); });

View file

@ -26,10 +26,10 @@
><i class="ph-image-square ph-bold ph-lg icon"></i ><i class="ph-image-square ph-bold ph-lg icon"></i
>{{ i18n.ts.gallery }}</MkA >{{ i18n.ts.gallery }}</MkA
> >
<button class="_button link" active-class="active" @click="search()"> <MkA to="/search" class="link" active-class="active">
<i class="ph-magnifying-glass ph-bold ph-lg icon"></i <i class="ph-magnifying-glass ph-bold ph-lg icon"></i
><span>{{ i18n.ts.search }}</span> ><span>{{ i18n.ts.search }}</span>
</button> </MkA>
<div v-if="info" class="page active link"> <div v-if="info" class="page active link">
<div class="title"> <div class="title">
<i v-if="info.icon" class="icon" :class="info.icon"></i> <i v-if="info.icon" class="icon" :class="info.icon"></i>
@ -106,7 +106,6 @@ import XSigninDialog from "@/components/MkSigninDialog.vue";
import XSignupDialog from "@/components/MkSignupDialog.vue"; import XSignupDialog from "@/components/MkSignupDialog.vue";
import * as os from "@/os"; import * as os from "@/os";
import { instance } from "@/instance"; import { instance } from "@/instance";
import { search } from "@/scripts/search";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
export default defineComponent({ export default defineComponent({
@ -154,8 +153,6 @@ export default defineComponent({
"closed", "closed",
); );
}, },
search,
}, },
}); });
</script> </script>