feat: recent lists
Docker Build / build (push) Has been cancelled
TS Lint / ESLint (push) Has been cancelled
TS Lint / Oxlint (push) Has been cancelled
TS Lint / TypeScript (push) Has been cancelled

This commit is contained in:
2026-07-15 16:34:37 +02:00
parent 5e60b5ad20
commit ea9a50a2f7
9 changed files with 85 additions and 31 deletions
+2 -1
View File
@@ -83,5 +83,6 @@
"cancel_button": "Cancel editing", "cancel_button": "Cancel editing",
"save_button": "Save task", "save_button": "Save task",
"clone_button": "Clone task", "clone_button": "Clone task",
"delete_button": "Delete task" "delete_button": "Delete task",
"recent": "Recent lists"
} }
+2 -1
View File
@@ -83,5 +83,6 @@
"cancel_button": "Annuler la modification", "cancel_button": "Annuler la modification",
"save_button": "Sauvegarder la tâche", "save_button": "Sauvegarder la tâche",
"clone_button": "Cloner la tâche", "clone_button": "Cloner la tâche",
"delete_button": "Supprimer la tâche" "delete_button": "Supprimer la tâche",
"recent": "Listes récentes"
} }
+2 -1
View File
@@ -2,7 +2,8 @@
import { ref, onBeforeMount, watch } from "vue"; import { ref, onBeforeMount, watch } from "vue";
import { getCron, parseCron, validCron } from "@/lib/cron"; import { getCron, parseCron, validCron } from "@/lib/cron";
import { CronType } from "@/enums"; import { CronType } from "@/enums";
import { DEFAULT_CRON } from "@/constants";
const DEFAULT_CRON = "0 0 * * *";
const cron = defineModel<string | null>({ required: true }); const cron = defineModel<string | null>({ required: true });
const rawCron = ref<string>(DEFAULT_CRON); const rawCron = ref<string>(DEFAULT_CRON);
+15
View File
@@ -0,0 +1,15 @@
<script setup lang="ts">
import { ArrowBigRightDashIcon } from "@lucide/vue";
defineProps<{list: string}>();
</script>
<template>
<router-link
:to="`/${list}`"
class="w-full flex gap-2 mb-2"
>
<arrow-big-right-dash-icon :size="16" class="inline" />
<span class="underline">{{ list }}</span>
</router-link>
</template>
-1
View File
@@ -1 +0,0 @@
export const DEFAULT_CRON = "0 0 * * *";
+1 -1
View File
@@ -1,4 +1,4 @@
import { getCookie, setCookie } from "./cookies"; import { getCookie, setCookie } from "@/lib/cookies";
export function getLang() { export function getLang() {
const params = new URLSearchParams(document.location.search); const params = new URLSearchParams(document.location.search);
+20
View File
@@ -0,0 +1,20 @@
import { getCookie, setCookie } from "@/lib/cookies";
const COOKIE_NAME = "recent-lists";
const MAX_RECENT_LISTS = 32;
export function getRecentLists(): string[] {
const rawData = getCookie(COOKIE_NAME, "");
if (!rawData) {
return [];
}
return rawData.split(",");
}
export function saveRecentList(list: string): void {
const recentLists = [list]
.concat(getRecentLists())
.filter((v, i, a) => a.indexOf(v) === i)
.slice(0, MAX_RECENT_LISTS);
setCookie(COOKIE_NAME, recentLists.join(","));
}
+19 -6
View File
@@ -1,10 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ArrowBigRightDashIcon, DicesIcon } from "@lucide/vue"; import { getRecentLists } from "@/lib/lists";
import { ArrowBigRightDashIcon, DicesIcon, HistoryIcon } from "@lucide/vue";
import { generateSlug } from "random-word-slugs"; import { generateSlug } from "random-word-slugs";
import { onBeforeMount, ref } from "vue"; import { defineAsyncComponent, onBeforeMount, ref } from "vue";
import { onBeforeRouteUpdate } from "vue-router"; import { onBeforeRouteUpdate } from "vue-router";
const RecentItem = defineAsyncComponent(
() => import("@/components/RecentItem.vue"),
);
const listNameInput = ref<string>(generateSlug(4)); const listNameInput = ref<string>(generateSlug(4));
const recentLists = ref<string[]>(getRecentLists());
function onRandom() { function onRandom() {
listNameInput.value = generateSlug(4); listNameInput.value = generateSlug(4);
@@ -27,11 +32,10 @@ onBeforeRouteUpdate(updateTitle);
</script> </script>
<template> <template>
<div class="p-4">
<div <div
class="mb-4 italic text-xl font-extralight" class="p-4 italic text-xl font-extralight"
>{{ $t('home') }}<br/>{{ $t('call_to_action') }}</div> >{{ $t('home') }}<br/>{{ $t('call_to_action') }}</div>
<div class="flex gap-2 mb-4"> <div class="flex gap-2 p-4">
<input <input
v-model="listNameInput" v-model="listNameInput"
type="text" type="text"
@@ -45,7 +49,7 @@ onBeforeRouteUpdate(updateTitle);
</div> </div>
<div class="w-full"> <div class="w-full">
<router-link <router-link
class="btn btn-lg px-4" class="btn btn-lg"
:class="listNameInput.trim().length === 0 ? 'btn-disabled' : ''" :class="listNameInput.trim().length === 0 ? 'btn-disabled' : ''"
:to="`/${listNameInput.trim()}`" :to="`/${listNameInput.trim()}`"
> >
@@ -53,5 +57,14 @@ onBeforeRouteUpdate(updateTitle);
{{ $t("home_button") }} {{ $t("home_button") }}
</router-link> </router-link>
</div> </div>
<div v-if="recentLists.length" class="collapse collapse-arrow font-light text-sm">
<input id="recent-dropdown" type="checkbox" />
<div class="collapse-title">
<history-icon class="inline" :size="16" />
{{ $t("recent") }}
</div>
<div class="collapse-content">
<recent-item v-for="(list, i) in recentLists" :key="`recent-${i}`" :list="list" />
</div>
</div> </div>
</template> </template>
+4
View File
@@ -21,6 +21,7 @@ import { useAlertStore } from "@/stores/alerts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { onBeforeRouteUpdate, useRoute } from "vue-router"; import { onBeforeRouteUpdate, useRoute } from "vue-router";
import { shareLink } from "@/lib/share"; import { shareLink } from "@/lib/share";
import { saveRecentList } from "@/lib/lists";
const route = useRoute(); const route = useRoute();
@@ -56,6 +57,9 @@ function fetchTasks() {
tasks.value = data; tasks.value = data;
reSortTasks(); reSortTasks();
loading.value = false; loading.value = false;
if (data.length && list.value) {
saveRecentList(list.value);
}
setTimeout(fetchTasks, 10000); setTimeout(fetchTasks, 10000);
}) })
.catch(() => { .catch(() => {