Merge branch 'develop' into fix/search-filter-behavior

This commit is contained in:
PrivateGER 2023-06-12 14:54:44 +00:00
commit 8e46059d61
20 changed files with 267 additions and 61 deletions

View File

@ -968,6 +968,9 @@ rateLimitExceeded: "Rate limit exceeded"
cropImage: "Crop image" cropImage: "Crop image"
cropImageAsk: "Do you want to crop this image?" cropImageAsk: "Do you want to crop this image?"
file: "File" file: "File"
image: "Image"
video: "Video"
audio: "Audio"
recentNHours: "Last {n} hours" recentNHours: "Last {n} hours"
recentNDays: "Last {n} days" recentNDays: "Last {n} days"
noEmailServerWarning: "Email server not configured." noEmailServerWarning: "Email server not configured."
@ -1442,6 +1445,14 @@ _time:
minute: "Minute(s)" minute: "Minute(s)"
hour: "Hour(s)" hour: "Hour(s)"
day: "Day(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: _tutorial:
title: "How to use Calckey" title: "How to use Calckey"
step1_1: "Welcome!" step1_1: "Welcome!"

View File

@ -29,7 +29,7 @@ pub fn convert_id(in_id: String, id_convert_type: IdConvertType) -> napi::Result
Err(_) => { Err(_) => {
return Err(Error::new( return Err(Error::new(
Status::InvalidArg, Status::InvalidArg,
"Unable to parse ID as MasstodonId", "Unable to parse ID as MastodonId",
)) ))
} }
}; };

View File

@ -2,9 +2,9 @@ import { In } from "typeorm";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
import { Note } from "@/models/entities/note.js"; import { Note } from "@/models/entities/note.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import es from "../../../../db/elasticsearch.js"; import es from "@/db/elasticsearch.js";
import sonic from "../../../../db/sonic.js"; import sonic from "@/db/sonic.js";
import meilisearch, { MeilisearchNote } from "../../../../db/meilisearch.js"; import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js";
import define from "../../define.js"; import define from "../../define.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";

View File

@ -20,8 +20,7 @@ export default define(meta, paramDef, async () => {
const cachedPatrons = await redisClient.get("patrons"); const cachedPatrons = await redisClient.get("patrons");
if (cachedPatrons) { if (cachedPatrons) {
patrons = JSON.parse(cachedPatrons); patrons = JSON.parse(cachedPatrons);
} } else {
else {
patrons = await fetch( patrons = await fetch(
"https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json", "https://codeberg.org/calckey/calckey/raw/branch/develop/patrons.json",
).then((response) => response.json()); ).then((response) => response.json());

View File

@ -1,6 +1,6 @@
import { Instances, NoteReactions, Notes, Users } from "@/models/index.js"; import { Instances, NoteReactions, Notes, Users } from "@/models/index.js";
import define from "../define.js"; import define from "../define.js";
import {} from "@/services/chart/index.js"; import { driveChart, notesChart, usersChart } from "@/services/chart/index.js";
import { IsNull } from "typeorm"; import { IsNull } from "typeorm";
export const meta = { export const meta = {
@ -60,19 +60,25 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async () => { export default define(meta, paramDef, async () => {
const notesChartData = await notesChart.getChart("hour", 1, null);
const notesCount =
notesChartData.local.total[0] + notesChartData.remote.total[0];
const originalNotesCount = notesChartData.local.total[0];
const usersChartData = await usersChart.getChart("hour", 1, null);
const usersCount =
usersChartData.local.total[0] + usersChartData.remote.total[0];
const originalUsersCount = usersChartData.local.total[0];
const driveChartData = await driveChart.getChart("hour", 1, null);
//TODO: fixme currently returns 0
const driveUsageLocal = driveChartData.local.incSize[0];
const driveUsageRemote = driveChartData.remote.incSize[0];
const [ const [
notesCount,
originalNotesCount,
usersCount,
originalUsersCount,
reactionsCount, reactionsCount,
//originalReactionsCount, //originalReactionsCount,
instances, instances,
] = await Promise.all([ ] = await Promise.all([
Notes.count({ cache: 3600000 }), // 1 hour
Notes.count({ where: { userHost: IsNull() }, cache: 3600000 }),
Users.count({ cache: 3600000 }),
Users.count({ where: { host: IsNull() }, cache: 3600000 }),
NoteReactions.count({ cache: 3600000 }), // 1 hour NoteReactions.count({ cache: 3600000 }), // 1 hour
//NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }), //NoteReactions.count({ where: { userHost: IsNull() }, cache: 3600000 }),
Instances.count({ cache: 3600000 }), Instances.count({ cache: 3600000 }),
@ -86,7 +92,7 @@ export default define(meta, paramDef, async () => {
reactionsCount, reactionsCount,
//originalReactionsCount, //originalReactionsCount,
instances, instances,
driveUsageLocal: 0, driveUsageLocal,
driveUsageRemote: 0, driveUsageRemote,
}; };
}); });

View File

@ -37,6 +37,12 @@ export const meta = {
code: "FORBIDDEN", code: "FORBIDDEN",
id: "3c6a84db-d619-26af-ca14-06232a21df8a", id: "3c6a84db-d619-26af-ca14-06232a21df8a",
}, },
nullFollowers: {
message: "No followers found.",
code: "NULL_FOLLOWERS",
id: "174a6507-a6c2-4925-8e5d-92fd08aedc9e",
},
}, },
} as const; } as const;
@ -97,7 +103,7 @@ export default define(meta, paramDef, async (ps, me) => {
followerId: me.id, followerId: me.id,
}); });
if (following == null) { if (following == null) {
throw new ApiError(meta.errors.forbidden); throw new ApiError(meta.errors.nullFollowers);
} }
} }
} }

View File

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

View File

@ -2,7 +2,6 @@
<MkModal <MkModal
ref="modal" ref="modal"
:prefer-type="'dialog'" :prefer-type="'dialog'"
:z-priority="'high'"
@click="done(true)" @click="done(true)"
@closed="emit('closed')" @closed="emit('closed')"
> >
@ -56,16 +55,29 @@
</header> </header>
<div v-if="text" :class="$style.text"><Mfm :text="text" /></div> <div v-if="text" :class="$style.text"><Mfm :text="text" /></div>
<MkInput <MkInput
ref="inputEl"
v-if="input && input.type !== 'paragraph'" v-if="input && input.type !== 'paragraph'"
v-model="inputValue" v-model="inputValue"
autofocus autofocus
:type="input.type || 'text'" :type="input.type == 'search' ? 'search' : input.type || 'text'"
:placeholder="input.placeholder || undefined" :placeholder="input.placeholder || undefined"
@keydown="onInputKeydown" @keydown="onInputKeydown"
:style="{
width: input.type === 'search' ? '300px' : null,
}"
> >
<template v-if="input.type === 'password'" #prefix <template v-if="input.type === 'password'" #prefix
><i class="ph-password ph-bold ph-lg"></i ><i class="ph-password ph-bold ph-lg"></i
></template> ></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> </MkInput>
<MkTextarea <MkTextarea
v-if="input && input.type === 'paragraph'" v-if="input && input.type === 'paragraph'"
@ -95,6 +107,7 @@
</optgroup> </optgroup>
</template> </template>
</MkSelect> </MkSelect>
<div <div
v-if="(showOkButton || showCancelButton) && !actions" v-if="(showOkButton || showCancelButton) && !actions"
:class="$style.buttons" :class="$style.buttons"
@ -162,7 +175,9 @@ import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
import MkTextarea from "@/components/form/textarea.vue"; 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 { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import * as Acct from "calckey-js/built/acct";
type Input = { type Input = {
type: HTMLInputElement["type"]; type: HTMLInputElement["type"];
@ -193,7 +208,8 @@ const props = withDefaults(
| "warning" | "warning"
| "info" | "info"
| "question" | "question"
| "waiting"; | "waiting"
| "search";
title: string; title: string;
text?: string; text?: string;
input?: Input; input?: Input;
@ -229,9 +245,11 @@ const emit = defineEmits<{
const modal = shallowRef<InstanceType<typeof MkModal>>(); 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 selectedValue = ref(props.select?.default || null);
const inputEl = ref<typeof MkInput>();
function done(canceled: boolean, result?) { function done(canceled: boolean, result?) {
emit("done", { canceled, result }); emit("done", { canceled, result });
modal.value?.close(); modal.value?.close();
@ -268,6 +286,115 @@ function onInputKeydown(evt: KeyboardEvent) {
} }
} }
function formatDateToYYYYMMDD(date) {
const year = date.getFullYear();
const month = ("0" + (date.getMonth() + 1)).slice(-2);
const day = ("0" + date.getDate()).slice(-2);
return `${year}${month}${day}`;
}
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);
});
},
},
{
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: () => {
inputValue.value += " has:image";
},
},
{
text: i18n.ts.video,
icon: "ph-video-camera ph-bold ph-lg",
action: () => {
inputValue.value += " has:video";
},
},
{
text: i18n.ts.audio,
icon: "ph-music-note ph-bold ph-lg",
action: () => {
inputValue.value += " has:audio";
},
},
{
text: i18n.ts.file,
icon: "ph-file ph-bold ph-lg",
action: () => {
inputValue.value += " has:file";
},
},
],
},
{
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:" + 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;
inputValue.value +=
" after:" + formatDateToYYYYMMDD(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(() => { onMounted(() => {
document.addEventListener("keydown", onKeydown); document.addEventListener("keydown", onKeydown);
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -472,12 +472,24 @@ let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`);
let preview_search = $ref( let preview_search = $ref(
`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]\n${i18n.ts._mfm.dummy} 検索` `${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]\n${i18n.ts._mfm.dummy} 検索`
); );
let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]"); let preview_jelly = $ref(
let preview_tada = $ref("$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]"); "$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]"
let preview_jump = $ref("$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]"); );
let preview_bounce = $ref("$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]"); let preview_tada = $ref(
let preview_shake = $ref("$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]"); "$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]"
let preview_twitch = $ref("$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]"); );
let preview_jump = $ref(
"$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]"
);
let preview_bounce = $ref(
"$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]"
);
let preview_shake = $ref(
"$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]"
);
let preview_twitch = $ref(
"$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]"
);
let preview_spin = $ref( let preview_spin = $ref(
"$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]" "$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]"
); );
@ -491,14 +503,14 @@ let preview_x2 = $ref("$[x2 🍮]");
let preview_x3 = $ref("$[x3 🍮]"); let preview_x3 = $ref("$[x3 🍮]");
let preview_x4 = $ref("$[x4 🍮]"); let preview_x4 = $ref("$[x4 🍮]");
let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`); let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`);
let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]"); let preview_rainbow = $ref(
"$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]"
);
let preview_sparkle = $ref("$[sparkle 🍮]"); let preview_sparkle = $ref("$[sparkle 🍮]");
let preview_rotate = $ref( let preview_rotate = $ref(
"$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]" "$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"
); );
let preview_position = $ref( let preview_position = $ref("$[position.y=-1 🍮]\n$[position.x=-1 🍮]");
"$[position.y=-1 🍮]\n$[position.x=-1 🍮]"
);
let preview_crop = $ref( let preview_crop = $ref(
"$[crop.top=50 🍮] $[crop.right=50 🍮] $[crop.bottom=50 🍮] $[crop.left=50 🍮]" "$[crop.top=50 🍮] $[crop.right=50 🍮] $[crop.bottom=50 🍮] $[crop.left=50 🍮]"
); );
@ -510,7 +522,9 @@ let preview_bg = $ref("$[bg.color=31748f Background color]");
let preview_plain = $ref( let preview_plain = $ref(
"<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>" "<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>"
); );
let preview_fade = $ref("$[fade 🍮] $[fade.out 🍮] $[fade.speed=3s 🍮] $[fade.delay=3s 🍮]"); let preview_fade = $ref(
"$[fade 🍮] $[fade.out 🍮] $[fade.speed=3s 🍮] $[fade.delay=3s 🍮]"
);
definePageMetadata({ definePageMetadata({
title: i18n.ts._mfm.cheatSheet, title: i18n.ts._mfm.cheatSheet,

View File

@ -1,26 +1,13 @@
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"; import { instance } from "@/instance";
export async function search() { export async function search() {
// const searchOptions =
// "Advanced search operators\n" +
// "from:user => filter by user\n" +
// "has:image/video/audio/text/file => filter by attachment types\n" +
// "domain:domain.com => filter by domain\n" +
// "before:Date => show posts made before Date\n" +
// "after:Date => show posts made after Date\n" +
// '"text" => get posts with exact text between quotes\n' +
// "filter:following => show results only from users you follow\n" +
// "filter:followers => show results only from followers\n";
// const searchFiltersAvailable = instance.searchFilters;
const { canceled, result: query } = await os.inputText({ const { canceled, result: query } = await os.inputText({
type: instance.features.searchFilters ? "search" : "text",
title: i18n.ts.search, title: i18n.ts.search,
placeholder: i18n.ts.searchPlaceholder, placeholder: i18n.ts.searchPlaceholder,
// text: searchOptions,
}); });
if (canceled || query == null || query === "") return; 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 { ._formLinks {
> *:not(:last-child) { > *:not(:last-child) {
margin-bottom: 8px; margin-bottom: 8px;

View File

@ -39,7 +39,9 @@
:meta="meta" :meta="meta"
/> />
<XMeili <XMeili
v-else-if="instance.searchFilters && widgetProps.view === 5" v-else-if="
instance.features.searchFilters && widgetProps.view === 5
"
:connection="connection" :connection="connection"
:meta="meta" :meta="meta"
/> />
@ -110,8 +112,8 @@ os.api("server-info", {}).then((res) => {
const toggleView = () => { const toggleView = () => {
if ( if (
(widgetProps.view === 5 && instance.searchFilters) || (widgetProps.view === 5 && instance.features.searchFilters) ||
(widgetProps.view === 4 && !instance.searchFilters) (widgetProps.view === 4 && !instance.features.searchFilters)
) { ) {
widgetProps.view = 0; widgetProps.view = 0;
} else { } else {

View File

@ -4,6 +4,7 @@
v-tooltip="i18n.ts.meiliIndexCount" v-tooltip="i18n.ts.meiliIndexCount"
class="pie" class="pie"
:value="progress" :value="progress"
:reverse="true"
/> />
<div> <div>
<p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p> <p><i class="ph-file-search ph-bold ph-lg"></i>MeiliSearch</p>

View File

@ -29,11 +29,17 @@ import {} from "vue";
const props = defineProps<{ const props = defineProps<{
value: number; value: number;
reverse?: boolean;
}>(); }>();
const r = 0.45; const r = 0.45;
const color = $computed(() => `hsl(${180 - props.value * 180}, 80%, 70%)`); const color = $computed(
() =>
`hsl(${
props.reverse ? props.value * 180 : 180 - props.value * 180
}, 80%, 70%)`
);
const strokeDashoffset = $computed( const strokeDashoffset = $computed(
() => (1 - props.value) * (Math.PI * (r * 2)) () => (1 - props.value) * (Math.PI * (r * 2))
); );