@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.
@@ -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>>;
@@ -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/gui';
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
- let tasks = $state(list.tasks),
23
- open = $state(false);
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
- <div class="task" style:margin-left="{depth * 1.75}em">
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
- <input
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
- <div
93
- class="menu-item"
94
- onclick={() => {
95
- fetchAPI('PUT', 'task_lists/:id', { summary: '', parentId: task.id }, list.id).then(t => tasks.push(t));
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
- {/if}
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
- input {
324
+ textarea {
317
325
  padding: 0.125em 0.25em;
326
+ width: 100%;
318
327
  }
319
328
  }
320
329
 
package/lib/tsconfig.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "moduleResolution": "Bundler",
8
8
  "types": ["@sveltejs/kit"]
9
9
  },
10
- "include": ["**/*.svelte", "**/*.ts"],
10
+ "include": ["**/*.svelte", "**/*.ts", "../src/.*.ts", "../../client/dist/.*.ts"],
11
11
  "exclude": [],
12
12
  "references": [{ "path": ".." }]
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/tasks",
3
- "version": "0.5.7",
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.27.0",
40
- "@axium/core": ">=0.23.0",
41
- "@axium/server": ">=0.47.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/web_hook.js"
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()}
@@ -1,12 +1,19 @@
1
- import { getCurrentSession } from '@axium/client/user';
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 getCurrentSession().catch(() => null);
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
 
@@ -1,8 +1,6 @@
1
1
  import { fetchAPI } from '@axium/client/requests';
2
2
  import { redirect } from '@sveltejs/kit';
3
3
 
4
- export const ssr = false;
5
-
6
4
  export async function load({ parent }) {
7
5
  const { session } = await parent();
8
6
 
@@ -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', { name: data.list.name })}</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={data.list} user={data.session?.user} />
43
+ <TaskList bind:list />
38
44
  </div>
39
45
 
40
46
  <style>
@@ -1,8 +1,6 @@
1
1
  import { fetchAPI } from '@axium/client/requests';
2
2
  import type { LoadEvent } from '@sveltejs/kit';
3
3
 
4
- export const ssr = false;
5
-
6
4
  export async function load({ params }: LoadEvent) {
7
5
  const list = await fetchAPI('GET', 'task_lists/:id', {}, params.id!);
8
6
 
@@ -7,6 +7,6 @@
7
7
  "target": "esnext",
8
8
  "rootDir": ".."
9
9
  },
10
- "include": ["**/*", "../lib/*"],
10
+ "include": ["**/*", "../lib/*", "../src/.*.ts", "../../client/dist/.*.ts"],
11
11
  "references": [{ "path": ".." }]
12
12
  }
@@ -1,8 +0,0 @@
1
- import en from '../locales/en.json';
2
- import './common.js';
3
- type en = typeof en;
4
- declare module '@axium/client/locales' {
5
- interface Locale extends en {
6
- }
7
- }
8
- export {};
package/dist/web_hook.js DELETED
@@ -1,4 +0,0 @@
1
- import { extendLocale } from '@axium/client';
2
- import en from '../locales/en.json' with { type: 'json' };
3
- import './common.js';
4
- extendLocale('en', en);