firefish/packages/client/src/components/MkPostSearch.vue
2024-04-08 17:17:08 +09:00

251 lines
5.5 KiB
Vue

<template>
<MkModal
ref="modal"
:prefer-type="'dialog'"
@click="done(true)"
@closed="emit('closed')"
>
<div :class="$style.root">
<header :class="$style.title">
<i :class="icon('ph-magnifying-glass', false)"></i>
{{ i18n.ts.search }}
</header>
<MkInput
v-model="searchWords"
autofocus
type="search"
:placeholder="i18n.ts.searchWords"
:class="$style.input"
@keydown="onInputKeydown"
>
<template #suffix>
<button
v-tooltip.noDelay="i18n.ts.help"
class="_buttonIcon"
@click.stop="openDescription('words')"
>
<i :class="icon('ph-question', false)"></i>
</button>
</template>
</MkInput>
<MkInput
v-model="searchUsers"
type="search"
:placeholder="i18n.ts.searchUsers"
:class="$style.input"
@keydown="onInputKeydown"
>
<template #suffix>
<button
v-tooltip.noDelay="i18n.ts.help"
class="_buttonIcon"
@click.stop="openDescription('users')"
>
<i :class="icon('ph-question', false)"></i>
</button>
</template>
</MkInput>
<MkInput
v-model="searchRange"
type="search"
:placeholder="i18n.ts.searchRange"
:class="$style.input"
@keydown="onInputKeydown"
>
<template #suffix>
<button
v-tooltip.noDelay="i18n.ts.help"
class="_buttonIcon"
@click.stop="openDescription('range')"
>
<i :class="icon('ph-question', false)"></i>
</button>
</template>
</MkInput>
<FormSwitch
v-model="searchCwAndAlt"
class="form-switch"
:class="$style.input"
>{{ i18n.ts.searchCwAndAlt }}</FormSwitch
>
<FormSwitch
v-model="searchPostsWithFiles"
class="form-switch"
:class="$style.input"
>{{ i18n.ts.searchPostsWithFiles }}</FormSwitch
>
<div :class="$style.buttons">
<MkButton inline primary @click="search"
>{{ i18n.ts.search }}
</MkButton>
<MkButton inline @click="lookup">{{ i18n.ts.lookup }}</MkButton>
<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
</div>
</div>
</MkModal>
</template>
<script lang="ts" setup>
import {
defineAsyncComponent,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
} from "vue";
import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
import FormSwitch from "@/components/form/switch.vue";
import { i18n } from "@/i18n";
import icon from "@/scripts/icon";
import { popup } from "@/os";
type searchQuery =
| {
action: "lookup";
query: string;
}
| {
action: "search";
query?: string;
from?: string;
range?: string;
withFiles: boolean;
searchCwAndAlt: boolean;
};
const emit = defineEmits<{
(ev: "done", v: { canceled: boolean; result?: searchQuery }): void;
(ev: "closed"): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const searchParams = new URLSearchParams(window.location.search);
const searchWords = ref(searchParams.get("q") ?? "");
const searchUsers = ref(
searchParams.get("user") ?? searchParams.get("host") ?? "",
);
const searchRange = ref(
searchParams.has("since") || searchParams.has("until")
? `${searchParams.get("since") ?? ""}-${searchParams.get("until") ?? ""}`
: "",
);
const searchPostsWithFiles = ref(searchParams.get("withFiles") === "1");
const searchCwAndAlt = ref(searchParams.get("detailed") !== "0");
function done(canceled: boolean, result?: searchQuery) {
emit("done", { canceled, result });
modal.value?.close(null);
}
function search() {
searchWords.value = searchWords.value.trim();
searchUsers.value = searchUsers.value.trim();
searchRange.value = searchRange.value.trim();
if (
searchWords.value === "" &&
searchUsers.value === "" &&
searchRange.value === ""
)
return;
done(false, {
action: "search",
query: searchWords.value === "" ? undefined : searchWords.value,
from: searchUsers.value === "" ? undefined : searchUsers.value,
range: searchRange.value === "" ? undefined : searchRange.value,
withFiles: searchPostsWithFiles.value,
searchCwAndAlt: searchCwAndAlt.value,
});
}
function lookup() {
searchWords.value = searchWords.value.trim();
if (searchWords.value === "") return;
done(false, {
action: "lookup",
query: searchWords.value,
});
}
function cancel() {
done(true);
}
function onKeydown(evt: KeyboardEvent) {
if (evt.key === "Escape") cancel();
}
function onInputKeydown(evt: KeyboardEvent) {
if (evt.key === "Enter") {
evt.preventDefault();
evt.stopPropagation();
search();
}
}
function openDescription(kind: "words" | "users" | "range"): void {
const descriptions = {
words: i18n.ts.searchWordsDescription,
users: i18n.ts.searchUsersDescription,
range: i18n.ts.searchRangeDescription,
};
popup(
defineAsyncComponent(() => import("@/components/MkSimpleTextWindow.vue")),
{
title: i18n.ts.help,
description: descriptions[kind],
},
{},
"closed",
);
}
onMounted(() => {
document.addEventListener("keydown", onKeydown);
});
onBeforeUnmount(() => {
document.removeEventListener("keydown", onKeydown);
});
</script>
<style lang="scss" module>
.root {
position: relative;
margin: auto;
padding: 32px;
min-width: 320px;
max-width: 480px;
box-sizing: border-box;
text-align: center;
background: var(--panel);
border-radius: var(--radius);
}
.input {
margin: 12px 0;
}
.title {
margin: 0 0 25px;
font-weight: bold;
font-size: 1.3em;
}
.buttons {
margin-top: 16px;
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
</style>