mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 11:27:31 -07:00
Merge pull request '[PR]: Advanced Search dropdown' (#10299) from Freeplay/calckey:search2 into develop
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10299
This commit is contained in:
commit
1d8f4a0a9c
10 changed files with 184 additions and 15 deletions
|
@ -968,6 +968,9 @@ rateLimitExceeded: "Rate limit exceeded"
|
|||
cropImage: "Crop image"
|
||||
cropImageAsk: "Do you want to crop this image?"
|
||||
file: "File"
|
||||
image: "Image"
|
||||
video: "Video"
|
||||
audio: "Audio"
|
||||
recentNHours: "Last {n} hours"
|
||||
recentNDays: "Last {n} days"
|
||||
noEmailServerWarning: "Email server not configured."
|
||||
|
@ -1442,6 +1445,14 @@ _time:
|
|||
minute: "Minute(s)"
|
||||
hour: "Hour(s)"
|
||||
day: "Day(s)"
|
||||
_filters:
|
||||
fromUser: "From user"
|
||||
withFile: "With file"
|
||||
fromDomain: "From domain"
|
||||
notesBefore: "Posts before"
|
||||
notesAfter: "Posts after"
|
||||
followingOnly: "Following only"
|
||||
followersOnly: "Followers only"
|
||||
_tutorial:
|
||||
title: "How to use Calckey"
|
||||
step1_1: "Welcome!"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<MkModal
|
||||
ref="modal"
|
||||
:prefer-type="'dialog'"
|
||||
:z-priority="'high'"
|
||||
@click="done(true)"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
|
@ -56,16 +55,27 @@
|
|||
</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
|
||||
:type="input.type || 'text'"
|
||||
:type="input.type == 'searchAdvanced' ? 'search' : input.type || 'text'"
|
||||
:placeholder="input.placeholder || undefined"
|
||||
@keydown="onInputKeydown"
|
||||
:style="{ width: input.type === 'searchAdvanced' ? '300px' : null }"
|
||||
>
|
||||
<template v-if="input.type === 'password'" #prefix
|
||||
><i class="ph-password ph-bold ph-lg"></i
|
||||
></template>
|
||||
<template v-if="input.type === 'searchAdvanced'" #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 +105,7 @@
|
|||
</optgroup>
|
||||
</template>
|
||||
</MkSelect>
|
||||
|
||||
<div
|
||||
v-if="(showOkButton || showCancelButton) && !actions"
|
||||
:class="$style.buttons"
|
||||
|
@ -162,7 +173,9 @@ 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 +206,8 @@ const props = withDefaults(
|
|||
| "warning"
|
||||
| "info"
|
||||
| "question"
|
||||
| "waiting";
|
||||
| "waiting"
|
||||
| "search";
|
||||
title: string;
|
||||
text?: string;
|
||||
input?: Input;
|
||||
|
@ -229,9 +243,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 +284,101 @@ function onInputKeydown(evt: KeyboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
async function openSearchFilters(ev) {
|
||||
await os.popupMenu(
|
||||
[
|
||||
{
|
||||
icon: "ph-user ph-bold ph-lg",
|
||||
text: i18n.ts._filters.fromUser,
|
||||
action: () => {
|
||||
os.selectUser().then((user) => {
|
||||
inputValue.value += " from:@" + Acct.toString(user);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "ph-file ph-bold ph-lg",
|
||||
text: i18n.ts._filters.withFile,
|
||||
action: () => {
|
||||
os.select({
|
||||
title: i18n.ts._filters.withFile,
|
||||
items: [
|
||||
{
|
||||
text: i18n.ts.image,
|
||||
value: "image",
|
||||
},
|
||||
{
|
||||
text: i18n.ts.video,
|
||||
value: "video",
|
||||
},
|
||||
{
|
||||
text: i18n.ts.audio,
|
||||
value: "audio",
|
||||
},
|
||||
{
|
||||
text: i18n.ts.file,
|
||||
value: "file",
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.canceled) return;
|
||||
inputValue.value += " has:" + res.result;
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "ph-link ph-bold ph-lg",
|
||||
text: i18n.ts._filters.fromDomain,
|
||||
action: () => {
|
||||
inputValue.value += " domain:";
|
||||
},
|
||||
},
|
||||
{
|
||||
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;
|
||||
inputValue.value += " before:" + 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;
|
||||
inputValue.value += " after:" + res.result;
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "ph-eye ph-bold ph-lg",
|
||||
text: i18n.ts._filters.followingOnly,
|
||||
action: () => {
|
||||
inputValue.value += " filter:following ";
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "ph-users-three ph-bold ph-lg",
|
||||
text: i18n.ts._filters.followersOnly,
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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 (!props.noReturnFocus) {
|
||||
focusedElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function onBgClick() {
|
||||
if (contentClicking) return;
|
||||
focusedElement.focus();
|
||||
if (!props.noReturnFocus) {
|
||||
focusedElement.focus();
|
||||
}
|
||||
emit("click");
|
||||
}
|
||||
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: {
|
||||
|
@ -491,7 +492,7 @@ export function inputDate(props: {
|
|||
{
|
||||
done: (result) => {
|
||||
resolve(
|
||||
result
|
||||
(result && isFinite(new Date(result.result)))
|
||||
? { result: new Date(result.result), canceled: false }
|
||||
: { canceled: true },
|
||||
);
|
||||
|
@ -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: () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
import { mainRouter } from "@/router";
|
||||
// import { instance } from "@/instance";
|
||||
import { instance } from "@/instance";
|
||||
|
||||
export async function search() {
|
||||
// const searchOptions =
|
||||
|
@ -15,12 +15,12 @@ export async function search() {
|
|||
// "filter:following => show results only from users you follow\n" +
|
||||
// "filter:followers => show results only from followers\n";
|
||||
|
||||
// const searchFiltersAvailable = instance.searchFilters;
|
||||
const searchFiltersAvailable = instance.searchFilters;
|
||||
|
||||
const { canceled, result: query } = await os.inputText({
|
||||
type: searchFiltersAvailable ? "searchAdvanced" : "search",
|
||||
title: i18n.ts.search,
|
||||
placeholder: i18n.ts.searchPlaceholder,
|
||||
// text: searchOptions,
|
||||
});
|
||||
if (canceled || query == null || query === "") return;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue