@tangle-network/ui 11.12.0 → 11.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.
- package/CHANGELOG.md +17 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/auth/auth.d.ts +53 -0
- package/dist/{auth-DOETfnd9.js → auth/auth.js} +7 -37
- package/dist/auth/login-layout.d.ts +20 -0
- package/dist/auth/login-layout.js +36 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +2 -1
- package/dist/chat/agent-timeline.d.ts +83 -0
- package/dist/chat/agent-timeline.js +236 -0
- package/dist/chat/chat-container.d.ts +51 -0
- package/dist/chat/chat-container.js +309 -0
- package/dist/chat/chat-message.d.ts +22 -0
- package/dist/chat/chat-message.js +38 -0
- package/dist/chat/message-list.d.ts +32 -0
- package/dist/chat/message-list.js +37 -0
- package/dist/chat/thinking-indicator.d.ts +6 -0
- package/dist/chat/thinking-indicator.js +38 -0
- package/dist/chat/user-message.d.ts +21 -0
- package/dist/chat/user-message.js +42 -0
- package/dist/chat.d.ts +6 -1
- package/dist/chat.js +6 -1
- package/dist/editor/document-editor-pane.d.ts +37 -0
- package/dist/editor/document-editor-pane.js +199 -0
- package/dist/editor/editor-lazy.js +37 -0
- package/dist/editor/editor-loading.js +21 -0
- package/dist/editor/editor-peers.d.ts +6 -0
- package/dist/editor/editor-peers.js +155 -0
- package/dist/editor/editor-provider.d.ts +97 -0
- package/dist/editor/editor-provider.js +253 -0
- package/dist/editor/editor-toolbar.d.ts +11 -0
- package/dist/editor/editor-toolbar.js +106 -0
- package/dist/editor/markdown-conversion.js +23 -0
- package/dist/editor/markdown-document-editor.js +103 -0
- package/dist/editor/tiptap-editor.d.ts +45 -0
- package/dist/editor/tiptap-editor.js +271 -0
- package/dist/editor/use-editor.d.ts +67 -0
- package/dist/editor/use-editor.js +158 -0
- package/dist/editor.d.ts +6 -119
- package/dist/editor.js +5 -1
- package/dist/files/file-artifact-pane.d.ts +47 -0
- package/dist/files/file-artifact-pane.js +114 -0
- package/dist/files/file-format.d.ts +51 -0
- package/dist/{file-format-BgXbr-Ib.js → files/file-format.js} +1 -1
- package/dist/files/file-preview.d.ts +27 -0
- package/dist/files/file-preview.js +359 -0
- package/dist/files/file-tabs.d.ts +19 -0
- package/dist/files/file-tabs.js +48 -0
- package/dist/files/file-tree.d.ts +31 -0
- package/dist/files/file-tree.js +135 -0
- package/dist/files/rich-file-tree.d.ts +62 -0
- package/dist/files/rich-file-tree.js +122 -0
- package/dist/files.d.ts +6 -1
- package/dist/files.js +6 -2
- package/dist/hooks/use-auth.d.ts +40 -0
- package/dist/hooks/use-auth.js +91 -0
- package/dist/hooks/use-auto-scroll.d.ts +11 -0
- package/dist/hooks/use-auto-scroll.js +48 -0
- package/dist/hooks/use-dropdown-menu.d.ts +12 -0
- package/dist/hooks/use-dropdown-menu.js +31 -0
- package/dist/hooks/use-live-time.d.ts +7 -0
- package/dist/hooks/use-live-time.js +22 -0
- package/dist/hooks/use-realtime-session.d.ts +32 -0
- package/dist/hooks/use-realtime-session.js +235 -0
- package/dist/hooks/use-run-collapse-state.d.ts +13 -0
- package/dist/hooks/use-run-collapse-state.js +24 -0
- package/dist/hooks/use-run-groups.d.ts +11 -0
- package/dist/hooks/use-run-groups.js +85 -0
- package/dist/hooks/use-sdk-session.d.ts +59 -0
- package/dist/hooks/use-sdk-session.js +322 -0
- package/dist/hooks/use-sse-stream.d.ts +124 -0
- package/dist/hooks/use-sse-stream.js +229 -0
- package/dist/hooks/use-tool-call-stream.d.ts +23 -0
- package/dist/hooks/use-tool-call-stream.js +75 -0
- package/dist/hooks.d.ts +11 -49
- package/dist/hooks.js +10 -110
- package/dist/index.d.ts +99 -25
- package/dist/index.js +86 -36
- package/dist/lib/focus.d.ts +55 -0
- package/dist/{focus-BcE_SHqz.js → lib/focus.js} +1 -1
- package/dist/lib/utils.d.ts +4 -0
- package/dist/{utils-C_uf36nf.js → lib/utils.js} +1 -1
- package/dist/{code-block-B9miUPht.d.ts → markdown/code-block.d.ts} +5 -6
- package/dist/{code-block-B36KVUSN.js → markdown/code-block.js} +22 -47
- package/dist/{markdown-C0atSabr.d.ts → markdown/markdown.d.ts} +4 -5
- package/dist/{markdown-Db08KAYF.js → markdown/markdown.js} +3 -3
- package/dist/markdown.d.ts +2 -2
- package/dist/markdown.js +2 -2
- package/dist/{openui-artifact-renderer-fkBQaoLO.d.ts → openui/openui-artifact-renderer.d.ts} +20 -21
- package/dist/{openui-artifact-renderer-DG_i2lOR.js → openui/openui-artifact-renderer.js} +9 -7
- package/dist/{schema-BpW9rSSG.js → openui/schema-compiled.js} +2 -354
- package/dist/{schema-DAstk-J4.d.ts → openui/schema-data.d.ts} +3 -24
- package/dist/openui/schema-data.js +206 -0
- package/dist/openui-schema.d.ts +22 -3
- package/dist/openui-schema.js +138 -1
- package/dist/openui.d.ts +3 -2
- package/dist/openui.js +3 -2
- package/dist/{artifact-pane-BL_DFWNl.d.ts → primitives/artifact-pane.d.ts} +3 -4
- package/dist/{artifact-pane-DMXT9a8U.js → primitives/artifact-pane.js} +2 -2
- package/dist/primitives/avatar.d.ts +8 -0
- package/dist/primitives/avatar.js +25 -0
- package/dist/primitives/badge.d.ts +22 -0
- package/dist/primitives/badge.js +47 -0
- package/dist/{button-yoIv77e2.d.ts → primitives/button.d.ts} +2 -2
- package/dist/primitives/button.js +113 -0
- package/dist/primitives/card.d.ts +18 -0
- package/dist/primitives/card.js +52 -0
- package/dist/primitives/code-block.d.ts +6 -0
- package/dist/primitives/code-block.js +13 -0
- package/dist/primitives/dialog.d.ts +23 -0
- package/dist/primitives/dialog.js +59 -0
- package/dist/primitives/drop-zone.d.ts +23 -0
- package/dist/primitives/drop-zone.js +104 -0
- package/dist/primitives/dropdown-menu.d.ts +29 -0
- package/dist/{dropdown-menu-BpcLa5vF.js → primitives/dropdown-menu.js} +2 -23
- package/dist/primitives/empty-state.d.ts +11 -0
- package/dist/primitives/empty-state.js +32 -0
- package/dist/primitives/heading.d.ts +23 -0
- package/dist/primitives/heading.js +38 -0
- package/dist/primitives/input.d.ts +23 -0
- package/dist/primitives/input.js +110 -0
- package/dist/primitives/label.d.ts +6 -0
- package/dist/primitives/label.js +16 -0
- package/dist/primitives/metric-strip.d.ts +48 -0
- package/dist/primitives/metric-strip.js +52 -0
- package/dist/primitives/page-header.d.ts +25 -0
- package/dist/primitives/page-header.js +47 -0
- package/dist/primitives/page-shell.d.ts +14 -0
- package/dist/primitives/page-shell.js +17 -0
- package/dist/primitives/progress.d.ts +9 -0
- package/dist/primitives/progress.js +29 -0
- package/dist/primitives/segmented-control.d.ts +40 -0
- package/dist/primitives/segmented-control.js +57 -0
- package/dist/primitives/select.d.ts +15 -0
- package/dist/primitives/select.js +74 -0
- package/dist/primitives/sidebar-drop-zone.d.ts +21 -0
- package/dist/primitives/sidebar-drop-zone.js +88 -0
- package/dist/primitives/skeleton.d.ts +10 -0
- package/dist/primitives/skeleton.js +50 -0
- package/dist/primitives/stat-card.d.ts +16 -0
- package/dist/primitives/stat-card.js +60 -0
- package/dist/primitives/status-pill.d.ts +44 -0
- package/dist/primitives/status-pill.js +119 -0
- package/dist/primitives/switch.d.ts +6 -0
- package/dist/primitives/switch.js +16 -0
- package/dist/primitives/table.d.ts +22 -0
- package/dist/primitives/table.js +63 -0
- package/dist/primitives/tabs.d.ts +13 -0
- package/dist/{tabs-BVHR6NkO.js → primitives/tabs.js} +3 -3
- package/dist/primitives/terminal-display.d.ts +25 -0
- package/dist/primitives/terminal-display.js +109 -0
- package/dist/primitives/text-shimmer.d.ts +16 -0
- package/dist/{text-shimmer-BUUo6I-g.js → primitives/text-shimmer.js} +2 -2
- package/dist/primitives/theme-toggle.d.ts +11 -0
- package/dist/primitives/theme-toggle.js +154 -0
- package/dist/primitives/toast.d.ts +29 -0
- package/dist/primitives/toast.js +142 -0
- package/dist/primitives/toolbar.d.ts +55 -0
- package/dist/primitives/toolbar.js +37 -0
- package/dist/primitives/upload-progress.d.ts +23 -0
- package/dist/primitives/upload-progress.js +72 -0
- package/dist/primitives.d.ts +35 -5
- package/dist/primitives.js +35 -9
- package/dist/{index-COgqsFAQ.d.ts → redaction/redacted-document.d.ts} +6 -7
- package/dist/{redaction-CGGB2ENG.js → redaction/redacted-document.js} +2 -2
- package/dist/redaction.d.ts +1 -1
- package/dist/redaction.js +1 -1
- package/dist/run/expandable-content.js +41 -0
- package/dist/run/expanded-tool-detail.d.ts +12 -0
- package/dist/run/expanded-tool-detail.js +163 -0
- package/dist/run/inline-thinking-item.d.ts +11 -0
- package/dist/run/inline-thinking-item.js +60 -0
- package/dist/run/inline-tool-item.d.ts +25 -0
- package/dist/run/inline-tool-item.js +63 -0
- package/dist/run/run-group.d.ts +26 -0
- package/dist/{run-group-BWGeesgs.js → run/run-group.js} +9 -60
- package/dist/run/run-item-primitives.d.ts +5 -0
- package/dist/{run-ZsIKPA6J.js → run/run-item-primitives.js} +2 -2
- package/dist/run/run-row-shell.d.ts +53 -0
- package/dist/run/run-row-shell.js +114 -0
- package/dist/{tool-call-feed-BeB1_4pX.d.ts → run/tool-call-feed.d.ts} +7 -12
- package/dist/{tool-call-feed-BpmMLpPX.js → run/tool-call-feed.js} +4 -4
- package/dist/run/tool-call-step.d.ts +5 -0
- package/dist/run/tool-call-step.js +82 -0
- package/dist/run.d.ts +8 -2
- package/dist/run.js +7 -4
- package/dist/sdk-hooks.d.ts +8 -1
- package/dist/sdk-hooks.js +8 -2
- package/dist/stores/active-sessions-store.d.ts +83 -0
- package/dist/{active-sessions-store-Gt1R0rFG.js → stores/active-sessions-store.js} +1 -1
- package/dist/stores/chat-store.d.ts +14 -0
- package/dist/stores/chat-store.js +33 -0
- package/dist/stores.d.ts +3 -16
- package/dist/stores.js +2 -33
- package/dist/tool-previews/command-preview.d.ts +11 -0
- package/dist/tool-previews/command-preview.js +91 -0
- package/dist/tool-previews/diff-preview.d.ts +7 -0
- package/dist/tool-previews/diff-preview.js +76 -0
- package/dist/tool-previews/glob-results-preview.d.ts +7 -0
- package/dist/tool-previews/glob-results-preview.js +53 -0
- package/dist/tool-previews/grep-results-preview.d.ts +7 -0
- package/dist/tool-previews/grep-results-preview.js +99 -0
- package/dist/tool-previews/preview-primitives.js +58 -0
- package/dist/tool-previews/question-preview.d.ts +7 -0
- package/dist/tool-previews/question-preview.js +63 -0
- package/dist/tool-previews/web-search-preview.d.ts +7 -0
- package/dist/tool-previews/web-search-preview.js +78 -0
- package/dist/tool-previews/write-file-preview.d.ts +11 -0
- package/dist/tool-previews/write-file-preview.js +52 -0
- package/dist/tool-previews.d.ts +7 -1
- package/dist/tool-previews.js +7 -1
- package/dist/{branding-26d5ARIr.d.ts → types/branding.d.ts} +2 -3
- package/dist/{message-vhUI0c5G.d.ts → types/message.d.ts} +2 -3
- package/dist/{parts-5mhy9yJ3.d.ts → types/parts.d.ts} +8 -9
- package/dist/{run-OyWmIDqu.d.ts → types/run.d.ts} +9 -10
- package/dist/{tool-display-DgmL5rIU.d.ts → types/tool-display.d.ts} +5 -6
- package/dist/types.d.ts +5 -5
- package/dist/utils/copy-text.d.ts +7 -0
- package/dist/{utils-Bl_CZ0Qp.js → utils/copy-text.js} +1 -11
- package/dist/utils/format.d.ts +22 -0
- package/dist/{format-DfcLWqMa.js → utils/format.js} +1 -1
- package/dist/utils/time-ago.d.ts +3 -0
- package/dist/utils/time-ago.js +11 -0
- package/dist/utils/tool-display.d.ts +16 -0
- package/dist/{tool-display-BAuAVa43.js → utils/tool-display.js} +1 -1
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +6 -5
- package/package.json +2 -2
- package/src/files/rich-file-tree.stories.tsx +1 -1
- package/src/files/rich-file-tree.tsx +12 -9
- package/src/markdown/code-block.test.tsx +46 -2
- package/src/markdown/code-block.tsx +23 -60
- package/src/primitives/button.stories.tsx +37 -1
- package/src/primitives/button.test.tsx +230 -0
- package/src/primitives/button.tsx +62 -14
- package/src/primitives/card-anatomy.stories.tsx +51 -0
- package/src/primitives/card.test.tsx +71 -0
- package/src/primitives/card.tsx +25 -13
- package/src/primitives/heading.stories.tsx +35 -0
- package/src/primitives/heading.test.tsx +56 -0
- package/src/primitives/heading.tsx +76 -0
- package/src/primitives/index.ts +8 -0
- package/src/primitives/input.stories.tsx +28 -0
- package/src/primitives/input.test.tsx +161 -0
- package/src/primitives/input.tsx +34 -28
- package/src/primitives/page-header.stories.tsx +51 -0
- package/src/primitives/page-header.test.tsx +48 -0
- package/src/primitives/page-header.tsx +35 -35
- package/src/primitives/page-shell.stories.tsx +89 -0
- package/src/primitives/page-shell.test.tsx +24 -0
- package/src/primitives/page-shell.tsx +26 -0
- package/src/primitives/table-wrapper.stories.tsx +54 -0
- package/src/primitives/table.test.tsx +72 -0
- package/src/primitives/table.tsx +33 -12
- package/src/primitives/theme-toggle.test.tsx +167 -0
- package/src/primitives/theme-toggle.tsx +111 -39
- package/dist/active-sessions-store-nl70ZnMC.d.ts +0 -84
- package/dist/button-C87nEe2R.js +0 -78
- package/dist/chat-DDvF2oGQ.js +0 -672
- package/dist/diff-preview-BcK3nRXb.js +0 -533
- package/dist/document-editor-pane-BilJfiCo.d.ts +0 -136
- package/dist/document-editor-pane-POsq5Q0o.js +0 -1279
- package/dist/files-hywocVZz.js +0 -753
- package/dist/index-2UntyjBb.d.ts +0 -52
- package/dist/index-BgUHilZm.d.ts +0 -108
- package/dist/index-CJ8wRfiC.d.ts +0 -236
- package/dist/index-CPDET_s1.d.ts +0 -549
- package/dist/index-Cg6gYAqh.d.ts +0 -73
- package/dist/index-Cs7j8V7l.d.ts +0 -203
- package/dist/index-DjKxO357.d.ts +0 -128
- package/dist/primitives-Bx59fT-U.js +0 -1239
- package/dist/table-Bss73DhB.js +0 -146
- package/dist/tool-call-step-DNebqhmX.js +0 -434
- package/dist/use-auto-scroll-C5Pjeqon.js +0 -153
- package/dist/use-sse-stream-DjqRuDft.js +0 -884
- 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 };
|