@axium/notes 0.3.13 → 0.4.0
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.json +10 -0
- package/dist/common.d.ts +135 -0
- package/dist/common.js +3 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +10 -4
- package/lib/Note.svelte +81 -52
- package/locales/en.json +7 -4
- package/package.json +2 -2
- package/routes/notes/+page.svelte +4 -0
package/db.json
CHANGED
package/dist/common.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const NoteInit: z.ZodObject<{
|
|
|
3
3
|
title: z.ZodString;
|
|
4
4
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
5
5
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
6
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
6
7
|
}, z.core.$strip>;
|
|
7
8
|
export interface NoteInit extends z.infer<typeof NoteInit> {
|
|
8
9
|
}
|
|
@@ -10,10 +11,32 @@ export declare const Note: z.ZodObject<{
|
|
|
10
11
|
title: z.ZodString;
|
|
11
12
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
13
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
13
15
|
id: z.ZodUUID;
|
|
14
16
|
userId: z.ZodUUID;
|
|
15
17
|
created: z.ZodCoercedDate<unknown>;
|
|
16
18
|
modified: z.ZodCoercedDate<unknown>;
|
|
19
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
20
|
+
itemId: z.ZodUUID;
|
|
21
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
22
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
23
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25
|
+
id: z.ZodUUID;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
28
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
29
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
30
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
}, z.core.$strip>>>;
|
|
32
|
+
roles: z.ZodArray<z.ZodString>;
|
|
33
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
34
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
35
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
36
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
}, z.core.$strip>>>;
|
|
38
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
39
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
17
40
|
}, z.core.$strip>;
|
|
18
41
|
export interface Note extends z.infer<typeof Note> {
|
|
19
42
|
}
|
|
@@ -23,23 +46,68 @@ declare const NotesAPI: {
|
|
|
23
46
|
title: z.ZodString;
|
|
24
47
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
48
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
49
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
26
50
|
id: z.ZodUUID;
|
|
27
51
|
userId: z.ZodUUID;
|
|
28
52
|
created: z.ZodCoercedDate<unknown>;
|
|
29
53
|
modified: z.ZodCoercedDate<unknown>;
|
|
54
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
55
|
+
itemId: z.ZodUUID;
|
|
56
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
57
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
60
|
+
id: z.ZodUUID;
|
|
61
|
+
name: z.ZodString;
|
|
62
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
63
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
64
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
65
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
66
|
+
}, z.core.$strip>>>;
|
|
67
|
+
roles: z.ZodArray<z.ZodString>;
|
|
68
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
69
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
70
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
72
|
+
}, z.core.$strip>>>;
|
|
73
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
74
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
30
75
|
}, z.core.$strip>>;
|
|
31
76
|
readonly PUT: readonly [z.ZodObject<{
|
|
32
77
|
title: z.ZodString;
|
|
33
78
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
34
79
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
80
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
35
81
|
}, z.core.$strip>, z.ZodObject<{
|
|
36
82
|
title: z.ZodString;
|
|
37
83
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
84
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
85
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
39
86
|
id: z.ZodUUID;
|
|
40
87
|
userId: z.ZodUUID;
|
|
41
88
|
created: z.ZodCoercedDate<unknown>;
|
|
42
89
|
modified: z.ZodCoercedDate<unknown>;
|
|
90
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
91
|
+
itemId: z.ZodUUID;
|
|
92
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
93
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
94
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
95
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
96
|
+
id: z.ZodUUID;
|
|
97
|
+
name: z.ZodString;
|
|
98
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
99
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
100
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
101
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
102
|
+
}, z.core.$strip>>>;
|
|
103
|
+
roles: z.ZodArray<z.ZodString>;
|
|
104
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
105
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
106
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
107
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
108
|
+
}, z.core.$strip>>>;
|
|
109
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
110
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
43
111
|
}, z.core.$strip>];
|
|
44
112
|
};
|
|
45
113
|
readonly 'notes/:id': {
|
|
@@ -47,32 +115,99 @@ declare const NotesAPI: {
|
|
|
47
115
|
title: z.ZodString;
|
|
48
116
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
117
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
118
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
50
119
|
id: z.ZodUUID;
|
|
51
120
|
userId: z.ZodUUID;
|
|
52
121
|
created: z.ZodCoercedDate<unknown>;
|
|
53
122
|
modified: z.ZodCoercedDate<unknown>;
|
|
123
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
124
|
+
itemId: z.ZodUUID;
|
|
125
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
126
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
127
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
128
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
129
|
+
id: z.ZodUUID;
|
|
130
|
+
name: z.ZodString;
|
|
131
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
132
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
133
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
134
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
135
|
+
}, z.core.$strip>>>;
|
|
136
|
+
roles: z.ZodArray<z.ZodString>;
|
|
137
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
138
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
139
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
140
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
141
|
+
}, z.core.$strip>>>;
|
|
142
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
143
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
54
144
|
}, z.core.$strip>;
|
|
55
145
|
readonly PATCH: readonly [z.ZodObject<{
|
|
56
146
|
title: z.ZodString;
|
|
57
147
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
148
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
149
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
59
150
|
}, z.core.$strip>, z.ZodObject<{
|
|
60
151
|
title: z.ZodString;
|
|
61
152
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
62
153
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
154
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
63
155
|
id: z.ZodUUID;
|
|
64
156
|
userId: z.ZodUUID;
|
|
65
157
|
created: z.ZodCoercedDate<unknown>;
|
|
66
158
|
modified: z.ZodCoercedDate<unknown>;
|
|
159
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
160
|
+
itemId: z.ZodUUID;
|
|
161
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
162
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
164
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
165
|
+
id: z.ZodUUID;
|
|
166
|
+
name: z.ZodString;
|
|
167
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
168
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
169
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
170
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
171
|
+
}, z.core.$strip>>>;
|
|
172
|
+
roles: z.ZodArray<z.ZodString>;
|
|
173
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
174
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
175
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
176
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
177
|
+
}, z.core.$strip>>>;
|
|
178
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
179
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
67
180
|
}, z.core.$strip>];
|
|
68
181
|
readonly DELETE: z.ZodObject<{
|
|
69
182
|
title: z.ZodString;
|
|
70
183
|
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
184
|
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
185
|
+
pinned: z.ZodDefault<z.ZodBoolean>;
|
|
72
186
|
id: z.ZodUUID;
|
|
73
187
|
userId: z.ZodUUID;
|
|
74
188
|
created: z.ZodCoercedDate<unknown>;
|
|
75
189
|
modified: z.ZodCoercedDate<unknown>;
|
|
190
|
+
acl: z.ZodArray<z.ZodObject<{
|
|
191
|
+
itemId: z.ZodUUID;
|
|
192
|
+
userId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
193
|
+
role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
194
|
+
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
195
|
+
user: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
196
|
+
id: z.ZodUUID;
|
|
197
|
+
name: z.ZodString;
|
|
198
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
199
|
+
emailVerified: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
200
|
+
preferences: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
201
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
202
|
+
}, z.core.$strip>>>;
|
|
203
|
+
roles: z.ZodArray<z.ZodString>;
|
|
204
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
205
|
+
registeredAt: z.ZodCoercedDate<unknown>;
|
|
206
|
+
isAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
207
|
+
isSuspended: z.ZodOptional<z.ZodBoolean>;
|
|
208
|
+
}, z.core.$strip>>>;
|
|
209
|
+
createdAt: z.ZodCoercedDate<unknown>;
|
|
210
|
+
}, z.core.$catchall<z.ZodBoolean>>>;
|
|
76
211
|
}, z.core.$strip>;
|
|
77
212
|
};
|
|
78
213
|
};
|
package/dist/common.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { AccessControl } from '@axium/core';
|
|
1
2
|
import { $API } from '@axium/core/api';
|
|
2
3
|
import * as z from 'zod';
|
|
3
4
|
export const NoteInit = z.object({
|
|
4
5
|
title: z.string().max(100),
|
|
5
6
|
content: z.string().max(10_000).nullish(),
|
|
6
7
|
labels: z.array(z.string().max(30)).default([]),
|
|
8
|
+
pinned: z.boolean().default(false),
|
|
7
9
|
});
|
|
8
10
|
export const Note = NoteInit.extend({
|
|
9
11
|
id: z.uuid(),
|
|
10
12
|
userId: z.uuid(),
|
|
11
13
|
created: z.coerce.date(),
|
|
12
14
|
modified: z.coerce.date(),
|
|
15
|
+
acl: AccessControl.array(),
|
|
13
16
|
});
|
|
14
17
|
const NotesAPI = {
|
|
15
18
|
'users/:id/notes': {
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as acl from '@axium/server/acl';
|
|
1
2
|
import { authRequestForItem, checkAuthForUser } from '@axium/server/auth';
|
|
2
3
|
import { database } from '@axium/server/database';
|
|
3
4
|
import { parseBody, withError } from '@axium/server/requests';
|
|
@@ -8,23 +9,26 @@ addRoute({
|
|
|
8
9
|
path: '/api/users/:id/notes',
|
|
9
10
|
params: { id: z.uuid() },
|
|
10
11
|
async GET(request, { id: userId }) {
|
|
11
|
-
await checkAuthForUser(request, userId);
|
|
12
|
+
const { user } = await checkAuthForUser(request, userId);
|
|
12
13
|
return await database
|
|
13
14
|
.selectFrom('notes')
|
|
14
15
|
.selectAll()
|
|
15
|
-
.
|
|
16
|
+
.select(acl.from('notes'))
|
|
17
|
+
.where(eb => eb.or([eb('userId', '=', userId), acl.existsIn('notes', user)(eb)]))
|
|
18
|
+
.orderBy('pinned', 'desc')
|
|
19
|
+
.orderBy('modified', 'desc')
|
|
16
20
|
.execute()
|
|
17
21
|
.catch(withError('Could not get notes'));
|
|
18
22
|
},
|
|
19
23
|
async PUT(request, { id: userId }) {
|
|
20
24
|
const init = await parseBody(request, NoteInit);
|
|
21
25
|
await checkAuthForUser(request, userId);
|
|
22
|
-
return await database
|
|
26
|
+
return Object.assign(await database
|
|
23
27
|
.insertInto('notes')
|
|
24
28
|
.values({ ...init, userId })
|
|
25
29
|
.returningAll()
|
|
26
30
|
.executeTakeFirstOrThrow()
|
|
27
|
-
.catch(withError('Could not create note'));
|
|
31
|
+
.catch(withError('Could not create note')), { acl: [] });
|
|
28
32
|
},
|
|
29
33
|
});
|
|
30
34
|
addRoute({
|
|
@@ -43,6 +47,7 @@ addRoute({
|
|
|
43
47
|
.set('modified', new Date())
|
|
44
48
|
.where('id', '=', id)
|
|
45
49
|
.returningAll()
|
|
50
|
+
.returning(acl.from('notes'))
|
|
46
51
|
.executeTakeFirstOrThrow()
|
|
47
52
|
.catch(withError('Could not update note'));
|
|
48
53
|
},
|
|
@@ -52,6 +57,7 @@ addRoute({
|
|
|
52
57
|
.deleteFrom('notes')
|
|
53
58
|
.where('id', '=', id)
|
|
54
59
|
.returningAll()
|
|
60
|
+
.returning(acl.from('notes'))
|
|
55
61
|
.executeTakeFirstOrThrow()
|
|
56
62
|
.catch(withError('Could not delete note'));
|
|
57
63
|
},
|
package/lib/Note.svelte
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import { AccessControlDialog, Icon, Popover } from '@axium/client/components';
|
|
7
7
|
import { copy } from '@axium/client/gui';
|
|
8
8
|
import { toastStatus } from '@axium/client/toast';
|
|
9
|
+
import { checkAndMatchACL, type UserPublic } from '@axium/core';
|
|
9
10
|
import type { Note } from '@axium/notes/common';
|
|
10
|
-
import { download } from 'utilium/dom
|
|
11
|
+
import { download } from 'utilium/dom';
|
|
11
12
|
|
|
12
13
|
let {
|
|
13
14
|
note = $bindable(),
|
|
@@ -16,50 +17,66 @@
|
|
|
16
17
|
user,
|
|
17
18
|
}: { note: Note; notes?: Note[]; pageMode?: boolean; user?: UserPublic } = $props();
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
const canEdit = user && (user.id === note.userId || !checkAndMatchACL(note.acl, user, { edit: true }).size);
|
|
21
|
+
const canManage = user && (user.id === note.userId || !checkAndMatchACL(note.acl, user, { manage: true }).size);
|
|
20
22
|
</script>
|
|
21
23
|
|
|
22
24
|
<div class={['note', pageMode && 'full-page']}>
|
|
23
25
|
<div class="note-header">
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
note.title
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
{#if note.pinned}
|
|
27
|
+
<div class="pin"><Icon i="thumbtack" /></div>
|
|
28
|
+
{/if}
|
|
29
|
+
{#if canEdit}
|
|
30
|
+
<input
|
|
31
|
+
type="text"
|
|
32
|
+
bind:value={note.title}
|
|
33
|
+
placeholder="Unnamed Note"
|
|
34
|
+
class="note-title"
|
|
35
|
+
oninput={e => {
|
|
36
|
+
note.title = e.currentTarget.value;
|
|
37
|
+
fetchAPI('PATCH', 'notes/:id', note, note.id);
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
{:else}
|
|
41
|
+
<span class="note-title">{note.title}</span>
|
|
42
|
+
{/if}
|
|
34
43
|
<Popover showToggle="hover">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
{#if canEdit}
|
|
45
|
+
<div
|
|
46
|
+
class="menu-item"
|
|
47
|
+
onclick={() => {
|
|
48
|
+
note.pinned = !note.pinned;
|
|
49
|
+
fetchAPI('PATCH', 'notes/:id', note, note.id);
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Icon i="thumbtack{note.pinned ? '-slash' : ''}" />
|
|
53
|
+
<span>{note.pinned ? text('notes.unpin') : text('notes.pin')}</span>
|
|
54
|
+
</div>
|
|
55
|
+
{/if}
|
|
56
|
+
{#if canManage}
|
|
57
|
+
<div
|
|
58
|
+
class="menu-item"
|
|
59
|
+
onclick={() =>
|
|
60
|
+
toastStatus(
|
|
61
|
+
fetchAPI('DELETE', 'notes/:id', {}, note.id).then(() => {
|
|
62
|
+
if (!notes) goto('/notes');
|
|
63
|
+
else notes.splice(notes.indexOf(note), 1);
|
|
64
|
+
}),
|
|
65
|
+
text('notes.toast_deleted')
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
<Icon i="trash" />
|
|
69
|
+
<span>{text('generic.delete')}</span>
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
49
72
|
<div class="menu-item" onclick={() => download(note.title + '.txt', note.content ?? '')}>
|
|
50
73
|
<Icon i="download" />
|
|
51
74
|
<span>{text('notes.download')}</span>
|
|
52
75
|
</div>
|
|
53
|
-
<
|
|
54
|
-
class="menu-item"
|
|
55
|
-
onclick={() => {
|
|
56
|
-
acl!.showModal();
|
|
57
|
-
acl!.click();
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
76
|
+
<button class="reset menu-item" command="show-modal" commandfor="acl#{note.id}">
|
|
60
77
|
<Icon i="user-group" />
|
|
61
78
|
<span>{text('notes.share')}</span>
|
|
62
|
-
</
|
|
79
|
+
</button>
|
|
63
80
|
<div class="menu-item" onclick={() => copy('text/plain', `${location.origin}/notes/${note.id}`)}>
|
|
64
81
|
<Icon i="link-horizontal" />
|
|
65
82
|
<span>{text('notes.copy_link')}</span>
|
|
@@ -77,24 +94,39 @@
|
|
|
77
94
|
</div>
|
|
78
95
|
{/if}
|
|
79
96
|
</Popover>
|
|
80
|
-
<AccessControlDialog
|
|
97
|
+
<AccessControlDialog item={note} itemType="notes" {user} id="acl#{note.id}" />
|
|
81
98
|
</div>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
{#if canEdit}
|
|
100
|
+
<textarea
|
|
101
|
+
bind:value={note.content}
|
|
102
|
+
name="content"
|
|
103
|
+
class="note-content"
|
|
104
|
+
placeholder={text('notes.content_placeholder')}
|
|
105
|
+
oninput={() => fetchAPI('PATCH', 'notes/:id', note, note.id)}
|
|
106
|
+
{@attach dynamicRows()}>{note.content}</textarea
|
|
107
|
+
>
|
|
108
|
+
{:else}
|
|
109
|
+
<div class="note-content">
|
|
110
|
+
{#if note.content}
|
|
111
|
+
<span>{note.content}</span>
|
|
112
|
+
{:else}
|
|
113
|
+
<i class="subtle">{text('notes.content_missing')}</i>
|
|
114
|
+
{/if}
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
90
117
|
</div>
|
|
91
118
|
|
|
92
119
|
<style>
|
|
93
|
-
.
|
|
120
|
+
.note-title,
|
|
121
|
+
.note-content {
|
|
94
122
|
background: none;
|
|
95
123
|
border: none;
|
|
96
124
|
}
|
|
97
125
|
|
|
126
|
+
div.note-content i.subtle {
|
|
127
|
+
font-size: inherit;
|
|
128
|
+
}
|
|
129
|
+
|
|
98
130
|
.note {
|
|
99
131
|
display: flex;
|
|
100
132
|
flex-direction: column;
|
|
@@ -106,13 +138,6 @@
|
|
|
106
138
|
height: fit-content;
|
|
107
139
|
max-height: 40em;
|
|
108
140
|
anchor-name: --note;
|
|
109
|
-
|
|
110
|
-
textarea {
|
|
111
|
-
resize: none;
|
|
112
|
-
field-sizing: content;
|
|
113
|
-
height: max-content;
|
|
114
|
-
overflow-y: scroll;
|
|
115
|
-
}
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
.note.full-page {
|
|
@@ -125,7 +150,11 @@
|
|
|
125
150
|
justify-content: space-between;
|
|
126
151
|
align-items: center;
|
|
127
152
|
|
|
128
|
-
|
|
153
|
+
.pin {
|
|
154
|
+
flex: 0 0 auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.note-title {
|
|
129
158
|
font-size: 1.5em;
|
|
130
159
|
font-weight: bold;
|
|
131
160
|
padding: 0;
|
package/locales/en.json
CHANGED
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
"notes": {
|
|
6
6
|
"back_to_main": "Back to Notes",
|
|
7
7
|
"content_placeholder": "It's a beautiful day outside...",
|
|
8
|
+
"content_missing": "This note is shared with you but has no content.",
|
|
8
9
|
"copy_id": "Copy ID",
|
|
9
|
-
"copy_link": "Copy
|
|
10
|
+
"copy_link": "Copy link",
|
|
10
11
|
"download": "Download",
|
|
11
|
-
"new": "New
|
|
12
|
+
"new": "New note",
|
|
12
13
|
"note_title": "Notes — {title}",
|
|
13
|
-
"open_new_tab": "Open in
|
|
14
|
+
"open_new_tab": "Open in new tab",
|
|
14
15
|
"share": "Share",
|
|
15
|
-
"toast_deleted": "Note deleted"
|
|
16
|
+
"toast_deleted": "Note deleted",
|
|
17
|
+
"pin": "Pin note",
|
|
18
|
+
"unpin": "Unpin note"
|
|
16
19
|
}
|
|
17
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/notes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev>",
|
|
5
5
|
"description": "Notes for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@axium/core": ">=0.23.0",
|
|
41
41
|
"@axium/server": ">=0.35.0",
|
|
42
42
|
"@sveltejs/kit": "^2.27.3",
|
|
43
|
-
"utilium": "^
|
|
43
|
+
"utilium": "^3.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"zod": "^4.0.5"
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
const { data } = $props();
|
|
8
8
|
|
|
9
9
|
let notes = $state(data.notes);
|
|
10
|
+
$effect(() => {
|
|
11
|
+
notes.sort((a, b) => +!!b.pinned - +!!a.pinned);
|
|
12
|
+
});
|
|
10
13
|
</script>
|
|
11
14
|
|
|
12
15
|
<svelte:head>
|
|
@@ -48,6 +51,7 @@
|
|
|
48
51
|
|
|
49
52
|
.lists-container {
|
|
50
53
|
display: grid;
|
|
54
|
+
display: grid-lanes;
|
|
51
55
|
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
|
52
56
|
gap: 1em;
|
|
53
57
|
}
|