feat: share list
TS Lint / TypeScript (push) Successful in 6m0s
TS Lint / ESLint (push) Successful in 6m0s
TS Lint / Oxlint (push) Successful in 6m0s

This commit is contained in:
2026-07-14 14:49:22 +02:00
parent 79e48090fb
commit 84630f2049
7 changed files with 50 additions and 9 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
- [x] handle errors - [x] handle errors
- [x] list per URL - [x] list per URL
- [x] home screen - [x] home screen
- [ ] share link - [x] share link
- [ ] fill new list with "fill todo" item - [ ] fill new list with "fill todo" item
- [x] lang in cookies - [x] lang in cookies
- [ ] simple view - [ ] simple view
+5 -2
View File
@@ -68,10 +68,13 @@
"task_create_error": "Could not create task", "task_create_error": "Could not create task",
"task_cloned": "Task cloned", "task_cloned": "Task cloned",
"task_deleted": "Task deleted", "task_deleted": "Task deleted",
"task_delete_error": "Could not delete task" "task_delete_error": "Could not delete task",
"share_clipboard": "Link copied to clipboard",
"share_error": "Could not share link"
}, },
"not_found": "Looks like there's nothing here...", "not_found": "Looks like there's nothing here...",
"not_found_button": "Go back to familiar territory", "not_found_button": "Go back to familiar territory",
"home": "A shareable task list with auto resetting items.<br/>Create your list below.", "home": "A shareable task list with auto resetting items.<br/>Create your list below.",
"home_button": "Let's go !" "home_button": "Let's go !",
"share_button": "Share my list"
} }
+5 -2
View File
@@ -68,10 +68,13 @@
"task_create_error": "Impossible de créer la tâche", "task_create_error": "Impossible de créer la tâche",
"task_cloned": "Tâche clonée", "task_cloned": "Tâche clonée",
"task_deleted": "Tâche supprimée", "task_deleted": "Tâche supprimée",
"task_delete_error": "Impossible de supprimer la tâche" "task_delete_error": "Impossible de supprimer la tâche",
"share_clipboard": "Lien copié dans le presse-papier",
"share_error": "Impossible de partager le lien"
}, },
"not_found": "On dirait qu'il n'y a rien ici...", "not_found": "On dirait qu'il n'y a rien ici...",
"not_found_button": "Retour en terrain connu", "not_found_button": "Retour en terrain connu",
"home": "Une liste de tâches avec réinitialisation automatique des éléments.<br/>Créez ta liste ci-dessous.", "home": "Une liste de tâches avec réinitialisation automatique des éléments.<br/>Créez ta liste ci-dessous.",
"home_button": "C'est parti !" "home_button": "C'est parti !",
"share_button": "Partager ma liste"
} }
+16
View File
@@ -0,0 +1,16 @@
export async function shareLink(
title: string,
text: string,
url: string,
): Promise<boolean> {
try {
if (navigator.canShare()) {
await navigator.share({ title, text, url });
return true;
}
} catch {
// do nothing
}
await navigator.clipboard.writeText(url);
return false;
}
+2 -2
View File
@@ -44,9 +44,9 @@ onBeforeRouteUpdate(updateTitle);
<dices-icon /> <dices-icon />
</button> </button>
</div> </div>
<div class="w-full px-4"> <div class="w-full">
<router-link <router-link
class="btn btn-lg" class="btn btn-lg px-4"
:class="listNameInput.trim().length === 0 ? 'btn-disabled' : ''" :class="listNameInput.trim().length === 0 ? 'btn-disabled' : ''"
:to="`/${listNameInput.trim()}`" :to="`/${listNameInput.trim()}`"
> >
+19
View File
@@ -9,6 +9,7 @@ import {
CircleCheckBigIcon, CircleCheckBigIcon,
CheckCheckIcon, CheckCheckIcon,
SearchIcon, SearchIcon,
Share2Icon
} from "@lucide/vue"; } from "@lucide/vue";
const TaskItem = defineAsyncComponent( const TaskItem = defineAsyncComponent(
() => import("@/components/TaskItem.vue"), () => import("@/components/TaskItem.vue"),
@@ -19,6 +20,7 @@ import { booleanCookieRef, enumCookieRef } from "@/lib/cookies";
import { useAlertStore } from "@/stores/alerts"; 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";
const route = useRoute(); const route = useRoute();
@@ -125,6 +127,18 @@ function onChangeSortReverse() {
reSortTasks(); reSortTasks();
} }
function onShare() {
shareLink(`Tout Doux - ${list.value ?? ''}`, '', `${document.location.href.split('?')[0] ?? ''}?lang=${i18n.locale.value}`)
.then((viaShare) => {
if (!viaShare) {
alertSuccess(i18n.t("alerts.share_clipboard"));
}
})
.catch(() => {
alertError(i18n.t("alerts.share_error"))
});
}
function updateTitle() { function updateTitle() {
if (list.value !== null) { if (list.value !== null) {
document.title = `Tout Doux - ${list.value} (${completedCount.value.toFixed(0)}/${taskCount.value.toFixed(0)})` document.title = `Tout Doux - ${list.value} (${completedCount.value.toFixed(0)}/${taskCount.value.toFixed(0)})`
@@ -227,6 +241,11 @@ onBeforeRouteUpdate((to) => {
</ul> </ul>
</div> </div>
</div> </div>
<div>
<button class="btn btn-sm px-4" @click="onShare">
<share-2-icon class="inline" :size="16" /> {{ $t('share_button') }}
</button>
</div>
<div class="collapse collapse-arrow font-light text-sm"> <div class="collapse collapse-arrow font-light text-sm">
<input id="options-dropdown" type="checkbox" /> <input id="options-dropdown" type="checkbox" />
<div class="collapse-title"> <div class="collapse-title">
+2 -2
View File
@@ -15,7 +15,7 @@ onBeforeRouteUpdate(updateTitle);
<div class="italic text-xl font-extralight mb-4"> <div class="italic text-xl font-extralight mb-4">
<circle-question-mark-icon class="inline" /> {{ $t('not_found') }} <circle-question-mark-icon class="inline" /> {{ $t('not_found') }}
</div> </div>
<div class="w-full px-4"> <div class="w-full">
<router-link to="/" class="btn mb-4">{{ $t('not_found_button') }}</router-link> <router-link to="/" class="btn mb-4 px-4">{{ $t('not_found_button') }}</router-link>
</div> </div>
</template> </template>