Add dropdown for advanced search

This commit is contained in:
Freeplay 2023-06-08 13:01:51 -04:00
parent 75d3bb923c
commit 9b16ff6db1
9 changed files with 171 additions and 12 deletions

View file

@ -2,7 +2,7 @@
<button
v-if="!link"
class="bghgjjyj _button"
:class="{ inline, primary, gradate, danger, rounded, full, mini }"
:class="{ inline, primary, gradate, danger, rounded, full, mini, chip }"
:type="type"
@click="emit('click', $event)"
@mousedown="onMousedown"
@ -41,7 +41,8 @@ const props = defineProps<{
wait?: boolean;
danger?: boolean;
full?: boolean;
mini: boolean;
mini?: boolean;
chip?: boolean;
}>();
const emit = defineEmits<{
@ -198,6 +199,13 @@ function onMousedown(evt: MouseEvent): void {
border-radius: 100px;
}
&.chip {
padding: 4px 12px;
font-size: max(12px, 0.9em);
min-width: unset;
border-radius: 100px;
}
&:disabled {
opacity: 0.7;
}

View file

@ -2,7 +2,6 @@
<MkModal
ref="modal"
:prefer-type="'dialog'"
:z-priority="'high'"
@click="done(true)"
@closed="emit('closed')"
>
@ -56,6 +55,7 @@
</header>
<div v-if="text" :class="$style.text"><Mfm :text="text" /></div>
<MkInput
ref="inputEl"
v-if="input && input.type !== 'paragraph'"
v-model="inputValue"
autofocus
@ -66,6 +66,15 @@
<template v-if="input.type === 'password'" #prefix
><i class="ph-password ph-bold ph-lg"></i
></template>
<template v-if="input.type === 'search'" #suffix>
<button
class="_buttonIcon"
@click.stop="openSearchFilters"
v-tooltip.noDelay="i18n.ts.filter"
>
<i class="ph-funnel ph-bold"></i>
</button>
</template>
</MkInput>
<MkTextarea
v-if="input && input.type === 'paragraph'"
@ -95,6 +104,7 @@
</optgroup>
</template>
</MkSelect>
<div
v-if="(showOkButton || showCancelButton) && !actions"
:class="$style.buttons"
@ -156,13 +166,15 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import { nextTick, 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 MkTextarea from "@/components/form/textarea.vue";
import MkSelect from "@/components/form/select.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
import * as Acct from "calckey-js/built/acct";
type Input = {
type: HTMLInputElement["type"];
@ -193,7 +205,8 @@ const props = withDefaults(
| "warning"
| "info"
| "question"
| "waiting";
| "waiting"
| "search";
title: string;
text?: string;
input?: Input;
@ -229,9 +242,11 @@ const emit = defineEmits<{
const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref(props.input?.default || null);
const inputValue = ref(props.input?.default || "");
const selectedValue = ref(props.select?.default || null);
const inputEl = ref<typeof MkInput>();
function done(canceled: boolean, result?) {
emit("done", { canceled, result });
modal.value?.close();
@ -268,6 +283,99 @@ function onInputKeydown(evt: KeyboardEvent) {
}
}
async function openSearchFilters(ev) {
await os.popupMenu([
{
icon: "ph-user ph-bold ph-lg",
text: "From user",
action: () => {
os.selectUser().then((user) => {
inputValue.value += " from:@" + Acct.toString(user);
});
}
},
{
icon: "ph-file ph-bold ph-lg",
text: "With file",
action: () => {
os.select({
title: "Has file",
items: [
{
text: "Image",
value: "image",
},
{
text: "Video",
value: "video",
},
{
text: "Audio",
value: "audio",
},
{
text: "File",
value: "file",
}
]
}).then((res) => {
if (res.canceled) return;
inputValue.value += " has:" + res.result;
});
}
},
{
icon: "ph-link ph-bold ph-lg",
text: "From domain",
action: () => {
inputValue.value += " domain:";
}
},
{
icon: "ph-calendar-blank ph-bold ph-lg",
text: "Posts before",
action: () => {
os.inputDate({
title: "Posts before"
}).then((res) => {
inputEl.value.focus();
if (res.canceled) return;
inputValue.value += " before:" + res.result;
});
}
},
{
icon: "ph-calendar-blank ph-bold ph-lg",
text: "Posts after",
action: () => {
os.inputDate({
title: "Posts after"
}).then((res) => {
inputEl.value.focus();
if (res.canceled) return;
inputValue.value += " after:" + res.result;
});
}
},
{
icon: "ph-eye ph-bold ph-lg",
text: "Following only",
action: () => {
inputValue.value += " filter:following ";
}
},
{
icon: "ph-users-three ph-bold ph-lg",
text: "Followers only",
action: () => {
inputValue.value += " filter:followers ";
}
},
], ev.target, { noReturnFocus: true });
inputEl.value.focus();
inputEl.value.selectRange(inputValue.value.length, inputValue.value.length); // cursor at end
}
onMounted(() => {
document.addEventListener("keydown", onKeydown);
});
@ -333,6 +441,10 @@ onBeforeUnmount(() => {
margin: 16px 0 0 0;
}
:deep(input[type="search"]) {
width: 300px;
}
.buttons {
margin-top: 16px;
display: flex;

View file

@ -1,5 +1,9 @@
<template>
<FocusTrap :active="false" ref="focusTrap">
<FocusTrap
:active="false"
ref="focusTrap"
:return-focus-on-deactivate="!noReturnFocus"
>
<div tabindex="-1">
<div
ref="itemsEl"
@ -205,6 +209,7 @@ const props = defineProps<{
align?: "center" | string;
width?: number;
maxHeight?: number;
noReturnFocus?: boolean;
}>();
const emit = defineEmits<{

View file

@ -18,7 +18,10 @@
@enter="emit('opening')"
@after-enter="onOpened"
>
<FocusTrap v-model:active="isActive">
<FocusTrap
v-model:active="isActive"
:return-focus-on-deactivate="!noReturnFocus"
>
<div
v-show="manualShowing != null ? manualShowing : showing"
v-hotkey.global="keymap"
@ -102,6 +105,7 @@ const props = withDefaults(
zPriority?: "low" | "middle" | "high";
noOverlap?: boolean;
transparentBg?: boolean;
noReturnFocus?: boolean;
}>(),
{
manualShowing: null,
@ -111,6 +115,7 @@ const props = withDefaults(
zPriority: "low",
noOverlap: true,
transparentBg: false,
noReturnFocus: false,
}
);
@ -189,12 +194,16 @@ function close(ev, opts: { useSendAnimation?: boolean } = {}) {
if (props.src) props.src.style.pointerEvents = "auto";
showing = false;
emit("close");
focusedElement.focus();
if (!noReturnFocus) {
focusedElement.focus();
}
}
function onBgClick() {
if (contentClicking) return;
focusedElement.focus();
if (!noReturnFocus) {
focusedElement.focus();
}
emit("click");
}

View file

@ -18,6 +18,7 @@
:as-drawer="type === 'drawer'"
class="sfhdhdhq"
:class="{ drawer: type === 'drawer' }"
:no-return-focus="noReturnFocus"
@close="modal.close()"
/>
</MkModal>
@ -35,6 +36,7 @@ defineProps<{
width?: number;
viaKeyboard?: boolean;
src?: any;
noReturnFocus?;
}>();
const emit = defineEmits<{

View file

@ -108,6 +108,7 @@ const suffixEl = ref<HTMLElement>();
const height = props.small ? 36 : props.large ? 40 : 38;
const focus = () => inputEl.value.focus();
const selectRange = (start, end) => inputEl.value.setSelectionRange(start, end);
const onInput = (ev: KeyboardEvent) => {
changed.value = true;
emit("change", ev);
@ -178,6 +179,11 @@ onMounted(() => {
}
});
});
defineExpose({
focus,
selectRange
});
</script>
<style lang="scss" scoped>
@ -255,6 +261,9 @@ onMounted(() => {
white-space: nowrap;
text-overflow: ellipsis;
}
> :deep(button) {
pointer-events: all;
}
}
> .prefix {

View file

@ -346,7 +346,7 @@ export function yesno(props: {
}
export function inputText(props: {
type?: "text" | "email" | "password" | "url";
type?: "text" | "email" | "password" | "url" | "search";
title?: string | null;
text?: string | null;
placeholder?: string | null;
@ -366,6 +366,7 @@ export function inputText(props: {
delay: 1000,
}),
{
type: props.type,
title: props.title,
text: props.text,
input: {
@ -837,6 +838,7 @@ export function popupMenu(
align?: string;
width?: number;
viaKeyboard?: boolean;
noReturnFocus?: boolean;
},
) {
return new Promise((resolve, reject) => {
@ -853,6 +855,7 @@ export function popupMenu(
width: options?.width,
align: options?.align,
viaKeyboard: options?.viaKeyboard,
noReturnFocus: options?.noReturnFocus,
},
{
closed: () => {

View file

@ -18,9 +18,9 @@ export async function search() {
// const searchFiltersAvailable = instance.searchFilters;
const { canceled, result: query } = await os.inputText({
type: "search",
title: i18n.ts.search,
placeholder: i18n.ts.searchPlaceholder,
// text: searchOptions,
});
if (canceled || query == null || query === "") return;

View file

@ -482,6 +482,17 @@ hr {
}
}
._flexList {
display: flex;
flex-wrap: wrap;
gap: .2em;
width: min-content;
min-width: 100%;
&._center {
justify-content: center;
}
}
._formLinks {
> *:not(:last-child) {
margin-bottom: 8px;