mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2025-01-10 15:40:57 -07:00
[client] Improve search filter menu
This commit is contained in:
parent
b4616d3f36
commit
1b4fedc59f
9 changed files with 246 additions and 38 deletions
|
@ -5,7 +5,7 @@ introIceshrimp: "Welcome! Iceshrimp is an open source, decentralized social medi
|
||||||
platform that's free forever! 🚀"
|
platform that's free forever! 🚀"
|
||||||
monthAndDay: "{month}/{day}"
|
monthAndDay: "{month}/{day}"
|
||||||
search: "Search"
|
search: "Search"
|
||||||
searchPlaceholder: "Search Iceshrimp"
|
searchPlaceholder: "Search the Fediverse"
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
username: "Username"
|
username: "Username"
|
||||||
password: "Password"
|
password: "Password"
|
||||||
|
@ -1488,13 +1488,32 @@ _time:
|
||||||
hour: "Hour(s)"
|
hour: "Hour(s)"
|
||||||
day: "Day(s)"
|
day: "Day(s)"
|
||||||
_filters:
|
_filters:
|
||||||
|
_dialog:
|
||||||
|
title: "Advanced search filters"
|
||||||
|
learnMore: "View advanced filters"
|
||||||
|
wordFilters: "Filter by post text"
|
||||||
|
miscFilters: "Filter by following relationship and/or note type"
|
||||||
|
userDomain: "Filter by author, mentioned users, reply user or instance domain"
|
||||||
|
postDate: "Filter by post date"
|
||||||
|
exclusivity: "Note that the before: filter is exclusive, while the after: filter is inclusive."
|
||||||
|
word: "word"
|
||||||
|
phrase: "literal phrase that contains (arbitrary) characters"
|
||||||
|
attachmentType: "Filter by attachment type(s)"
|
||||||
|
info: "Nomenclature"
|
||||||
|
info1: "Text in brackets signifies available optional filter parameters. Filter aliases or parameter options are signified by a pipe character."
|
||||||
|
info2: "A dash enclosed in brackets denotes the ability to invert/negate a filter with the dash character."
|
||||||
fromUser: "From user"
|
fromUser: "From user"
|
||||||
|
replyTo: "Replying to"
|
||||||
|
mentioning: "Mentioning"
|
||||||
withFile: "With file"
|
withFile: "With file"
|
||||||
fromDomain: "From domain"
|
fromDomain: "From domain"
|
||||||
notesBefore: "Posts before"
|
notesBefore: "Posts before"
|
||||||
notesAfter: "Posts after"
|
notesAfter: "Posts after"
|
||||||
followingOnly: "Following only"
|
followingOnly: "Following only"
|
||||||
followersOnly: "Followers only"
|
followersOnly: "Followers only"
|
||||||
|
repliesOnly: "Replies only"
|
||||||
|
excludeReplies: "Exclude replies"
|
||||||
|
excludeRenotes: "Exclude boosts"
|
||||||
_tutorial:
|
_tutorial:
|
||||||
title: "How to use Iceshrimp"
|
title: "How to use Iceshrimp"
|
||||||
step1_1: "Welcome!"
|
step1_1: "Welcome!"
|
||||||
|
|
|
@ -10,8 +10,6 @@ const filters = {
|
||||||
"-mention": mentionFilterInverse,
|
"-mention": mentionFilterInverse,
|
||||||
"reply": replyFilter,
|
"reply": replyFilter,
|
||||||
"-reply": replyFilterInverse,
|
"-reply": replyFilterInverse,
|
||||||
"replyto": replyFilter,
|
|
||||||
"-replyto": replyFilterInverse,
|
|
||||||
"to": replyFilter,
|
"to": replyFilter,
|
||||||
"-to": replyFilterInverse,
|
"-to": replyFilterInverse,
|
||||||
"before": beforeFilter,
|
"before": beforeFilter,
|
||||||
|
@ -19,14 +17,15 @@ const filters = {
|
||||||
"after": afterFilter,
|
"after": afterFilter,
|
||||||
"since": afterFilter,
|
"since": afterFilter,
|
||||||
"domain": domainFilter,
|
"domain": domainFilter,
|
||||||
|
"-domain": domainFilterInverse,
|
||||||
"host": domainFilter,
|
"host": domainFilter,
|
||||||
|
"-host": domainFilterInverse,
|
||||||
"filter": miscFilter,
|
"filter": miscFilter,
|
||||||
"-filter": miscFilterInverse,
|
"-filter": miscFilterInverse,
|
||||||
"has": attachmentFilter,
|
"has": attachmentFilter,
|
||||||
} as Record<string, (query: SelectQueryBuilder<any>, search: string, id: number) => any>
|
} as Record<string, (query: SelectQueryBuilder<any>, search: string, id: number) => any>
|
||||||
|
|
||||||
//TODO: editing the query should be possible, clicking search again resets it (it should be a twitter-like top of the page kind of deal)
|
//TODO: editing the query should be possible, clicking search again resets it (it should be a twitter-like top of the page kind of deal)
|
||||||
//TODO: new filters are missing from the filter dropdown, and said dropdown should always show (remove the searchFilters meta prop), also we should fix the null bug
|
|
||||||
|
|
||||||
export function generateFtsQuery(query: SelectQueryBuilder<any>, q: string): void {
|
export function generateFtsQuery(query: SelectQueryBuilder<any>, q: string): void {
|
||||||
const components = q.trim().split(" ");
|
const components = q.trim().split(" ");
|
||||||
|
@ -132,6 +131,10 @@ function domainFilter(query: SelectQueryBuilder<any>, filter: string) {
|
||||||
query.andWhere('note.userHost = :domain', { domain: filter });
|
query.andWhere('note.userHost = :domain', { domain: filter });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function domainFilterInverse(query: SelectQueryBuilder<any>, filter: string) {
|
||||||
|
query.andWhere('note.userHost <> :domain', { domain: filter });
|
||||||
|
}
|
||||||
|
|
||||||
function miscFilter(query: SelectQueryBuilder<any>, filter: string) {
|
function miscFilter(query: SelectQueryBuilder<any>, filter: string) {
|
||||||
let subQuery: SelectQueryBuilder<any> | null = null;
|
let subQuery: SelectQueryBuilder<any> | null = null;
|
||||||
if (filter === 'followers') {
|
if (filter === 'followers') {
|
||||||
|
@ -144,7 +147,7 @@ function miscFilter(query: SelectQueryBuilder<any>, filter: string) {
|
||||||
.where('following.followerId = :meId')
|
.where('following.followerId = :meId')
|
||||||
} else if (filter === 'replies') {
|
} else if (filter === 'replies') {
|
||||||
query.andWhere('note.replyId IS NOT NULL');
|
query.andWhere('note.replyId IS NOT NULL');
|
||||||
} else if (filter === 'boosts') {
|
} else if (filter === 'boosts' || filter === 'renotes') {
|
||||||
query.andWhere('note.renoteId IS NOT NULL');
|
query.andWhere('note.renoteId IS NOT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -305,11 +305,6 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
searchFilters: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
hcaptcha: {
|
hcaptcha: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
@ -489,7 +484,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
recommendedTimeline: !instance.disableRecommendedTimeline,
|
recommendedTimeline: !instance.disableRecommendedTimeline,
|
||||||
globalTimeLine: !instance.disableGlobalTimeline,
|
globalTimeLine: !instance.disableGlobalTimeline,
|
||||||
emailRequiredForSignup: instance.emailRequiredForSignup,
|
emailRequiredForSignup: instance.emailRequiredForSignup,
|
||||||
searchFilters: true,
|
|
||||||
hcaptcha: instance.enableHcaptcha,
|
hcaptcha: instance.enableHcaptcha,
|
||||||
recaptcha: instance.enableRecaptcha,
|
recaptcha: instance.enableRecaptcha,
|
||||||
objectStorage: instance.useObjectStorage,
|
objectStorage: instance.useObjectStorage,
|
||||||
|
|
|
@ -78,7 +78,6 @@ const nodeinfo2 = async () => {
|
||||||
disableRecommendedTimeline: meta.disableRecommendedTimeline,
|
disableRecommendedTimeline: meta.disableRecommendedTimeline,
|
||||||
disableGlobalTimeline: meta.disableGlobalTimeline,
|
disableGlobalTimeline: meta.disableGlobalTimeline,
|
||||||
emailRequiredForSignup: meta.emailRequiredForSignup,
|
emailRequiredForSignup: meta.emailRequiredForSignup,
|
||||||
searchFilters: true,
|
|
||||||
postEditing: true,
|
postEditing: true,
|
||||||
postImports: meta.experimentalFeatures?.postImports || false,
|
postImports: meta.experimentalFeatures?.postImports || false,
|
||||||
enableHcaptcha: meta.enableHcaptcha,
|
enableHcaptcha: meta.enableHcaptcha,
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
v-model="inputValue"
|
v-model="inputValue"
|
||||||
autofocus
|
autofocus
|
||||||
:autocomplete="input.autocomplete"
|
:autocomplete="input.autocomplete"
|
||||||
:type="input.type == 'search' ? 'search' : input.type || 'text'"
|
:type="(input.type || 'text') as 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | undefined"
|
||||||
:placeholder="input.placeholder || undefined"
|
:placeholder="input.placeholder || undefined"
|
||||||
:style="{
|
:style="{
|
||||||
width: input.type === 'search' ? '300px' : null,
|
width: input.type === 'search' ? '300px' : null,
|
||||||
|
@ -208,6 +208,7 @@ 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 * 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"];
|
||||||
|
@ -278,7 +279,7 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
|
|
||||||
const inputValue = ref<string | number | null>(props.input?.default ?? null);
|
const inputValue = ref<string | number | null>(props.input?.default ?? "");
|
||||||
const selectedValue = ref(props.select?.default ?? null);
|
const selectedValue = ref(props.select?.default ?? null);
|
||||||
|
|
||||||
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">(
|
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">(
|
||||||
|
@ -353,6 +354,13 @@ function formatDateToYYYYMMDD(date) {
|
||||||
return `${year}-${month}-${day}`;
|
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) {
|
async function openSearchFilters(ev) {
|
||||||
await os.popupMenu(
|
await os.popupMenu(
|
||||||
[
|
[
|
||||||
|
@ -361,10 +369,35 @@ async function openSearchFilters(ev) {
|
||||||
text: i18n.ts._filters.fromUser,
|
text: i18n.ts._filters.fromUser,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.selectUser().then((user) => {
|
os.selectUser().then((user) => {
|
||||||
inputValue.value += " from:@" + Acct.toString(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)}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "ph-link ph-bold ph-lg",
|
||||||
|
text: i18n.ts._filters.fromDomain,
|
||||||
|
action: () => {
|
||||||
|
appendSearchFilter("domain:", false);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "parent",
|
type: "parent",
|
||||||
text: i18n.ts._filters.withFile,
|
text: i18n.ts._filters.withFile,
|
||||||
|
@ -374,39 +407,33 @@ async function openSearchFilters(ev) {
|
||||||
text: i18n.ts.image,
|
text: i18n.ts.image,
|
||||||
icon: "ph-image-square ph-bold ph-lg",
|
icon: "ph-image-square ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:image";
|
appendSearchFilter("has:image");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.video,
|
text: i18n.ts.video,
|
||||||
icon: "ph-video-camera ph-bold ph-lg",
|
icon: "ph-video-camera ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:video";
|
appendSearchFilter("has:video");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.audio,
|
text: i18n.ts.audio,
|
||||||
icon: "ph-music-note ph-bold ph-lg",
|
icon: "ph-music-note ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:audio";
|
appendSearchFilter("has:audio");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.file,
|
text: i18n.ts.file,
|
||||||
icon: "ph-file ph-bold ph-lg",
|
icon: "ph-file ph-bold ph-lg",
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:file";
|
appendSearchFilter("has:file");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
null,
|
||||||
icon: "ph-link ph-bold ph-lg",
|
|
||||||
text: i18n.ts._filters.fromDomain,
|
|
||||||
action: () => {
|
|
||||||
inputValue.value += " domain:";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: "ph-calendar-blank ph-bold ph-lg",
|
icon: "ph-calendar-blank ph-bold ph-lg",
|
||||||
text: i18n.ts._filters.notesBefore,
|
text: i18n.ts._filters.notesBefore,
|
||||||
|
@ -415,8 +442,7 @@ async function openSearchFilters(ev) {
|
||||||
title: i18n.ts._filters.notesBefore,
|
title: i18n.ts._filters.notesBefore,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.canceled) return;
|
if (res.canceled) return;
|
||||||
inputValue.value +=
|
appendSearchFilter("before:" + formatDateToYYYYMMDD(res.result));
|
||||||
" before:" + formatDateToYYYYMMDD(res.result);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -428,31 +454,61 @@ async function openSearchFilters(ev) {
|
||||||
title: i18n.ts._filters.notesAfter,
|
title: i18n.ts._filters.notesAfter,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.canceled) return;
|
if (res.canceled) return;
|
||||||
inputValue.value +=
|
appendSearchFilter("after:" + formatDateToYYYYMMDD(res.result));
|
||||||
" after:" + formatDateToYYYYMMDD(res.result);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
null,
|
||||||
{
|
{
|
||||||
icon: "ph-eye ph-bold ph-lg",
|
icon: "ph-eye ph-bold ph-lg",
|
||||||
text: i18n.ts._filters.followingOnly,
|
text: i18n.ts._filters.followingOnly,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " filter:following ";
|
appendSearchFilter("filter:following");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-users-three ph-bold ph-lg",
|
icon: "ph-users-three ph-bold ph-lg",
|
||||||
text: i18n.ts._filters.followersOnly,
|
text: i18n.ts._filters.followersOnly,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " filter:followers ";
|
appendSearchFilter("filter:followers");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "ph-arrow-u-up-left ph-bold ph-lg",
|
||||||
|
text: i18n.ts._filters.repliesOnly,
|
||||||
|
action: () => {
|
||||||
|
appendSearchFilter("filter:replies");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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-question ph-bold ph-lg",
|
||||||
|
text: i18n.ts._filters._dialog.learnMore,
|
||||||
|
action: () => {
|
||||||
|
os.popup(XSearchFilterDialog, {}, {}, "closed");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ev.target,
|
ev.target,
|
||||||
{ noReturnFocus: true },
|
{ noReturnFocus: true },
|
||||||
);
|
);
|
||||||
inputEl.value.focus();
|
inputEl.value!.focus();
|
||||||
inputEl.value.selectRange(inputValue.value.length, inputValue.value.length); // cursor at end
|
inputEl.value!.selectRange((inputValue.value as string).length, (inputValue.value as string).length); // cursor at end
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
39
packages/client/src/components/MkSearchFilterDialog.vue
Normal file
39
packages/client/src/components/MkSearchFilterDialog.vue
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<XModalWindow
|
||||||
|
ref="dialog"
|
||||||
|
:width="600"
|
||||||
|
@close="dialog?.close()"
|
||||||
|
@closed="$emit('closed')"
|
||||||
|
>
|
||||||
|
<template #header>{{ i18n.ts._filters._dialog.title }}</template>
|
||||||
|
<XSearchFilters :popup="true" style="background: var(--bg)" />
|
||||||
|
</XModalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
|
import XSearchFilters from "@/pages/search-filters.vue";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: "done"): void;
|
||||||
|
(ev: "closed"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
|
function close(res) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
100
packages/client/src/pages/search-filters.vue
Normal file
100
packages/client/src/pages/search-filters.vue
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header><MkPageHeader v-if="!popup" /></template>
|
||||||
|
<MkSpacer :content-max="800">
|
||||||
|
<div class="search-filters">
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.info }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{ i18n.ts._filters._dialog.info1 }}</p>
|
||||||
|
<p>{{ i18n.ts._filters._dialog.info2 }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.wordFilters }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p><code>[-]{{ i18n.ts._filters._dialog.word }}</code></p>
|
||||||
|
<p><code>"{{ i18n.ts._filters._dialog.phrase }}"</code></p>
|
||||||
|
<p><code>(one OR of OR multiple OR words)</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.userDomain }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p><code>[-]from:[@]user[@host.tld]</code></p>
|
||||||
|
<p><code>[-]mention:[@]user[@host.tld]</code></p>
|
||||||
|
<p><code>[-]reply|to:[@]user[@host.tld]</code></p>
|
||||||
|
<p><code>[-]domain|host:host.tld</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.miscFilters }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p><code>[-]filter:followers|following|replies|renotes|boosts</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.attachmentType }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p><code>has:image|video|audio|file</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ i18n.ts._filters._dialog.postDate }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{ i18n.ts._filters._dialog.exclusivity }}</p>
|
||||||
|
<p><code>before|until:yyyy-mm-dd</code></p>
|
||||||
|
<p><code>after|since:yyyy-mm-dd</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import MkTextarea from "@/components/form/textarea.vue";
|
||||||
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { instance } from "@/instance";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
popup?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts._filters._dialog.title,
|
||||||
|
icon: "ph-funnel ph-bold ph-lg",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.search-filters {
|
||||||
|
> .section {
|
||||||
|
> .title {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
|
top: var(--stickyTop, 0px);
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-backdrop-filter: var(--blur, blur(10px));
|
||||||
|
backdrop-filter: var(--blur, blur(10px));
|
||||||
|
background-color: var(--panelTransparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
> p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .preview {
|
||||||
|
border-top: solid 0.5px var(--divider);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,11 +1,10 @@
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { mainRouter } from "@/router";
|
import { mainRouter } from "@/router";
|
||||||
import { instance } from "@/instance";
|
|
||||||
|
|
||||||
export async function search() {
|
export async function search() {
|
||||||
const { canceled, result: query } = await os.inputText({
|
const { canceled, result: query } = await os.inputText({
|
||||||
type: instance.features.searchFilters ? "search" : "text",
|
type: "search",
|
||||||
title: i18n.ts.search,
|
title: i18n.ts.search,
|
||||||
placeholder: i18n.ts.searchPlaceholder,
|
placeholder: i18n.ts.searchPlaceholder,
|
||||||
});
|
});
|
||||||
|
|
|
@ -103,8 +103,7 @@ os.apiGet("server-info", {}).then((res) => {
|
||||||
|
|
||||||
const toggleView = () => {
|
const toggleView = () => {
|
||||||
if (
|
if (
|
||||||
(widgetProps.view === 5 && instance.features.searchFilters) ||
|
(widgetProps.view === 5)
|
||||||
(widgetProps.view === 4 && !instance.features.searchFilters)
|
|
||||||
) {
|
) {
|
||||||
widgetProps.view = 0;
|
widgetProps.view = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue