@kernhq/module-quire 0.3.0 → 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/dist/contract/events.d.ts +5 -0
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +1 -0
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/models.d.ts +82 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +41 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/notifications.d.ts +9 -0
- package/dist/contract/notifications.d.ts.map +1 -0
- package/dist/contract/notifications.js +16 -0
- package/dist/contract/notifications.js.map +1 -0
- package/dist/contract/permissions.d.ts +7 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +8 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +270 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +33 -1
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +303 -0
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +55 -0
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +132 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/schema.d.ts +335 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +44 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/comments.d.ts +172 -0
- package/dist/server/services/comments.d.ts.map +1 -0
- package/dist/server/services/comments.js +182 -0
- package/dist/server/services/comments.js.map +1 -0
- package/dist/server/services/index.d.ts +3 -0
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +3 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/notify.d.ts +24 -0
- package/dist/server/services/notify.d.ts.map +1 -0
- package/dist/server/services/notify.js +54 -0
- package/dist/server/services/notify.js.map +1 -0
- package/migrations/0003_comments.sql +30 -0
- package/migrations/meta/0003_snapshot.json +668 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/index.ts +2 -0
- package/src/contract/events.ts +4 -0
- package/src/contract/index.ts +1 -0
- package/src/contract/models.ts +48 -0
- package/src/contract/notifications.ts +17 -0
- package/src/contract/permissions.ts +8 -0
- package/src/contract/router.ts +47 -1
package/src/contract/events.ts
CHANGED
|
@@ -22,6 +22,10 @@ export const quireEvents = {
|
|
|
22
22
|
'quire.page.restored_version',
|
|
23
23
|
z.object({ pageId: Id, workspaceId: WorkspaceId, versionId: Id }),
|
|
24
24
|
),
|
|
25
|
+
commentCreated: defineEvent(
|
|
26
|
+
'quire.comment.created',
|
|
27
|
+
z.object({ commentId: Id, pageId: Id, workspaceId: WorkspaceId }),
|
|
28
|
+
),
|
|
25
29
|
/** The page and its descendants are gone; anything holding a reference should drop it. */
|
|
26
30
|
pageDeleted: defineEvent('quire.page.deleted', page.extend({ pageIds: z.array(Id) })),
|
|
27
31
|
} as const
|
package/src/contract/index.ts
CHANGED
package/src/contract/models.ts
CHANGED
|
@@ -113,4 +113,52 @@ export const PageVersion = z.object({
|
|
|
113
113
|
})
|
|
114
114
|
export type PageVersion = z.infer<typeof PageVersion>
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Where a comment is attached, as **Yjs relative positions**.
|
|
118
|
+
*
|
|
119
|
+
* Not character offsets: an offset names a place that only exists while nobody else is typing, and
|
|
120
|
+
* two words inserted above would move a comment onto text it was never about. A relative position
|
|
121
|
+
* points at the content rather than the index, so it survives concurrent editing — which is the
|
|
122
|
+
* whole reason a comment on a collaborative document is harder than a comment on a row.
|
|
123
|
+
*/
|
|
124
|
+
export const CommentAnchor = z.object({
|
|
125
|
+
/** base64 `Y.encodeRelativePosition` */
|
|
126
|
+
from: z.base64(),
|
|
127
|
+
to: z.base64(),
|
|
128
|
+
})
|
|
129
|
+
export type CommentAnchor = z.infer<typeof CommentAnchor>
|
|
130
|
+
|
|
131
|
+
/** A Tiptap/ProseMirror document. Kept opaque here; the renderer is what knows its shape. */
|
|
132
|
+
export const RichDoc = z.record(z.string(), z.unknown())
|
|
133
|
+
|
|
134
|
+
export const Comment = z.object({
|
|
135
|
+
id: Id,
|
|
136
|
+
workspaceId: WorkspaceId,
|
|
137
|
+
pageId: Id,
|
|
138
|
+
parentId: Id.nullable(),
|
|
139
|
+
threadId: Id,
|
|
140
|
+
authorId: UserId.nullable(),
|
|
141
|
+
body: RichDoc,
|
|
142
|
+
bodyText: z.string(),
|
|
143
|
+
mentionIds: z.array(UserId),
|
|
144
|
+
/** null for a comment about the page rather than a piece of it */
|
|
145
|
+
anchor: CommentAnchor.nullable(),
|
|
146
|
+
/** what the anchor pointed at when it was written, so a thread whose text is gone still reads */
|
|
147
|
+
quotedText: z.string(),
|
|
148
|
+
resolvedAt: Timestamp.nullable(),
|
|
149
|
+
resolvedBy: UserId.nullable(),
|
|
150
|
+
editedAt: Timestamp.nullable(),
|
|
151
|
+
createdAt: Timestamp,
|
|
152
|
+
})
|
|
153
|
+
export type Comment = z.infer<typeof Comment>
|
|
154
|
+
|
|
155
|
+
/** A root comment and its replies, which is how a page's margin is actually read. */
|
|
156
|
+
export const CommentThread = z.object({
|
|
157
|
+
id: Id,
|
|
158
|
+
root: Comment,
|
|
159
|
+
replies: z.array(Comment),
|
|
160
|
+
resolved: z.boolean(),
|
|
161
|
+
})
|
|
162
|
+
export type CommentThread = z.infer<typeof CommentThread>
|
|
163
|
+
|
|
116
164
|
export const Ok = z.object({ ok: z.literal(true) })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { core } from '@kernhq/contracts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What Quire can put in somebody's inbox.
|
|
5
|
+
*
|
|
6
|
+
* Declared rather than invented at the call site: the inbox renders a notification from its type,
|
|
7
|
+
* and one that was never declared arrives as a blank row.
|
|
8
|
+
*/
|
|
9
|
+
export const quireNotificationTypes: core.NotificationTypeDef[] = [
|
|
10
|
+
{
|
|
11
|
+
type: 'quire.mention',
|
|
12
|
+
label: 'Mentioned in a page comment',
|
|
13
|
+
description: 'Somebody named you in a remark on a page',
|
|
14
|
+
defaults: { inapp: true, push: true, email: false },
|
|
15
|
+
urgent: false,
|
|
16
|
+
},
|
|
17
|
+
]
|
|
@@ -48,6 +48,14 @@ export const quirePermissions = definePermissions([
|
|
|
48
48
|
defaultRoles: ['owner', 'admin', 'member'],
|
|
49
49
|
dangerous: false,
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
key: 'quire.page.comment',
|
|
53
|
+
label: 'Comment on pages',
|
|
54
|
+
description: 'Leave a remark in the margin without being able to change the page',
|
|
55
|
+
scope: 'space',
|
|
56
|
+
defaultRoles: ['owner', 'admin', 'member', 'guest'],
|
|
57
|
+
dangerous: false,
|
|
58
|
+
},
|
|
51
59
|
{
|
|
52
60
|
key: 'quire.page.publish',
|
|
53
61
|
label: 'Publish pages',
|
package/src/contract/router.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { baseContract, Id, PageInput, page, WorkspaceId } from '@kernhq/contracts'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CommentAnchor,
|
|
5
|
+
CommentThread,
|
|
6
|
+
Ok,
|
|
7
|
+
Page,
|
|
8
|
+
PageKind,
|
|
9
|
+
PageNode,
|
|
10
|
+
PageVersion,
|
|
11
|
+
RichDoc,
|
|
12
|
+
Space,
|
|
13
|
+
SpaceVisibility,
|
|
14
|
+
} from './models.js'
|
|
4
15
|
|
|
5
16
|
const ws = z.object({ workspaceId: WorkspaceId })
|
|
6
17
|
const t = (...tags: string[]) => ({ tags })
|
|
@@ -141,6 +152,41 @@ export const quireContract = {
|
|
|
141
152
|
.output(PageVersion),
|
|
142
153
|
},
|
|
143
154
|
|
|
155
|
+
comments: {
|
|
156
|
+
/** Every open thread on a page, and optionally the resolved ones too. */
|
|
157
|
+
list: baseContract
|
|
158
|
+
.route({ method: 'GET', path: '/pages/{pageId}/comments', ...t('comments') })
|
|
159
|
+
.input(ws.extend({ pageId: Id, includeResolved: z.boolean().default(false) }))
|
|
160
|
+
.output(z.array(CommentThread)),
|
|
161
|
+
create: baseContract
|
|
162
|
+
.route({ method: 'POST', path: '/pages/{pageId}/comments', ...t('comments') })
|
|
163
|
+
.input(
|
|
164
|
+
ws.extend({
|
|
165
|
+
pageId: Id,
|
|
166
|
+
body: RichDoc,
|
|
167
|
+
/** omit for a comment about the page rather than a piece of it */
|
|
168
|
+
anchor: CommentAnchor.nullable().default(null),
|
|
169
|
+
quotedText: z.string().max(2000).default(''),
|
|
170
|
+
/** reply to this comment; the thread is inferred from it */
|
|
171
|
+
parentId: Id.nullable().default(null),
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
174
|
+
.output(CommentThread.shape.root),
|
|
175
|
+
update: baseContract
|
|
176
|
+
.route({ method: 'PATCH', path: '/comments/{commentId}', ...t('comments') })
|
|
177
|
+
.input(ws.extend({ commentId: Id, body: RichDoc }))
|
|
178
|
+
.output(CommentThread.shape.root),
|
|
179
|
+
remove: baseContract
|
|
180
|
+
.route({ method: 'DELETE', path: '/comments/{commentId}', ...t('comments') })
|
|
181
|
+
.input(ws.extend({ commentId: Id }))
|
|
182
|
+
.output(Ok),
|
|
183
|
+
/** Settle a thread. Resolving the root resolves the thread; it is not a per-reply state. */
|
|
184
|
+
resolve: baseContract
|
|
185
|
+
.route({ method: 'POST', path: '/comments/{commentId}/resolve', ...t('comments') })
|
|
186
|
+
.input(ws.extend({ commentId: Id, resolved: z.boolean().default(true) }))
|
|
187
|
+
.output(CommentThread),
|
|
188
|
+
},
|
|
189
|
+
|
|
144
190
|
publishing: {
|
|
145
191
|
/** Make what is written now the version readers are served. Only meaningful for a `page`. */
|
|
146
192
|
publish: baseContract
|