feat(tauri): move-to-list, rename, group toggle, keyboard shortcuts, WebDAV fix
- TaskDetailView: 'Move to...' submenu in kebab menu - TasksScreen: list rename (inline input), group-by-due-date toggle, global Escape key handler for closing overlays - SettingsScreen: auto-populate WebDAV URL/credentials on open - SetupScreen: add window dragging, minimize/close buttons, 'Open Existing Folder' button - Store: moveTask, renameList, setGroupByDueDate methods + fs-changed event listener for file watcher Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5faf285d28
commit
1a967c7fdd
|
|
@ -14,10 +14,13 @@
|
||||||
let title = $state(task.title);
|
let title = $state(task.title);
|
||||||
let description = $state(task.description);
|
let description = $state(task.description);
|
||||||
let showMenu = $state(false);
|
let showMenu = $state(false);
|
||||||
|
let showMoveSubmenu = $state(false);
|
||||||
let menuEl = $state<HTMLDivElement | null>(null);
|
let menuEl = $state<HTMLDivElement | null>(null);
|
||||||
let showDatePicker = $state(false);
|
let showDatePicker = $state(false);
|
||||||
let saveTimer: ReturnType<typeof setTimeout>;
|
let saveTimer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
let otherLists = $derived(app.lists.filter((l) => l.id !== app.activeListId));
|
||||||
|
|
||||||
function handleHeaderMouseDown(e: MouseEvent) {
|
function handleHeaderMouseDown(e: MouseEvent) {
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
if ((e.target as HTMLElement).closest("button")) return;
|
if ((e.target as HTMLElement).closest("button")) return;
|
||||||
|
|
@ -126,6 +129,34 @@
|
||||||
</svg>
|
</svg>
|
||||||
{isCompleted ? "Restore task" : "Mark as completed"}
|
{isCompleted ? "Restore task" : "Mark as completed"}
|
||||||
</button>
|
</button>
|
||||||
|
{#if otherLists.length > 0}
|
||||||
|
<div class="relative">
|
||||||
|
<button
|
||||||
|
onclick={() => (showMoveSubmenu = !showMoveSubmenu)}
|
||||||
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
|
||||||
|
</svg>
|
||||||
|
Move to...
|
||||||
|
<svg class="ml-auto h-3 w-3 opacity-40" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{#if showMoveSubmenu}
|
||||||
|
<div class="absolute left-full top-0 z-50 ml-1 min-w-[160px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
|
||||||
|
{#each otherLists as list}
|
||||||
|
<button
|
||||||
|
onclick={async () => { showMenu = false; showMoveSubmenu = false; await app.moveTask(task.id, list.id); onback(); }}
|
||||||
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
{list.title}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
onclick={handleDelete}
|
onclick={handleDelete}
|
||||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,22 @@
|
||||||
let webdavPass = $state("");
|
let webdavPass = $state("");
|
||||||
let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle");
|
let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle");
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const ws = app.config?.current_workspace;
|
||||||
|
if (!ws) return;
|
||||||
|
const cfg = app.config?.workspaces[ws];
|
||||||
|
if (cfg?.webdav_url) {
|
||||||
|
webdavUrl = cfg.webdav_url;
|
||||||
|
try {
|
||||||
|
const domain = new URL(cfg.webdav_url).hostname;
|
||||||
|
invoke<[string, string]>("load_credentials", { domain }).then(([u, p]) => {
|
||||||
|
webdavUser = u;
|
||||||
|
webdavPass = p;
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function testConnection() {
|
async function testConnection() {
|
||||||
testStatus = "testing";
|
testStatus = "testing";
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { app } from "../stores/app.svelte";
|
import { app } from "../stores/app.svelte";
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
|
|
||||||
|
const appWindow = getCurrentWindow();
|
||||||
|
const currentPlatform = platform();
|
||||||
|
const isDesktop = currentPlatform === "linux" || currentPlatform === "windows";
|
||||||
|
const isWindows = currentPlatform === "windows";
|
||||||
|
|
||||||
let name = $state("");
|
let name = $state("");
|
||||||
let path = $state("");
|
let path = $state("");
|
||||||
|
|
@ -14,15 +21,59 @@
|
||||||
if (!name.trim() || !path.trim()) return;
|
if (!name.trim() || !path.trim()) return;
|
||||||
await app.addWorkspace(name.trim(), path.trim());
|
await app.addWorkspace(name.trim(), path.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleOpen() {
|
||||||
|
const selected = await open({ directory: true, multiple: false });
|
||||||
|
if (!selected) return;
|
||||||
|
const folder = selected as string;
|
||||||
|
// Derive workspace name from folder name
|
||||||
|
const parts = folder.replace(/\\/g, "/").split("/");
|
||||||
|
const wsName = parts[parts.length - 1] || "workspace";
|
||||||
|
await app.addWorkspace(wsName, folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrag(e: MouseEvent) {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
if ((e.target as HTMLElement).closest("button, input")) return;
|
||||||
|
if (isDesktop) appWindow.startDragging();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full items-center justify-center p-6">
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="flex h-full flex-col" onmousedown={handleDrag}>
|
||||||
|
<!-- Title bar area with window controls -->
|
||||||
|
<header class="flex h-11 shrink-0 items-center justify-end px-2">
|
||||||
|
{#if isDesktop}
|
||||||
|
<div class="flex items-center gap-0.5">
|
||||||
|
{#if isWindows}
|
||||||
|
<button
|
||||||
|
onclick={() => appWindow.minimize()}
|
||||||
|
class="rounded p-1.5 opacity-50 hover:bg-black/10 hover:opacity-80 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
onclick={() => appWindow.close()}
|
||||||
|
class="rounded p-1.5 opacity-50 hover:bg-danger/20 hover:opacity-100 hover:text-danger dark:hover:bg-danger/20"
|
||||||
|
>
|
||||||
|
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex flex-1 items-center justify-center p-6">
|
||||||
<div
|
<div
|
||||||
class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark"
|
class="w-full max-w-sm rounded-2xl bg-card-light p-8 shadow-lg dark:bg-card-dark"
|
||||||
>
|
>
|
||||||
<h1 class="mb-1 text-2xl font-bold">Onyx</h1>
|
<h1 class="mb-1 text-2xl font-bold">Onyx</h1>
|
||||||
<p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
|
<p class="mb-6 text-sm text-text-secondary-light dark:text-text-secondary-dark">
|
||||||
Create or open a workspace to get started.
|
Create a new workspace or open an existing one.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<label class="mb-1 block text-sm font-medium">
|
<label class="mb-1 block text-sm font-medium">
|
||||||
|
|
@ -60,5 +111,19 @@
|
||||||
>
|
>
|
||||||
Create Workspace
|
Create Workspace
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="my-4 flex items-center gap-3">
|
||||||
|
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div>
|
||||||
|
<span class="text-xs opacity-40">or</span>
|
||||||
|
<div class="h-px flex-1 bg-border-light dark:bg-border-dark"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={handleOpen}
|
||||||
|
class="w-full rounded-lg border border-border-light py-2.5 text-sm font-medium hover:bg-black/5 dark:border-border-dark dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
Open Existing Folder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@
|
||||||
let completedVisible = $state(false);
|
let completedVisible = $state(false);
|
||||||
let listMenuId = $state<string | null>(null);
|
let listMenuId = $state<string | null>(null);
|
||||||
let wsMenuName = $state<string | null>(null);
|
let wsMenuName = $state<string | null>(null);
|
||||||
|
let renamingListId = $state<string | null>(null);
|
||||||
|
let renameValue = $state("");
|
||||||
let dragId = $state<string | null>(null);
|
let dragId = $state<string | null>(null);
|
||||||
let dragOverId = $state<string | null>(null);
|
let dragOverId = $state<string | null>(null);
|
||||||
let resizing = $state(false);
|
let resizing = $state(false);
|
||||||
|
|
@ -77,6 +79,40 @@
|
||||||
await app.deleteList(id);
|
await app.deleteList(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startRenameList(id: string) {
|
||||||
|
listMenuId = null;
|
||||||
|
const list = app.lists.find(l => l.id === id);
|
||||||
|
if (!list) return;
|
||||||
|
renamingListId = id;
|
||||||
|
renameValue = list.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRenameList() {
|
||||||
|
if (!renamingListId || !renameValue.trim()) { renamingListId = null; return; }
|
||||||
|
const list = app.lists.find(l => l.id === renamingListId);
|
||||||
|
if (renameValue.trim() !== list?.title) {
|
||||||
|
await app.renameList(renamingListId, renameValue.trim());
|
||||||
|
}
|
||||||
|
renamingListId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleToggleGroupByDueDate(id: string) {
|
||||||
|
listMenuId = null;
|
||||||
|
const list = app.lists.find(l => l.id === id);
|
||||||
|
if (!list) return;
|
||||||
|
await app.setGroupByDueDate(id, !list.group_by_due_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key !== "Escape") return;
|
||||||
|
if (showSettings) { showSettings = false; return; }
|
||||||
|
if (selectedTaskId) { selectedTaskId = null; return; }
|
||||||
|
if (showDrawer) { closeDrawer(); return; }
|
||||||
|
if (listMenuId) { listMenuId = null; return; }
|
||||||
|
if (wsMenuName) { wsMenuName = null; return; }
|
||||||
|
if (showWorkspacePicker) { showWorkspacePicker = false; return; }
|
||||||
|
}
|
||||||
|
|
||||||
function handleDragStart(e: DragEvent, taskId: string) {
|
function handleDragStart(e: DragEvent, taskId: string) {
|
||||||
dragId = taskId;
|
dragId = taskId;
|
||||||
if (e.dataTransfer) {
|
if (e.dataTransfer) {
|
||||||
|
|
@ -148,6 +184,8 @@
|
||||||
let translateX = $derived(showDrawer ? '0' : '-80cqi');
|
let translateX = $derived(showDrawer ? '0' : '-80cqi');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
<!-- Viewport clip -->
|
<!-- Viewport clip -->
|
||||||
<div class="h-full w-full overflow-hidden">
|
<div class="h-full w-full overflow-hidden">
|
||||||
<!-- Sliding container: left drawer + main content -->
|
<!-- Sliding container: left drawer + main content -->
|
||||||
|
|
@ -238,6 +276,18 @@
|
||||||
<div class="flex-1 overflow-y-auto py-2">
|
<div class="flex-1 overflow-y-auto py-2">
|
||||||
{#each app.lists as list (list.id)}
|
{#each app.lists as list (list.id)}
|
||||||
<div class="group relative flex items-center px-2 hover:bg-black/5 dark:hover:bg-white/10">
|
<div class="group relative flex items-center px-2 hover:bg-black/5 dark:hover:bg-white/10">
|
||||||
|
{#if renamingListId === list.id}
|
||||||
|
<div class="flex flex-1 items-center px-3 py-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={renameValue}
|
||||||
|
class="w-full rounded border border-primary bg-transparent px-2 py-1.5 text-sm outline-none"
|
||||||
|
onkeydown={(e) => { if (e.key === "Enter") handleRenameList(); if (e.key === "Escape") renamingListId = null; }}
|
||||||
|
onblur={handleRenameList}
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<button
|
<button
|
||||||
onclick={() => { app.selectList(list.id); closeDrawer(); }}
|
onclick={() => { app.selectList(list.id); closeDrawer(); }}
|
||||||
class="flex flex-1 items-center gap-2 px-3 py-2.5 text-left text-sm {list.id === app.activeListId ? 'font-bold' : ''}"
|
class="flex flex-1 items-center gap-2 px-3 py-2.5 text-left text-sm {list.id === app.activeListId ? 'font-bold' : ''}"
|
||||||
|
|
@ -249,6 +299,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<span>{list.title}</span>
|
<span>{list.title}</span>
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
<div class="relative shrink-0" data-list-menu>
|
<div class="relative shrink-0" data-list-menu>
|
||||||
<button
|
<button
|
||||||
onclick={() => (listMenuId = listMenuId === list.id ? null : list.id)}
|
onclick={() => (listMenuId = listMenuId === list.id ? null : list.id)}
|
||||||
|
|
@ -259,7 +310,30 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{#if listMenuId === list.id}
|
{#if listMenuId === list.id}
|
||||||
<div class="absolute right-0 top-full z-40 mt-1 min-w-[140px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
|
<div class="absolute right-0 top-full z-40 mt-1 min-w-[180px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
|
||||||
|
<button
|
||||||
|
onclick={() => startRenameList(list.id)}
|
||||||
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||||
|
</svg>
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={() => handleToggleGroupByDueDate(list.id)}
|
||||||
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Group by due date
|
||||||
|
{#if list.group_by_due_date}
|
||||||
|
<svg class="ml-auto h-4 w-4 text-primary" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleDeleteList(list.id)}
|
onclick={() => handleDeleteList(list.id)}
|
||||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
|
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import type {
|
import type {
|
||||||
AppConfig,
|
AppConfig,
|
||||||
Task,
|
Task,
|
||||||
|
|
@ -7,6 +8,11 @@ import type {
|
||||||
SyncResult,
|
SyncResult,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
|
// Listen for file system changes from the backend watcher
|
||||||
|
listen("fs-changed", () => {
|
||||||
|
loadLists();
|
||||||
|
});
|
||||||
|
|
||||||
// ── Reactive state ───────────────────────────────────────────────────
|
// ── Reactive state ───────────────────────────────────────────────────
|
||||||
|
|
||||||
let screen = $state<Screen>("setup");
|
let screen = $state<Screen>("setup");
|
||||||
|
|
@ -54,6 +60,7 @@ async function addWorkspace(name: string, path: string) {
|
||||||
await invoke("add_workspace", { name, path });
|
await invoke("add_workspace", { name, path });
|
||||||
config = await invoke<AppConfig>("get_config");
|
config = await invoke<AppConfig>("get_config");
|
||||||
await loadLists();
|
await loadLists();
|
||||||
|
invoke("watch_workspace", { path }).catch(() => {});
|
||||||
screen = "tasks";
|
screen = "tasks";
|
||||||
error = null;
|
error = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -67,6 +74,8 @@ async function switchWorkspace(name: string) {
|
||||||
config = await invoke<AppConfig>("get_config");
|
config = await invoke<AppConfig>("get_config");
|
||||||
activeListId = null;
|
activeListId = null;
|
||||||
await loadLists();
|
await loadLists();
|
||||||
|
const ws = config?.workspaces[name];
|
||||||
|
if (ws) invoke("watch_workspace", { path: ws.path }).catch(() => {});
|
||||||
error = null;
|
error = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = String(e);
|
error = String(e);
|
||||||
|
|
@ -206,6 +215,43 @@ async function deleteTask(taskId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function moveTask(taskId: string, targetListId: string) {
|
||||||
|
if (!activeListId) return;
|
||||||
|
try {
|
||||||
|
await invoke("move_task", {
|
||||||
|
fromListId: activeListId,
|
||||||
|
toListId: targetListId,
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
|
tasks = tasks.filter((t) => t.id !== taskId);
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renameList(listId: string, newName: string) {
|
||||||
|
try {
|
||||||
|
await invoke("rename_list", { listId, newName });
|
||||||
|
lists = lists.map((l) =>
|
||||||
|
l.id === listId ? { ...l, title: newName } : l,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setGroupByDueDate(listId: string, enabled: boolean) {
|
||||||
|
try {
|
||||||
|
await invoke("set_group_by_due_date", { listId, enabled });
|
||||||
|
lists = lists.map((l) =>
|
||||||
|
l.id === listId ? { ...l, group_by_due_date: enabled } : l,
|
||||||
|
);
|
||||||
|
if (listId === activeListId) await loadTasks();
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function triggerSync() {
|
async function triggerSync() {
|
||||||
if (!config?.current_workspace) return;
|
if (!config?.current_workspace) return;
|
||||||
const ws = config.workspaces[config.current_workspace];
|
const ws = config.workspaces[config.current_workspace];
|
||||||
|
|
@ -300,6 +346,9 @@ export const app = {
|
||||||
updateTask,
|
updateTask,
|
||||||
reorderTask,
|
reorderTask,
|
||||||
deleteTask,
|
deleteTask,
|
||||||
|
moveTask,
|
||||||
|
renameList,
|
||||||
|
setGroupByDueDate,
|
||||||
triggerSync,
|
triggerSync,
|
||||||
toggleDarkMode,
|
toggleDarkMode,
|
||||||
setScreen,
|
setScreen,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue