@actuate-media/cms-admin 0.12.0 → 0.12.1

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.
Files changed (152) hide show
  1. package/dist/__tests__/components/comment-mark.test.d.ts +2 -0
  2. package/dist/__tests__/components/comment-mark.test.d.ts.map +1 -0
  3. package/dist/__tests__/components/comment-mark.test.js +89 -0
  4. package/dist/__tests__/components/comment-mark.test.js.map +1 -0
  5. package/dist/__tests__/components/comment-side-panel.test.d.ts +2 -0
  6. package/dist/__tests__/components/comment-side-panel.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/comment-side-panel.test.js +291 -0
  8. package/dist/__tests__/components/comment-side-panel.test.js.map +1 -0
  9. package/dist/__tests__/components/notification-bell.test.d.ts +2 -0
  10. package/dist/__tests__/components/notification-bell.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/notification-bell.test.js +200 -0
  12. package/dist/__tests__/components/notification-bell.test.js.map +1 -0
  13. package/dist/__tests__/components/offline-status.test.d.ts +2 -0
  14. package/dist/__tests__/components/offline-status.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/offline-status.test.js +56 -0
  16. package/dist/__tests__/components/offline-status.test.js.map +1 -0
  17. package/dist/__tests__/components/presence-chips-helpers.test.d.ts +2 -0
  18. package/dist/__tests__/components/presence-chips-helpers.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/presence-chips-helpers.test.js +35 -0
  20. package/dist/__tests__/components/presence-chips-helpers.test.js.map +1 -0
  21. package/dist/__tests__/lib/active-editor.test.d.ts +2 -0
  22. package/dist/__tests__/lib/active-editor.test.d.ts.map +1 -0
  23. package/dist/__tests__/lib/active-editor.test.js +57 -0
  24. package/dist/__tests__/lib/active-editor.test.js.map +1 -0
  25. package/dist/__tests__/lib/coauthor-client.test.d.ts +2 -0
  26. package/dist/__tests__/lib/coauthor-client.test.d.ts.map +1 -0
  27. package/dist/__tests__/lib/coauthor-client.test.js +173 -0
  28. package/dist/__tests__/lib/coauthor-client.test.js.map +1 -0
  29. package/dist/__tests__/lib/collaboration-provider.test.d.ts +2 -0
  30. package/dist/__tests__/lib/collaboration-provider.test.d.ts.map +1 -0
  31. package/dist/__tests__/lib/collaboration-provider.test.js +335 -0
  32. package/dist/__tests__/lib/collaboration-provider.test.js.map +1 -0
  33. package/dist/__tests__/lib/comment-anchor.test.d.ts +2 -0
  34. package/dist/__tests__/lib/comment-anchor.test.d.ts.map +1 -0
  35. package/dist/__tests__/lib/comment-anchor.test.js +173 -0
  36. package/dist/__tests__/lib/comment-anchor.test.js.map +1 -0
  37. package/dist/__tests__/lib/comments-client.test.d.ts +2 -0
  38. package/dist/__tests__/lib/comments-client.test.d.ts.map +1 -0
  39. package/dist/__tests__/lib/comments-client.test.js +202 -0
  40. package/dist/__tests__/lib/comments-client.test.js.map +1 -0
  41. package/dist/__tests__/lib/notifications-client.test.d.ts +2 -0
  42. package/dist/__tests__/lib/notifications-client.test.d.ts.map +1 -0
  43. package/dist/__tests__/lib/notifications-client.test.js +155 -0
  44. package/dist/__tests__/lib/notifications-client.test.js.map +1 -0
  45. package/dist/__tests__/lib/slash-command.test.d.ts +2 -0
  46. package/dist/__tests__/lib/slash-command.test.d.ts.map +1 -0
  47. package/dist/__tests__/lib/slash-command.test.js +123 -0
  48. package/dist/__tests__/lib/slash-command.test.js.map +1 -0
  49. package/dist/actuate-admin.css +1 -1
  50. package/dist/components/AICoauthorBubbleMenu.d.ts +18 -0
  51. package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -0
  52. package/dist/components/AICoauthorBubbleMenu.js +113 -0
  53. package/dist/components/AICoauthorBubbleMenu.js.map +1 -0
  54. package/dist/components/CoauthorResultPopover.d.ts +28 -0
  55. package/dist/components/CoauthorResultPopover.d.ts.map +1 -0
  56. package/dist/components/CoauthorResultPopover.js +42 -0
  57. package/dist/components/CoauthorResultPopover.js.map +1 -0
  58. package/dist/components/CommandPalette.d.ts +1 -1
  59. package/dist/components/CommandPalette.d.ts.map +1 -1
  60. package/dist/components/CommandPalette.js +138 -40
  61. package/dist/components/CommandPalette.js.map +1 -1
  62. package/dist/components/CommentMark.d.ts +54 -0
  63. package/dist/components/CommentMark.d.ts.map +1 -0
  64. package/dist/components/CommentMark.js +104 -0
  65. package/dist/components/CommentMark.js.map +1 -0
  66. package/dist/components/CommentSidePanel.d.ts +86 -0
  67. package/dist/components/CommentSidePanel.d.ts.map +1 -0
  68. package/dist/components/CommentSidePanel.js +165 -0
  69. package/dist/components/CommentSidePanel.js.map +1 -0
  70. package/dist/components/LivePreview.d.ts.map +1 -1
  71. package/dist/components/LivePreview.js +80 -16
  72. package/dist/components/LivePreview.js.map +1 -1
  73. package/dist/components/NotificationBell.d.ts +45 -0
  74. package/dist/components/NotificationBell.d.ts.map +1 -0
  75. package/dist/components/NotificationBell.js +90 -0
  76. package/dist/components/NotificationBell.js.map +1 -0
  77. package/dist/components/OfflineStatus.d.ts +39 -0
  78. package/dist/components/OfflineStatus.d.ts.map +1 -0
  79. package/dist/components/OfflineStatus.js +47 -0
  80. package/dist/components/OfflineStatus.js.map +1 -0
  81. package/dist/components/PresenceChips.d.ts +31 -0
  82. package/dist/components/PresenceChips.d.ts.map +1 -0
  83. package/dist/components/PresenceChips.js +58 -0
  84. package/dist/components/PresenceChips.js.map +1 -0
  85. package/dist/components/TipTapEditor.d.ts +61 -1
  86. package/dist/components/TipTapEditor.d.ts.map +1 -1
  87. package/dist/components/TipTapEditor.js +211 -8
  88. package/dist/components/TipTapEditor.js.map +1 -1
  89. package/dist/components/ui/Badge.d.ts +1 -1
  90. package/dist/index.d.ts +8 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +4 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/lib/active-editor.d.ts +6 -0
  95. package/dist/lib/active-editor.d.ts.map +1 -0
  96. package/dist/lib/active-editor.js +35 -0
  97. package/dist/lib/active-editor.js.map +1 -0
  98. package/dist/lib/coauthor-client.d.ts +72 -0
  99. package/dist/lib/coauthor-client.d.ts.map +1 -0
  100. package/dist/lib/coauthor-client.js +98 -0
  101. package/dist/lib/coauthor-client.js.map +1 -0
  102. package/dist/lib/collaboration-provider.d.ts +142 -0
  103. package/dist/lib/collaboration-provider.d.ts.map +1 -0
  104. package/dist/lib/collaboration-provider.js +168 -0
  105. package/dist/lib/collaboration-provider.js.map +1 -0
  106. package/dist/lib/comment-anchor.d.ts +86 -0
  107. package/dist/lib/comment-anchor.d.ts.map +1 -0
  108. package/dist/lib/comment-anchor.js +139 -0
  109. package/dist/lib/comment-anchor.js.map +1 -0
  110. package/dist/lib/comments-client.d.ts +106 -0
  111. package/dist/lib/comments-client.d.ts.map +1 -0
  112. package/dist/lib/comments-client.js +138 -0
  113. package/dist/lib/comments-client.js.map +1 -0
  114. package/dist/lib/notifications-client.d.ts +68 -0
  115. package/dist/lib/notifications-client.d.ts.map +1 -0
  116. package/dist/lib/notifications-client.js +85 -0
  117. package/dist/lib/notifications-client.js.map +1 -0
  118. package/dist/lib/slash-command.d.ts +39 -0
  119. package/dist/lib/slash-command.d.ts.map +1 -0
  120. package/dist/lib/slash-command.js +50 -0
  121. package/dist/lib/slash-command.js.map +1 -0
  122. package/package.json +12 -3
  123. package/src/__tests__/components/comment-mark.test.ts +99 -0
  124. package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
  125. package/src/__tests__/components/notification-bell.test.tsx +213 -0
  126. package/src/__tests__/components/offline-status.test.tsx +66 -0
  127. package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
  128. package/src/__tests__/lib/active-editor.test.ts +70 -0
  129. package/src/__tests__/lib/coauthor-client.test.ts +213 -0
  130. package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
  131. package/src/__tests__/lib/comment-anchor.test.ts +188 -0
  132. package/src/__tests__/lib/comments-client.test.ts +239 -0
  133. package/src/__tests__/lib/notifications-client.test.ts +195 -0
  134. package/src/__tests__/lib/slash-command.test.ts +146 -0
  135. package/src/components/AICoauthorBubbleMenu.tsx +187 -0
  136. package/src/components/CoauthorResultPopover.tsx +193 -0
  137. package/src/components/CommandPalette.tsx +397 -209
  138. package/src/components/CommentMark.ts +138 -0
  139. package/src/components/CommentSidePanel.tsx +521 -0
  140. package/src/components/LivePreview.tsx +117 -11
  141. package/src/components/NotificationBell.tsx +242 -0
  142. package/src/components/OfflineStatus.tsx +90 -0
  143. package/src/components/PresenceChips.tsx +150 -0
  144. package/src/components/TipTapEditor.tsx +310 -12
  145. package/src/index.ts +20 -0
  146. package/src/lib/active-editor.ts +39 -0
  147. package/src/lib/coauthor-client.ts +160 -0
  148. package/src/lib/collaboration-provider.ts +306 -0
  149. package/src/lib/comment-anchor.ts +179 -0
  150. package/src/lib/comments-client.ts +208 -0
  151. package/src/lib/notifications-client.ts +146 -0
  152. package/src/lib/slash-command.ts +67 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Comment anchor helpers — convert between Yjs **relative positions** and
3
+ * the Base64 wire format the comments REST API speaks.
4
+ *
5
+ * Why relative positions: storing absolute offsets (e.g. char 42–58) is
6
+ * fatal under concurrent editing because someone inserting text before
7
+ * char 42 silently shifts the meaning of every saved anchor. Yjs ships
8
+ * `createRelativePositionFromTypeIndex` /
9
+ * `createAbsolutePositionFromRelativePosition` for exactly this case —
10
+ * they identify a slot in the CRDT graph that survives concurrent edits.
11
+ *
12
+ * The wire shape is `{ from: string; to: string }` where each end is the
13
+ * Base64 of `Y.encodeRelativePosition(rel)`. The slice 4 server treats
14
+ * the field as opaque; this module is the only place in the workspace
15
+ * that interprets it.
16
+ *
17
+ * ## Choosing the right Yjs type to anchor against
18
+ *
19
+ * `createRelativePositionFromTypeIndex(type, index)` interprets `index`
20
+ * differently depending on the type:
21
+ *
22
+ * - `Y.XmlText` / `Y.Text` — index is the character offset within the
23
+ * text. **This is what comment anchors use** because character offsets
24
+ * are the natural unit for a text selection.
25
+ * - `Y.XmlFragment` / `Y.XmlElement` — index counts *children*. Useful
26
+ * for "before/after this child" anchors but not what we want here.
27
+ *
28
+ * Slice 5's TipTap binding maps a PM selection to the underlying
29
+ * `Y.XmlText` instance + character offset before calling `encodeAnchor`,
30
+ * and inverts that mapping after `decodeAnchor` returns. The y-prosemirror
31
+ * plugin keeps a `mapping` for this conversion; consumers that aren't
32
+ * using y-prosemirror can talk to the Y types directly.
33
+ */
34
+ import * as Y from 'yjs';
35
+ import type { CommentAnchor } from './comments-client.js';
36
+ export interface CreateAnchorInput {
37
+ /** Live Yjs document the editor is bound to. */
38
+ doc: Y.Doc;
39
+ /**
40
+ * Yjs text type the selection lives in. Almost always a `Y.XmlText`
41
+ * for TipTap; we keep the bound a little loose so plain `Y.Text` works
42
+ * for non-rich-text editors.
43
+ */
44
+ yType: Y.XmlText | Y.Text;
45
+ /** Character offset (inclusive) of the selection start within `yType`. */
46
+ from: number;
47
+ /** Character offset (exclusive) of the selection end within `yType`. */
48
+ to: number;
49
+ }
50
+ /**
51
+ * Build a comment anchor from an editor selection. Returns `null` when
52
+ * the range is empty or inverted — the caller should not save anchors
53
+ * that would resolve to nothing.
54
+ */
55
+ export declare function encodeAnchor(input: CreateAnchorInput): CommentAnchor | null;
56
+ export interface ResolveAnchorInput {
57
+ doc: Y.Doc;
58
+ anchor: CommentAnchor;
59
+ }
60
+ export interface ResolvedAnchor {
61
+ /**
62
+ * The text type the relative position landed on. Comparing identity
63
+ * (`resolved.yType === editorBoundText`) lets the caller decide
64
+ * whether a comment is still attached to the expected text node.
65
+ */
66
+ yType: Y.AbstractType<unknown>;
67
+ /** Character offset of the saved selection start, after concurrent edits. */
68
+ from: number;
69
+ /** Character offset of the saved selection end. */
70
+ to: number;
71
+ }
72
+ /**
73
+ * Resolve a stored anchor back to live editor positions. Returns `null`
74
+ * when:
75
+ *
76
+ * - the anchor bytes are not valid base64 / not a Yjs relative position,
77
+ * - the relative position cannot be mapped (the anchored content was
78
+ * deleted entirely), or
79
+ * - the two ends collapse to the same offset (text between them was
80
+ * deleted, effectively detaching the comment).
81
+ *
82
+ * Slice 5 surfaces detached comments in the side panel with a small
83
+ * "anchor lost" badge so editors can still resolve / discuss them.
84
+ */
85
+ export declare function decodeAnchor(input: ResolveAnchorInput): ResolvedAnchor | null;
86
+ //# sourceMappingURL=comment-anchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-anchor.d.ts","sourceRoot":"","sources":["../../src/lib/comment-anchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,GAAG,EAAE,CAAC,CAAC,GAAG,CAAA;IACV;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAAA;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,GAAG,IAAI,CAe3E;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAA;IACV,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,KAAK,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC9B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,cAAc,GAAG,IAAI,CAmC7E"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Comment anchor helpers — convert between Yjs **relative positions** and
3
+ * the Base64 wire format the comments REST API speaks.
4
+ *
5
+ * Why relative positions: storing absolute offsets (e.g. char 42–58) is
6
+ * fatal under concurrent editing because someone inserting text before
7
+ * char 42 silently shifts the meaning of every saved anchor. Yjs ships
8
+ * `createRelativePositionFromTypeIndex` /
9
+ * `createAbsolutePositionFromRelativePosition` for exactly this case —
10
+ * they identify a slot in the CRDT graph that survives concurrent edits.
11
+ *
12
+ * The wire shape is `{ from: string; to: string }` where each end is the
13
+ * Base64 of `Y.encodeRelativePosition(rel)`. The slice 4 server treats
14
+ * the field as opaque; this module is the only place in the workspace
15
+ * that interprets it.
16
+ *
17
+ * ## Choosing the right Yjs type to anchor against
18
+ *
19
+ * `createRelativePositionFromTypeIndex(type, index)` interprets `index`
20
+ * differently depending on the type:
21
+ *
22
+ * - `Y.XmlText` / `Y.Text` — index is the character offset within the
23
+ * text. **This is what comment anchors use** because character offsets
24
+ * are the natural unit for a text selection.
25
+ * - `Y.XmlFragment` / `Y.XmlElement` — index counts *children*. Useful
26
+ * for "before/after this child" anchors but not what we want here.
27
+ *
28
+ * Slice 5's TipTap binding maps a PM selection to the underlying
29
+ * `Y.XmlText` instance + character offset before calling `encodeAnchor`,
30
+ * and inverts that mapping after `decodeAnchor` returns. The y-prosemirror
31
+ * plugin keeps a `mapping` for this conversion; consumers that aren't
32
+ * using y-prosemirror can talk to the Y types directly.
33
+ */
34
+ import * as Y from 'yjs';
35
+ /**
36
+ * Build a comment anchor from an editor selection. Returns `null` when
37
+ * the range is empty or inverted — the caller should not save anchors
38
+ * that would resolve to nothing.
39
+ */
40
+ export function encodeAnchor(input) {
41
+ if (input.from >= input.to)
42
+ return null;
43
+ if (input.from < 0)
44
+ return null;
45
+ // `createRelativePositionFromTypeIndex` clamps `index` to `[0, type.length]`
46
+ // internally, but we surface an explicit failure when the *intent* is
47
+ // out of bounds so the caller can flag a stale selection.
48
+ if (input.to > input.yType.length)
49
+ return null;
50
+ const relFrom = Y.createRelativePositionFromTypeIndex(input.yType, input.from);
51
+ const relTo = Y.createRelativePositionFromTypeIndex(input.yType, input.to);
52
+ return {
53
+ from: encodeBase64(Y.encodeRelativePosition(relFrom)),
54
+ to: encodeBase64(Y.encodeRelativePosition(relTo)),
55
+ };
56
+ }
57
+ /**
58
+ * Resolve a stored anchor back to live editor positions. Returns `null`
59
+ * when:
60
+ *
61
+ * - the anchor bytes are not valid base64 / not a Yjs relative position,
62
+ * - the relative position cannot be mapped (the anchored content was
63
+ * deleted entirely), or
64
+ * - the two ends collapse to the same offset (text between them was
65
+ * deleted, effectively detaching the comment).
66
+ *
67
+ * Slice 5 surfaces detached comments in the side panel with a small
68
+ * "anchor lost" badge so editors can still resolve / discuss them.
69
+ */
70
+ export function decodeAnchor(input) {
71
+ let relFromBytes;
72
+ let relToBytes;
73
+ try {
74
+ relFromBytes = decodeBase64(input.anchor.from);
75
+ relToBytes = decodeBase64(input.anchor.to);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ let relFrom;
81
+ let relTo;
82
+ try {
83
+ relFrom = Y.decodeRelativePosition(relFromBytes);
84
+ relTo = Y.decodeRelativePosition(relToBytes);
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ const absFrom = Y.createAbsolutePositionFromRelativePosition(relFrom, input.doc);
90
+ const absTo = Y.createAbsolutePositionFromRelativePosition(relTo, input.doc);
91
+ if (absFrom === null || absTo === null)
92
+ return null;
93
+ // Both ends must land on the same text node — otherwise the user's
94
+ // original selection has been split across nodes and we cannot paint
95
+ // a clean highlight without further work in slice 5.
96
+ if (absFrom.type !== absTo.type)
97
+ return null;
98
+ if (absFrom.index >= absTo.index)
99
+ return null;
100
+ return {
101
+ yType: absFrom.type,
102
+ from: absFrom.index,
103
+ to: absTo.index,
104
+ };
105
+ }
106
+ /**
107
+ * Base64 encode without depending on Node's `Buffer` (the admin bundle
108
+ * is browser-first). We hand-roll the encode because both `btoa` and
109
+ * `Buffer` have ergonomic mis-fits for `Uint8Array` payloads.
110
+ */
111
+ function encodeBase64(bytes) {
112
+ if (typeof btoa === 'function') {
113
+ let binary = '';
114
+ for (let i = 0; i < bytes.byteLength; i++) {
115
+ binary += String.fromCharCode(bytes[i]);
116
+ }
117
+ return btoa(binary);
118
+ }
119
+ const BufferCtor = globalThis.Buffer;
120
+ if (BufferCtor)
121
+ return BufferCtor.from(bytes).toString('base64');
122
+ throw new Error('No base64 encoder available');
123
+ }
124
+ function decodeBase64(s) {
125
+ if (typeof atob === 'function') {
126
+ const binary = atob(s);
127
+ const out = new Uint8Array(binary.length);
128
+ for (let i = 0; i < binary.length; i++) {
129
+ out[i] = binary.charCodeAt(i);
130
+ }
131
+ return out;
132
+ }
133
+ const BufferCtor = globalThis
134
+ .Buffer;
135
+ if (BufferCtor)
136
+ return new Uint8Array(BufferCtor.from(s, 'base64'));
137
+ throw new Error('No base64 decoder available');
138
+ }
139
+ //# sourceMappingURL=comment-anchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-anchor.js","sourceRoot":"","sources":["../../src/lib/comment-anchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAmBxB;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IACvC,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/B,6EAA6E;IAC7E,sEAAsE;IACtE,0DAA0D;IAC1D,IAAI,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAE9C,MAAM,OAAO,GAAG,CAAC,CAAC,mCAAmC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAC9E,MAAM,KAAK,GAAG,CAAC,CAAC,mCAAmC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;IAE1E,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QACrD,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;KAClD,CAAA;AACH,CAAC;AAoBD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB;IACpD,IAAI,YAAwB,CAAA;IAC5B,IAAI,UAAsB,CAAA;IAC1B,IAAI,CAAC;QACH,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC9C,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,OAA2B,CAAA;IAC/B,IAAI,KAAyB,CAAA;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,sBAAsB,CAAC,YAAY,CAAC,CAAA;QAChD,KAAK,GAAG,CAAC,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAA;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,CAAC,0CAA0C,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IAChF,MAAM,KAAK,GAAG,CAAC,CAAC,0CAA0C,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5E,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAEnD,mEAAmE;IACnE,qEAAqE;IACrE,qDAAqD;IACrD,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAE5C,IAAI,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IAE7C,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,IAAI;QACnB,IAAI,EAAE,OAAO,CAAC,KAAK;QACnB,EAAE,EAAE,KAAK,CAAC,KAAK;KAChB,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAiB;IACrC,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,MAAM,GAAG,EAAE,CAAA;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAA;QAC1C,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;IACrB,CAAC;IACD,MAAM,UAAU,GACd,UAGD,CAAC,MAAM,CAAA;IACR,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAChE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;AAChD,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACtB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,MAAM,UAAU,GAAI,UAAwE;SACzF,MAAM,CAAA;IACT,IAAI,UAAU;QAAE,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;IACnE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;AAChD,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Comments REST client — thin, typed wrapper around the
3
+ * `/documents/:id/comments` + `/comments/:id` endpoints shipped in slice 4
4
+ * of Phase 3 (see `packages/cms-core/src/realtime/comments.ts`).
5
+ *
6
+ * The client lives in `cms-admin` so the side panel, the bubble-menu
7
+ * "comment on selection" action, and any future MCP/AI hooks all share one
8
+ * request path, error model, and DTO shape.
9
+ *
10
+ * Mirrors the server contract:
11
+ * POST /documents/:documentId/comments -> { body, anchor?, parentId? }
12
+ * GET /documents/:documentId/comments?… -> { data: CommentDTO[] }
13
+ * PATCH /comments/:id -> { body }
14
+ * POST /comments/:id/resolve -> 200 with updated DTO
15
+ * POST /comments/:id/reopen -> 200 with updated DTO
16
+ * DELETE /comments/:id -> 200 with soft-deleted DTO
17
+ */
18
+ /**
19
+ * Yjs relative-position anchor returned by the server. The encoding is
20
+ * intentionally opaque on the wire (Base64 of `Y.encodeRelativePosition`);
21
+ * `lib/comment-anchor.ts` handles the round-trip when slice 5 paints a
22
+ * highlight in the editor.
23
+ */
24
+ export interface CommentAnchor {
25
+ from: string;
26
+ to: string;
27
+ }
28
+ /**
29
+ * Public DTO returned by every comments endpoint. Mirrors `CommentDTO` in
30
+ * `@actuate-media/cms-core/realtime`; we duplicate the shape here so
31
+ * `cms-admin` doesn't take a hard dependency on the server-side service
32
+ * layer (which carries Prisma types we don't want pulled into the bundle).
33
+ */
34
+ export interface Comment {
35
+ id: string;
36
+ documentId: string;
37
+ parentId: string | null;
38
+ userId: string | null;
39
+ body: string;
40
+ anchor: CommentAnchor | null;
41
+ resolvedAt: string | null;
42
+ resolvedById: string | null;
43
+ deletedAt: string | null;
44
+ createdAt: string;
45
+ updatedAt: string;
46
+ isResolved: boolean;
47
+ }
48
+ export type CommentOutcome<T> = {
49
+ ok: true;
50
+ result: T;
51
+ } | {
52
+ ok: false;
53
+ error: string;
54
+ status: number;
55
+ };
56
+ export interface CreateCommentInput {
57
+ documentId: string;
58
+ body: string;
59
+ anchor?: CommentAnchor | null;
60
+ parentId?: string | null;
61
+ }
62
+ export interface ListCommentsOptions {
63
+ /** Default: false. Resolved threads are filtered out unless requested. */
64
+ includeResolved?: boolean;
65
+ /** Default: false. Admin-only; the server rejects non-admin callers. */
66
+ includeDeleted?: boolean;
67
+ /** Default: server-side cap (100). Hard upper bound is 200. */
68
+ pageSize?: number;
69
+ }
70
+ /**
71
+ * Create a top-level comment or a reply. Pass `parentId` to thread the
72
+ * comment under an existing one — the server enforces single-level
73
+ * threading (replies cannot themselves be parents).
74
+ */
75
+ export declare function createComment(input: CreateCommentInput): Promise<CommentOutcome<Comment>>;
76
+ /**
77
+ * List comments for a document. Active comments (not resolved, not deleted)
78
+ * are returned by default; pass `includeResolved` to keep resolved threads
79
+ * in the response. The server rejects `includeDeleted` for non-admins.
80
+ */
81
+ export declare function listComments(documentId: string, opts?: ListCommentsOptions): Promise<CommentOutcome<Comment[]>>;
82
+ /** Edit a comment body. Only the author or an admin is permitted. */
83
+ export declare function updateComment(id: string, body: string): Promise<CommentOutcome<Comment>>;
84
+ /** Resolve a thread. Write-role or the author may resolve. */
85
+ export declare function resolveComment(id: string): Promise<CommentOutcome<Comment>>;
86
+ /** Re-open a resolved thread. Same permission as resolve. */
87
+ export declare function reopenComment(id: string): Promise<CommentOutcome<Comment>>;
88
+ /**
89
+ * Soft-delete a comment. The row is preserved so its replies don't lose
90
+ * their thread root — the server simply stops returning it from the list
91
+ * endpoint unless an admin asks for `includeDeleted`.
92
+ */
93
+ export declare function deleteComment(id: string): Promise<CommentOutcome<Comment>>;
94
+ /**
95
+ * Sort comments into thread groups suitable for rendering. Roots are
96
+ * returned in chronological order; replies are ordered the same way and
97
+ * attached to their root. Replies whose root is missing (e.g. the root was
98
+ * filtered out by `includeResolved=false`) are dropped — the UI can fetch
99
+ * them explicitly if it needs to render orphaned replies.
100
+ */
101
+ export interface CommentThread {
102
+ root: Comment;
103
+ replies: Comment[];
104
+ }
105
+ export declare function groupCommentsIntoThreads(comments: readonly Comment[]): CommentThread[];
106
+ //# sourceMappingURL=comments-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments-client.d.ts","sourceRoot":"","sources":["../../src/lib/comments-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACvB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAsBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAiB/F;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAOpC;AAED,qEAAqE;AACrE,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAU9F;AAED,8DAA8D;AAC9D,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAOjF;AAED,6DAA6D;AAC7D,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAOhF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAMhF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,OAAO,EAAE,CAAA;CACnB;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,aAAa,EAAE,CAgBtF"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Comments REST client — thin, typed wrapper around the
3
+ * `/documents/:id/comments` + `/comments/:id` endpoints shipped in slice 4
4
+ * of Phase 3 (see `packages/cms-core/src/realtime/comments.ts`).
5
+ *
6
+ * The client lives in `cms-admin` so the side panel, the bubble-menu
7
+ * "comment on selection" action, and any future MCP/AI hooks all share one
8
+ * request path, error model, and DTO shape.
9
+ *
10
+ * Mirrors the server contract:
11
+ * POST /documents/:documentId/comments -> { body, anchor?, parentId? }
12
+ * GET /documents/:documentId/comments?… -> { data: CommentDTO[] }
13
+ * PATCH /comments/:id -> { body }
14
+ * POST /comments/:id/resolve -> 200 with updated DTO
15
+ * POST /comments/:id/reopen -> 200 with updated DTO
16
+ * DELETE /comments/:id -> 200 with soft-deleted DTO
17
+ */
18
+ import { cmsApi } from './api.js';
19
+ function encodeOptions(opts = {}) {
20
+ const params = new URLSearchParams();
21
+ if (opts.includeResolved)
22
+ params.set('includeResolved', 'true');
23
+ if (opts.includeDeleted)
24
+ params.set('includeDeleted', 'true');
25
+ if (opts.pageSize !== undefined)
26
+ params.set('pageSize', String(opts.pageSize));
27
+ const qs = params.toString();
28
+ return qs ? `?${qs}` : '';
29
+ }
30
+ function failure(error, status) {
31
+ return { ok: false, error, status };
32
+ }
33
+ function envelope(res) {
34
+ if (res.error || res.data === undefined) {
35
+ return failure(res.error ?? `Request failed (${res.status})`, res.status);
36
+ }
37
+ return { ok: true, result: res.data };
38
+ }
39
+ /**
40
+ * Create a top-level comment or a reply. Pass `parentId` to thread the
41
+ * comment under an existing one — the server enforces single-level
42
+ * threading (replies cannot themselves be parents).
43
+ */
44
+ export async function createComment(input) {
45
+ if (typeof input.documentId !== 'string' || input.documentId.length === 0) {
46
+ return failure('documentId is required', 0);
47
+ }
48
+ if (typeof input.body !== 'string' || input.body.trim().length === 0) {
49
+ return failure('Comment cannot be empty', 0);
50
+ }
51
+ const body = { body: input.body.trim() };
52
+ if (input.anchor)
53
+ body.anchor = input.anchor;
54
+ if (input.parentId)
55
+ body.parentId = input.parentId;
56
+ const res = await cmsApi(`/documents/${encodeURIComponent(input.documentId)}/comments`, {
57
+ method: 'POST',
58
+ body: JSON.stringify(body),
59
+ });
60
+ return envelope(res);
61
+ }
62
+ /**
63
+ * List comments for a document. Active comments (not resolved, not deleted)
64
+ * are returned by default; pass `includeResolved` to keep resolved threads
65
+ * in the response. The server rejects `includeDeleted` for non-admins.
66
+ */
67
+ export async function listComments(documentId, opts = {}) {
68
+ if (typeof documentId !== 'string' || documentId.length === 0) {
69
+ return failure('documentId is required', 0);
70
+ }
71
+ const path = `/documents/${encodeURIComponent(documentId)}/comments${encodeOptions(opts)}`;
72
+ const res = await cmsApi(path, { method: 'GET' });
73
+ return envelope(res);
74
+ }
75
+ /** Edit a comment body. Only the author or an admin is permitted. */
76
+ export async function updateComment(id, body) {
77
+ if (!id)
78
+ return failure('comment id is required', 0);
79
+ if (typeof body !== 'string' || body.trim().length === 0) {
80
+ return failure('Comment cannot be empty', 0);
81
+ }
82
+ const res = await cmsApi(`/comments/${encodeURIComponent(id)}`, {
83
+ method: 'PATCH',
84
+ body: JSON.stringify({ body: body.trim() }),
85
+ });
86
+ return envelope(res);
87
+ }
88
+ /** Resolve a thread. Write-role or the author may resolve. */
89
+ export async function resolveComment(id) {
90
+ if (!id)
91
+ return failure('comment id is required', 0);
92
+ const res = await cmsApi(`/comments/${encodeURIComponent(id)}/resolve`, {
93
+ method: 'POST',
94
+ body: '{}',
95
+ });
96
+ return envelope(res);
97
+ }
98
+ /** Re-open a resolved thread. Same permission as resolve. */
99
+ export async function reopenComment(id) {
100
+ if (!id)
101
+ return failure('comment id is required', 0);
102
+ const res = await cmsApi(`/comments/${encodeURIComponent(id)}/reopen`, {
103
+ method: 'POST',
104
+ body: '{}',
105
+ });
106
+ return envelope(res);
107
+ }
108
+ /**
109
+ * Soft-delete a comment. The row is preserved so its replies don't lose
110
+ * their thread root — the server simply stops returning it from the list
111
+ * endpoint unless an admin asks for `includeDeleted`.
112
+ */
113
+ export async function deleteComment(id) {
114
+ if (!id)
115
+ return failure('comment id is required', 0);
116
+ const res = await cmsApi(`/comments/${encodeURIComponent(id)}`, {
117
+ method: 'DELETE',
118
+ });
119
+ return envelope(res);
120
+ }
121
+ export function groupCommentsIntoThreads(comments) {
122
+ const roots = new Map();
123
+ const sorted = [...comments].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
124
+ for (const c of sorted) {
125
+ if (c.parentId === null) {
126
+ roots.set(c.id, { root: c, replies: [] });
127
+ }
128
+ }
129
+ for (const c of sorted) {
130
+ if (c.parentId !== null) {
131
+ const root = roots.get(c.parentId);
132
+ if (root)
133
+ root.replies.push(c);
134
+ }
135
+ }
136
+ return Array.from(roots.values());
137
+ }
138
+ //# sourceMappingURL=comments-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments-client.js","sourceRoot":"","sources":["../../src/lib/comments-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Notifications REST client — typed wrapper around the slice-6 endpoints
3
+ * (`/notifications`, `/notifications/unread-count`,
4
+ * `/notifications/:id/read`, `/notifications/read-all`).
5
+ *
6
+ * Lives in `cms-admin` so the bell, the toast layer, and any future
7
+ * background sync agent share one request path, error model, and DTO
8
+ * shape. The wire format mirrors `NotificationDTO` in
9
+ * `@actuate-media/cms-core/realtime/notifications.ts`; we duplicate the
10
+ * shape here to avoid taking a hard dependency on the server module
11
+ * (which carries Prisma types that don't belong in the admin bundle).
12
+ */
13
+ export type NotificationKind = 'comment_reply' | 'comment_resolved' | 'comment_mention';
14
+ export interface CommentReplyPayload {
15
+ kind: 'comment_reply';
16
+ actorId: string | null;
17
+ actorName: string | null;
18
+ excerpt: string;
19
+ }
20
+ export interface CommentResolvedPayload {
21
+ kind: 'comment_resolved';
22
+ actorId: string | null;
23
+ actorName: string | null;
24
+ rootExcerpt: string;
25
+ }
26
+ export interface CommentMentionPayload {
27
+ kind: 'comment_mention';
28
+ actorId: string | null;
29
+ actorName: string | null;
30
+ excerpt: string;
31
+ }
32
+ export type NotificationPayload = CommentReplyPayload | CommentResolvedPayload | CommentMentionPayload;
33
+ export interface Notification {
34
+ id: string;
35
+ userId: string;
36
+ kind: NotificationKind;
37
+ documentId: string | null;
38
+ commentId: string | null;
39
+ payload: NotificationPayload;
40
+ readAt: string | null;
41
+ createdAt: string;
42
+ }
43
+ export type NotificationOutcome<T> = {
44
+ ok: true;
45
+ result: T;
46
+ } | {
47
+ ok: false;
48
+ error: string;
49
+ status: number;
50
+ };
51
+ export interface ListNotificationsOptions {
52
+ /** Default: false. When true the response includes already-read rows. */
53
+ includeRead?: boolean;
54
+ /** Default: server-side cap. Hard upper bound is 200. */
55
+ pageSize?: number;
56
+ }
57
+ export declare function listNotifications(opts?: ListNotificationsOptions): Promise<NotificationOutcome<Notification[]>>;
58
+ export declare function unreadCount(): Promise<NotificationOutcome<number>>;
59
+ export declare function markNotificationRead(id: string): Promise<NotificationOutcome<Notification>>;
60
+ export declare function markAllNotificationsRead(): Promise<NotificationOutcome<number>>;
61
+ /**
62
+ * Cheap single-line summary for the bell list — the server gives us a
63
+ * structured payload, but the dropdown wants one human-readable string
64
+ * per row. Falls back to `Notification` when an unknown kind sneaks in
65
+ * (defensive against future server-side additions).
66
+ */
67
+ export declare function notificationSummary(notification: Notification): string;
68
+ //# sourceMappingURL=notifications-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-client.d.ts","sourceRoot":"","sources":["../../src/lib/notifications-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,kBAAkB,GAAG,iBAAiB,CAAA;AAEvF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAA;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,mBAAmB,GAC3B,mBAAmB,GACnB,sBAAsB,GACtB,qBAAqB,CAAA;AAEzB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,gBAAgB,CAAA;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,mBAAmB,CAAA;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACvB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhD,MAAM,WAAW,wBAAwB;IACvC,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAyBD,wBAAsB,iBAAiB,CACrC,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC,mBAAmB,CAAC,YAAY,EAAE,CAAC,CAAC,CAK9C;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAQxE;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAOjG;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CASrF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAYtE"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Notifications REST client — typed wrapper around the slice-6 endpoints
3
+ * (`/notifications`, `/notifications/unread-count`,
4
+ * `/notifications/:id/read`, `/notifications/read-all`).
5
+ *
6
+ * Lives in `cms-admin` so the bell, the toast layer, and any future
7
+ * background sync agent share one request path, error model, and DTO
8
+ * shape. The wire format mirrors `NotificationDTO` in
9
+ * `@actuate-media/cms-core/realtime/notifications.ts`; we duplicate the
10
+ * shape here to avoid taking a hard dependency on the server module
11
+ * (which carries Prisma types that don't belong in the admin bundle).
12
+ */
13
+ import { cmsApi } from './api.js';
14
+ function failure(error, status) {
15
+ return { ok: false, error, status };
16
+ }
17
+ function envelope(res) {
18
+ if (res.error || res.data === undefined) {
19
+ return failure(res.error ?? `Request failed (${res.status})`, res.status);
20
+ }
21
+ return { ok: true, result: res.data };
22
+ }
23
+ function buildQuery(opts = {}) {
24
+ const params = new URLSearchParams();
25
+ if (opts.includeRead)
26
+ params.set('includeRead', 'true');
27
+ if (opts.pageSize !== undefined)
28
+ params.set('pageSize', String(opts.pageSize));
29
+ const qs = params.toString();
30
+ return qs ? `?${qs}` : '';
31
+ }
32
+ export async function listNotifications(opts = {}) {
33
+ const res = await cmsApi(`/notifications${buildQuery(opts)}`, {
34
+ method: 'GET',
35
+ });
36
+ return envelope(res);
37
+ }
38
+ export async function unreadCount() {
39
+ const res = await cmsApi(`/notifications/unread-count`, {
40
+ method: 'GET',
41
+ });
42
+ if (res.error || !res.data) {
43
+ return failure(res.error ?? `Request failed (${res.status})`, res.status);
44
+ }
45
+ return { ok: true, result: res.data.count };
46
+ }
47
+ export async function markNotificationRead(id) {
48
+ if (!id)
49
+ return failure('notification id is required', 0);
50
+ const res = await cmsApi(`/notifications/${encodeURIComponent(id)}/read`, {
51
+ method: 'POST',
52
+ body: '{}',
53
+ });
54
+ return envelope(res);
55
+ }
56
+ export async function markAllNotificationsRead() {
57
+ const res = await cmsApi(`/notifications/read-all`, {
58
+ method: 'POST',
59
+ body: '{}',
60
+ });
61
+ if (res.error || !res.data) {
62
+ return failure(res.error ?? `Request failed (${res.status})`, res.status);
63
+ }
64
+ return { ok: true, result: res.data.count };
65
+ }
66
+ /**
67
+ * Cheap single-line summary for the bell list — the server gives us a
68
+ * structured payload, but the dropdown wants one human-readable string
69
+ * per row. Falls back to `Notification` when an unknown kind sneaks in
70
+ * (defensive against future server-side additions).
71
+ */
72
+ export function notificationSummary(notification) {
73
+ const actor = notification.payload.actorName ?? 'Someone';
74
+ switch (notification.payload.kind) {
75
+ case 'comment_reply':
76
+ return `${actor} replied: "${notification.payload.excerpt}"`;
77
+ case 'comment_resolved':
78
+ return `${actor} resolved your thread: "${notification.payload.rootExcerpt}"`;
79
+ case 'comment_mention':
80
+ return `${actor} mentioned you: "${notification.payload.excerpt}"`;
81
+ default:
82
+ return 'Notification';
83
+ }
84
+ }
85
+ //# sourceMappingURL=notifications-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-client.js","sourceRoot":"","sources":["../../src/lib/notifications-client.ts"],"names":[],"mappings":"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"}