@actuate-media/cms-admin 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) 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 +346 -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 +283 -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 +66 -0
  74. package/dist/components/NotificationBell.d.ts.map +1 -0
  75. package/dist/components/NotificationBell.js +142 -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/api.d.ts +8 -0
  99. package/dist/lib/api.d.ts.map +1 -1
  100. package/dist/lib/api.js +10 -0
  101. package/dist/lib/api.js.map +1 -1
  102. package/dist/lib/coauthor-client.d.ts +72 -0
  103. package/dist/lib/coauthor-client.d.ts.map +1 -0
  104. package/dist/lib/coauthor-client.js +98 -0
  105. package/dist/lib/coauthor-client.js.map +1 -0
  106. package/dist/lib/collaboration-provider.d.ts +142 -0
  107. package/dist/lib/collaboration-provider.d.ts.map +1 -0
  108. package/dist/lib/collaboration-provider.js +168 -0
  109. package/dist/lib/collaboration-provider.js.map +1 -0
  110. package/dist/lib/comment-anchor.d.ts +86 -0
  111. package/dist/lib/comment-anchor.d.ts.map +1 -0
  112. package/dist/lib/comment-anchor.js +139 -0
  113. package/dist/lib/comment-anchor.js.map +1 -0
  114. package/dist/lib/comments-client.d.ts +106 -0
  115. package/dist/lib/comments-client.d.ts.map +1 -0
  116. package/dist/lib/comments-client.js +138 -0
  117. package/dist/lib/comments-client.js.map +1 -0
  118. package/dist/lib/notifications-client.d.ts +121 -0
  119. package/dist/lib/notifications-client.d.ts.map +1 -0
  120. package/dist/lib/notifications-client.js +173 -0
  121. package/dist/lib/notifications-client.js.map +1 -0
  122. package/dist/lib/slash-command.d.ts +39 -0
  123. package/dist/lib/slash-command.d.ts.map +1 -0
  124. package/dist/lib/slash-command.js +50 -0
  125. package/dist/lib/slash-command.js.map +1 -0
  126. package/package.json +12 -3
  127. package/src/__tests__/components/comment-mark.test.ts +99 -0
  128. package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
  129. package/src/__tests__/components/notification-bell.test.tsx +379 -0
  130. package/src/__tests__/components/offline-status.test.tsx +66 -0
  131. package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
  132. package/src/__tests__/lib/active-editor.test.ts +70 -0
  133. package/src/__tests__/lib/coauthor-client.test.ts +213 -0
  134. package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
  135. package/src/__tests__/lib/comment-anchor.test.ts +188 -0
  136. package/src/__tests__/lib/comments-client.test.ts +239 -0
  137. package/src/__tests__/lib/notifications-client.test.ts +339 -0
  138. package/src/__tests__/lib/slash-command.test.ts +146 -0
  139. package/src/components/AICoauthorBubbleMenu.tsx +187 -0
  140. package/src/components/CoauthorResultPopover.tsx +193 -0
  141. package/src/components/CommandPalette.tsx +397 -209
  142. package/src/components/CommentMark.ts +138 -0
  143. package/src/components/CommentSidePanel.tsx +521 -0
  144. package/src/components/LivePreview.tsx +117 -11
  145. package/src/components/NotificationBell.tsx +314 -0
  146. package/src/components/OfflineStatus.tsx +90 -0
  147. package/src/components/PresenceChips.tsx +150 -0
  148. package/src/components/TipTapEditor.tsx +310 -12
  149. package/src/index.ts +20 -0
  150. package/src/lib/active-editor.ts +39 -0
  151. package/src/lib/api.ts +11 -0
  152. package/src/lib/coauthor-client.ts +160 -0
  153. package/src/lib/collaboration-provider.ts +306 -0
  154. package/src/lib/comment-anchor.ts +179 -0
  155. package/src/lib/comments-client.ts +208 -0
  156. package/src/lib/notifications-client.ts +292 -0
  157. package/src/lib/slash-command.ts +67 -0
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { listNotifications as defaultListNotifications, markAllNotificationsRead as defaultMarkAllRead, markNotificationRead as defaultMarkRead, notificationSummary, subscribeNotificationStream as defaultSubscribeStream, unreadCount as defaultUnreadCount, } from '../lib/notifications-client.js';
5
+ const defaultApi = {
6
+ list: (opts) => defaultListNotifications(opts),
7
+ unread: () => defaultUnreadCount(),
8
+ markRead: (id) => defaultMarkRead(id),
9
+ markAllRead: () => defaultMarkAllRead(),
10
+ subscribe: (opts) => defaultSubscribeStream(opts),
11
+ };
12
+ export function NotificationBell({ pollIntervalMs = 60_000, onSelect, onError, api = defaultApi, className, }) {
13
+ const [open, setOpen] = useState(false);
14
+ const [unread, setUnread] = useState(0);
15
+ const [items, setItems] = useState([]);
16
+ const [loading, setLoading] = useState(false);
17
+ const mountedRef = useRef(true);
18
+ // Set of notification IDs we've already rendered. The SSE handler
19
+ // consults this synchronously so duplicate deliveries (REST refresh
20
+ // + SSE push, or two SSE retries) don't double-bump the badge.
21
+ // Kept in sync with `items` via an effect — the items state is the
22
+ // source of truth; this ref is a derived O(1) lookup index.
23
+ const seenIdsRef = useRef(new Set());
24
+ useEffect(() => {
25
+ seenIdsRef.current = new Set(items.map((n) => n.id));
26
+ }, [items]);
27
+ useEffect(() => {
28
+ return () => {
29
+ mountedRef.current = false;
30
+ };
31
+ }, []);
32
+ const refreshUnread = useCallback(async () => {
33
+ const out = await api.unread();
34
+ if (!mountedRef.current)
35
+ return;
36
+ if (!out.ok) {
37
+ onError?.(out.error);
38
+ return;
39
+ }
40
+ setUnread(out.result);
41
+ }, [api, onError]);
42
+ const refreshList = useCallback(async (includeRead = false) => {
43
+ setLoading(true);
44
+ const out = await api.list({ includeRead });
45
+ if (!mountedRef.current)
46
+ return;
47
+ if (!out.ok) {
48
+ onError?.(out.error);
49
+ setItems([]);
50
+ }
51
+ else {
52
+ setItems(out.result);
53
+ }
54
+ setLoading(false);
55
+ }, [api, onError]);
56
+ useEffect(() => {
57
+ void refreshUnread();
58
+ if (!pollIntervalMs)
59
+ return;
60
+ const id = setInterval(() => void refreshUnread(), pollIntervalMs);
61
+ return () => clearInterval(id);
62
+ }, [refreshUnread, pollIntervalMs]);
63
+ // SSE push subscription. Bumps the badge + prepends the new row to
64
+ // the open dropdown the instant the server publishes. Falls back
65
+ // silently when the api/runtime doesn't support EventSource — the
66
+ // poll loop above keeps the bell correct in that case.
67
+ useEffect(() => {
68
+ if (!api.subscribe)
69
+ return;
70
+ const handle = api.subscribe({
71
+ onNotification: (n) => {
72
+ if (!mountedRef.current)
73
+ return;
74
+ // Defensive: SSE events can race with REST refreshes (or with
75
+ // their own retries). The seen-id ref is updated
76
+ // synchronously so a second delivery of the same row is a
77
+ // no-op for BOTH `items` and the unread badge — guarding only
78
+ // setItems would still let the counter drift.
79
+ if (seenIdsRef.current.has(n.id))
80
+ return;
81
+ seenIdsRef.current.add(n.id);
82
+ setItems((arr) => [n, ...arr]);
83
+ if (!n.readAt)
84
+ setUnread((v) => v + 1);
85
+ },
86
+ onError: () => {
87
+ // Don't surface every transient SSE disconnect as a UI error —
88
+ // the browser auto-reconnects, and the poll loop keeps state
89
+ // converging. We deliberately stay quiet here.
90
+ },
91
+ });
92
+ return () => handle.close();
93
+ }, [api]);
94
+ useEffect(() => {
95
+ if (!open)
96
+ return;
97
+ void refreshList(false);
98
+ }, [open, refreshList]);
99
+ const handleClickRow = useCallback(async (n) => {
100
+ // Optimistic update: drop the unread count + tag the row read.
101
+ if (!n.readAt) {
102
+ setUnread((v) => Math.max(0, v - 1));
103
+ setItems((arr) => arr.map((row) => (row.id === n.id ? { ...row, readAt: new Date().toISOString() } : row)));
104
+ const out = await api.markRead(n.id);
105
+ if (!out.ok) {
106
+ onError?.(out.error);
107
+ // Roll back on failure.
108
+ setUnread((v) => v + 1);
109
+ setItems((arr) => arr.map((row) => (row.id === n.id ? { ...row, readAt: null } : row)));
110
+ }
111
+ }
112
+ onSelect?.(n);
113
+ }, [api, onError, onSelect]);
114
+ const handleMarkAll = useCallback(async () => {
115
+ // Snapshot ONLY the previously-unread IDs (and their original
116
+ // readAt values, which are null by definition). On rollback we
117
+ // restore those specific rows and leave anything that arrived
118
+ // via SSE while the request was in flight alone — otherwise a
119
+ // failed mark-all-read would drop fresh notifications.
120
+ const previouslyUnreadIds = items.filter((n) => n.readAt === null).map((n) => n.id);
121
+ if (previouslyUnreadIds.length === 0)
122
+ return;
123
+ const ids = new Set(previouslyUnreadIds);
124
+ const now = new Date().toISOString();
125
+ setItems((arr) => arr.map((n) => (ids.has(n.id) ? { ...n, readAt: n.readAt ?? now } : n)));
126
+ setUnread(0);
127
+ const out = await api.markAllRead();
128
+ if (!out.ok) {
129
+ onError?.(out.error);
130
+ // Functional rollback: only revert the rows we optimistically
131
+ // marked. Anything mid-flight (SSE arrivals, locally-added) is
132
+ // untouched.
133
+ setItems((arr) => arr.map((n) => (ids.has(n.id) ? { ...n, readAt: null } : n)));
134
+ await refreshUnread();
135
+ }
136
+ }, [api, items, onError, refreshUnread]);
137
+ return (_jsxs("div", { className: `relative ${className ?? ''}`, "data-testid": "notification-bell", children: [_jsxs("button", { type: "button", className: "relative rounded p-1.5 text-gray-600 hover:bg-gray-100 hover:text-gray-900", "aria-label": "Notifications", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen((v) => !v), "data-testid": "bell-button", children: [_jsx(BellIcon, {}), unread > 0 && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-red-600 px-1 text-[10px] font-semibold text-white", "data-testid": "bell-badge", children: unread > 99 ? '99+' : unread }))] }), open && (_jsxs("div", { role: "menu", className: "absolute right-0 z-50 mt-1 w-80 rounded border border-gray-200 bg-white shadow-lg", "data-testid": "bell-dropdown", children: [_jsxs("header", { className: "flex items-center justify-between border-b border-gray-100 px-3 py-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Notifications" }), _jsx("button", { type: "button", className: "text-xs text-gray-600 hover:text-gray-900 disabled:opacity-50", onClick: handleMarkAll, disabled: unread === 0, "data-testid": "bell-mark-all", children: "Mark all read" })] }), _jsx("div", { className: "max-h-80 overflow-y-auto", "aria-busy": loading, "data-testid": "bell-list", children: loading ? (_jsx("p", { className: "px-3 py-2 text-xs text-gray-500", children: "Loading\u2026" })) : items.length === 0 ? (_jsx("p", { className: "px-3 py-2 text-xs text-gray-500", children: "All caught up." })) : (_jsx("ul", { children: items.map((n) => (_jsx("li", { children: _jsxs("button", { type: "button", role: "menuitem", className: `block w-full px-3 py-2 text-left text-xs hover:bg-gray-50 ${n.readAt ? 'text-gray-500' : 'font-medium text-gray-900'}`, onClick: () => handleClickRow(n), "data-testid": `bell-row-${n.id}`, "data-read": n.readAt ? 'true' : 'false', children: [_jsx("div", { children: notificationSummary(n) }), _jsx("div", { className: "mt-0.5 text-[10px] text-gray-400", children: n.createdAt })] }) }, n.id))) })) })] }))] }));
138
+ }
139
+ function BellIcon() {
140
+ return (_jsxs("svg", { "aria-hidden": "true", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), _jsx("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })] }));
141
+ }
142
+ //# sourceMappingURL=NotificationBell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationBell.js","sourceRoot":"","sources":["../../src/components/NotificationBell.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import type { CollaborationStatus, OfflinePersistenceStatus } from '../lib/collaboration-provider.js';
3
+ /**
4
+ * Compact connection + offline-draft indicator. Rendered alongside the
5
+ * presence chips above the editor toolbar so editors get a single,
6
+ * always-visible signal:
7
+ *
8
+ * - **Saved & synced** — IndexedDB store is ready and the gateway
9
+ * socket is connected.
10
+ * - **Saved locally** — IndexedDB ready but the socket is disconnected
11
+ * (offline drafts in flight; will sync on reconnect).
12
+ * - **Syncing local copy…** — IndexedDB is replaying the last snapshot
13
+ * into the doc. The editor is still usable but undo / cursor sync
14
+ * may flicker briefly.
15
+ * - **Local drafts unsupported** — `indexedDB` is unavailable; the
16
+ * editor will rely on the gateway only. Refresh loses uncommitted
17
+ * edits.
18
+ * - **Local drafts failed** — IndexedDB threw on init (quota, private
19
+ * browsing). Same risk as "unsupported", but with a different cause.
20
+ *
21
+ * Keeping the component dumb and prop-driven keeps the unit tests
22
+ * trivial — the parent owns the status transitions.
23
+ */
24
+ export interface OfflineStatusProps {
25
+ connection: CollaborationStatus;
26
+ offline: OfflinePersistenceStatus;
27
+ }
28
+ export declare function OfflineStatus({ connection, offline }: OfflineStatusProps): React.JSX.Element;
29
+ export type OfflineTone = 'good' | 'warn' | 'info' | 'bad';
30
+ /**
31
+ * Pure helper that maps the (connection, offline) status pair to a
32
+ * single user-facing label + colour tone. Exported so the unit tests
33
+ * can exercise every combination without rendering React.
34
+ */
35
+ export declare function describeOfflineStatus(connection: CollaborationStatus, offline: OfflinePersistenceStatus): {
36
+ label: string;
37
+ tone: OfflineTone;
38
+ };
39
+ //# sourceMappingURL=OfflineStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineStatus.d.ts","sourceRoot":"","sources":["../../src/components/OfflineStatus.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,kCAAkC,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,mBAAmB,CAAA;IAC/B,OAAO,EAAE,wBAAwB,CAAA;CAClC;AAED,wBAAgB,aAAa,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAa5F;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAgB1D;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,mBAAmB,EAC/B,OAAO,EAAE,wBAAwB,GAChC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAetC"}
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function OfflineStatus({ connection, offline }) {
4
+ const { label, tone } = describeOfflineStatus(connection, offline);
5
+ const baseClass = 'inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-medium';
6
+ const toneClass = TONE_CLASSES[tone];
7
+ return (_jsxs("span", { className: `${baseClass} ${toneClass}`, "data-testid": "offline-status", "data-tone": tone, children: [_jsx("span", { "aria-hidden": "true", className: `inline-block h-1.5 w-1.5 rounded-full ${DOT_CLASSES[tone]}` }), label] }));
8
+ }
9
+ const TONE_CLASSES = {
10
+ good: 'bg-emerald-50 text-emerald-700',
11
+ warn: 'bg-amber-50 text-amber-800',
12
+ info: 'bg-blue-50 text-blue-700',
13
+ bad: 'bg-red-50 text-red-700',
14
+ };
15
+ const DOT_CLASSES = {
16
+ good: 'bg-emerald-500',
17
+ warn: 'bg-amber-500',
18
+ info: 'bg-blue-500',
19
+ bad: 'bg-red-500',
20
+ };
21
+ /**
22
+ * Pure helper that maps the (connection, offline) status pair to a
23
+ * single user-facing label + colour tone. Exported so the unit tests
24
+ * can exercise every combination without rendering React.
25
+ */
26
+ export function describeOfflineStatus(connection, offline) {
27
+ if (offline === 'error')
28
+ return { label: 'Local drafts failed', tone: 'bad' };
29
+ if (offline === 'unsupported') {
30
+ // No local persistence; the WebSocket is the only safety net.
31
+ if (connection === 'connected')
32
+ return { label: 'Synced', tone: 'good' };
33
+ if (connection === 'connecting')
34
+ return { label: 'Connecting…', tone: 'info' };
35
+ return { label: 'Offline — drafts not persisted', tone: 'warn' };
36
+ }
37
+ if (offline === 'loading' || offline === 'pending') {
38
+ return { label: 'Restoring local copy…', tone: 'info' };
39
+ }
40
+ // offline === 'ready' from here on.
41
+ if (connection === 'connected')
42
+ return { label: 'Saved & synced', tone: 'good' };
43
+ if (connection === 'connecting')
44
+ return { label: 'Saved locally — reconnecting…', tone: 'warn' };
45
+ return { label: 'Saved locally — offline', tone: 'warn' };
46
+ }
47
+ //# sourceMappingURL=OfflineStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineStatus.js","sourceRoot":"","sources":["../../src/components/OfflineStatus.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAmCZ,MAAM,UAAU,aAAa,CAAC,EAAE,UAAU,EAAE,OAAO,EAAsB;IACvE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IAClE,MAAM,SAAS,GAAG,8EAA8E,CAAA;IAChG,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACpC,OAAO,CACL,gBAAM,SAAS,EAAE,GAAG,SAAS,IAAI,SAAS,EAAE,iBAAc,gBAAgB,eAAY,IAAI,aACxF,8BACc,MAAM,EAClB,SAAS,EAAE,yCAAyC,WAAW,CAAC,IAAI,CAAC,EAAE,GACvE,EACD,KAAK,IACD,CACR,CAAA;AACH,CAAC;AAID,MAAM,YAAY,GAAgC;IAChD,IAAI,EAAE,gCAAgC;IACtC,IAAI,EAAE,4BAA4B;IAClC,IAAI,EAAE,0BAA0B;IAChC,GAAG,EAAE,wBAAwB;CAC9B,CAAA;AAED,MAAM,WAAW,GAAgC;IAC/C,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,aAAa;IACnB,GAAG,EAAE,YAAY;CAClB,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAA+B,EAC/B,OAAiC;IAEjC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAC7E,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;QAC9B,8DAA8D;QAC9D,IAAI,UAAU,KAAK,WAAW;YAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QACxE,IAAI,UAAU,KAAK,YAAY;YAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QAC9E,OAAO,EAAE,KAAK,EAAE,gCAAgC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAClE,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACnD,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzD,CAAC;IACD,oCAAoC;IACpC,IAAI,UAAU,KAAK,WAAW;QAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAChF,IAAI,UAAU,KAAK,YAAY;QAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAChG,OAAO,EAAE,KAAK,EAAE,yBAAyB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAC3D,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { Awareness } from 'y-protocols/awareness';
2
+ import { type CollaborationStatus } from '../lib/collaboration-provider.js';
3
+ export interface PresenceChipsProps {
4
+ /** Awareness instance returned from `createCollaborationProvider`. */
5
+ awareness: Awareness;
6
+ /**
7
+ * Optional connection status. When provided the strip renders a small
8
+ * status pill (Connected / Connecting / Disconnected) on the left side.
9
+ */
10
+ status?: CollaborationStatus;
11
+ /**
12
+ * Hide the local user from the strip. Defaults to `true` — the local user
13
+ * already knows they're editing.
14
+ */
15
+ hideSelf?: boolean;
16
+ /** Max chips to show before collapsing to "+N". Defaults to 5. */
17
+ maxVisible?: number;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Small avatar / initials strip showing who else is currently editing the
22
+ * document. Subscribes to the supplied `Awareness` instance for live
23
+ * updates.
24
+ *
25
+ * The component is intentionally stateless about transport — it only reads
26
+ * from Awareness and `status`. Wiring is the caller's job.
27
+ */
28
+ export declare function PresenceChips({ awareness, status, hideSelf, maxVisible, className, }: PresenceChipsProps): import("react/jsx-runtime").JSX.Element;
29
+ /** Convert "Alice Cooper" → "AC", single names → "A", empty → "?". */
30
+ export declare function nameToInitials(name: string): string;
31
+ //# sourceMappingURL=PresenceChips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceChips.d.ts","sourceRoot":"","sources":["../../src/components/PresenceChips.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,SAAS,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,MAAM,EACN,QAAe,EACf,UAAc,EACd,SAAS,GACV,EAAE,kBAAkB,2CA8CpB;AAiDD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUnD"}
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { summarisePresence, } from '../lib/collaboration-provider.js';
5
+ /**
6
+ * Small avatar / initials strip showing who else is currently editing the
7
+ * document. Subscribes to the supplied `Awareness` instance for live
8
+ * updates.
9
+ *
10
+ * The component is intentionally stateless about transport — it only reads
11
+ * from Awareness and `status`. Wiring is the caller's job.
12
+ */
13
+ export function PresenceChips({ awareness, status, hideSelf = true, maxVisible = 5, className, }) {
14
+ const [snapshot, setSnapshot] = useState(() => summarisePresence(awareness));
15
+ useEffect(() => {
16
+ const update = () => {
17
+ setSnapshot(summarisePresence(awareness));
18
+ };
19
+ update();
20
+ awareness.on('update', update);
21
+ awareness.on('change', update);
22
+ return () => {
23
+ awareness.off('update', update);
24
+ awareness.off('change', update);
25
+ };
26
+ }, [awareness]);
27
+ const visible = hideSelf
28
+ ? snapshot.users.filter((u) => u.clientId !== snapshot.selfClientId)
29
+ : snapshot.users;
30
+ const overflow = Math.max(0, visible.length - maxVisible);
31
+ const shown = visible.slice(0, maxVisible);
32
+ return (_jsxs("div", { className: `inline-flex items-center gap-1.5 text-xs ${className ?? ''}`, "data-testid": "presence-chips", children: [status && _jsx(StatusPill, { status: status }), shown.length === 0 && status !== 'connecting' && (_jsx("span", { className: "text-gray-500", "data-testid": "presence-empty", children: "You're the only one here" })), shown.map(({ clientId, user }) => (_jsx(Chip, { name: user.name, color: user.color, avatarUrl: user.avatarUrl }, clientId))), overflow > 0 && (_jsxs("span", { className: "rounded-full bg-gray-100 px-2 py-0.5 text-gray-700", "data-testid": "presence-overflow", children: ["+", overflow] }))] }));
33
+ }
34
+ function StatusPill({ status }) {
35
+ const palette = {
36
+ connected: { dot: 'bg-emerald-500', label: 'Live', text: 'text-emerald-700' },
37
+ connecting: { dot: 'bg-amber-400 animate-pulse', label: 'Connecting', text: 'text-amber-700' },
38
+ disconnected: { dot: 'bg-gray-300', label: 'Offline', text: 'text-gray-500' },
39
+ };
40
+ const cfg = palette[status];
41
+ return (_jsxs("span", { className: `inline-flex items-center gap-1 rounded-full bg-gray-50 px-2 py-0.5 ${cfg.text}`, "data-testid": `presence-status-${status}`, children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${cfg.dot}` }), cfg.label] }));
42
+ }
43
+ function Chip({ name, color, avatarUrl }) {
44
+ const initials = nameToInitials(name);
45
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-white px-1.5 py-0.5 ring-1 ring-gray-200", title: name, "data-testid": "presence-chip", children: [avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", className: "h-4 w-4 rounded-full object-cover", style: { boxShadow: `0 0 0 1.5px ${color}` } })) : (_jsx("span", { "aria-hidden": "true", className: "flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-semibold text-white", style: { backgroundColor: color }, children: initials })), _jsx("span", { className: "max-w-[80px] truncate text-gray-700", children: name })] }));
46
+ }
47
+ /** Convert "Alice Cooper" → "AC", single names → "A", empty → "?". */
48
+ export function nameToInitials(name) {
49
+ const cleaned = name.trim();
50
+ if (!cleaned)
51
+ return '?';
52
+ const parts = cleaned.split(/\s+/).slice(0, 2);
53
+ return (parts
54
+ .map((p) => (p[0] ?? '').toUpperCase())
55
+ .filter(Boolean)
56
+ .join('') || '?');
57
+ }
58
+ //# sourceMappingURL=PresenceChips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenceChips.js","sourceRoot":"","sources":["../../src/components/PresenceChips.tsx"],"names":[],"mappings":"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"}
@@ -1,9 +1,69 @@
1
+ import { type CollaborationUser } from '../lib/collaboration-provider.js';
2
+ /**
3
+ * Configuration for real-time collaboration on this editor.
4
+ *
5
+ * When supplied the editor switches into CRDT mode: history is delegated to
6
+ * Yjs, edits sync through the cms-core realtime gateway, and a presence
7
+ * strip with live cursors appears beside the toolbar. The `content` prop is
8
+ * ignored in collaboration mode (the gateway is the source of truth);
9
+ * `onChange` still fires on every local update so existing autosave wiring
10
+ * keeps working.
11
+ */
12
+ export interface TipTapCollaborationConfig {
13
+ documentId: string;
14
+ /**
15
+ * Base URL of the realtime gateway. `wss://your.app/api/cms/realtime/sync`
16
+ * in production, `ws://localhost:3000/api/cms/realtime/sync` for dev.
17
+ */
18
+ url: string;
19
+ user: CollaborationUser;
20
+ /** Extra query params (e.g. auth token for cross-origin deployments). */
21
+ params?: Record<string, string>;
22
+ }
1
23
  export interface TipTapEditorProps {
2
24
  content: string;
3
25
  onChange: (html: string) => void;
4
26
  placeholder?: string;
5
27
  editable?: boolean;
6
28
  className?: string;
29
+ /**
30
+ * Toggle the AI co-author bubble menu on text selection.
31
+ * Defaults to `true` when the editor is editable. Set to `false` to opt
32
+ * out (e.g. for preview-only renders, or environments where the
33
+ * `/ai/coauthor` endpoint is intentionally disabled).
34
+ */
35
+ enableAI?: boolean;
36
+ /**
37
+ * Enable real-time collaboration via the cms-core realtime gateway. When
38
+ * set, the editor swaps in Yjs-backed history + the collaboration cursor
39
+ * extension and renders a presence strip above the toolbar.
40
+ */
41
+ collaboration?: TipTapCollaborationConfig;
42
+ /**
43
+ * Enable inline comments. When set, the `CommentMark` extension is
44
+ * loaded and a `CommentSidePanel` renders next to the editor. Anchors
45
+ * are encoded against the active Yjs text node when collaboration is
46
+ * also on, so highlights survive concurrent edits.
47
+ *
48
+ * Comments are independent of the collaboration prop — you can attach
49
+ * comments to a static document, but cross-user "live" highlighting
50
+ * only kicks in when collaboration is also enabled.
51
+ */
52
+ comments?: TipTapCommentsConfig;
53
+ }
54
+ /**
55
+ * Configuration for the comments side panel. The panel issues REST calls
56
+ * against the slice-4 endpoints; this prop is just the small set of
57
+ * presentation + identity bits the panel can't infer from the editor.
58
+ */
59
+ export interface TipTapCommentsConfig {
60
+ documentId: string;
61
+ /** Logged-in user id — gates edit + delete affordances. */
62
+ currentUserId: string;
63
+ /** Set to true to expose moderation actions on every comment. */
64
+ isAdmin?: boolean;
65
+ /** Error sink so the host shell can show a toast. */
66
+ onError?: (message: string) => void;
7
67
  }
8
- export declare function TipTapEditor({ content, onChange, placeholder, editable, className, }: TipTapEditorProps): import("react/jsx-runtime").JSX.Element | null;
68
+ export declare function TipTapEditor({ content, onChange, placeholder, editable, className, enableAI, collaboration, comments, }: TipTapEditorProps): import("react/jsx-runtime").JSX.Element | null;
9
69
  //# sourceMappingURL=TipTapEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TipTapEditor.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.tsx"],"names":[],"mappings":"AA+CA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwVD,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAe,EACf,SAAS,GACV,EAAE,iBAAiB,kDA6DnB"}
1
+ {"version":3,"file":"TipTapEditor.d.ts","sourceRoot":"","sources":["../../src/components/TipTapEditor.tsx"],"names":[],"mappings":"AAWA,OAAO,EAIL,KAAK,iBAAiB,EAEvB,MAAM,kCAAkC,CAAA;AAgDzC;;;;;;;;;GASG;AACH,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,yBAAyB,CAAA;IACzC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAA;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAwVD,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAe,EACf,SAAS,EACT,QAAe,EACf,aAAa,EACb,QAAQ,GACT,EAAE,iBAAiB,kDAiOnB"}