iceshrimp/packages/client/src/components/MkSearch.vue

256 lines
5.9 KiB
Vue

<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;
hideFilters?: boolean;
}>();
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,
{
icon: "ph-star ph-bold ph-lg",
text: i18n.ts._filters.inFavorites,
action: () => {
appendSearchFilter("in:favorites");
},
},
{
icon: "ph-bookmark-simple ph-bold ph-lg",
text: i18n.ts._filters.inBookmarks,
action: () => {
appendSearchFilter("in:bookmarks");
},
},
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 v-if="!hideFilters" #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>