@axium/tasks 0.5.7 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/db-scripts/add_task_list_sync_event.sql +15 -0
- package/db.json +16 -0
- package/dist/common.d.ts +6 -0
- package/lib/TaskList.svelte +47 -38
- package/lib/tsconfig.json +1 -1
- package/package.json +7 -5
- package/routes/tasks/+layout.svelte +10 -0
- package/routes/tasks/+layout.ts +9 -2
- package/routes/tasks/+page.svelte +6 -3
- package/routes/tasks/+page.ts +0 -2
- package/routes/tasks/[id]/+page.svelte +8 -2
- package/routes/tasks/[id]/+page.ts +0 -2
- package/routes/tsconfig.json +1 -1
- package/dist/web_hook.d.ts +0 -8
- package/dist/web_hook.js +0 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION add_task_list_sync_event()
|
|
2
|
+
RETURNS TRIGGER AS $$
|
|
3
|
+
BEGIN
|
|
4
|
+
IF (TG_OP = 'DELETE') THEN
|
|
5
|
+
INSERT INTO sync_events (type, id, op)
|
|
6
|
+
VALUES ('task_lists', OLD."listId", 'u');
|
|
7
|
+
RETURN OLD;
|
|
8
|
+
END IF;
|
|
9
|
+
|
|
10
|
+
INSERT INTO sync_events (type, id, op)
|
|
11
|
+
VALUES ('task_lists', NEW."listId", 'u');
|
|
12
|
+
|
|
13
|
+
RETURN NEW;
|
|
14
|
+
END;
|
|
15
|
+
$$ LANGUAGE plpgsql;
|
package/db.json
CHANGED
|
@@ -86,6 +86,22 @@
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"delta": true,
|
|
92
|
+
"alter_tables": {
|
|
93
|
+
"tasks": {
|
|
94
|
+
"add_triggers": {
|
|
95
|
+
"sync_events": {
|
|
96
|
+
"forEach": "row",
|
|
97
|
+
"when": "after",
|
|
98
|
+
"events": ["insert", "update", "delete"],
|
|
99
|
+
"execute": "add_task_list_sync_event"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"scripts": [{ "when": "before", "path": "db-scripts/add_task_list_sync_event.sql" }]
|
|
89
105
|
}
|
|
90
106
|
],
|
|
91
107
|
"wipe": ["task_lists", "tasks", "acl.task_lists"],
|
package/dist/common.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ export declare const TaskList: z.ZodObject<{
|
|
|
67
67
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
68
68
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
69
69
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
70
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
70
71
|
}, z.core.$strip>>>;
|
|
71
72
|
roles: z.ZodArray<z.ZodString>;
|
|
72
73
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -119,6 +120,7 @@ declare const TasksAPI: {
|
|
|
119
120
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
120
121
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
121
122
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
123
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
122
124
|
}, z.core.$strip>>>;
|
|
123
125
|
roles: z.ZodArray<z.ZodString>;
|
|
124
126
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -162,6 +164,7 @@ declare const TasksAPI: {
|
|
|
162
164
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
163
165
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
164
166
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
167
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
165
168
|
}, z.core.$strip>>>;
|
|
166
169
|
roles: z.ZodArray<z.ZodString>;
|
|
167
170
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -204,6 +207,7 @@ declare const TasksAPI: {
|
|
|
204
207
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
205
208
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
206
209
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
210
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
207
211
|
}, z.core.$strip>>>;
|
|
208
212
|
roles: z.ZodArray<z.ZodString>;
|
|
209
213
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -247,6 +251,7 @@ declare const TasksAPI: {
|
|
|
247
251
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
248
252
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
249
253
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
254
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
250
255
|
}, z.core.$strip>>>;
|
|
251
256
|
roles: z.ZodArray<z.ZodString>;
|
|
252
257
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -306,6 +311,7 @@ declare const TasksAPI: {
|
|
|
306
311
|
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
307
312
|
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
308
313
|
debug: z.ZodDefault<z.ZodBoolean>;
|
|
314
|
+
theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
|
|
309
315
|
}, z.core.$strip>>>;
|
|
310
316
|
roles: z.ZodArray<z.ZodString>;
|
|
311
317
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/lib/TaskList.svelte
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { goto } from '$app/navigation';
|
|
3
3
|
import { page } from '$app/state';
|
|
4
4
|
import { fetchAPI, preferences, text } from '@axium/client';
|
|
5
|
+
import { contextMenu, dynamicRows, type ContextMenuItem } from '@axium/client/attachments';
|
|
5
6
|
import { AccessControlDialog, Icon, Popover } from '@axium/client/components';
|
|
6
|
-
import { copy } from '@axium/client/
|
|
7
|
+
import { copy } from '@axium/client/web';
|
|
7
8
|
import { toastStatus } from '@axium/client/toast';
|
|
8
9
|
import { buildTaskTree, type Task, type TaskList, type TaskTreeNode } from '@axium/tasks/common';
|
|
9
10
|
import type { WithRequired } from 'utilium';
|
|
@@ -19,8 +20,9 @@
|
|
|
19
20
|
|
|
20
21
|
const { user } = page.data.session || {};
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const tasks = $derived(list.tasks);
|
|
24
|
+
|
|
25
|
+
let open = $state(false);
|
|
24
26
|
|
|
25
27
|
const { show_completed_subtasks } = user ? await preferences.get(user.id, 'tasks') : {};
|
|
26
28
|
const showCompletedInline = show_completed_subtasks == 'inline';
|
|
@@ -39,6 +41,30 @@
|
|
|
39
41
|
return `[${task.completed ? 'x' : ' '}] ${task.summary}` + children;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
function taskActions(task: Task): WithRequired<ContextMenuItem, 'i'>[] {
|
|
45
|
+
const actions: WithRequired<ContextMenuItem, 'i'>[] = [
|
|
46
|
+
{
|
|
47
|
+
i: 'arrow-turn-down-right',
|
|
48
|
+
text: text('tasks.add_subtask'),
|
|
49
|
+
action: () => fetchAPI('PUT', 'task_lists/:id', { summary: '', parentId: task.id }, list.id).then(t => tasks.push(t)),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
i: 'trash',
|
|
53
|
+
text: text('generic.delete'),
|
|
54
|
+
action: () =>
|
|
55
|
+
toastStatus(
|
|
56
|
+
fetchAPI('DELETE', 'tasks/:id', {}, task.id).then(() => {
|
|
57
|
+
tasks.splice(tasks.indexOf(task), 1);
|
|
58
|
+
}),
|
|
59
|
+
text('tasks.toast_task_deleted')
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
if (user?.preferences?.debug)
|
|
64
|
+
actions.push({ i: 'hashtag', text: text('tasks.copy_id'), action: () => copy('text/plain', task.id) });
|
|
65
|
+
return actions;
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
let acl = $state<HTMLDialogElement>();
|
|
43
69
|
</script>
|
|
44
70
|
|
|
@@ -75,48 +101,30 @@
|
|
|
75
101
|
{/snippet}
|
|
76
102
|
|
|
77
103
|
{#snippet task_tree(node: TaskTreeNode, depth: number = 0, isCompletedMirror: boolean = false)}
|
|
78
|
-
{const task = $derived(node.task)
|
|
79
|
-
|
|
104
|
+
{const task = $derived(node.task),
|
|
105
|
+
actions = $derived(taskActions(task))}
|
|
106
|
+
<div class="task" style:margin-left="{depth * 1.75}em" {@attach contextMenu(...actions)}>
|
|
80
107
|
{@render task_checkbox(node, !showCompletedInline && isCompletedMirror != task.completed)}
|
|
81
|
-
<
|
|
82
|
-
type="text"
|
|
108
|
+
<textarea
|
|
83
109
|
name="summary"
|
|
84
110
|
class="editable-text"
|
|
111
|
+
rows="1"
|
|
85
112
|
bind:value={task.summary}
|
|
113
|
+
onkeydown={e => {
|
|
114
|
+
if (e.key == 'Enter' && !e.isComposing) e.preventDefault();
|
|
115
|
+
}}
|
|
86
116
|
oninput={e => {
|
|
87
|
-
task.summary = e.currentTarget.value;
|
|
117
|
+
task.summary = e.currentTarget.value.replaceAll('\n', ' ');
|
|
88
118
|
fetchAPI('PATCH', 'tasks/:id', { summary: task.summary }, task.id);
|
|
89
119
|
}}
|
|
90
|
-
|
|
120
|
+
{@attach dynamicRows(40, 1)}></textarea>
|
|
91
121
|
<Popover showToggle="hover">
|
|
92
|
-
|
|
93
|
-
class="menu-item"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}}
|
|
97
|
-
>
|
|
98
|
-
<Icon i="arrow-turn-down-right" />
|
|
99
|
-
<span>{text('tasks.add_subtask')}</span>
|
|
100
|
-
</div>
|
|
101
|
-
<div
|
|
102
|
-
class="menu-item"
|
|
103
|
-
onclick={() =>
|
|
104
|
-
toastStatus(
|
|
105
|
-
fetchAPI('DELETE', 'tasks/:id', {}, task.id).then(() => {
|
|
106
|
-
tasks.splice(tasks.indexOf(task), 1);
|
|
107
|
-
}),
|
|
108
|
-
text('tasks.toast_task_deleted')
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
<Icon i="trash" />
|
|
112
|
-
<span>{text('generic.delete')}</span>
|
|
113
|
-
</div>
|
|
114
|
-
{#if user?.preferences?.debug}
|
|
115
|
-
<div class="menu-item" onclick={() => copy('text/plain', task.id)}>
|
|
116
|
-
<Icon i="hashtag" --size="14px" />
|
|
117
|
-
<span>{text('tasks.copy_id')}</span>
|
|
122
|
+
{#each actions as item}
|
|
123
|
+
<div class="menu-item" onclick={item.action}>
|
|
124
|
+
<Icon i={item.i} />
|
|
125
|
+
<span>{item.text}</span>
|
|
118
126
|
</div>
|
|
119
|
-
{/
|
|
127
|
+
{/each}
|
|
120
128
|
</Popover>
|
|
121
129
|
</div>
|
|
122
130
|
{#each node.subtasks as sub (sub.task.id)}
|
|
@@ -211,7 +219,7 @@
|
|
|
211
219
|
<div
|
|
212
220
|
class="menu-item"
|
|
213
221
|
onclick={() => {
|
|
214
|
-
tasks = tasks.filter(task => !task.completed);
|
|
222
|
+
list.tasks = tasks.filter(task => !task.completed);
|
|
215
223
|
toastStatus(
|
|
216
224
|
fetchAPI('POST', 'task_lists/:id', { action: 'delete_completed' }, list.id),
|
|
217
225
|
text('tasks.toast_delete_completed')
|
|
@@ -313,8 +321,9 @@
|
|
|
313
321
|
}
|
|
314
322
|
}
|
|
315
323
|
|
|
316
|
-
|
|
324
|
+
textarea {
|
|
317
325
|
padding: 0.125em 0.25em;
|
|
326
|
+
width: 100%;
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
|
package/lib/tsconfig.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/tasks",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev>",
|
|
5
5
|
"description": "Tasks for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"./components/*": "./lib/*.svelte"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
+
"db-scripts",
|
|
29
30
|
"dist",
|
|
30
31
|
"lib",
|
|
31
32
|
"routes",
|
|
@@ -36,9 +37,9 @@
|
|
|
36
37
|
"build": "tsc"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"@axium/client": ">=0.
|
|
40
|
-
"@axium/core": ">=0.
|
|
41
|
-
"@axium/server": ">=0.
|
|
40
|
+
"@axium/client": ">=0.39.0",
|
|
41
|
+
"@axium/core": ">=0.41.0",
|
|
42
|
+
"@axium/server": ">=0.55.0",
|
|
42
43
|
"@sveltejs/kit": "^2.27.3",
|
|
43
44
|
"kysely": "^0.29.0",
|
|
44
45
|
"utilium": "^3.0.0"
|
|
@@ -47,11 +48,12 @@
|
|
|
47
48
|
"zod": "^4.0.5"
|
|
48
49
|
},
|
|
49
50
|
"axium": {
|
|
51
|
+
"locales": "locales",
|
|
50
52
|
"server": {
|
|
51
53
|
"routes": "routes",
|
|
52
54
|
"hooks": "./dist/hooks.js",
|
|
53
55
|
"db": "./db.json",
|
|
54
|
-
"web_client_hooks": "./dist/
|
|
56
|
+
"web_client_hooks": "./dist/common.js"
|
|
55
57
|
},
|
|
56
58
|
"apps": [
|
|
57
59
|
{
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
3
|
+
import { TopBarContent } from '@axium/client/components';
|
|
4
|
+
|
|
5
|
+
const { children } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<TopBarContent name={text('app_name.tasks')} icon="list-check" href="/tasks" />
|
|
9
|
+
|
|
10
|
+
{@render children()}
|
package/routes/tasks/+layout.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { connect } from '@axium/client/socket';
|
|
2
|
+
import { schemas } from '@axium/client/sync';
|
|
3
|
+
import { currentSession } from '@axium/client/user';
|
|
2
4
|
import type { Session, UserPublic } from '@axium/core';
|
|
5
|
+
import { TaskList } from '@axium/tasks/common';
|
|
6
|
+
|
|
7
|
+
schemas.set('task_lists', TaskList);
|
|
3
8
|
|
|
4
9
|
export const ssr = false;
|
|
5
10
|
|
|
6
11
|
export async function load({ parent }) {
|
|
7
12
|
let { session }: { session?: (Session & { user: UserPublic }) | null } = await parent();
|
|
8
13
|
|
|
9
|
-
session ||= await
|
|
14
|
+
session ||= await currentSession();
|
|
15
|
+
|
|
16
|
+
if (session) await connect().catch(() => null);
|
|
10
17
|
|
|
11
18
|
return { session };
|
|
12
19
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { FormDialog, Icon, AppPreferencesDialog } from '@axium/client/components';
|
|
3
2
|
import { fetchAPI, text } from '@axium/client';
|
|
3
|
+
import { AppPreferencesDialog, FormDialog, Icon } from '@axium/client/components';
|
|
4
|
+
import { syncObjects } from '@axium/client/reactive';
|
|
4
5
|
import { TaskListInit, TasksPreferences } from '@axium/tasks/common';
|
|
5
6
|
import { TaskList } from '@axium/tasks/components';
|
|
6
7
|
|
|
@@ -8,15 +9,17 @@
|
|
|
8
9
|
|
|
9
10
|
let lists = $state(data.lists);
|
|
10
11
|
let dialog = $state<HTMLDialogElement>();
|
|
12
|
+
|
|
13
|
+
const onfocusout = syncObjects(lists, 'task_lists');
|
|
11
14
|
</script>
|
|
12
15
|
|
|
16
|
+
<svelte:window {onfocusout} />
|
|
17
|
+
|
|
13
18
|
<svelte:head>
|
|
14
19
|
<title>{text('app_name.tasks')}</title>
|
|
15
20
|
</svelte:head>
|
|
16
21
|
|
|
17
22
|
<div id="tasks-main">
|
|
18
|
-
<h1>{text('app_name.tasks')}</h1>
|
|
19
|
-
|
|
20
23
|
<div>
|
|
21
24
|
<AppPreferencesDialog appId="tasks" userId={data.session.userId} schema={TasksPreferences} />
|
|
22
25
|
|
package/routes/tasks/+page.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { goto } from '$app/navigation';
|
|
2
3
|
import { text } from '@axium/client';
|
|
3
4
|
import { Icon } from '@axium/client/components';
|
|
5
|
+
import { syncObject } from '@axium/client/reactive';
|
|
4
6
|
import { TaskList } from '@axium/tasks/components';
|
|
5
7
|
|
|
6
8
|
const { data } = $props();
|
|
7
9
|
|
|
10
|
+
let list = $state(data.list);
|
|
11
|
+
|
|
12
|
+
syncObject(list, () => goto('/tasks'), 'task_lists');
|
|
13
|
+
|
|
8
14
|
let opener = $state.raw<Window | null>(window.opener);
|
|
9
15
|
|
|
10
16
|
opener?.addEventListener('beforeunload', () => (opener = null));
|
|
@@ -15,7 +21,7 @@
|
|
|
15
21
|
</script>
|
|
16
22
|
|
|
17
23
|
<svelte:head>
|
|
18
|
-
<title>{text('tasks.list_page_title',
|
|
24
|
+
<title>{text('tasks.list_page_title', list)}</title>
|
|
19
25
|
</svelte:head>
|
|
20
26
|
|
|
21
27
|
<div class="list-container">
|
|
@@ -34,7 +40,7 @@
|
|
|
34
40
|
</div>
|
|
35
41
|
{/if}
|
|
36
42
|
|
|
37
|
-
<TaskList list
|
|
43
|
+
<TaskList bind:list />
|
|
38
44
|
</div>
|
|
39
45
|
|
|
40
46
|
<style>
|
package/routes/tsconfig.json
CHANGED
package/dist/web_hook.d.ts
DELETED
package/dist/web_hook.js
DELETED