From 84630f204935a73d94cb3bf29861486f5cacce44 Mon Sep 17 00:00:00 2001 From: klemek Date: Tue, 14 Jul 2026 14:49:22 +0200 Subject: [PATCH] feat: share list --- README.md | 2 +- resources/lang/en.json | 7 +++++-- resources/lang/fr.json | 7 +++++-- resources/ts/lib/share.ts | 16 ++++++++++++++++ resources/ts/views/HomeView.vue | 4 ++-- resources/ts/views/ListView.vue | 19 +++++++++++++++++++ resources/ts/views/NotFoundView.vue | 4 ++-- 7 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 resources/ts/lib/share.ts diff --git a/README.md b/README.md index 843019f..a5771d5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - [x] handle errors - [x] list per URL - [x] home screen -- [ ] share link +- [x] share link - [ ] fill new list with "fill todo" item - [x] lang in cookies - [ ] simple view diff --git a/resources/lang/en.json b/resources/lang/en.json index 9df9aa5..8373ed7 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -68,10 +68,13 @@ "task_create_error": "Could not create task", "task_cloned": "Task cloned", "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_button": "Go back to familiar territory", "home": "A shareable task list with auto resetting items.
Create your list below.", - "home_button": "Let's go !" + "home_button": "Let's go !", + "share_button": "Share my list" } diff --git a/resources/lang/fr.json b/resources/lang/fr.json index f6321f1..07558c5 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -68,10 +68,13 @@ "task_create_error": "Impossible de créer la tâche", "task_cloned": "Tâche cloné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_button": "Retour en terrain connu", "home": "Une liste de tâches avec réinitialisation automatique des éléments.
Créez ta liste ci-dessous.", - "home_button": "C'est parti !" + "home_button": "C'est parti !", + "share_button": "Partager ma liste" } diff --git a/resources/ts/lib/share.ts b/resources/ts/lib/share.ts new file mode 100644 index 0000000..f0459db --- /dev/null +++ b/resources/ts/lib/share.ts @@ -0,0 +1,16 @@ +export async function shareLink( + title: string, + text: string, + url: string, +): Promise { + try { + if (navigator.canShare()) { + await navigator.share({ title, text, url }); + return true; + } + } catch { + // do nothing + } + await navigator.clipboard.writeText(url); + return false; +} diff --git a/resources/ts/views/HomeView.vue b/resources/ts/views/HomeView.vue index ab70009..ddf2b61 100644 --- a/resources/ts/views/HomeView.vue +++ b/resources/ts/views/HomeView.vue @@ -44,9 +44,9 @@ onBeforeRouteUpdate(updateTitle); -
+
diff --git a/resources/ts/views/ListView.vue b/resources/ts/views/ListView.vue index 3ec66e2..65b732a 100644 --- a/resources/ts/views/ListView.vue +++ b/resources/ts/views/ListView.vue @@ -9,6 +9,7 @@ import { CircleCheckBigIcon, CheckCheckIcon, SearchIcon, + Share2Icon } from "@lucide/vue"; const TaskItem = defineAsyncComponent( () => import("@/components/TaskItem.vue"), @@ -19,6 +20,7 @@ import { booleanCookieRef, enumCookieRef } from "@/lib/cookies"; import { useAlertStore } from "@/stores/alerts"; import { useI18n } from "vue-i18n"; import { onBeforeRouteUpdate, useRoute } from "vue-router"; +import { shareLink } from "@/lib/share"; const route = useRoute(); @@ -125,6 +127,18 @@ function onChangeSortReverse() { 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() { if (list.value !== null) { document.title = `Tout Doux - ${list.value} (${completedCount.value.toFixed(0)}/${taskCount.value.toFixed(0)})` @@ -227,6 +241,11 @@ onBeforeRouteUpdate((to) => {
+
+ +
diff --git a/resources/ts/views/NotFoundView.vue b/resources/ts/views/NotFoundView.vue index 3ee27f2..27a1ebd 100644 --- a/resources/ts/views/NotFoundView.vue +++ b/resources/ts/views/NotFoundView.vue @@ -15,7 +15,7 @@ onBeforeRouteUpdate(updateTitle);
{{ $t('not_found') }}
-
- {{ $t('not_found_button') }} +
+ {{ $t('not_found_button') }}