@tangle-network/ui 11.12.0 → 11.13.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 (276) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/auth/auth.d.ts +53 -0
  4. package/dist/{auth-DOETfnd9.js → auth/auth.js} +7 -37
  5. package/dist/auth/login-layout.d.ts +20 -0
  6. package/dist/auth/login-layout.js +36 -0
  7. package/dist/auth.d.ts +2 -1
  8. package/dist/auth.js +2 -1
  9. package/dist/chat/agent-timeline.d.ts +83 -0
  10. package/dist/chat/agent-timeline.js +236 -0
  11. package/dist/chat/chat-container.d.ts +51 -0
  12. package/dist/chat/chat-container.js +309 -0
  13. package/dist/chat/chat-message.d.ts +22 -0
  14. package/dist/chat/chat-message.js +38 -0
  15. package/dist/chat/message-list.d.ts +32 -0
  16. package/dist/chat/message-list.js +37 -0
  17. package/dist/chat/thinking-indicator.d.ts +6 -0
  18. package/dist/chat/thinking-indicator.js +38 -0
  19. package/dist/chat/user-message.d.ts +21 -0
  20. package/dist/chat/user-message.js +42 -0
  21. package/dist/chat.d.ts +6 -1
  22. package/dist/chat.js +6 -1
  23. package/dist/editor/document-editor-pane.d.ts +37 -0
  24. package/dist/editor/document-editor-pane.js +199 -0
  25. package/dist/editor/editor-lazy.js +37 -0
  26. package/dist/editor/editor-loading.js +21 -0
  27. package/dist/editor/editor-peers.d.ts +6 -0
  28. package/dist/editor/editor-peers.js +155 -0
  29. package/dist/editor/editor-provider.d.ts +97 -0
  30. package/dist/editor/editor-provider.js +253 -0
  31. package/dist/editor/editor-toolbar.d.ts +11 -0
  32. package/dist/editor/editor-toolbar.js +106 -0
  33. package/dist/editor/markdown-conversion.js +23 -0
  34. package/dist/editor/markdown-document-editor.js +103 -0
  35. package/dist/editor/tiptap-editor.d.ts +45 -0
  36. package/dist/editor/tiptap-editor.js +271 -0
  37. package/dist/editor/use-editor.d.ts +67 -0
  38. package/dist/editor/use-editor.js +158 -0
  39. package/dist/editor.d.ts +6 -119
  40. package/dist/editor.js +5 -1
  41. package/dist/files/file-artifact-pane.d.ts +47 -0
  42. package/dist/files/file-artifact-pane.js +114 -0
  43. package/dist/files/file-format.d.ts +51 -0
  44. package/dist/{file-format-BgXbr-Ib.js → files/file-format.js} +1 -1
  45. package/dist/files/file-preview.d.ts +27 -0
  46. package/dist/files/file-preview.js +359 -0
  47. package/dist/files/file-tabs.d.ts +19 -0
  48. package/dist/files/file-tabs.js +48 -0
  49. package/dist/files/file-tree.d.ts +31 -0
  50. package/dist/files/file-tree.js +135 -0
  51. package/dist/files/rich-file-tree.d.ts +62 -0
  52. package/dist/files/rich-file-tree.js +122 -0
  53. package/dist/files.d.ts +6 -1
  54. package/dist/files.js +6 -2
  55. package/dist/hooks/use-auth.d.ts +40 -0
  56. package/dist/hooks/use-auth.js +91 -0
  57. package/dist/hooks/use-auto-scroll.d.ts +11 -0
  58. package/dist/hooks/use-auto-scroll.js +48 -0
  59. package/dist/hooks/use-dropdown-menu.d.ts +12 -0
  60. package/dist/hooks/use-dropdown-menu.js +31 -0
  61. package/dist/hooks/use-live-time.d.ts +7 -0
  62. package/dist/hooks/use-live-time.js +22 -0
  63. package/dist/hooks/use-realtime-session.d.ts +32 -0
  64. package/dist/hooks/use-realtime-session.js +235 -0
  65. package/dist/hooks/use-run-collapse-state.d.ts +13 -0
  66. package/dist/hooks/use-run-collapse-state.js +24 -0
  67. package/dist/hooks/use-run-groups.d.ts +11 -0
  68. package/dist/hooks/use-run-groups.js +85 -0
  69. package/dist/hooks/use-sdk-session.d.ts +59 -0
  70. package/dist/hooks/use-sdk-session.js +322 -0
  71. package/dist/hooks/use-sse-stream.d.ts +124 -0
  72. package/dist/hooks/use-sse-stream.js +229 -0
  73. package/dist/hooks/use-tool-call-stream.d.ts +23 -0
  74. package/dist/hooks/use-tool-call-stream.js +75 -0
  75. package/dist/hooks.d.ts +11 -49
  76. package/dist/hooks.js +10 -110
  77. package/dist/index.d.ts +99 -25
  78. package/dist/index.js +86 -36
  79. package/dist/lib/focus.d.ts +55 -0
  80. package/dist/{focus-BcE_SHqz.js → lib/focus.js} +1 -1
  81. package/dist/lib/utils.d.ts +4 -0
  82. package/dist/{utils-C_uf36nf.js → lib/utils.js} +1 -1
  83. package/dist/{code-block-B9miUPht.d.ts → markdown/code-block.d.ts} +5 -6
  84. package/dist/{code-block-B36KVUSN.js → markdown/code-block.js} +60 -55
  85. package/dist/{markdown-C0atSabr.d.ts → markdown/markdown.d.ts} +4 -5
  86. package/dist/{markdown-Db08KAYF.js → markdown/markdown.js} +3 -3
  87. package/dist/markdown.d.ts +2 -2
  88. package/dist/markdown.js +2 -2
  89. package/dist/{openui-artifact-renderer-fkBQaoLO.d.ts → openui/openui-artifact-renderer.d.ts} +20 -21
  90. package/dist/{openui-artifact-renderer-DG_i2lOR.js → openui/openui-artifact-renderer.js} +9 -7
  91. package/dist/{schema-BpW9rSSG.js → openui/schema-compiled.js} +2 -354
  92. package/dist/{schema-DAstk-J4.d.ts → openui/schema-data.d.ts} +3 -24
  93. package/dist/openui/schema-data.js +206 -0
  94. package/dist/openui-schema.d.ts +22 -3
  95. package/dist/openui-schema.js +138 -1
  96. package/dist/openui.d.ts +3 -2
  97. package/dist/openui.js +3 -2
  98. package/dist/{artifact-pane-BL_DFWNl.d.ts → primitives/artifact-pane.d.ts} +3 -4
  99. package/dist/{artifact-pane-DMXT9a8U.js → primitives/artifact-pane.js} +2 -2
  100. package/dist/primitives/avatar.d.ts +8 -0
  101. package/dist/primitives/avatar.js +25 -0
  102. package/dist/primitives/badge.d.ts +22 -0
  103. package/dist/primitives/badge.js +47 -0
  104. package/dist/{button-yoIv77e2.d.ts → primitives/button.d.ts} +2 -2
  105. package/dist/primitives/button.js +113 -0
  106. package/dist/primitives/card.d.ts +18 -0
  107. package/dist/primitives/card.js +52 -0
  108. package/dist/primitives/code-block.d.ts +6 -0
  109. package/dist/primitives/code-block.js +13 -0
  110. package/dist/primitives/dialog.d.ts +23 -0
  111. package/dist/primitives/dialog.js +59 -0
  112. package/dist/primitives/drop-zone.d.ts +23 -0
  113. package/dist/primitives/drop-zone.js +104 -0
  114. package/dist/primitives/dropdown-menu.d.ts +29 -0
  115. package/dist/{dropdown-menu-BpcLa5vF.js → primitives/dropdown-menu.js} +2 -23
  116. package/dist/primitives/empty-state.d.ts +11 -0
  117. package/dist/primitives/empty-state.js +32 -0
  118. package/dist/primitives/heading.d.ts +23 -0
  119. package/dist/primitives/heading.js +38 -0
  120. package/dist/primitives/input.d.ts +23 -0
  121. package/dist/primitives/input.js +110 -0
  122. package/dist/primitives/label.d.ts +6 -0
  123. package/dist/primitives/label.js +16 -0
  124. package/dist/primitives/metric-strip.d.ts +48 -0
  125. package/dist/primitives/metric-strip.js +52 -0
  126. package/dist/primitives/page-header.d.ts +25 -0
  127. package/dist/primitives/page-header.js +47 -0
  128. package/dist/primitives/page-shell.d.ts +14 -0
  129. package/dist/primitives/page-shell.js +17 -0
  130. package/dist/primitives/progress.d.ts +9 -0
  131. package/dist/primitives/progress.js +29 -0
  132. package/dist/primitives/segmented-control.d.ts +40 -0
  133. package/dist/primitives/segmented-control.js +57 -0
  134. package/dist/primitives/select.d.ts +15 -0
  135. package/dist/primitives/select.js +74 -0
  136. package/dist/primitives/sidebar-drop-zone.d.ts +21 -0
  137. package/dist/primitives/sidebar-drop-zone.js +88 -0
  138. package/dist/primitives/skeleton.d.ts +10 -0
  139. package/dist/primitives/skeleton.js +50 -0
  140. package/dist/primitives/stat-card.d.ts +16 -0
  141. package/dist/primitives/stat-card.js +60 -0
  142. package/dist/primitives/status-pill.d.ts +44 -0
  143. package/dist/primitives/status-pill.js +119 -0
  144. package/dist/primitives/switch.d.ts +6 -0
  145. package/dist/primitives/switch.js +16 -0
  146. package/dist/primitives/table.d.ts +22 -0
  147. package/dist/primitives/table.js +63 -0
  148. package/dist/primitives/tabs.d.ts +13 -0
  149. package/dist/{tabs-BVHR6NkO.js → primitives/tabs.js} +3 -3
  150. package/dist/primitives/terminal-display.d.ts +25 -0
  151. package/dist/primitives/terminal-display.js +109 -0
  152. package/dist/primitives/text-shimmer.d.ts +16 -0
  153. package/dist/{text-shimmer-BUUo6I-g.js → primitives/text-shimmer.js} +2 -2
  154. package/dist/primitives/theme-toggle.d.ts +11 -0
  155. package/dist/primitives/theme-toggle.js +154 -0
  156. package/dist/primitives/toast.d.ts +29 -0
  157. package/dist/primitives/toast.js +142 -0
  158. package/dist/primitives/toolbar.d.ts +55 -0
  159. package/dist/primitives/toolbar.js +37 -0
  160. package/dist/primitives/upload-progress.d.ts +23 -0
  161. package/dist/primitives/upload-progress.js +72 -0
  162. package/dist/primitives.d.ts +35 -5
  163. package/dist/primitives.js +35 -9
  164. package/dist/{index-COgqsFAQ.d.ts → redaction/redacted-document.d.ts} +6 -7
  165. package/dist/{redaction-CGGB2ENG.js → redaction/redacted-document.js} +2 -2
  166. package/dist/redaction.d.ts +1 -1
  167. package/dist/redaction.js +1 -1
  168. package/dist/run/expandable-content.js +41 -0
  169. package/dist/run/expanded-tool-detail.d.ts +12 -0
  170. package/dist/run/expanded-tool-detail.js +163 -0
  171. package/dist/run/inline-thinking-item.d.ts +11 -0
  172. package/dist/run/inline-thinking-item.js +60 -0
  173. package/dist/run/inline-tool-item.d.ts +25 -0
  174. package/dist/run/inline-tool-item.js +63 -0
  175. package/dist/run/run-group.d.ts +26 -0
  176. package/dist/{run-group-BWGeesgs.js → run/run-group.js} +9 -60
  177. package/dist/run/run-item-primitives.d.ts +5 -0
  178. package/dist/{run-ZsIKPA6J.js → run/run-item-primitives.js} +2 -2
  179. package/dist/run/run-row-shell.d.ts +53 -0
  180. package/dist/run/run-row-shell.js +114 -0
  181. package/dist/{tool-call-feed-BeB1_4pX.d.ts → run/tool-call-feed.d.ts} +7 -12
  182. package/dist/{tool-call-feed-BpmMLpPX.js → run/tool-call-feed.js} +4 -4
  183. package/dist/run/tool-call-step.d.ts +5 -0
  184. package/dist/run/tool-call-step.js +82 -0
  185. package/dist/run.d.ts +8 -2
  186. package/dist/run.js +7 -4
  187. package/dist/sdk-hooks.d.ts +8 -1
  188. package/dist/sdk-hooks.js +8 -2
  189. package/dist/stores/active-sessions-store.d.ts +83 -0
  190. package/dist/{active-sessions-store-Gt1R0rFG.js → stores/active-sessions-store.js} +1 -1
  191. package/dist/stores/chat-store.d.ts +14 -0
  192. package/dist/stores/chat-store.js +33 -0
  193. package/dist/stores.d.ts +3 -16
  194. package/dist/stores.js +2 -33
  195. package/dist/tool-previews/command-preview.d.ts +11 -0
  196. package/dist/tool-previews/command-preview.js +91 -0
  197. package/dist/tool-previews/diff-preview.d.ts +7 -0
  198. package/dist/tool-previews/diff-preview.js +76 -0
  199. package/dist/tool-previews/glob-results-preview.d.ts +7 -0
  200. package/dist/tool-previews/glob-results-preview.js +53 -0
  201. package/dist/tool-previews/grep-results-preview.d.ts +7 -0
  202. package/dist/tool-previews/grep-results-preview.js +99 -0
  203. package/dist/tool-previews/preview-primitives.js +58 -0
  204. package/dist/tool-previews/question-preview.d.ts +7 -0
  205. package/dist/tool-previews/question-preview.js +63 -0
  206. package/dist/tool-previews/web-search-preview.d.ts +7 -0
  207. package/dist/tool-previews/web-search-preview.js +78 -0
  208. package/dist/tool-previews/write-file-preview.d.ts +11 -0
  209. package/dist/tool-previews/write-file-preview.js +52 -0
  210. package/dist/tool-previews.d.ts +7 -1
  211. package/dist/tool-previews.js +7 -1
  212. package/dist/{branding-26d5ARIr.d.ts → types/branding.d.ts} +2 -3
  213. package/dist/{message-vhUI0c5G.d.ts → types/message.d.ts} +2 -3
  214. package/dist/{parts-5mhy9yJ3.d.ts → types/parts.d.ts} +8 -9
  215. package/dist/{run-OyWmIDqu.d.ts → types/run.d.ts} +9 -10
  216. package/dist/{tool-display-DgmL5rIU.d.ts → types/tool-display.d.ts} +5 -6
  217. package/dist/types.d.ts +5 -5
  218. package/dist/utils/copy-text.d.ts +7 -0
  219. package/dist/{utils-Bl_CZ0Qp.js → utils/copy-text.js} +1 -11
  220. package/dist/utils/format.d.ts +22 -0
  221. package/dist/{format-DfcLWqMa.js → utils/format.js} +1 -1
  222. package/dist/utils/time-ago.d.ts +3 -0
  223. package/dist/utils/time-ago.js +11 -0
  224. package/dist/utils/tool-display.d.ts +16 -0
  225. package/dist/{tool-display-BAuAVa43.js → utils/tool-display.js} +1 -1
  226. package/dist/utils.d.ts +6 -1
  227. package/dist/utils.js +6 -5
  228. package/package.json +2 -2
  229. package/src/files/rich-file-tree.stories.tsx +1 -1
  230. package/src/files/rich-file-tree.tsx +12 -9
  231. package/src/markdown/code-block.test.tsx +80 -2
  232. package/src/markdown/code-block.tsx +76 -80
  233. package/src/primitives/button.stories.tsx +37 -1
  234. package/src/primitives/button.test.tsx +230 -0
  235. package/src/primitives/button.tsx +62 -14
  236. package/src/primitives/card-anatomy.stories.tsx +51 -0
  237. package/src/primitives/card.test.tsx +71 -0
  238. package/src/primitives/card.tsx +25 -13
  239. package/src/primitives/heading.stories.tsx +35 -0
  240. package/src/primitives/heading.test.tsx +56 -0
  241. package/src/primitives/heading.tsx +76 -0
  242. package/src/primitives/index.ts +8 -0
  243. package/src/primitives/input.stories.tsx +28 -0
  244. package/src/primitives/input.test.tsx +161 -0
  245. package/src/primitives/input.tsx +34 -28
  246. package/src/primitives/page-header.stories.tsx +51 -0
  247. package/src/primitives/page-header.test.tsx +48 -0
  248. package/src/primitives/page-header.tsx +35 -35
  249. package/src/primitives/page-shell.stories.tsx +89 -0
  250. package/src/primitives/page-shell.test.tsx +24 -0
  251. package/src/primitives/page-shell.tsx +26 -0
  252. package/src/primitives/table-wrapper.stories.tsx +54 -0
  253. package/src/primitives/table.test.tsx +72 -0
  254. package/src/primitives/table.tsx +33 -12
  255. package/src/primitives/theme-toggle.test.tsx +167 -0
  256. package/src/primitives/theme-toggle.tsx +111 -39
  257. package/dist/active-sessions-store-nl70ZnMC.d.ts +0 -84
  258. package/dist/button-C87nEe2R.js +0 -78
  259. package/dist/chat-DDvF2oGQ.js +0 -672
  260. package/dist/diff-preview-BcK3nRXb.js +0 -533
  261. package/dist/document-editor-pane-BilJfiCo.d.ts +0 -136
  262. package/dist/document-editor-pane-POsq5Q0o.js +0 -1279
  263. package/dist/files-hywocVZz.js +0 -753
  264. package/dist/index-2UntyjBb.d.ts +0 -52
  265. package/dist/index-BgUHilZm.d.ts +0 -108
  266. package/dist/index-CJ8wRfiC.d.ts +0 -236
  267. package/dist/index-CPDET_s1.d.ts +0 -549
  268. package/dist/index-Cg6gYAqh.d.ts +0 -73
  269. package/dist/index-Cs7j8V7l.d.ts +0 -203
  270. package/dist/index-DjKxO357.d.ts +0 -128
  271. package/dist/primitives-Bx59fT-U.js +0 -1239
  272. package/dist/table-Bss73DhB.js +0 -146
  273. package/dist/tool-call-step-DNebqhmX.js +0 -434
  274. package/dist/use-auto-scroll-C5Pjeqon.js +0 -153
  275. package/dist/use-sse-stream-DjqRuDft.js +0 -884
  276. package/dist/use-sse-stream-Dm6R5YWz.d.ts +0 -282
@@ -0,0 +1,253 @@
1
+ "use client";
2
+ import { loadEditorProviderPeers } from "./editor-peers.js";
3
+ import { retryableLazyEditor } from "./editor-lazy.js";
4
+ import { Suspense, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/editor/editor-provider.tsx
7
+ const EditorContext = createContext(null);
8
+ /**
9
+ * Generate a random color for user presence.
10
+ */
11
+ function generateUserColor() {
12
+ const colors = [
13
+ "#FF6B6B",
14
+ "#4ECDC4",
15
+ "#45B7D1",
16
+ "#96CEB4",
17
+ "#FFEAA7",
18
+ "#DDA0DD",
19
+ "#98D8C8",
20
+ "#F7DC6F",
21
+ "#BB8FCE",
22
+ "#85C1E9"
23
+ ];
24
+ return colors[Math.floor(Math.random() * colors.length)];
25
+ }
26
+ /**
27
+ * Builds the collaboration provider against loaded yjs and Hocuspocus
28
+ * namespaces. The peers arrive as an argument so that this module reaches
29
+ * them through type-only imports, which a bundler erases.
30
+ */
31
+ function createEditorProvider(peers) {
32
+ const { Doc } = peers.yjs;
33
+ const { HocuspocusProvider } = peers.hocuspocus;
34
+ return function EditorProvider({ websocketUrl, documentName, token, tokenExpiresAt, user, autoConnect = true, autoReconnect = true, maxReconnectAttempts = 5, onConnectionChange, onSync, onAuthError, onRefreshToken, children }) {
35
+ const [connectionState, setConnectionState] = useState("disconnected");
36
+ const [collaborators, setCollaborators] = useState([]);
37
+ const [isSynced, setIsSynced] = useState(false);
38
+ const docRef = useRef(null);
39
+ const providerRef = useRef(null);
40
+ const reconnectAttemptsRef = useRef(0);
41
+ const tokenRef = useRef(token);
42
+ const tokenExpiryRef = useRef(tokenExpiresAt);
43
+ const refreshPromiseRef = useRef(null);
44
+ const refreshTimerRef = useRef(null);
45
+ tokenRef.current = token;
46
+ tokenExpiryRef.current = tokenExpiresAt;
47
+ if (!docRef.current) docRef.current = new Doc();
48
+ const doc = docRef.current;
49
+ const userColor = useMemo(() => user.color ?? generateUserColor(), [user.color]);
50
+ const updateConnectionState = useCallback((state) => {
51
+ setConnectionState(state);
52
+ onConnectionChange?.(state);
53
+ }, [onConnectionChange]);
54
+ const updateCollaborators = useCallback((awareness) => {
55
+ if (!awareness) return;
56
+ const states = awareness.getStates();
57
+ const collabs = [];
58
+ states.forEach((state, clientId) => {
59
+ if (clientId === awareness.clientID) return;
60
+ if (state.user) collabs.push({
61
+ clientId,
62
+ user: state.user
63
+ });
64
+ });
65
+ setCollaborators(collabs);
66
+ }, []);
67
+ const clearRefreshTimer = useCallback(() => {
68
+ if (refreshTimerRef.current != null) {
69
+ window.clearTimeout(refreshTimerRef.current);
70
+ refreshTimerRef.current = null;
71
+ }
72
+ }, []);
73
+ const refreshToken = useCallback(async () => {
74
+ if (!onRefreshToken) return null;
75
+ if (refreshPromiseRef.current) return refreshPromiseRef.current;
76
+ const refreshPromise = (async () => {
77
+ const next = await onRefreshToken();
78
+ const resolvedToken = typeof next === "string" ? next : next.token;
79
+ const resolvedExpiry = typeof next === "string" ? void 0 : next.expiresAt;
80
+ tokenRef.current = resolvedToken;
81
+ tokenExpiryRef.current = resolvedExpiry;
82
+ return resolvedToken;
83
+ })().catch((error) => {
84
+ onAuthError?.(error instanceof Error ? error : new Error(String(error)));
85
+ return null;
86
+ }).finally(() => {
87
+ refreshPromiseRef.current = null;
88
+ });
89
+ refreshPromiseRef.current = refreshPromise;
90
+ return refreshPromise;
91
+ }, [onAuthError, onRefreshToken]);
92
+ const scheduleTokenRefresh = useCallback(() => {
93
+ clearRefreshTimer();
94
+ if (!tokenExpiryRef.current || !onRefreshToken || typeof window === "undefined") return;
95
+ const delay = tokenExpiryRef.current * 1e3 - 6e4 - Date.now();
96
+ if (delay <= 0) {
97
+ refreshToken();
98
+ return;
99
+ }
100
+ refreshTimerRef.current = window.setTimeout(() => {
101
+ refreshToken();
102
+ }, delay);
103
+ }, [
104
+ clearRefreshTimer,
105
+ onRefreshToken,
106
+ refreshToken
107
+ ]);
108
+ const connect = useCallback(() => {
109
+ if (providerRef.current) {
110
+ providerRef.current.connect();
111
+ return;
112
+ }
113
+ updateConnectionState("connecting");
114
+ const provider = new HocuspocusProvider({
115
+ url: websocketUrl,
116
+ name: documentName,
117
+ document: doc,
118
+ token: async () => tokenRef.current,
119
+ onConnect: () => {
120
+ reconnectAttemptsRef.current = 0;
121
+ updateConnectionState("connected");
122
+ scheduleTokenRefresh();
123
+ },
124
+ onSynced: () => {
125
+ setIsSynced(true);
126
+ updateConnectionState("synced");
127
+ onSync?.();
128
+ },
129
+ onDisconnect: () => {
130
+ updateConnectionState("disconnected");
131
+ setIsSynced(false);
132
+ clearRefreshTimer();
133
+ if (autoReconnect && reconnectAttemptsRef.current < maxReconnectAttempts) {
134
+ reconnectAttemptsRef.current += 1;
135
+ const delay = Math.min(1e3 * 2 ** reconnectAttemptsRef.current, 3e4);
136
+ setTimeout(() => {
137
+ if (providerRef.current && !providerRef.current.isConnected) providerRef.current.connect();
138
+ }, delay);
139
+ }
140
+ },
141
+ onAuthenticationFailed: ({ reason }) => {
142
+ const error = new Error(reason ?? "Authentication failed");
143
+ updateConnectionState("disconnected");
144
+ clearRefreshTimer();
145
+ if (onRefreshToken) {
146
+ refreshToken().then((nextToken) => {
147
+ if (nextToken && providerRef.current) {
148
+ providerRef.current.connect();
149
+ return;
150
+ }
151
+ onAuthError?.(error);
152
+ });
153
+ return;
154
+ }
155
+ onAuthError?.(error);
156
+ },
157
+ onAwarenessUpdate: () => {
158
+ updateCollaborators(provider.awareness);
159
+ }
160
+ });
161
+ provider.awareness?.setLocalStateField("user", {
162
+ name: user.name,
163
+ color: userColor,
164
+ userId: user.userId
165
+ });
166
+ providerRef.current = provider;
167
+ }, [
168
+ websocketUrl,
169
+ documentName,
170
+ doc,
171
+ user.name,
172
+ user.userId,
173
+ userColor,
174
+ autoReconnect,
175
+ maxReconnectAttempts,
176
+ clearRefreshTimer,
177
+ updateConnectionState,
178
+ updateCollaborators,
179
+ onSync,
180
+ onAuthError,
181
+ onRefreshToken,
182
+ refreshToken,
183
+ scheduleTokenRefresh
184
+ ]);
185
+ const disconnect = useCallback(() => {
186
+ if (providerRef.current) {
187
+ providerRef.current.disconnect();
188
+ updateConnectionState("disconnected");
189
+ }
190
+ }, [updateConnectionState]);
191
+ useEffect(() => {
192
+ if (autoConnect) connect();
193
+ return () => {
194
+ clearRefreshTimer();
195
+ if (providerRef.current) {
196
+ providerRef.current.destroy();
197
+ providerRef.current = null;
198
+ }
199
+ };
200
+ }, [
201
+ autoConnect,
202
+ clearRefreshTimer,
203
+ connect
204
+ ]);
205
+ const contextValue = useMemo(() => ({
206
+ doc,
207
+ provider: providerRef.current,
208
+ connectionState,
209
+ collaborators,
210
+ isSynced,
211
+ connect,
212
+ disconnect
213
+ }), [
214
+ doc,
215
+ connectionState,
216
+ collaborators,
217
+ isSynced,
218
+ connect,
219
+ disconnect
220
+ ]);
221
+ return /* @__PURE__ */ jsx(EditorContext.Provider, {
222
+ value: contextValue,
223
+ children
224
+ });
225
+ };
226
+ }
227
+ const lazyEditorProvider = retryableLazyEditor(async () => createEditorProvider(await loadEditorProviderPeers()));
228
+ /**
229
+ * EditorProvider wraps children with Hocuspocus collaboration context.
230
+ * Manages WebSocket connection, Y.Doc, and awareness state. Loads `yjs` and
231
+ * `@hocuspocus/provider` — and no tiptap package — before it renders children,
232
+ * because every child hook reads a context that only the loaded provider can
233
+ * supply. A consumer that drives its own editor from that context therefore
234
+ * needs those two peers alone.
235
+ */
236
+ function EditorProvider(props) {
237
+ const LazyEditorProvider = lazyEditorProvider();
238
+ return /* @__PURE__ */ jsx(Suspense, {
239
+ fallback: null,
240
+ children: /* @__PURE__ */ jsx(LazyEditorProvider, { ...props })
241
+ });
242
+ }
243
+ /**
244
+ * Hook to access the editor context.
245
+ * Must be used within an EditorProvider.
246
+ */
247
+ function useEditorContext() {
248
+ const context = useContext(EditorContext);
249
+ if (!context) throw new Error("useEditorContext must be used within an EditorProvider");
250
+ return context;
251
+ }
252
+ //#endregion
253
+ export { EditorProvider, createEditorProvider, useEditorContext };
@@ -0,0 +1,11 @@
1
+ import { Editor } from "@tiptap/react";
2
+ //#region src/editor/editor-toolbar.d.ts
3
+ /**
4
+ * Editor toolbar component.
5
+ * Provides basic formatting controls.
6
+ */
7
+ export declare function EditorToolbar({ editor, className }: {
8
+ editor: Editor | null;
9
+ className?: string;
10
+ }): import("react").JSX.Element | null;
11
+ //#endregion
@@ -0,0 +1,106 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/editor/editor-toolbar.tsx
5
+ /**
6
+ * Editor toolbar component.
7
+ * Provides basic formatting controls.
8
+ */
9
+ function EditorToolbar({ editor, className }) {
10
+ if (!editor) return null;
11
+ const buttons = [
12
+ {
13
+ id: "bold",
14
+ label: "Bold",
15
+ action: () => editor.chain().focus().toggleBold().run(),
16
+ isActive: editor.isActive("bold"),
17
+ shortcut: "Ctrl+B"
18
+ },
19
+ {
20
+ id: "italic",
21
+ label: "Italic",
22
+ action: () => editor.chain().focus().toggleItalic().run(),
23
+ isActive: editor.isActive("italic"),
24
+ shortcut: "Ctrl+I"
25
+ },
26
+ {
27
+ id: "strike",
28
+ label: "Strike",
29
+ action: () => editor.chain().focus().toggleStrike().run(),
30
+ isActive: editor.isActive("strike"),
31
+ shortcut: "Ctrl+Shift+X"
32
+ },
33
+ {
34
+ id: "code",
35
+ label: "Code",
36
+ action: () => editor.chain().focus().toggleCode().run(),
37
+ isActive: editor.isActive("code"),
38
+ shortcut: "Ctrl+E"
39
+ },
40
+ {
41
+ id: "sep-1",
42
+ type: "separator"
43
+ },
44
+ {
45
+ id: "h1",
46
+ label: "H1",
47
+ action: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
48
+ isActive: editor.isActive("heading", { level: 1 })
49
+ },
50
+ {
51
+ id: "h2",
52
+ label: "H2",
53
+ action: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
54
+ isActive: editor.isActive("heading", { level: 2 })
55
+ },
56
+ {
57
+ id: "h3",
58
+ label: "H3",
59
+ action: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
60
+ isActive: editor.isActive("heading", { level: 3 })
61
+ },
62
+ {
63
+ id: "sep-2",
64
+ type: "separator"
65
+ },
66
+ {
67
+ id: "bullet-list",
68
+ label: "Bullet List",
69
+ action: () => editor.chain().focus().toggleBulletList().run(),
70
+ isActive: editor.isActive("bulletList")
71
+ },
72
+ {
73
+ id: "ordered-list",
74
+ label: "Ordered List",
75
+ action: () => editor.chain().focus().toggleOrderedList().run(),
76
+ isActive: editor.isActive("orderedList")
77
+ },
78
+ {
79
+ id: "code-block",
80
+ label: "Code Block",
81
+ action: () => editor.chain().focus().toggleCodeBlock().run(),
82
+ isActive: editor.isActive("codeBlock")
83
+ },
84
+ {
85
+ id: "blockquote",
86
+ label: "Blockquote",
87
+ action: () => editor.chain().focus().toggleBlockquote().run(),
88
+ isActive: editor.isActive("blockquote")
89
+ }
90
+ ];
91
+ return /* @__PURE__ */ jsx("div", {
92
+ className: cn("flex items-center gap-1 border-border border-b bg-card p-2", className),
93
+ children: buttons.map((button) => {
94
+ if ("type" in button && button.type === "separator") return /* @__PURE__ */ jsx("div", { className: "mx-1 h-6 w-px bg-border" }, button.id);
95
+ return /* @__PURE__ */ jsx("button", {
96
+ onClick: button.action,
97
+ type: "button",
98
+ title: "shortcut" in button ? `${button.label} (${button.shortcut})` : button.label,
99
+ className: cn("rounded px-2 py-1 font-medium text-xs transition-colors", "hover:bg-accent hover:text-accent-foreground", button.isActive && "bg-accent text-accent-foreground"),
100
+ children: button.label
101
+ }, button.id);
102
+ })
103
+ });
104
+ }
105
+ //#endregion
106
+ export { EditorToolbar };
@@ -0,0 +1,23 @@
1
+ import { marked } from "marked";
2
+ import TurndownService from "turndown";
3
+ //#region src/editor/markdown-conversion.ts
4
+ const turndown = new TurndownService({
5
+ bulletListMarker: "-",
6
+ codeBlockStyle: "fenced",
7
+ emDelimiter: "*",
8
+ headingStyle: "atx"
9
+ });
10
+ function markdownToHtml(markdown) {
11
+ return String(marked.parse(markdown, {
12
+ async: false,
13
+ gfm: true
14
+ }));
15
+ }
16
+ function htmlToMarkdown(html) {
17
+ return turndown.turndown(html);
18
+ }
19
+ function normalizeMarkdown(markdown) {
20
+ return markdown.replace(/\r\n/g, "\n").trimEnd();
21
+ }
22
+ //#endregion
23
+ export { htmlToMarkdown, markdownToHtml, normalizeMarkdown };
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { loadDocumentEditorPeers } from "./editor-peers.js";
4
+ import { retryableLazyEditor } from "./editor-lazy.js";
5
+ import { EditorLoadingPlaceholder } from "./editor-loading.js";
6
+ import { EditorToolbar } from "./editor-toolbar.js";
7
+ import { htmlToMarkdown, markdownToHtml, normalizeMarkdown } from "./markdown-conversion.js";
8
+ import { Suspense, useEffect, useMemo, useRef } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/editor/markdown-document-editor.tsx
11
+ /**
12
+ * Builds the local markdown editor against loaded tiptap namespaces. The
13
+ * peers arrive as an argument so that this module reaches them through
14
+ * type-only imports, which a bundler erases.
15
+ */
16
+ function createMarkdownDocumentEditor(peers) {
17
+ const { EditorContent, useEditor } = peers.react;
18
+ const StarterKit = peers.starterKit.default;
19
+ return function MarkdownDocumentEditor({ value = "", placeholder = "Start writing...", readOnly = false, autoFocus = false, className, contentClassName, onChange, onReady }) {
20
+ const initialHtml = useMemo(() => markdownToHtml(value), []);
21
+ const lastAppliedMarkdownRef = useRef(normalizeMarkdown(value));
22
+ const editor = useEditor({
23
+ extensions: [StarterKit.configure({ codeBlock: { HTMLAttributes: { class: "hljs" } } })],
24
+ content: initialHtml,
25
+ editable: !readOnly,
26
+ autofocus: autoFocus,
27
+ editorProps: { attributes: {
28
+ class: cn("prose prose-sm sm:prose-base max-w-none focus:outline-none", "prose-headings:text-foreground prose-p:text-foreground prose-li:text-foreground", "prose-strong:text-foreground prose-code:text-foreground prose-pre:text-foreground", contentClassName),
29
+ "data-placeholder": placeholder
30
+ } },
31
+ onUpdate: ({ editor: currentEditor }) => {
32
+ const nextMarkdown = normalizeMarkdown(htmlToMarkdown(currentEditor.getHTML()));
33
+ lastAppliedMarkdownRef.current = nextMarkdown;
34
+ onChange?.(nextMarkdown, currentEditor);
35
+ },
36
+ onCreate: ({ editor: currentEditor }) => {
37
+ onReady?.(currentEditor);
38
+ }
39
+ });
40
+ useEffect(() => {
41
+ if (editor) editor.setEditable(!readOnly);
42
+ }, [editor, readOnly]);
43
+ useEffect(() => {
44
+ if (!editor) return;
45
+ const normalizedValue = normalizeMarkdown(value);
46
+ if (normalizedValue === lastAppliedMarkdownRef.current) return;
47
+ editor.commands.setContent(markdownToHtml(value), { emitUpdate: false });
48
+ lastAppliedMarkdownRef.current = normalizedValue;
49
+ }, [editor, value]);
50
+ return /* @__PURE__ */ jsxs("div", {
51
+ className: cn("flex min-h-[14rem] w-full flex-col overflow-hidden rounded-lg border border-border bg-background", className),
52
+ children: [
53
+ /* @__PURE__ */ jsx(EditorToolbar, {
54
+ editor,
55
+ className: "border-border bg-card px-2 py-2"
56
+ }),
57
+ /* @__PURE__ */ jsx("div", {
58
+ className: "min-h-0 flex-1 overflow-auto px-5 py-4",
59
+ children: /* @__PURE__ */ jsx(EditorContent, { editor })
60
+ }),
61
+ /* @__PURE__ */ jsx("style", { children: `
62
+ .ProseMirror p.is-editor-empty:first-child::before {
63
+ content: attr(data-placeholder);
64
+ float: left;
65
+ color: hsl(var(--muted-foreground));
66
+ pointer-events: none;
67
+ height: 0;
68
+ }
69
+
70
+ .ProseMirror pre {
71
+ background: hsl(var(--muted));
72
+ border: 1px solid hsl(var(--border));
73
+ border-radius: 0.75rem;
74
+ padding: 0.875rem 1rem;
75
+ overflow-x: auto;
76
+ }
77
+
78
+ .ProseMirror code {
79
+ background: hsl(var(--muted));
80
+ border-radius: 0.35rem;
81
+ padding: 0.12rem 0.3rem;
82
+ }
83
+
84
+ .ProseMirror pre code {
85
+ background: transparent;
86
+ padding: 0;
87
+ }
88
+ ` })
89
+ ]
90
+ });
91
+ };
92
+ }
93
+ const lazyMarkdownDocumentEditor = retryableLazyEditor(async () => createMarkdownDocumentEditor(await loadDocumentEditorPeers()));
94
+ /** Local markdown editor. Loads its tiptap peers on first render. */
95
+ function MarkdownDocumentEditor(props) {
96
+ const LazyMarkdownDocumentEditor = lazyMarkdownDocumentEditor();
97
+ return /* @__PURE__ */ jsx(Suspense, {
98
+ fallback: /* @__PURE__ */ jsx(EditorLoadingPlaceholder, { className: cn("min-h-[14rem]", props.className) }),
99
+ children: /* @__PURE__ */ jsx(LazyMarkdownDocumentEditor, { ...props })
100
+ });
101
+ }
102
+ //#endregion
103
+ export { MarkdownDocumentEditor, createMarkdownDocumentEditor };
@@ -0,0 +1,45 @@
1
+ import "./editor-peers.js";
2
+ import { Collaborator } from "./editor-provider.js";
3
+ import { EditorToolbar } from "./editor-toolbar.js";
4
+ import "react";
5
+ import { Editor } from "@tiptap/react";
6
+ //#region src/editor/tiptap-editor.d.ts
7
+ /**
8
+ * Props for TiptapEditor component.
9
+ */
10
+ export interface TiptapEditorProps {
11
+ /** Initial content for new documents (markdown string) */
12
+ initialContent?: string;
13
+ /** Placeholder text when editor is empty */
14
+ placeholder?: string;
15
+ /** Whether the editor is read-only */
16
+ readOnly?: boolean;
17
+ /** Whether to auto-focus on mount */
18
+ autoFocus?: boolean;
19
+ /** Custom className for the editor wrapper */
20
+ className?: string;
21
+ /** Custom className for the editor content area */
22
+ contentClassName?: string;
23
+ /** Callback when content changes */
24
+ onUpdate?: (editor: Editor) => void;
25
+ /** Callback when selection changes */
26
+ onSelectionUpdate?: (editor: Editor) => void;
27
+ /** Callback when editor is ready */
28
+ onReady?: (editor: Editor) => void;
29
+ }
30
+ /**
31
+ * TiptapEditor - Collaborative markdown editor with Y.js sync.
32
+ * Must be used within an EditorProvider. Loads its tiptap, Hocuspocus and
33
+ * yjs peers on first render.
34
+ */
35
+ export declare function TiptapEditor(props: TiptapEditorProps): import("react").JSX.Element;
36
+ /**
37
+ * Collaborators list component.
38
+ * Shows active users in the document.
39
+ */
40
+ export declare function CollaboratorsList({ collaborators, className }: {
41
+ collaborators: Collaborator[];
42
+ className?: string;
43
+ }): import("react").JSX.Element | null;
44
+ //#endregion
45
+ export { EditorToolbar };