@actuate-media/cms-admin 0.11.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.
- package/LICENSE +21 -21
- package/dist/__tests__/components/comment-mark.test.d.ts +2 -0
- package/dist/__tests__/components/comment-mark.test.d.ts.map +1 -0
- package/dist/__tests__/components/comment-mark.test.js +89 -0
- package/dist/__tests__/components/comment-mark.test.js.map +1 -0
- package/dist/__tests__/components/comment-side-panel.test.d.ts +2 -0
- package/dist/__tests__/components/comment-side-panel.test.d.ts.map +1 -0
- package/dist/__tests__/components/comment-side-panel.test.js +291 -0
- package/dist/__tests__/components/comment-side-panel.test.js.map +1 -0
- package/dist/__tests__/components/notification-bell.test.d.ts +2 -0
- package/dist/__tests__/components/notification-bell.test.d.ts.map +1 -0
- package/dist/__tests__/components/notification-bell.test.js +200 -0
- package/dist/__tests__/components/notification-bell.test.js.map +1 -0
- package/dist/__tests__/components/offline-status.test.d.ts +2 -0
- package/dist/__tests__/components/offline-status.test.d.ts.map +1 -0
- package/dist/__tests__/components/offline-status.test.js +56 -0
- package/dist/__tests__/components/offline-status.test.js.map +1 -0
- package/dist/__tests__/components/presence-chips-helpers.test.d.ts +2 -0
- package/dist/__tests__/components/presence-chips-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/components/presence-chips-helpers.test.js +35 -0
- package/dist/__tests__/components/presence-chips-helpers.test.js.map +1 -0
- package/dist/__tests__/fields/component-block-helpers.test.d.ts +7 -0
- package/dist/__tests__/fields/component-block-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/fields/component-block-helpers.test.js +592 -0
- package/dist/__tests__/fields/component-block-helpers.test.js.map +1 -0
- package/dist/__tests__/lib/active-editor.test.d.ts +2 -0
- package/dist/__tests__/lib/active-editor.test.d.ts.map +1 -0
- package/dist/__tests__/lib/active-editor.test.js +57 -0
- package/dist/__tests__/lib/active-editor.test.js.map +1 -0
- package/dist/__tests__/lib/coauthor-client.test.d.ts +2 -0
- package/dist/__tests__/lib/coauthor-client.test.d.ts.map +1 -0
- package/dist/__tests__/lib/coauthor-client.test.js +173 -0
- package/dist/__tests__/lib/coauthor-client.test.js.map +1 -0
- package/dist/__tests__/lib/collaboration-provider.test.d.ts +2 -0
- package/dist/__tests__/lib/collaboration-provider.test.d.ts.map +1 -0
- package/dist/__tests__/lib/collaboration-provider.test.js +335 -0
- package/dist/__tests__/lib/collaboration-provider.test.js.map +1 -0
- package/dist/__tests__/lib/comment-anchor.test.d.ts +2 -0
- package/dist/__tests__/lib/comment-anchor.test.d.ts.map +1 -0
- package/dist/__tests__/lib/comment-anchor.test.js +173 -0
- package/dist/__tests__/lib/comment-anchor.test.js.map +1 -0
- package/dist/__tests__/lib/comments-client.test.d.ts +2 -0
- package/dist/__tests__/lib/comments-client.test.d.ts.map +1 -0
- package/dist/__tests__/lib/comments-client.test.js +202 -0
- package/dist/__tests__/lib/comments-client.test.js.map +1 -0
- package/dist/__tests__/lib/notifications-client.test.d.ts +2 -0
- package/dist/__tests__/lib/notifications-client.test.d.ts.map +1 -0
- package/dist/__tests__/lib/notifications-client.test.js +155 -0
- package/dist/__tests__/lib/notifications-client.test.js.map +1 -0
- package/dist/__tests__/lib/slash-command.test.d.ts +2 -0
- package/dist/__tests__/lib/slash-command.test.d.ts.map +1 -0
- package/dist/__tests__/lib/slash-command.test.js +123 -0
- package/dist/__tests__/lib/slash-command.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/AICoauthorBubbleMenu.d.ts +18 -0
- package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -0
- package/dist/components/AICoauthorBubbleMenu.js +113 -0
- package/dist/components/AICoauthorBubbleMenu.js.map +1 -0
- package/dist/components/CoauthorResultPopover.d.ts +28 -0
- package/dist/components/CoauthorResultPopover.d.ts.map +1 -0
- package/dist/components/CoauthorResultPopover.js +42 -0
- package/dist/components/CoauthorResultPopover.js.map +1 -0
- package/dist/components/CommandPalette.d.ts +1 -1
- package/dist/components/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette.js +138 -40
- package/dist/components/CommandPalette.js.map +1 -1
- package/dist/components/CommentMark.d.ts +54 -0
- package/dist/components/CommentMark.d.ts.map +1 -0
- package/dist/components/CommentMark.js +104 -0
- package/dist/components/CommentMark.js.map +1 -0
- package/dist/components/CommentSidePanel.d.ts +86 -0
- package/dist/components/CommentSidePanel.d.ts.map +1 -0
- package/dist/components/CommentSidePanel.js +165 -0
- package/dist/components/CommentSidePanel.js.map +1 -0
- package/dist/components/LivePreview.d.ts.map +1 -1
- package/dist/components/LivePreview.js +80 -16
- package/dist/components/LivePreview.js.map +1 -1
- package/dist/components/NotificationBell.d.ts +45 -0
- package/dist/components/NotificationBell.d.ts.map +1 -0
- package/dist/components/NotificationBell.js +90 -0
- package/dist/components/NotificationBell.js.map +1 -0
- package/dist/components/OfflineStatus.d.ts +39 -0
- package/dist/components/OfflineStatus.d.ts.map +1 -0
- package/dist/components/OfflineStatus.js +47 -0
- package/dist/components/OfflineStatus.js.map +1 -0
- package/dist/components/PresenceChips.d.ts +31 -0
- package/dist/components/PresenceChips.d.ts.map +1 -0
- package/dist/components/PresenceChips.js +58 -0
- package/dist/components/PresenceChips.js.map +1 -0
- package/dist/components/TipTapEditor.d.ts +61 -1
- package/dist/components/TipTapEditor.d.ts.map +1 -1
- package/dist/components/TipTapEditor.js +211 -8
- package/dist/components/TipTapEditor.js.map +1 -1
- package/dist/components/ui/Badge.d.ts +1 -1
- package/dist/fields/ComponentBlockField.d.ts +25 -0
- package/dist/fields/ComponentBlockField.d.ts.map +1 -0
- package/dist/fields/ComponentBlockField.js +74 -0
- package/dist/fields/ComponentBlockField.js.map +1 -0
- package/dist/fields/FieldRenderer.d.ts +3 -0
- package/dist/fields/FieldRenderer.d.ts.map +1 -1
- package/dist/fields/FieldRenderer.js +3 -1
- package/dist/fields/FieldRenderer.js.map +1 -1
- package/dist/fields/PropInput.d.ts +14 -0
- package/dist/fields/PropInput.d.ts.map +1 -0
- package/dist/fields/PropInput.js +163 -0
- package/dist/fields/PropInput.js.map +1 -0
- package/dist/fields/component-block-helpers.d.ts +96 -0
- package/dist/fields/component-block-helpers.d.ts.map +1 -0
- package/dist/fields/component-block-helpers.js +323 -0
- package/dist/fields/component-block-helpers.js.map +1 -0
- package/dist/fields/index.d.ts +4 -0
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +2 -0
- package/dist/fields/index.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/active-editor.d.ts +6 -0
- package/dist/lib/active-editor.d.ts.map +1 -0
- package/dist/lib/active-editor.js +35 -0
- package/dist/lib/active-editor.js.map +1 -0
- package/dist/lib/coauthor-client.d.ts +72 -0
- package/dist/lib/coauthor-client.d.ts.map +1 -0
- package/dist/lib/coauthor-client.js +98 -0
- package/dist/lib/coauthor-client.js.map +1 -0
- package/dist/lib/collaboration-provider.d.ts +142 -0
- package/dist/lib/collaboration-provider.d.ts.map +1 -0
- package/dist/lib/collaboration-provider.js +168 -0
- package/dist/lib/collaboration-provider.js.map +1 -0
- package/dist/lib/comment-anchor.d.ts +86 -0
- package/dist/lib/comment-anchor.d.ts.map +1 -0
- package/dist/lib/comment-anchor.js +139 -0
- package/dist/lib/comment-anchor.js.map +1 -0
- package/dist/lib/comments-client.d.ts +106 -0
- package/dist/lib/comments-client.d.ts.map +1 -0
- package/dist/lib/comments-client.js +138 -0
- package/dist/lib/comments-client.js.map +1 -0
- package/dist/lib/notifications-client.d.ts +68 -0
- package/dist/lib/notifications-client.d.ts.map +1 -0
- package/dist/lib/notifications-client.js +85 -0
- package/dist/lib/notifications-client.js.map +1 -0
- package/dist/lib/slash-command.d.ts +39 -0
- package/dist/lib/slash-command.d.ts.map +1 -0
- package/dist/lib/slash-command.js +50 -0
- package/dist/lib/slash-command.js.map +1 -0
- package/package.json +20 -4
- package/src/__tests__/components/comment-mark.test.ts +99 -0
- package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
- package/src/__tests__/components/notification-bell.test.tsx +213 -0
- package/src/__tests__/components/offline-status.test.tsx +66 -0
- package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
- package/src/__tests__/fields/component-block-helpers.test.ts +674 -0
- package/src/__tests__/lib/active-editor.test.ts +70 -0
- package/src/__tests__/lib/coauthor-client.test.ts +213 -0
- package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
- package/src/__tests__/lib/comment-anchor.test.ts +188 -0
- package/src/__tests__/lib/comments-client.test.ts +239 -0
- package/src/__tests__/lib/notifications-client.test.ts +195 -0
- package/src/__tests__/lib/slash-command.test.ts +146 -0
- package/src/components/AICoauthorBubbleMenu.tsx +187 -0
- package/src/components/CoauthorResultPopover.tsx +193 -0
- package/src/components/CommandPalette.tsx +397 -209
- package/src/components/CommentMark.ts +138 -0
- package/src/components/CommentSidePanel.tsx +521 -0
- package/src/components/LivePreview.tsx +117 -11
- package/src/components/NotificationBell.tsx +242 -0
- package/src/components/OfflineStatus.tsx +90 -0
- package/src/components/PresenceChips.tsx +150 -0
- package/src/components/TipTapEditor.tsx +310 -12
- package/src/fields/ComponentBlockField.tsx +179 -0
- package/src/fields/FieldRenderer.tsx +8 -0
- package/src/fields/PropInput.tsx +552 -0
- package/src/fields/component-block-helpers.ts +341 -0
- package/src/fields/index.ts +4 -0
- package/src/index.ts +27 -0
- package/src/lib/active-editor.ts +39 -0
- package/src/lib/coauthor-client.ts +160 -0
- package/src/lib/collaboration-provider.ts +306 -0
- package/src/lib/comment-anchor.ts +179 -0
- package/src/lib/comments-client.ts +208 -0
- package/src/lib/notifications-client.ts +146 -0
- package/src/lib/slash-command.ts +67 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TipTap inline mark that tags a text range with a `commentId`. The mark
|
|
3
|
+
* is the glue between the comment side panel and the editor — selecting
|
|
4
|
+
* a range, creating a comment, and then re-painting the highlight when
|
|
5
|
+
* the comment list updates all flow through this extension.
|
|
6
|
+
*
|
|
7
|
+
* Storage shape: a single attribute, `commentId`, that mirrors the row
|
|
8
|
+
* id returned by the slice-4 REST API. The mark also stamps a
|
|
9
|
+
* `data-comment-id` attribute on its rendered span and a CSS class so
|
|
10
|
+
* the panel can style it (yellow band for active, dotted underline for
|
|
11
|
+
* resolved, faded for detached anchors).
|
|
12
|
+
*
|
|
13
|
+
* We deliberately use a TipTap Mark (vs. a ProseMirror decoration) so
|
|
14
|
+
* the comment anchor is *persisted* in the document HTML and round-trips
|
|
15
|
+
* through the autosave path. Decorations would be simpler but they're
|
|
16
|
+
* presentational only — they disappear on next render unless re-painted
|
|
17
|
+
* from scratch, which would defeat slice 5's main job (keeping anchors
|
|
18
|
+
* stable under collaborative edits).
|
|
19
|
+
*/
|
|
20
|
+
import { Mark, mergeAttributes } from '@tiptap/core';
|
|
21
|
+
export const CommentMark = Mark.create({
|
|
22
|
+
name: 'comment',
|
|
23
|
+
// Inline mark; we never want it on block-level nodes.
|
|
24
|
+
inclusive: false,
|
|
25
|
+
spanning: false,
|
|
26
|
+
// Allow the mark to be split when text is inserted at its boundaries —
|
|
27
|
+
// otherwise replies/edits in the middle of a comment would silently
|
|
28
|
+
// expand its scope.
|
|
29
|
+
excludes: '',
|
|
30
|
+
addOptions() {
|
|
31
|
+
return {
|
|
32
|
+
HTMLAttributes: {},
|
|
33
|
+
className: 'actuate-comment',
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
addAttributes() {
|
|
37
|
+
return {
|
|
38
|
+
commentId: {
|
|
39
|
+
default: null,
|
|
40
|
+
parseHTML: (element) => element.getAttribute('data-comment-id'),
|
|
41
|
+
renderHTML: (attributes) => {
|
|
42
|
+
if (!attributes.commentId)
|
|
43
|
+
return {};
|
|
44
|
+
return { 'data-comment-id': attributes.commentId };
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
parseHTML() {
|
|
50
|
+
return [{ tag: 'span[data-comment-id]' }];
|
|
51
|
+
},
|
|
52
|
+
renderHTML({ HTMLAttributes }) {
|
|
53
|
+
const opts = this.options;
|
|
54
|
+
return [
|
|
55
|
+
'span',
|
|
56
|
+
mergeAttributes(opts.HTMLAttributes, HTMLAttributes, {
|
|
57
|
+
class: opts.className,
|
|
58
|
+
}),
|
|
59
|
+
0,
|
|
60
|
+
];
|
|
61
|
+
},
|
|
62
|
+
addCommands() {
|
|
63
|
+
return {
|
|
64
|
+
setComment: (attrs) => ({ commands }) => {
|
|
65
|
+
if (!attrs?.commentId)
|
|
66
|
+
return false;
|
|
67
|
+
return commands.setMark(this.name, attrs);
|
|
68
|
+
},
|
|
69
|
+
updateComment: (attrs) => ({ commands }) => {
|
|
70
|
+
if (!attrs?.commentId)
|
|
71
|
+
return false;
|
|
72
|
+
return commands.updateAttributes(this.name, attrs);
|
|
73
|
+
},
|
|
74
|
+
unsetComment: () => ({ commands }) => {
|
|
75
|
+
return commands.unsetMark(this.name);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* CSS the editor styles include to render the comment highlights. The
|
|
82
|
+
* `TipTapEditor` injects this so consumers don't need to import a CSS
|
|
83
|
+
* file separately.
|
|
84
|
+
*/
|
|
85
|
+
export const COMMENT_MARK_STYLES = `
|
|
86
|
+
.actuate-comment {
|
|
87
|
+
background-color: rgba(250, 204, 21, 0.25);
|
|
88
|
+
border-bottom: 2px solid rgba(202, 138, 4, 0.65);
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
.actuate-comment:hover {
|
|
92
|
+
background-color: rgba(250, 204, 21, 0.4);
|
|
93
|
+
}
|
|
94
|
+
.actuate-comment--resolved {
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
border-bottom: 1px dotted rgba(107, 114, 128, 0.6);
|
|
97
|
+
}
|
|
98
|
+
.actuate-comment--active {
|
|
99
|
+
background-color: rgba(250, 204, 21, 0.55);
|
|
100
|
+
outline: 2px solid rgba(202, 138, 4, 0.9);
|
|
101
|
+
outline-offset: -1px;
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
//# sourceMappingURL=CommentMark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentMark.js","sourceRoot":"","sources":["../../src/components/CommentMark.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AA0BpD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAqB;IACzD,IAAI,EAAE,SAAS;IACf,sDAAsD;IACtD,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;IACf,uEAAuE;IACvE,oEAAoE;IACpE,oBAAoB;IACpB,QAAQ,EAAE,EAAE;IAEZ,UAAU;QACR,OAAO;YACL,cAAc,EAAE,EAAE;YAClB,SAAS,EAAE,iBAAiB;SAC7B,CAAA;IACH,CAAC;IAED,aAAa;QACX,OAAO;YACL,SAAS,EAAE;gBACT,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC;gBAC/D,UAAU,EAAE,CAAC,UAAU,EAAE,EAAE;oBACzB,IAAI,CAAC,UAAU,CAAC,SAAS;wBAAE,OAAO,EAAE,CAAA;oBACpC,OAAO,EAAE,iBAAiB,EAAE,UAAU,CAAC,SAAmB,EAAE,CAAA;gBAC9D,CAAC;aACF;SACF,CAAA;IACH,CAAC;IAED,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,uBAAuB,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAA;QACzB,OAAO;YACL,MAAM;YACN,eAAe,CAAC,IAAI,CAAC,cAAc,EAAE,cAAc,EAAE;gBACnD,KAAK,EAAE,IAAI,CAAC,SAAS;aACtB,CAAC;YACF,CAAC;SACF,CAAA;IACH,CAAC;IAED,WAAW;QACT,OAAO;YACL,UAAU,EACR,CAAC,KAAK,EAAE,EAAE,CACV,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;gBACf,IAAI,CAAC,KAAK,EAAE,SAAS;oBAAE,OAAO,KAAK,CAAA;gBACnC,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAC3C,CAAC;YACH,aAAa,EACX,CAAC,KAAK,EAAE,EAAE,CACV,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;gBACf,IAAI,CAAC,KAAK,EAAE,SAAS;oBAAE,OAAO,KAAK,CAAA;gBACnC,OAAO,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACpD,CAAC;YACH,YAAY,EACV,GAAG,EAAE,CACL,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;gBACf,OAAO,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACtC,CAAC;SACJ,CAAA;IACH,CAAC;CACF,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;CAkBlC,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Comment, type CommentAnchor, type CommentOutcome } from '../lib/comments-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Side-panel view of the slice-4 comments API. Renders threaded comments
|
|
5
|
+
* for a document, lets the current user compose new threads and replies,
|
|
6
|
+
* and offers per-comment actions: edit, resolve / reopen, delete.
|
|
7
|
+
*
|
|
8
|
+
* The component is *pure I/O* against the comments-client module —
|
|
9
|
+
* anchor encoding/decoding, editor mark painting, and bubble-menu wiring
|
|
10
|
+
* are layered on top in `TipTapEditor.tsx` through the `comments` prop.
|
|
11
|
+
* Keeping the panel logic agnostic of TipTap means the unit tests can
|
|
12
|
+
* verify thread ordering / state transitions without spinning up an
|
|
13
|
+
* editor.
|
|
14
|
+
*
|
|
15
|
+
* Behaviour
|
|
16
|
+
* ─────────
|
|
17
|
+
* - Active comments (not resolved, not deleted) are shown by default.
|
|
18
|
+
* - A toggle reveals resolved threads in the same list, sorted by
|
|
19
|
+
* resolved-at descending so the most recently closed appears first.
|
|
20
|
+
* - Composer accepts free-form text. When `onComposeAnchor` returns a
|
|
21
|
+
* non-null anchor the new comment is bound to that range; otherwise
|
|
22
|
+
* it is a document-level comment.
|
|
23
|
+
* - The list refreshes after every successful mutation so optimistic
|
|
24
|
+
* state can't drift from the server.
|
|
25
|
+
*
|
|
26
|
+
* Errors fall through to the `onError` callback so the host shell can
|
|
27
|
+
* surface them with its own toast / banner system (we don't import
|
|
28
|
+
* sonner here to keep the component testable in plain JSDOM).
|
|
29
|
+
*/
|
|
30
|
+
export interface CommentSidePanelProps {
|
|
31
|
+
documentId: string;
|
|
32
|
+
/** Current user id — used to gate edit / delete affordances. */
|
|
33
|
+
currentUserId: string;
|
|
34
|
+
/** Set to `true` to show admin-only affordances (delete others'). */
|
|
35
|
+
isAdmin?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Called when the user clicks "Comment on selection". Should return
|
|
38
|
+
* the encoded anchor for the active editor selection, or `null` for a
|
|
39
|
+
* doc-level comment. Wiring lives in `TipTapEditor` so this component
|
|
40
|
+
* stays editor-agnostic.
|
|
41
|
+
*/
|
|
42
|
+
onComposeAnchor?: () => CommentAnchor | null;
|
|
43
|
+
/**
|
|
44
|
+
* Called whenever the active comment changes (focus shift / open
|
|
45
|
+
* thread). The editor uses this to scroll the highlighted range into
|
|
46
|
+
* view and paint the `actuate-comment--active` modifier.
|
|
47
|
+
*/
|
|
48
|
+
onActiveCommentChange?: (commentId: string | null) => void;
|
|
49
|
+
/** Error sink — bubble up server failures to a toast / banner. */
|
|
50
|
+
onError?: (message: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Override the comments-client API. Tests inject a fake; real callers
|
|
53
|
+
* should leave this unset.
|
|
54
|
+
*/
|
|
55
|
+
api?: CommentsApi;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Tiny adapter type that lets tests swap the comments-client module out.
|
|
60
|
+
* Mirrors the slice-4 client surface 1:1 — keeping it explicit prevents
|
|
61
|
+
* accidental drift when new endpoints are added later.
|
|
62
|
+
*/
|
|
63
|
+
export interface CommentsApi {
|
|
64
|
+
list: (documentId: string, opts?: {
|
|
65
|
+
includeResolved?: boolean;
|
|
66
|
+
}) => Promise<CommentOutcome<Comment[]>>;
|
|
67
|
+
create: (input: {
|
|
68
|
+
documentId: string;
|
|
69
|
+
body: string;
|
|
70
|
+
anchor?: CommentAnchor | null;
|
|
71
|
+
parentId?: string | null;
|
|
72
|
+
}) => Promise<CommentOutcome<Comment>>;
|
|
73
|
+
update: (id: string, body: string) => Promise<CommentOutcome<Comment>>;
|
|
74
|
+
resolve: (id: string) => Promise<CommentOutcome<Comment>>;
|
|
75
|
+
reopen: (id: string) => Promise<CommentOutcome<Comment>>;
|
|
76
|
+
remove: (id: string) => Promise<CommentOutcome<Comment>>;
|
|
77
|
+
}
|
|
78
|
+
export declare function CommentSidePanel({ documentId, currentUserId, isAdmin, onComposeAnchor, onActiveCommentChange, onError, api, className, }: CommentSidePanelProps): React.JSX.Element;
|
|
79
|
+
/**
|
|
80
|
+
* Cheap "n seconds / minutes / hours / days ago" formatter. We avoid
|
|
81
|
+
* importing a full date-fns dependency just for this view; the panel is
|
|
82
|
+
* fine being approximate.
|
|
83
|
+
*/
|
|
84
|
+
declare function formatRelative(iso: string): string;
|
|
85
|
+
export { formatRelative as _formatRelative };
|
|
86
|
+
//# sourceMappingURL=CommentSidePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentSidePanel.d.ts","sourceRoot":"","sources":["../../src/components/CommentSidePanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAEhF,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EASlB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAA;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IAC5C;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1D,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;;OAGG;IACH,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,CACJ,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,KACjC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACvC,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,UAAU,EAAE,MAAM,CAAA;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;QAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KACzB,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACtC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACtE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACzD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;IACxD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;CACzD;AAWD,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,OAAe,EACf,eAAe,EACf,qBAAqB,EACrB,OAAO,EACP,GAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA2K3C;AAwND;;;;GAIG;AACH,iBAAS,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAW3C;AAGD,OAAO,EAAE,cAAc,IAAI,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { createComment as apiCreateComment, deleteComment as apiDeleteComment, groupCommentsIntoThreads, listComments as apiListComments, reopenComment as apiReopenComment, resolveComment as apiResolveComment, updateComment as apiUpdateComment, } from '../lib/comments-client.js';
|
|
5
|
+
const defaultApi = {
|
|
6
|
+
list: (documentId, opts) => apiListComments(documentId, opts),
|
|
7
|
+
create: (input) => apiCreateComment(input),
|
|
8
|
+
update: (id, body) => apiUpdateComment(id, body),
|
|
9
|
+
resolve: (id) => apiResolveComment(id),
|
|
10
|
+
reopen: (id) => apiReopenComment(id),
|
|
11
|
+
remove: (id) => apiDeleteComment(id),
|
|
12
|
+
};
|
|
13
|
+
export function CommentSidePanel({ documentId, currentUserId, isAdmin = false, onComposeAnchor, onActiveCommentChange, onError, api = defaultApi, className, }) {
|
|
14
|
+
const [comments, setComments] = useState([]);
|
|
15
|
+
const [includeResolved, setIncludeResolved] = useState(false);
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
const [draft, setDraft] = useState('');
|
|
18
|
+
const [activeCommentId, setActiveCommentId] = useState(null);
|
|
19
|
+
const mountedRef = useRef(true);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
return () => {
|
|
22
|
+
mountedRef.current = false;
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
const setActive = useCallback((id) => {
|
|
26
|
+
setActiveCommentId(id);
|
|
27
|
+
onActiveCommentChange?.(id);
|
|
28
|
+
}, [onActiveCommentChange]);
|
|
29
|
+
const refresh = useCallback(async () => {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
const out = await api.list(documentId, { includeResolved });
|
|
32
|
+
if (!mountedRef.current)
|
|
33
|
+
return;
|
|
34
|
+
if (!out.ok) {
|
|
35
|
+
onError?.(out.error);
|
|
36
|
+
setComments([]);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
setComments(out.result);
|
|
40
|
+
}
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}, [api, documentId, includeResolved, onError]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
void refresh();
|
|
45
|
+
}, [refresh]);
|
|
46
|
+
const threads = useMemo(() => groupCommentsIntoThreads(comments), [comments]);
|
|
47
|
+
const handleCompose = useCallback(async () => {
|
|
48
|
+
const body = draft.trim();
|
|
49
|
+
if (!body)
|
|
50
|
+
return;
|
|
51
|
+
const anchor = onComposeAnchor?.() ?? null;
|
|
52
|
+
const out = await api.create({ documentId, body, anchor });
|
|
53
|
+
if (!out.ok) {
|
|
54
|
+
onError?.(out.error);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
setDraft('');
|
|
58
|
+
setActive(out.result.id);
|
|
59
|
+
await refresh();
|
|
60
|
+
}, [draft, onComposeAnchor, api, documentId, onError, setActive, refresh]);
|
|
61
|
+
const handleReply = useCallback(async (parentId, body) => {
|
|
62
|
+
const trimmed = body.trim();
|
|
63
|
+
if (!trimmed)
|
|
64
|
+
return;
|
|
65
|
+
const out = await api.create({ documentId, body: trimmed, parentId });
|
|
66
|
+
if (!out.ok) {
|
|
67
|
+
onError?.(out.error);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await refresh();
|
|
71
|
+
}, [api, documentId, onError, refresh]);
|
|
72
|
+
const handleEdit = useCallback(async (id, body) => {
|
|
73
|
+
const out = await api.update(id, body);
|
|
74
|
+
if (!out.ok) {
|
|
75
|
+
onError?.(out.error);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
await refresh();
|
|
79
|
+
return true;
|
|
80
|
+
}, [api, onError, refresh]);
|
|
81
|
+
const handleResolve = useCallback(async (id, alreadyResolved) => {
|
|
82
|
+
const out = alreadyResolved ? await api.reopen(id) : await api.resolve(id);
|
|
83
|
+
if (!out.ok) {
|
|
84
|
+
onError?.(out.error);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await refresh();
|
|
88
|
+
}, [api, onError, refresh]);
|
|
89
|
+
const handleDelete = useCallback(async (id) => {
|
|
90
|
+
const out = await api.remove(id);
|
|
91
|
+
if (!out.ok) {
|
|
92
|
+
onError?.(out.error);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (activeCommentId === id)
|
|
96
|
+
setActive(null);
|
|
97
|
+
await refresh();
|
|
98
|
+
}, [api, onError, refresh, activeCommentId, setActive]);
|
|
99
|
+
const wrapperClass = `flex h-full flex-col gap-3 border-l border-gray-200 bg-white p-3 ${className ?? ''}`;
|
|
100
|
+
return (_jsxs("aside", { className: wrapperClass, "data-testid": "comment-side-panel", children: [_jsxs("header", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-sm font-semibold text-gray-900", children: "Comments" }), _jsxs("label", { className: "flex cursor-pointer items-center gap-1 text-xs text-gray-600", children: [_jsx("input", { type: "checkbox", checked: includeResolved, onChange: (e) => setIncludeResolved(e.target.checked), "data-testid": "include-resolved-toggle" }), "Show resolved"] })] }), _jsxs("section", { className: "flex flex-col gap-1.5", children: [_jsx("textarea", { className: "w-full resize-none rounded border border-gray-300 p-2 text-sm focus:border-blue-500 focus:outline-none", rows: 3, placeholder: "Write a comment\u2026", value: draft, onChange: (e) => setDraft(e.target.value), "data-testid": "comment-composer" }), _jsx("div", { className: "flex justify-end", children: _jsx("button", { type: "button", className: "rounded bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-500 disabled:opacity-50", disabled: !draft.trim(), onClick: handleCompose, "data-testid": "comment-submit", children: "Comment" }) })] }), _jsx("section", { className: "flex-1 overflow-y-auto", "aria-busy": loading, "data-testid": "comment-list", children: loading ? (_jsx("p", { className: "text-xs text-gray-500", children: "Loading\u2026" })) : threads.length === 0 ? (_jsx("p", { className: "text-xs text-gray-500", children: "No comments yet." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: threads.map((thread) => (_jsx(CommentThreadView, { thread: thread, currentUserId: currentUserId, isAdmin: isAdmin, isActive: activeCommentId === thread.root.id, onFocus: () => setActive(thread.root.id), onReply: (body) => handleReply(thread.root.id, body), onEdit: (id, body) => handleEdit(id, body), onToggleResolve: (id, alreadyResolved) => handleResolve(id, alreadyResolved), onDelete: (id) => handleDelete(id) }, thread.root.id))) })) })] }));
|
|
101
|
+
}
|
|
102
|
+
function CommentThreadView({ thread, currentUserId, isAdmin, isActive, onFocus, onReply, onEdit, onToggleResolve, onDelete, }) {
|
|
103
|
+
const [replyDraft, setReplyDraft] = useState('');
|
|
104
|
+
const cardClass = `rounded border bg-gray-50 p-2 ${isActive ? 'border-yellow-400 ring-1 ring-yellow-300' : 'border-gray-200'}`;
|
|
105
|
+
return (_jsxs("li", { className: cardClass, onClick: onFocus, "data-testid": `comment-thread-${thread.root.id}`, "data-active": isActive ? 'true' : 'false', children: [_jsx(CommentRow, { comment: thread.root, currentUserId: currentUserId, isAdmin: isAdmin, onEdit: onEdit, onToggleResolve: () => onToggleResolve(thread.root.id, thread.root.isResolved), onDelete: () => onDelete(thread.root.id) }), thread.replies.length > 0 && (_jsx("ul", { className: "mt-2 flex flex-col gap-1 border-l-2 border-gray-200 pl-2", children: thread.replies.map((reply) => (_jsx("li", { children: _jsx(CommentRow, { comment: reply, currentUserId: currentUserId, isAdmin: isAdmin, onEdit: onEdit,
|
|
106
|
+
// Replies inherit the thread's resolve action; we only
|
|
107
|
+
// expose delete here.
|
|
108
|
+
onToggleResolve: null, onDelete: () => onDelete(reply.id) }) }, reply.id))) })), _jsxs("div", { className: "mt-2 flex gap-1", children: [_jsx("input", { className: "flex-1 rounded border border-gray-300 px-2 py-1 text-xs focus:border-blue-500 focus:outline-none", placeholder: "Reply\u2026", value: replyDraft, onChange: (e) => setReplyDraft(e.target.value), "data-testid": `reply-input-${thread.root.id}`, onClick: (e) => e.stopPropagation() }), _jsx("button", { type: "button", className: "rounded bg-gray-700 px-2 py-1 text-xs font-medium text-white hover:bg-gray-600 disabled:opacity-50", disabled: !replyDraft.trim(), onClick: (e) => {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
onReply(replyDraft);
|
|
111
|
+
setReplyDraft('');
|
|
112
|
+
}, "data-testid": `reply-submit-${thread.root.id}`, children: "Reply" })] })] }));
|
|
113
|
+
}
|
|
114
|
+
function CommentRow({ comment, currentUserId, isAdmin, onEdit, onToggleResolve, onDelete, }) {
|
|
115
|
+
const [editing, setEditing] = useState(false);
|
|
116
|
+
const [draft, setDraft] = useState(comment.body);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
setDraft(comment.body);
|
|
119
|
+
}, [comment.body]);
|
|
120
|
+
const canMutate = isAdmin || comment.userId === currentUserId;
|
|
121
|
+
const meta = (_jsxs("div", { className: "text-xs text-gray-500", children: [comment.userId ?? 'deleted user', " \u00B7 ", formatRelative(comment.createdAt), comment.isResolved ? ' · resolved' : null] }));
|
|
122
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [meta, editing ? (_jsxs(_Fragment, { children: [_jsx("textarea", { className: "w-full resize-none rounded border border-gray-300 p-1 text-sm", rows: 3, value: draft, onChange: (e) => setDraft(e.target.value), "data-testid": `edit-textarea-${comment.id}`, onClick: (e) => e.stopPropagation() }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { type: "button", className: "rounded bg-blue-600 px-2 py-1 text-xs font-medium text-white", onClick: async (e) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
const ok = await onEdit(comment.id, draft);
|
|
125
|
+
if (ok)
|
|
126
|
+
setEditing(false);
|
|
127
|
+
}, "data-testid": `edit-save-${comment.id}`, children: "Save" }), _jsx("button", { type: "button", className: "rounded border border-gray-300 px-2 py-1 text-xs", onClick: (e) => {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
setDraft(comment.body);
|
|
130
|
+
setEditing(false);
|
|
131
|
+
}, "data-testid": `edit-cancel-${comment.id}`, children: "Cancel" })] })] })) : (_jsx("p", { className: "text-sm whitespace-pre-wrap text-gray-900", "data-testid": `comment-body-${comment.id}`, children: comment.body })), !editing && (_jsxs("div", { className: "flex flex-wrap gap-1 text-xs", children: [canMutate && (_jsx("button", { type: "button", className: "text-gray-600 hover:text-gray-900", onClick: (e) => {
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
setEditing(true);
|
|
134
|
+
}, "data-testid": `edit-${comment.id}`, children: "Edit" })), onToggleResolve && (_jsx("button", { type: "button", className: "text-gray-600 hover:text-gray-900", onClick: (e) => {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
onToggleResolve();
|
|
137
|
+
}, "data-testid": `resolve-${comment.id}`, children: comment.isResolved ? 'Reopen' : 'Resolve' })), canMutate && (_jsx("button", { type: "button", className: "text-red-600 hover:text-red-700", onClick: (e) => {
|
|
138
|
+
e.stopPropagation();
|
|
139
|
+
onDelete();
|
|
140
|
+
}, "data-testid": `delete-${comment.id}`, children: "Delete" }))] }))] }));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Cheap "n seconds / minutes / hours / days ago" formatter. We avoid
|
|
144
|
+
* importing a full date-fns dependency just for this view; the panel is
|
|
145
|
+
* fine being approximate.
|
|
146
|
+
*/
|
|
147
|
+
function formatRelative(iso) {
|
|
148
|
+
const t = Date.parse(iso);
|
|
149
|
+
if (Number.isNaN(t))
|
|
150
|
+
return iso;
|
|
151
|
+
const deltaSec = Math.max(0, Math.round((Date.now() - t) / 1000));
|
|
152
|
+
if (deltaSec < 60)
|
|
153
|
+
return `${deltaSec}s ago`;
|
|
154
|
+
const m = Math.round(deltaSec / 60);
|
|
155
|
+
if (m < 60)
|
|
156
|
+
return `${m}m ago`;
|
|
157
|
+
const h = Math.round(m / 60);
|
|
158
|
+
if (h < 24)
|
|
159
|
+
return `${h}h ago`;
|
|
160
|
+
const d = Math.round(h / 24);
|
|
161
|
+
return `${d}d ago`;
|
|
162
|
+
}
|
|
163
|
+
// Re-export for tests that build their own CommentsApi.
|
|
164
|
+
export { formatRelative as _formatRelative };
|
|
165
|
+
//# sourceMappingURL=CommentSidePanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentSidePanel.js","sourceRoot":"","sources":["../../src/components/CommentSidePanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivePreview.d.ts","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LivePreview.d.ts","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"AAeA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AA4BD,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,UAAU,EACV,UAAU,EACV,MAAM,EACN,OAAO,GACR,EAAE,gBAAgB,2CAqNlB"}
|
|
@@ -1,47 +1,105 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
-
import { Monitor, Tablet, Smartphone, RefreshCw, ExternalLink, X } from 'lucide-react';
|
|
4
|
+
import { Monitor, Tablet, Smartphone, RefreshCw, ExternalLink, X, Wifi, WifiOff, } from 'lucide-react';
|
|
5
5
|
import { cmsApi } from '../lib/api.js';
|
|
6
6
|
const VIEWPORT_WIDTHS = {
|
|
7
7
|
desktop: '100%',
|
|
8
8
|
tablet: '768px',
|
|
9
9
|
mobile: '375px',
|
|
10
10
|
};
|
|
11
|
+
const PREVIEW_STREAM_PATH = '/api/cms/preview/stream';
|
|
12
|
+
function postToIframe(iframe, src, data) {
|
|
13
|
+
if (!iframe?.contentWindow || !src)
|
|
14
|
+
return;
|
|
15
|
+
let targetOrigin;
|
|
16
|
+
try {
|
|
17
|
+
targetOrigin = new URL(src).origin;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
targetOrigin = window.location.origin;
|
|
21
|
+
}
|
|
22
|
+
iframe.contentWindow.postMessage({ type: 'actuate-preview-update', data }, targetOrigin);
|
|
23
|
+
}
|
|
11
24
|
export function LivePreview({ collection, documentId, previewUrl, values, onClose, }) {
|
|
12
25
|
const iframeRef = useRef(null);
|
|
13
26
|
const [viewport, setViewport] = useState('desktop');
|
|
14
27
|
const [loading, setLoading] = useState(true);
|
|
15
28
|
const [previewSrc, setPreviewSrc] = useState(null);
|
|
29
|
+
const [previewToken, setPreviewToken] = useState(null);
|
|
30
|
+
const [streamStatus, setStreamStatus] = useState('idle');
|
|
16
31
|
useEffect(() => {
|
|
17
32
|
if (!documentId || !previewUrl) {
|
|
18
33
|
setPreviewSrc(null);
|
|
34
|
+
setPreviewToken(null);
|
|
19
35
|
return;
|
|
20
36
|
}
|
|
37
|
+
let cancelled = false;
|
|
21
38
|
async function fetchToken() {
|
|
22
39
|
const res = await cmsApi('/preview/token', {
|
|
23
40
|
method: 'POST',
|
|
24
41
|
body: JSON.stringify({ collection, documentId }),
|
|
25
42
|
});
|
|
43
|
+
if (cancelled)
|
|
44
|
+
return;
|
|
26
45
|
if (res.data?.token) {
|
|
27
46
|
const sep = previewUrl.includes('?') ? '&' : '?';
|
|
47
|
+
setPreviewToken(res.data.token);
|
|
28
48
|
setPreviewSrc(`${previewUrl}${sep}preview=true#token=${res.data.token}`);
|
|
29
49
|
}
|
|
30
50
|
}
|
|
31
51
|
fetchToken();
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled = true;
|
|
54
|
+
};
|
|
32
55
|
}, [collection, documentId, previewUrl]);
|
|
33
56
|
useEffect(() => {
|
|
34
|
-
|
|
57
|
+
postToIframe(iframeRef.current, previewSrc, values);
|
|
58
|
+
}, [values, previewSrc]);
|
|
59
|
+
/**
|
|
60
|
+
* SSE pipe — when the server pushes a draft update for the same
|
|
61
|
+
* `{collection, documentId}`, forward the new `values` straight to the
|
|
62
|
+
* iframe via `postMessage`. The bus key matches the token's payload, so
|
|
63
|
+
* a second admin session updating the same draft (e.g. an AI agent)
|
|
64
|
+
* appears live in this preview without polling.
|
|
65
|
+
*/
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (typeof window === 'undefined' || typeof EventSource === 'undefined')
|
|
68
|
+
return;
|
|
69
|
+
if (!documentId || !previewToken) {
|
|
70
|
+
setStreamStatus('idle');
|
|
35
71
|
return;
|
|
36
|
-
let targetOrigin;
|
|
37
|
-
try {
|
|
38
|
-
targetOrigin = new URL(previewSrc).origin;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
targetOrigin = window.location.origin;
|
|
42
72
|
}
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
setStreamStatus('connecting');
|
|
74
|
+
const source = new EventSource(`${PREVIEW_STREAM_PATH}?token=${encodeURIComponent(previewToken)}`, { withCredentials: true });
|
|
75
|
+
const onReady = () => setStreamStatus('connected');
|
|
76
|
+
const onUpdate = (event) => {
|
|
77
|
+
try {
|
|
78
|
+
const payload = JSON.parse(event.data);
|
|
79
|
+
if (payload.values && typeof payload.values === 'object') {
|
|
80
|
+
postToIframe(iframeRef.current, previewSrc, payload.values);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Ignore malformed events — the SSE wire format guarantees JSON,
|
|
85
|
+
// but a future schema bump shouldn't break this consumer.
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const onError = () => setStreamStatus('offline');
|
|
89
|
+
const onOpen = () => setStreamStatus((s) => (s === 'offline' ? 'connecting' : s));
|
|
90
|
+
source.addEventListener('ready', onReady);
|
|
91
|
+
source.addEventListener('update', onUpdate);
|
|
92
|
+
source.addEventListener('error', onError);
|
|
93
|
+
source.addEventListener('open', onOpen);
|
|
94
|
+
return () => {
|
|
95
|
+
source.removeEventListener('ready', onReady);
|
|
96
|
+
source.removeEventListener('update', onUpdate);
|
|
97
|
+
source.removeEventListener('error', onError);
|
|
98
|
+
source.removeEventListener('open', onOpen);
|
|
99
|
+
source.close();
|
|
100
|
+
setStreamStatus('idle');
|
|
101
|
+
};
|
|
102
|
+
}, [documentId, previewToken, previewSrc]);
|
|
45
103
|
const handleRefresh = useCallback(() => {
|
|
46
104
|
if (iframeRef.current) {
|
|
47
105
|
setLoading(true);
|
|
@@ -57,10 +115,16 @@ export function LivePreview({ collection, documentId, previewUrl, values, onClos
|
|
|
57
115
|
? 'Save the document first to enable preview'
|
|
58
116
|
: 'Configure a preview URL in the collection settings to enable live preview' }), _jsx("button", { onClick: onClose, className: "mt-4 text-xs text-gray-400 hover:text-gray-600", children: "Close Preview" })] }));
|
|
59
117
|
}
|
|
60
|
-
return (_jsxs("div", { className: "flex h-full flex-col border-l border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50 px-3 py-2", children: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
118
|
+
return (_jsxs("div", { className: "flex h-full flex-col border-l border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [[
|
|
119
|
+
['desktop', Monitor],
|
|
120
|
+
['tablet', Tablet],
|
|
121
|
+
['mobile', Smartphone],
|
|
122
|
+
].map(([vp, Icon]) => (_jsx("button", { onClick: () => setViewport(vp), className: `rounded p-1.5 ${viewport === vp ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-400 hover:text-gray-600'}`, title: vp, children: _jsx(Icon, { className: "h-4 w-4" }) }, vp))), _jsx("span", { className: "ml-2 inline-flex items-center gap-1 rounded-full bg-white px-2 py-0.5 text-[10px] font-medium text-gray-600", title: streamStatus === 'connected'
|
|
123
|
+
? 'Live preview is streaming updates from the server.'
|
|
124
|
+
: streamStatus === 'connecting'
|
|
125
|
+
? 'Connecting to the live preview stream…'
|
|
126
|
+
: streamStatus === 'offline'
|
|
127
|
+
? 'Live stream disconnected. Updates from this admin still work; cross-session updates will reconnect automatically.'
|
|
128
|
+
: 'Live preview stream idle.', children: streamStatus === 'connected' ? (_jsxs(_Fragment, { children: [_jsx(Wifi, { className: "h-3 w-3 text-green-600", "aria-hidden": true }), " Live"] })) : streamStatus === 'connecting' ? (_jsxs(_Fragment, { children: [_jsx(Wifi, { className: "h-3 w-3 animate-pulse text-blue-500", "aria-hidden": true }), " Connecting"] })) : streamStatus === 'offline' ? (_jsxs(_Fragment, { children: [_jsx(WifiOff, { className: "h-3 w-3 text-amber-500", "aria-hidden": true }), " Reconnecting"] })) : (_jsxs(_Fragment, { children: [_jsx(WifiOff, { className: "h-3 w-3 text-gray-400", "aria-hidden": true }), " Idle"] })) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { onClick: handleRefresh, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Refresh", children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), _jsx("button", { onClick: handleOpenExternal, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Open in new tab", children: _jsx(ExternalLink, { className: "h-4 w-4" }) }), _jsx("button", { onClick: onClose, className: "rounded p-1.5 text-gray-400 hover:text-gray-600", title: "Close", children: _jsx(X, { className: "h-4 w-4" }) })] })] }), _jsx("div", { className: "flex flex-1 justify-center overflow-auto bg-gray-100 p-4", children: _jsxs("div", { style: { width: VIEWPORT_WIDTHS[viewport], maxWidth: '100%', transition: 'width 0.3s' }, className: "relative", children: [loading && (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-white/80", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600" }) })), _jsx("iframe", { ref: iframeRef, src: previewSrc, className: "h-full w-full rounded-lg border border-gray-200 bg-white shadow-lg", style: { minHeight: '600px' }, onLoad: () => setLoading(false), title: "Page Preview" })] }) })] }));
|
|
65
129
|
}
|
|
66
130
|
//# sourceMappingURL=LivePreview.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivePreview.js","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,
|
|
1
|
+
{"version":3,"file":"LivePreview.js","sourceRoot":"","sources":["../../src/components/LivePreview.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Notification, type NotificationOutcome } from '../lib/notifications-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Top-bar notification bell + dropdown panel. Slice 6 of Phase 3
|
|
5
|
+
* surfaces every collaboration event that affects the current user —
|
|
6
|
+
* comment replies, resolves, and mentions — without forcing the
|
|
7
|
+
* editor to round-trip through the comments side panel.
|
|
8
|
+
*
|
|
9
|
+
* Design choices:
|
|
10
|
+
*
|
|
11
|
+
* - **Polling, not SSE.** Phase 3 already ships an SSE stream for live
|
|
12
|
+
* previews (Phase 2 / `/preview/stream`), but extending it to
|
|
13
|
+
* per-user notifications adds a non-trivial fan-out path and a fresh
|
|
14
|
+
* security surface (privacy-leak risk on misconfigured streams). The
|
|
15
|
+
* bell polls every `pollIntervalMs` (default 30s) and on dropdown
|
|
16
|
+
* open. A push-based extension is queued as a Phase 4 follow-up.
|
|
17
|
+
* - **Optimistic read state.** When the user clicks a notification we
|
|
18
|
+
* mark it read locally before the server confirms — so the bell
|
|
19
|
+
* counter feels instant. Server failures roll the state back via
|
|
20
|
+
* the `onError` toast.
|
|
21
|
+
* - **API injection.** The dependency-injection seam matches the rest
|
|
22
|
+
* of the admin (`CommentSidePanel.api`) so unit tests can substitute
|
|
23
|
+
* in-memory fakes.
|
|
24
|
+
*/
|
|
25
|
+
export interface NotificationsApi {
|
|
26
|
+
list: (opts: {
|
|
27
|
+
includeRead?: boolean;
|
|
28
|
+
}) => Promise<NotificationOutcome<Notification[]>>;
|
|
29
|
+
unread: () => Promise<NotificationOutcome<number>>;
|
|
30
|
+
markRead: (id: string) => Promise<NotificationOutcome<Notification>>;
|
|
31
|
+
markAllRead: () => Promise<NotificationOutcome<number>>;
|
|
32
|
+
}
|
|
33
|
+
export interface NotificationBellProps {
|
|
34
|
+
/** Polling cadence in milliseconds. Default 30,000. Pass 0 to disable. */
|
|
35
|
+
pollIntervalMs?: number;
|
|
36
|
+
/** Optional click hook — called with the notification when a row is clicked. */
|
|
37
|
+
onSelect?: (notification: Notification) => void;
|
|
38
|
+
/** Error sink — bubble server failures to a toast / banner. */
|
|
39
|
+
onError?: (message: string) => void;
|
|
40
|
+
/** Override the API client (tests). */
|
|
41
|
+
api?: NotificationsApi;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function NotificationBell({ pollIntervalMs, onSelect, onError, api, className, }: NotificationBellProps): React.JSX.Element;
|
|
45
|
+
//# sourceMappingURL=NotificationBell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationBell.d.ts","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAMzB,MAAM,gCAAgC,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IACvF,MAAM,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;IAClD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAA;IACpE,WAAW,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAA;CACxD;AASD,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,uCAAuC;IACvC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,cAAuB,EACvB,QAAQ,EACR,OAAO,EACP,GAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA0J3C"}
|