@tangle-network/ui 11.9.0 → 11.11.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 +30 -0
- package/README.md +14 -0
- package/dist/active-sessions-store-Gt1R0rFG.js +262 -0
- package/dist/{active-sessions-store-CeOmXgv5.d.ts → active-sessions-store-nl70ZnMC.d.ts} +40 -41
- package/dist/artifact-pane-BL_DFWNl.d.ts +32 -0
- package/dist/artifact-pane-DMXT9a8U.js +70 -0
- package/dist/auth-DixfCMCk.js +154 -0
- package/dist/auth.d.ts +2 -74
- package/dist/auth.js +2 -15
- package/dist/branding-26d5ARIr.d.ts +14 -0
- package/dist/button-uhNnwdsD.js +78 -0
- package/dist/button-yoIv77e2.d.ts +16 -0
- package/dist/chat-CPLL0Fd9.js +667 -0
- package/dist/chat.d.ts +2 -201
- package/dist/chat.js +2 -30
- package/dist/code-block-B9miUPht.d.ts +23 -0
- package/dist/code-block-CngSn37j.js +165 -0
- package/dist/diff-preview-cySuqowW.js +533 -0
- package/dist/document-editor-pane-BcoABlhn.d.ts +131 -0
- package/dist/document-editor-pane-Cz6oEF1o.js +1287 -0
- package/dist/dropdown-menu-BpcLa5vF.js +101 -0
- package/dist/editor.d.ts +76 -78
- package/dist/editor.js +2 -33
- package/dist/file-format-BgXbr-Ib.js +160 -0
- package/dist/files-CYlOo0mE.js +743 -0
- package/dist/files.d.ts +2 -243
- package/dist/files.js +3 -40
- package/dist/focus-Cw02pNSr.js +56 -0
- package/dist/format-DfcLWqMa.js +54 -0
- package/dist/hooks.d.ts +30 -37
- package/dist/hooks.js +111 -45
- package/dist/index-2UntyjBb.d.ts +52 -0
- package/dist/index-COgqsFAQ.d.ts +42 -0
- package/dist/index-CPDET_s1.d.ts +549 -0
- package/dist/index-CUhfHT_V.d.ts +204 -0
- package/dist/index-Cg6gYAqh.d.ts +73 -0
- package/dist/index-Cq-EJzqW.d.ts +16 -0
- package/dist/index-DhYmSaLF.d.ts +245 -0
- package/dist/index-DjKxO357.d.ts +128 -0
- package/dist/index-b56ew9aF.d.ts +107 -0
- package/dist/index.d.ts +26 -43
- package/dist/index.js +35 -460
- package/dist/markdown-BprL96Gl.js +45 -0
- package/dist/markdown.d.ts +3 -18
- package/dist/markdown.js +3 -14
- package/dist/message-vhUI0c5G.d.ts +16 -0
- package/dist/nav.d.ts +6 -8
- package/dist/nav.js +29 -11
- package/dist/openui-artifact-renderer-D3E5bIAI.d.ts +114 -0
- package/dist/openui-artifact-renderer-DT4xbvWt.js +266 -0
- package/dist/openui.d.ts +2 -115
- package/dist/openui.js +2 -12
- package/dist/parts-5mhy9yJ3.d.ts +37 -0
- package/dist/primitives-DE-blUz-.js +1239 -0
- package/dist/primitives.d.ts +5 -523
- package/dist/primitives.js +9 -211
- package/dist/redaction-CGGB2ENG.js +74 -0
- package/dist/redaction.d.ts +2 -42
- package/dist/redaction.js +2 -7
- package/dist/run-OyWmIDqu.d.ts +41 -0
- package/dist/run-ZsIKPA6J.js +17 -0
- package/dist/run-group-D1InSdQu.js +410 -0
- package/dist/run.d.ts +3 -126
- package/dist/run.js +5 -39
- package/dist/sdk-hooks.d.ts +2 -285
- package/dist/sdk-hooks.js +3 -35
- package/dist/stores.d.ts +13 -14
- package/dist/stores.js +34 -76
- package/dist/table-DHyqDPbU.js +146 -0
- package/dist/tabs-ujuqAdtb.js +40 -0
- package/dist/text-shimmer-BUUo6I-g.js +64 -0
- package/dist/tool-call-feed-BeB1_4pX.d.ts +47 -0
- package/dist/tool-call-feed-C1W1N54c.js +109 -0
- package/dist/tool-call-step-Bi3_YJRV.js +434 -0
- package/dist/tool-display-BAuAVa43.js +158 -0
- package/dist/{tool-display-z4JcDmMQ.d.ts → tool-display-DgmL5rIU.d.ts} +16 -16
- package/dist/tool-previews.d.ts +2 -48
- package/dist/tool-previews.js +2 -22
- package/dist/types.d.ts +6 -19
- package/dist/types.js +0 -1
- package/dist/use-auto-scroll-C5Pjeqon.js +153 -0
- package/dist/use-sse-stream-BjVBDbxK.js +884 -0
- package/dist/use-sse-stream-Dm6R5YWz.d.ts +282 -0
- package/dist/utils-Bl_CZ0Qp.js +37 -0
- package/dist/{chunk-RQHJBTEU.js → utils-C_uf36nf.js} +4 -6
- package/dist/utils.d.ts +2 -51
- package/dist/utils.js +6 -32
- package/package.json +39 -41
- package/src/auth/auth.tsx +5 -1
- package/src/chat/agent-timeline.tsx +6 -1
- package/src/chat/chat-container.tsx +2 -1
- package/src/editor/document-editor-pane.test.tsx +3 -1
- package/src/editor/document-editor-pane.tsx +5 -1
- package/src/editor/markdown-document-editor.test.tsx +9 -4
- package/src/files/file-artifact-pane.tsx +3 -2
- package/src/files/file-preview.tsx +3 -1
- package/src/files/file-tabs.tsx +9 -2
- package/src/lib/focus.ts +63 -0
- package/src/markdown/code-block.tsx +5 -1
- package/src/primitives/badge.tsx +2 -1
- package/src/primitives/button.tsx +2 -1
- package/src/primitives/card.tsx +1 -2
- package/src/primitives/dialog.tsx +7 -1
- package/src/primitives/input.tsx +15 -14
- package/src/primitives/segmented-control.tsx +5 -3
- package/src/primitives/select.tsx +3 -1
- package/src/primitives/switch.tsx +3 -1
- package/src/primitives/tabs.tsx +6 -4
- package/src/primitives/terminal-display.tsx +6 -8
- package/src/primitives/toast.tsx +5 -1
- package/src/tool-previews/command-preview.tsx +5 -1
- package/src/tool-previews/web-search-preview.tsx +2 -1
- package/src/utils/index.ts +7 -0
- package/dist/artifact-pane-DpuWcAiF.d.ts +0 -33
- package/dist/button-CMQuQEW_.d.ts +0 -17
- package/dist/chunk-34A66VBG.js +0 -214
- package/dist/chunk-3OI2QKFD.js +0 -0
- package/dist/chunk-3TI5DEHV.js +0 -299
- package/dist/chunk-4CLN43XT.js +0 -45
- package/dist/chunk-565EATZZ.js +0 -156
- package/dist/chunk-5Z5ZYMOJ.js +0 -0
- package/dist/chunk-6BGQA4BQ.js +0 -0
- package/dist/chunk-7YOCS6IF.js +0 -728
- package/dist/chunk-EOAX2ENE.js +0 -60
- package/dist/chunk-GYPQXTJU.js +0 -230
- package/dist/chunk-HFL6R6IF.js +0 -37
- package/dist/chunk-HGML45QH.js +0 -104
- package/dist/chunk-KNYSKNZX.js +0 -550
- package/dist/chunk-L57WRIND.js +0 -59
- package/dist/chunk-LDLOJKPC.js +0 -1585
- package/dist/chunk-LTEKI7YJ.js +0 -59
- package/dist/chunk-MKTSMWVD.js +0 -109
- package/dist/chunk-ND3ZFXQK.js +0 -179
- package/dist/chunk-NKDZ7GZE.js +0 -192
- package/dist/chunk-NWECTSOH.js +0 -589
- package/dist/chunk-OEX7NZE3.js +0 -321
- package/dist/chunk-Q56BYXQF.js +0 -61
- package/dist/chunk-REJESC5U.js +0 -117
- package/dist/chunk-RQGKSCEZ.js +0 -0
- package/dist/chunk-TN4FUEWY.js +0 -437
- package/dist/chunk-VPWY4I5O.js +0 -1048
- package/dist/chunk-WUQDUBJG.js +0 -168
- package/dist/chunk-XGKULLYE.js +0 -40
- package/dist/chunk-XU3HB5QL.js +0 -127
- package/dist/chunk-XYAIX4SU.js +0 -133
- package/dist/chunk-YJ6RE5WV.js +0 -875
- package/dist/chunk-YTO5V6QS.js +0 -19
- package/dist/chunk-ZKCEAIIZ.js +0 -1395
- package/dist/code-block-0kSpWMnf.d.ts +0 -25
- package/dist/document-editor-pane-EX25CEYW.js +0 -12
- package/dist/document-editor-pane-IAEQn_Dl.d.ts +0 -128
- package/dist/message-BHWbxBtT.d.ts +0 -15
- package/dist/parts-dj7AcUg0.d.ts +0 -36
- package/dist/run-PfLmDAox.d.ts +0 -41
- package/dist/tool-call-feed-D9iofJgW.d.ts +0 -46
package/dist/chunk-ZKCEAIIZ.js
DELETED
|
@@ -1,1395 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Tabs,
|
|
3
|
-
TabsContent,
|
|
4
|
-
TabsList,
|
|
5
|
-
TabsTrigger
|
|
6
|
-
} from "./chunk-Q56BYXQF.js";
|
|
7
|
-
import {
|
|
8
|
-
ArtifactPane
|
|
9
|
-
} from "./chunk-LTEKI7YJ.js";
|
|
10
|
-
import {
|
|
11
|
-
Markdown
|
|
12
|
-
} from "./chunk-EOAX2ENE.js";
|
|
13
|
-
import {
|
|
14
|
-
cn
|
|
15
|
-
} from "./chunk-RQHJBTEU.js";
|
|
16
|
-
|
|
17
|
-
// src/editor/document-editor-pane.tsx
|
|
18
|
-
import { useEffect as useEffect5, useMemo as useMemo4, useState as useState3 } from "react";
|
|
19
|
-
import { PencilLine, Save, Users, Wifi, WifiOff } from "lucide-react";
|
|
20
|
-
|
|
21
|
-
// src/editor/tiptap-editor.tsx
|
|
22
|
-
import { Suspense as Suspense2, useEffect as useEffect2, useMemo as useMemo2 } from "react";
|
|
23
|
-
|
|
24
|
-
// src/editor/editor-provider.tsx
|
|
25
|
-
import {
|
|
26
|
-
createContext,
|
|
27
|
-
Suspense,
|
|
28
|
-
useCallback,
|
|
29
|
-
useContext,
|
|
30
|
-
useEffect,
|
|
31
|
-
useMemo,
|
|
32
|
-
useRef,
|
|
33
|
-
useState
|
|
34
|
-
} from "react";
|
|
35
|
-
|
|
36
|
-
// src/editor/editor-lazy.ts
|
|
37
|
-
import { lazy } from "react";
|
|
38
|
-
|
|
39
|
-
// src/editor/editor-peers.ts
|
|
40
|
-
var DOCUMENT_PEERS_MISSING = "@tangle-network/ui/editor needs its optional editor peers. Install @tiptap/react and @tiptap/starter-kit.";
|
|
41
|
-
var PROVIDER_PEERS_MISSING = "@tangle-network/ui/editor needs its optional collaboration transport peers. Install @hocuspocus/provider and yjs.";
|
|
42
|
-
var COLLABORATION_PEERS_MISSING = "@tangle-network/ui/editor needs its optional collaboration peers. Install @tiptap/react, @tiptap/starter-kit, @tiptap/extension-collaboration, @tiptap/extension-collaboration-caret, @hocuspocus/provider and yjs.";
|
|
43
|
-
var MissingEditorPeersError = class extends Error {
|
|
44
|
-
constructor(message, options) {
|
|
45
|
-
super(message, options);
|
|
46
|
-
this.name = "MissingEditorPeersError";
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
function isMissingEditorPeersError(error) {
|
|
50
|
-
return error instanceof Error && error.name === "MissingEditorPeersError";
|
|
51
|
-
}
|
|
52
|
-
var RESOLUTION_FAILURE = /could not resolve|cannot find (?:module|package)|can't resolve|failed to resolve|module not found/i;
|
|
53
|
-
function isMissingPeerError(error) {
|
|
54
|
-
return error instanceof Error && RESOLUTION_FAILURE.test(error.message);
|
|
55
|
-
}
|
|
56
|
-
function rethrow(error) {
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
var DEFERRED_PEERS = [
|
|
60
|
-
"@tiptap/core",
|
|
61
|
-
"@tiptap/react",
|
|
62
|
-
"@tiptap/starter-kit",
|
|
63
|
-
"@tiptap/extension-collaboration",
|
|
64
|
-
"@tiptap/extension-collaboration-caret",
|
|
65
|
-
"@hocuspocus/provider",
|
|
66
|
-
"yjs"
|
|
67
|
-
];
|
|
68
|
-
function unresolvedPeersFrom(error) {
|
|
69
|
-
if (!(error instanceof Error)) return [];
|
|
70
|
-
const named = DEFERRED_PEERS.filter((name) => error.message.includes(name));
|
|
71
|
-
return named.filter(
|
|
72
|
-
(name) => !named.some((other) => other !== name && other.includes(name))
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
function asMissingEditorPeersError(error, missingMessage) {
|
|
76
|
-
if (!isMissingPeerError(error)) return error;
|
|
77
|
-
const unresolved = unresolvedPeersFrom(error);
|
|
78
|
-
if (unresolved.length === 0) return error;
|
|
79
|
-
const detail = unresolved.length === 1 ? ` ${unresolved[0]} did not resolve.` : "";
|
|
80
|
-
return new MissingEditorPeersError(missingMessage + detail, { cause: error });
|
|
81
|
-
}
|
|
82
|
-
async function loadPeers(load, missingMessage) {
|
|
83
|
-
try {
|
|
84
|
-
return await load();
|
|
85
|
-
} catch (error) {
|
|
86
|
-
throw asMissingEditorPeersError(error, missingMessage);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function isConfigurableExtension(value) {
|
|
90
|
-
return typeof value?.configure === "function";
|
|
91
|
-
}
|
|
92
|
-
function assertDocumentEditorPeers(peers, missingMessage) {
|
|
93
|
-
if (typeof peers.react.useEditor !== "function" || peers.react.EditorContent === void 0 || !isConfigurableExtension(peers.starterKit.default)) {
|
|
94
|
-
throw new MissingEditorPeersError(missingMessage);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function assertEditorProviderPeers(peers, missingMessage) {
|
|
98
|
-
if (typeof peers.hocuspocus.HocuspocusProvider !== "function" || typeof peers.yjs.Doc !== "function") {
|
|
99
|
-
throw new MissingEditorPeersError(missingMessage);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function assertCollaborationPeers(peers) {
|
|
103
|
-
assertDocumentEditorPeers(peers, COLLABORATION_PEERS_MISSING);
|
|
104
|
-
assertEditorProviderPeers(peers, COLLABORATION_PEERS_MISSING);
|
|
105
|
-
if (!isConfigurableExtension(peers.collaboration.default) || !isConfigurableExtension(peers.collaborationCaret.default)) {
|
|
106
|
-
throw new MissingEditorPeersError(COLLABORATION_PEERS_MISSING);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function importDocumentEditorPeers() {
|
|
110
|
-
const [react, starterKit] = await Promise.all([
|
|
111
|
-
import("@tiptap/react").catch(rethrow),
|
|
112
|
-
import("@tiptap/starter-kit").catch(rethrow)
|
|
113
|
-
]);
|
|
114
|
-
return { react, starterKit };
|
|
115
|
-
}
|
|
116
|
-
async function importEditorProviderPeers() {
|
|
117
|
-
const [hocuspocus, yjs] = await Promise.all([
|
|
118
|
-
import("@hocuspocus/provider").catch(rethrow),
|
|
119
|
-
import("yjs").catch(rethrow)
|
|
120
|
-
]);
|
|
121
|
-
return { hocuspocus, yjs };
|
|
122
|
-
}
|
|
123
|
-
async function loadEditorProviderPeers() {
|
|
124
|
-
const peers = await loadPeers(importEditorProviderPeers, PROVIDER_PEERS_MISSING);
|
|
125
|
-
assertEditorProviderPeers(peers, PROVIDER_PEERS_MISSING);
|
|
126
|
-
return peers;
|
|
127
|
-
}
|
|
128
|
-
async function loadDocumentEditorPeers() {
|
|
129
|
-
const peers = await loadPeers(importDocumentEditorPeers, DOCUMENT_PEERS_MISSING);
|
|
130
|
-
assertDocumentEditorPeers(peers, DOCUMENT_PEERS_MISSING);
|
|
131
|
-
return peers;
|
|
132
|
-
}
|
|
133
|
-
async function loadCollaborationPeers() {
|
|
134
|
-
const peers = await loadPeers(async () => {
|
|
135
|
-
const [documentPeers, providerPeers, collaboration, collaborationCaret] = await Promise.all([
|
|
136
|
-
importDocumentEditorPeers(),
|
|
137
|
-
importEditorProviderPeers(),
|
|
138
|
-
import("@tiptap/extension-collaboration").catch(rethrow),
|
|
139
|
-
import("@tiptap/extension-collaboration-caret").catch(rethrow)
|
|
140
|
-
]);
|
|
141
|
-
return { ...documentPeers, ...providerPeers, collaboration, collaborationCaret };
|
|
142
|
-
}, COLLABORATION_PEERS_MISSING);
|
|
143
|
-
assertCollaborationPeers(peers);
|
|
144
|
-
return peers;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// src/editor/editor-lazy.ts
|
|
148
|
-
function retryableLazyEditor(loadComponent) {
|
|
149
|
-
function build() {
|
|
150
|
-
return lazy(async () => {
|
|
151
|
-
try {
|
|
152
|
-
return { default: await loadComponent() };
|
|
153
|
-
} catch (error) {
|
|
154
|
-
if (!isMissingEditorPeersError(error)) {
|
|
155
|
-
current = build();
|
|
156
|
-
}
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
let current = build();
|
|
162
|
-
return () => current;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// src/editor/editor-provider.tsx
|
|
166
|
-
import { jsx } from "react/jsx-runtime";
|
|
167
|
-
var EditorContext = createContext(null);
|
|
168
|
-
function generateUserColor() {
|
|
169
|
-
const colors = [
|
|
170
|
-
"#FF6B6B",
|
|
171
|
-
// Red
|
|
172
|
-
"#4ECDC4",
|
|
173
|
-
// Teal
|
|
174
|
-
"#45B7D1",
|
|
175
|
-
// Blue
|
|
176
|
-
"#96CEB4",
|
|
177
|
-
// Green
|
|
178
|
-
"#FFEAA7",
|
|
179
|
-
// Yellow
|
|
180
|
-
"#DDA0DD",
|
|
181
|
-
// Plum
|
|
182
|
-
"#98D8C8",
|
|
183
|
-
// Mint
|
|
184
|
-
"#F7DC6F",
|
|
185
|
-
// Gold
|
|
186
|
-
"#BB8FCE",
|
|
187
|
-
// Purple
|
|
188
|
-
"#85C1E9"
|
|
189
|
-
// Sky
|
|
190
|
-
];
|
|
191
|
-
return colors[Math.floor(Math.random() * colors.length)];
|
|
192
|
-
}
|
|
193
|
-
function createEditorProvider(peers) {
|
|
194
|
-
const { Doc } = peers.yjs;
|
|
195
|
-
const { HocuspocusProvider } = peers.hocuspocus;
|
|
196
|
-
return function EditorProvider2({
|
|
197
|
-
websocketUrl,
|
|
198
|
-
documentName,
|
|
199
|
-
token,
|
|
200
|
-
tokenExpiresAt,
|
|
201
|
-
user,
|
|
202
|
-
autoConnect = true,
|
|
203
|
-
autoReconnect = true,
|
|
204
|
-
maxReconnectAttempts = 5,
|
|
205
|
-
onConnectionChange,
|
|
206
|
-
onSync,
|
|
207
|
-
onAuthError,
|
|
208
|
-
onRefreshToken,
|
|
209
|
-
children
|
|
210
|
-
}) {
|
|
211
|
-
const [connectionState, setConnectionState] = useState("disconnected");
|
|
212
|
-
const [collaborators, setCollaborators] = useState([]);
|
|
213
|
-
const [isSynced, setIsSynced] = useState(false);
|
|
214
|
-
const docRef = useRef(null);
|
|
215
|
-
const providerRef = useRef(null);
|
|
216
|
-
const reconnectAttemptsRef = useRef(0);
|
|
217
|
-
const tokenRef = useRef(token);
|
|
218
|
-
const tokenExpiryRef = useRef(tokenExpiresAt);
|
|
219
|
-
const refreshPromiseRef = useRef(null);
|
|
220
|
-
const refreshTimerRef = useRef(null);
|
|
221
|
-
tokenRef.current = token;
|
|
222
|
-
tokenExpiryRef.current = tokenExpiresAt;
|
|
223
|
-
if (!docRef.current) {
|
|
224
|
-
docRef.current = new Doc();
|
|
225
|
-
}
|
|
226
|
-
const doc = docRef.current;
|
|
227
|
-
const userColor = useMemo(
|
|
228
|
-
() => user.color ?? generateUserColor(),
|
|
229
|
-
[user.color]
|
|
230
|
-
);
|
|
231
|
-
const updateConnectionState = useCallback(
|
|
232
|
-
(state) => {
|
|
233
|
-
setConnectionState(state);
|
|
234
|
-
onConnectionChange?.(state);
|
|
235
|
-
},
|
|
236
|
-
[onConnectionChange]
|
|
237
|
-
);
|
|
238
|
-
const updateCollaborators = useCallback(
|
|
239
|
-
(awareness) => {
|
|
240
|
-
if (!awareness) return;
|
|
241
|
-
const states = awareness.getStates();
|
|
242
|
-
const collabs = [];
|
|
243
|
-
states.forEach((state, clientId) => {
|
|
244
|
-
if (clientId === awareness.clientID) return;
|
|
245
|
-
if (state.user) {
|
|
246
|
-
collabs.push({
|
|
247
|
-
clientId,
|
|
248
|
-
user: state.user
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
setCollaborators(collabs);
|
|
253
|
-
},
|
|
254
|
-
[]
|
|
255
|
-
);
|
|
256
|
-
const clearRefreshTimer = useCallback(() => {
|
|
257
|
-
if (refreshTimerRef.current != null) {
|
|
258
|
-
window.clearTimeout(refreshTimerRef.current);
|
|
259
|
-
refreshTimerRef.current = null;
|
|
260
|
-
}
|
|
261
|
-
}, []);
|
|
262
|
-
const refreshToken = useCallback(async () => {
|
|
263
|
-
if (!onRefreshToken) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
if (refreshPromiseRef.current) {
|
|
267
|
-
return refreshPromiseRef.current;
|
|
268
|
-
}
|
|
269
|
-
const refreshPromise = (async () => {
|
|
270
|
-
const next = await onRefreshToken();
|
|
271
|
-
const resolvedToken = typeof next === "string" ? next : next.token;
|
|
272
|
-
const resolvedExpiry = typeof next === "string" ? void 0 : next.expiresAt;
|
|
273
|
-
tokenRef.current = resolvedToken;
|
|
274
|
-
tokenExpiryRef.current = resolvedExpiry;
|
|
275
|
-
return resolvedToken;
|
|
276
|
-
})().catch((error) => {
|
|
277
|
-
onAuthError?.(
|
|
278
|
-
error instanceof Error ? error : new Error(String(error))
|
|
279
|
-
);
|
|
280
|
-
return null;
|
|
281
|
-
}).finally(() => {
|
|
282
|
-
refreshPromiseRef.current = null;
|
|
283
|
-
});
|
|
284
|
-
refreshPromiseRef.current = refreshPromise;
|
|
285
|
-
return refreshPromise;
|
|
286
|
-
}, [onAuthError, onRefreshToken]);
|
|
287
|
-
const scheduleTokenRefresh = useCallback(() => {
|
|
288
|
-
clearRefreshTimer();
|
|
289
|
-
if (!tokenExpiryRef.current || !onRefreshToken || typeof window === "undefined") {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const refreshAtMs = tokenExpiryRef.current * 1e3 - 6e4;
|
|
293
|
-
const delay = refreshAtMs - Date.now();
|
|
294
|
-
if (delay <= 0) {
|
|
295
|
-
void refreshToken();
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
refreshTimerRef.current = window.setTimeout(() => {
|
|
299
|
-
void refreshToken();
|
|
300
|
-
}, delay);
|
|
301
|
-
}, [clearRefreshTimer, onRefreshToken, refreshToken]);
|
|
302
|
-
const connect = useCallback(() => {
|
|
303
|
-
if (providerRef.current) {
|
|
304
|
-
providerRef.current.connect();
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
updateConnectionState("connecting");
|
|
308
|
-
const provider = new HocuspocusProvider({
|
|
309
|
-
url: websocketUrl,
|
|
310
|
-
name: documentName,
|
|
311
|
-
document: doc,
|
|
312
|
-
token: async () => tokenRef.current,
|
|
313
|
-
onConnect: () => {
|
|
314
|
-
reconnectAttemptsRef.current = 0;
|
|
315
|
-
updateConnectionState("connected");
|
|
316
|
-
scheduleTokenRefresh();
|
|
317
|
-
},
|
|
318
|
-
onSynced: () => {
|
|
319
|
-
setIsSynced(true);
|
|
320
|
-
updateConnectionState("synced");
|
|
321
|
-
onSync?.();
|
|
322
|
-
},
|
|
323
|
-
onDisconnect: () => {
|
|
324
|
-
updateConnectionState("disconnected");
|
|
325
|
-
setIsSynced(false);
|
|
326
|
-
clearRefreshTimer();
|
|
327
|
-
if (autoReconnect && reconnectAttemptsRef.current < maxReconnectAttempts) {
|
|
328
|
-
reconnectAttemptsRef.current += 1;
|
|
329
|
-
const delay = Math.min(
|
|
330
|
-
1e3 * 2 ** reconnectAttemptsRef.current,
|
|
331
|
-
3e4
|
|
332
|
-
);
|
|
333
|
-
setTimeout(() => {
|
|
334
|
-
if (providerRef.current && !providerRef.current.isConnected) {
|
|
335
|
-
providerRef.current.connect();
|
|
336
|
-
}
|
|
337
|
-
}, delay);
|
|
338
|
-
}
|
|
339
|
-
},
|
|
340
|
-
onAuthenticationFailed: ({ reason }) => {
|
|
341
|
-
const error = new Error(reason ?? "Authentication failed");
|
|
342
|
-
updateConnectionState("disconnected");
|
|
343
|
-
clearRefreshTimer();
|
|
344
|
-
if (onRefreshToken) {
|
|
345
|
-
void refreshToken().then((nextToken) => {
|
|
346
|
-
if (nextToken && providerRef.current) {
|
|
347
|
-
providerRef.current.connect();
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
onAuthError?.(error);
|
|
351
|
-
});
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
onAuthError?.(error);
|
|
355
|
-
},
|
|
356
|
-
onAwarenessUpdate: () => {
|
|
357
|
-
updateCollaborators(provider.awareness);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
provider.awareness?.setLocalStateField("user", {
|
|
361
|
-
name: user.name,
|
|
362
|
-
color: userColor,
|
|
363
|
-
userId: user.userId
|
|
364
|
-
});
|
|
365
|
-
providerRef.current = provider;
|
|
366
|
-
}, [
|
|
367
|
-
websocketUrl,
|
|
368
|
-
documentName,
|
|
369
|
-
doc,
|
|
370
|
-
user.name,
|
|
371
|
-
user.userId,
|
|
372
|
-
userColor,
|
|
373
|
-
autoReconnect,
|
|
374
|
-
maxReconnectAttempts,
|
|
375
|
-
clearRefreshTimer,
|
|
376
|
-
updateConnectionState,
|
|
377
|
-
updateCollaborators,
|
|
378
|
-
onSync,
|
|
379
|
-
onAuthError,
|
|
380
|
-
onRefreshToken,
|
|
381
|
-
refreshToken,
|
|
382
|
-
scheduleTokenRefresh
|
|
383
|
-
]);
|
|
384
|
-
const disconnect = useCallback(() => {
|
|
385
|
-
if (providerRef.current) {
|
|
386
|
-
providerRef.current.disconnect();
|
|
387
|
-
updateConnectionState("disconnected");
|
|
388
|
-
}
|
|
389
|
-
}, [updateConnectionState]);
|
|
390
|
-
useEffect(() => {
|
|
391
|
-
if (autoConnect) {
|
|
392
|
-
connect();
|
|
393
|
-
}
|
|
394
|
-
return () => {
|
|
395
|
-
clearRefreshTimer();
|
|
396
|
-
if (providerRef.current) {
|
|
397
|
-
providerRef.current.destroy();
|
|
398
|
-
providerRef.current = null;
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
}, [autoConnect, clearRefreshTimer, connect]);
|
|
402
|
-
const contextValue = useMemo(
|
|
403
|
-
() => ({
|
|
404
|
-
doc,
|
|
405
|
-
provider: providerRef.current,
|
|
406
|
-
connectionState,
|
|
407
|
-
collaborators,
|
|
408
|
-
isSynced,
|
|
409
|
-
connect,
|
|
410
|
-
disconnect
|
|
411
|
-
}),
|
|
412
|
-
[doc, connectionState, collaborators, isSynced, connect, disconnect]
|
|
413
|
-
);
|
|
414
|
-
return /* @__PURE__ */ jsx(EditorContext.Provider, { value: contextValue, children });
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
var lazyEditorProvider = retryableLazyEditor(
|
|
418
|
-
async () => createEditorProvider(await loadEditorProviderPeers())
|
|
419
|
-
);
|
|
420
|
-
function EditorProvider(props) {
|
|
421
|
-
const LazyEditorProvider = lazyEditorProvider();
|
|
422
|
-
return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(LazyEditorProvider, { ...props }) });
|
|
423
|
-
}
|
|
424
|
-
function useEditorContext() {
|
|
425
|
-
const context = useContext(EditorContext);
|
|
426
|
-
if (!context) {
|
|
427
|
-
throw new Error("useEditorContext must be used within an EditorProvider");
|
|
428
|
-
}
|
|
429
|
-
return context;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// src/editor/editor-loading.tsx
|
|
433
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
434
|
-
function EditorLoadingPlaceholder({ className }) {
|
|
435
|
-
return /* @__PURE__ */ jsx2(
|
|
436
|
-
"div",
|
|
437
|
-
{
|
|
438
|
-
role: "status",
|
|
439
|
-
"aria-live": "polite",
|
|
440
|
-
className: cn(
|
|
441
|
-
"flex w-full items-center justify-center rounded-lg border border-border border-dashed bg-muted text-muted-foreground text-sm",
|
|
442
|
-
className
|
|
443
|
-
),
|
|
444
|
-
children: "Loading editor\u2026"
|
|
445
|
-
}
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// src/editor/editor-toolbar.tsx
|
|
450
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
451
|
-
function EditorToolbar({
|
|
452
|
-
editor,
|
|
453
|
-
className
|
|
454
|
-
}) {
|
|
455
|
-
if (!editor) {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
const buttons = [
|
|
459
|
-
{
|
|
460
|
-
id: "bold",
|
|
461
|
-
label: "Bold",
|
|
462
|
-
action: () => editor.chain().focus().toggleBold().run(),
|
|
463
|
-
isActive: editor.isActive("bold"),
|
|
464
|
-
shortcut: "Ctrl+B"
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
id: "italic",
|
|
468
|
-
label: "Italic",
|
|
469
|
-
action: () => editor.chain().focus().toggleItalic().run(),
|
|
470
|
-
isActive: editor.isActive("italic"),
|
|
471
|
-
shortcut: "Ctrl+I"
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
id: "strike",
|
|
475
|
-
label: "Strike",
|
|
476
|
-
action: () => editor.chain().focus().toggleStrike().run(),
|
|
477
|
-
isActive: editor.isActive("strike"),
|
|
478
|
-
shortcut: "Ctrl+Shift+X"
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
id: "code",
|
|
482
|
-
label: "Code",
|
|
483
|
-
action: () => editor.chain().focus().toggleCode().run(),
|
|
484
|
-
isActive: editor.isActive("code"),
|
|
485
|
-
shortcut: "Ctrl+E"
|
|
486
|
-
},
|
|
487
|
-
{ id: "sep-1", type: "separator" },
|
|
488
|
-
{
|
|
489
|
-
id: "h1",
|
|
490
|
-
label: "H1",
|
|
491
|
-
action: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
|
492
|
-
isActive: editor.isActive("heading", { level: 1 })
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
id: "h2",
|
|
496
|
-
label: "H2",
|
|
497
|
-
action: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
498
|
-
isActive: editor.isActive("heading", { level: 2 })
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
id: "h3",
|
|
502
|
-
label: "H3",
|
|
503
|
-
action: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
504
|
-
isActive: editor.isActive("heading", { level: 3 })
|
|
505
|
-
},
|
|
506
|
-
{ id: "sep-2", type: "separator" },
|
|
507
|
-
{
|
|
508
|
-
id: "bullet-list",
|
|
509
|
-
label: "Bullet List",
|
|
510
|
-
action: () => editor.chain().focus().toggleBulletList().run(),
|
|
511
|
-
isActive: editor.isActive("bulletList")
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
id: "ordered-list",
|
|
515
|
-
label: "Ordered List",
|
|
516
|
-
action: () => editor.chain().focus().toggleOrderedList().run(),
|
|
517
|
-
isActive: editor.isActive("orderedList")
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
id: "code-block",
|
|
521
|
-
label: "Code Block",
|
|
522
|
-
action: () => editor.chain().focus().toggleCodeBlock().run(),
|
|
523
|
-
isActive: editor.isActive("codeBlock")
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
id: "blockquote",
|
|
527
|
-
label: "Blockquote",
|
|
528
|
-
action: () => editor.chain().focus().toggleBlockquote().run(),
|
|
529
|
-
isActive: editor.isActive("blockquote")
|
|
530
|
-
}
|
|
531
|
-
];
|
|
532
|
-
return /* @__PURE__ */ jsx3(
|
|
533
|
-
"div",
|
|
534
|
-
{
|
|
535
|
-
className: cn(
|
|
536
|
-
"flex items-center gap-1 border-border border-b bg-card p-2",
|
|
537
|
-
className
|
|
538
|
-
),
|
|
539
|
-
children: buttons.map((button) => {
|
|
540
|
-
if ("type" in button && button.type === "separator") {
|
|
541
|
-
return /* @__PURE__ */ jsx3("div", { className: "mx-1 h-6 w-px bg-border" }, button.id);
|
|
542
|
-
}
|
|
543
|
-
return /* @__PURE__ */ jsx3(
|
|
544
|
-
"button",
|
|
545
|
-
{
|
|
546
|
-
onClick: button.action,
|
|
547
|
-
type: "button",
|
|
548
|
-
title: "shortcut" in button ? `${button.label} (${button.shortcut})` : button.label,
|
|
549
|
-
className: cn(
|
|
550
|
-
"rounded px-2 py-1 font-medium text-xs transition-colors",
|
|
551
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
552
|
-
button.isActive && "bg-accent text-accent-foreground"
|
|
553
|
-
),
|
|
554
|
-
children: button.label
|
|
555
|
-
},
|
|
556
|
-
button.id
|
|
557
|
-
);
|
|
558
|
-
})
|
|
559
|
-
}
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// src/editor/tiptap-editor.tsx
|
|
564
|
-
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
565
|
-
var cursorColors = {
|
|
566
|
-
"#FF6B6B": { background: "#FF6B6B", text: "#FFFFFF" },
|
|
567
|
-
"#4ECDC4": { background: "#4ECDC4", text: "#000000" },
|
|
568
|
-
"#45B7D1": { background: "#45B7D1", text: "#000000" },
|
|
569
|
-
"#96CEB4": { background: "#96CEB4", text: "#000000" },
|
|
570
|
-
"#FFEAA7": { background: "#FFEAA7", text: "#000000" },
|
|
571
|
-
"#DDA0DD": { background: "#DDA0DD", text: "#000000" },
|
|
572
|
-
"#98D8C8": { background: "#98D8C8", text: "#000000" },
|
|
573
|
-
"#F7DC6F": { background: "#F7DC6F", text: "#000000" },
|
|
574
|
-
"#BB8FCE": { background: "#BB8FCE", text: "#000000" },
|
|
575
|
-
"#85C1E9": { background: "#85C1E9", text: "#000000" }
|
|
576
|
-
};
|
|
577
|
-
function getCursorColors(color) {
|
|
578
|
-
return cursorColors[color] ?? { background: color, text: "#FFFFFF" };
|
|
579
|
-
}
|
|
580
|
-
function createTiptapEditor(peers) {
|
|
581
|
-
const { EditorContent, useEditor } = peers.react;
|
|
582
|
-
const StarterKit = peers.starterKit.default;
|
|
583
|
-
const Collaboration = peers.collaboration.default;
|
|
584
|
-
const CollaborationCaret = peers.collaborationCaret.default;
|
|
585
|
-
return function TiptapEditor2({
|
|
586
|
-
initialContent,
|
|
587
|
-
placeholder = "Start writing...",
|
|
588
|
-
readOnly = false,
|
|
589
|
-
autoFocus = false,
|
|
590
|
-
className,
|
|
591
|
-
contentClassName,
|
|
592
|
-
onUpdate,
|
|
593
|
-
onSelectionUpdate,
|
|
594
|
-
onReady
|
|
595
|
-
}) {
|
|
596
|
-
const { doc, provider, connectionState } = useEditorContext();
|
|
597
|
-
const fragment = useMemo2(() => doc.getXmlFragment("prosemirror"), [doc]);
|
|
598
|
-
const extensions = useMemo2(() => {
|
|
599
|
-
const baseExtensions = [
|
|
600
|
-
StarterKit.configure({
|
|
601
|
-
// Disable history - Y.js handles undo/redo
|
|
602
|
-
...{ history: false },
|
|
603
|
-
// Configure code block for syntax highlighting placeholder
|
|
604
|
-
codeBlock: {
|
|
605
|
-
HTMLAttributes: {
|
|
606
|
-
class: "hljs"
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}),
|
|
610
|
-
Collaboration.configure({
|
|
611
|
-
fragment
|
|
612
|
-
})
|
|
613
|
-
];
|
|
614
|
-
if (provider?.awareness) {
|
|
615
|
-
baseExtensions.push(
|
|
616
|
-
CollaborationCaret.configure({
|
|
617
|
-
provider,
|
|
618
|
-
user: provider.awareness.getLocalState()?.user ?? {
|
|
619
|
-
name: "Anonymous",
|
|
620
|
-
color: "#808080"
|
|
621
|
-
},
|
|
622
|
-
render: (user) => {
|
|
623
|
-
const { background, text } = getCursorColors(user.color);
|
|
624
|
-
const cursor = document.createElement("span");
|
|
625
|
-
cursor.className = "collaboration-cursor";
|
|
626
|
-
cursor.style.borderColor = background;
|
|
627
|
-
const label = document.createElement("span");
|
|
628
|
-
label.className = "collaboration-cursor-label";
|
|
629
|
-
label.style.backgroundColor = background;
|
|
630
|
-
label.style.color = text;
|
|
631
|
-
label.textContent = user.name;
|
|
632
|
-
cursor.appendChild(label);
|
|
633
|
-
return cursor;
|
|
634
|
-
}
|
|
635
|
-
})
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
return baseExtensions;
|
|
639
|
-
}, [fragment, provider]);
|
|
640
|
-
const editor = useEditor({
|
|
641
|
-
extensions,
|
|
642
|
-
editable: !readOnly,
|
|
643
|
-
autofocus: autoFocus,
|
|
644
|
-
editorProps: {
|
|
645
|
-
attributes: {
|
|
646
|
-
class: cn(
|
|
647
|
-
"prose prose-sm sm:prose-base dark:prose-invert max-w-none",
|
|
648
|
-
"focus:outline-none",
|
|
649
|
-
contentClassName
|
|
650
|
-
),
|
|
651
|
-
"data-placeholder": placeholder
|
|
652
|
-
}
|
|
653
|
-
},
|
|
654
|
-
onUpdate: ({ editor: ed }) => {
|
|
655
|
-
onUpdate?.(ed);
|
|
656
|
-
},
|
|
657
|
-
onSelectionUpdate: ({ editor: ed }) => {
|
|
658
|
-
onSelectionUpdate?.(ed);
|
|
659
|
-
},
|
|
660
|
-
onCreate: ({ editor: ed }) => {
|
|
661
|
-
onReady?.(ed);
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
useEffect2(() => {
|
|
665
|
-
if (editor) {
|
|
666
|
-
editor.setEditable(!readOnly);
|
|
667
|
-
}
|
|
668
|
-
}, [editor, readOnly]);
|
|
669
|
-
useEffect2(() => {
|
|
670
|
-
if (editor && initialContent && connectionState === "synced" && editor.isEmpty) {
|
|
671
|
-
editor.commands.setContent(initialContent);
|
|
672
|
-
}
|
|
673
|
-
}, [editor, initialContent, connectionState]);
|
|
674
|
-
return /* @__PURE__ */ jsxs(
|
|
675
|
-
"div",
|
|
676
|
-
{
|
|
677
|
-
className: cn(
|
|
678
|
-
"relative min-h-[200px] w-full rounded-lg border border-border",
|
|
679
|
-
"bg-background",
|
|
680
|
-
className
|
|
681
|
-
),
|
|
682
|
-
children: [
|
|
683
|
-
/* @__PURE__ */ jsx4("div", { className: "absolute top-2 right-2 z-10", children: /* @__PURE__ */ jsx4(ConnectionIndicator, { state: connectionState }) }),
|
|
684
|
-
/* @__PURE__ */ jsx4("div", { className: "p-4 pt-10", children: /* @__PURE__ */ jsx4(EditorContent, { editor }) }),
|
|
685
|
-
/* @__PURE__ */ jsx4("style", { children: `
|
|
686
|
-
.ProseMirror p.is-editor-empty:first-child::before {
|
|
687
|
-
content: attr(data-placeholder);
|
|
688
|
-
float: left;
|
|
689
|
-
color: var(--muted-foreground, #999);
|
|
690
|
-
pointer-events: none;
|
|
691
|
-
height: 0;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
.collaboration-cursor {
|
|
695
|
-
position: relative;
|
|
696
|
-
border-left: 2px solid;
|
|
697
|
-
margin-left: -1px;
|
|
698
|
-
margin-right: -1px;
|
|
699
|
-
pointer-events: none;
|
|
700
|
-
word-break: normal;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.collaboration-cursor-label {
|
|
704
|
-
position: absolute;
|
|
705
|
-
top: -1.4em;
|
|
706
|
-
left: -1px;
|
|
707
|
-
font-size: 12px;
|
|
708
|
-
font-style: normal;
|
|
709
|
-
font-weight: 600;
|
|
710
|
-
line-height: normal;
|
|
711
|
-
padding: 2px 6px;
|
|
712
|
-
border-radius: 4px 4px 4px 0;
|
|
713
|
-
white-space: nowrap;
|
|
714
|
-
user-select: none;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.ProseMirror pre {
|
|
718
|
-
background: var(--muted, #f4f4f5);
|
|
719
|
-
border-radius: 0.375rem;
|
|
720
|
-
padding: 0.75rem 1rem;
|
|
721
|
-
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
722
|
-
font-size: 0.875rem;
|
|
723
|
-
overflow-x: auto;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
.dark .ProseMirror pre {
|
|
727
|
-
background: var(--muted, #27272a);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
.ProseMirror code {
|
|
731
|
-
background: var(--muted, #f4f4f5);
|
|
732
|
-
border-radius: 0.25rem;
|
|
733
|
-
padding: 0.125rem 0.25rem;
|
|
734
|
-
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
735
|
-
font-size: 0.875em;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
.dark .ProseMirror code {
|
|
739
|
-
background: var(--muted, #27272a);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
.ProseMirror pre code {
|
|
743
|
-
background: transparent;
|
|
744
|
-
padding: 0;
|
|
745
|
-
font-size: inherit;
|
|
746
|
-
}
|
|
747
|
-
` })
|
|
748
|
-
]
|
|
749
|
-
}
|
|
750
|
-
);
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
var lazyTiptapEditor = retryableLazyEditor(
|
|
754
|
-
async () => createTiptapEditor(await loadCollaborationPeers())
|
|
755
|
-
);
|
|
756
|
-
function TiptapEditor(props) {
|
|
757
|
-
const LazyTiptapEditor = lazyTiptapEditor();
|
|
758
|
-
return /* @__PURE__ */ jsx4(
|
|
759
|
-
Suspense2,
|
|
760
|
-
{
|
|
761
|
-
fallback: /* @__PURE__ */ jsx4(EditorLoadingPlaceholder, { className: cn("min-h-[200px]", props.className) }),
|
|
762
|
-
children: /* @__PURE__ */ jsx4(LazyTiptapEditor, { ...props })
|
|
763
|
-
}
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
function ConnectionIndicator({
|
|
767
|
-
state
|
|
768
|
-
}) {
|
|
769
|
-
const config = {
|
|
770
|
-
disconnected: {
|
|
771
|
-
color: "bg-red-500",
|
|
772
|
-
label: "Disconnected"
|
|
773
|
-
},
|
|
774
|
-
connecting: {
|
|
775
|
-
color: "bg-yellow-500 animate-pulse",
|
|
776
|
-
label: "Connecting..."
|
|
777
|
-
},
|
|
778
|
-
connected: {
|
|
779
|
-
color: "bg-blue-500",
|
|
780
|
-
label: "Connected"
|
|
781
|
-
},
|
|
782
|
-
synced: {
|
|
783
|
-
color: "bg-green-500",
|
|
784
|
-
label: "Synced"
|
|
785
|
-
}
|
|
786
|
-
}[state];
|
|
787
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-muted-foreground text-xs", children: [
|
|
788
|
-
/* @__PURE__ */ jsx4("span", { className: cn("h-2 w-2 rounded-full", config.color) }),
|
|
789
|
-
/* @__PURE__ */ jsx4("span", { children: config.label })
|
|
790
|
-
] });
|
|
791
|
-
}
|
|
792
|
-
function CollaboratorsList({
|
|
793
|
-
collaborators,
|
|
794
|
-
className
|
|
795
|
-
}) {
|
|
796
|
-
if (collaborators.length === 0) {
|
|
797
|
-
return null;
|
|
798
|
-
}
|
|
799
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1", className), children: [
|
|
800
|
-
collaborators.slice(0, 5).map((collab) => /* @__PURE__ */ jsx4(
|
|
801
|
-
"div",
|
|
802
|
-
{
|
|
803
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full font-medium text-xs",
|
|
804
|
-
style: { backgroundColor: collab.user.color },
|
|
805
|
-
title: collab.user.name,
|
|
806
|
-
children: collab.user.name.charAt(0).toUpperCase()
|
|
807
|
-
},
|
|
808
|
-
collab.clientId
|
|
809
|
-
)),
|
|
810
|
-
collaborators.length > 5 && /* @__PURE__ */ jsxs("div", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs", children: [
|
|
811
|
-
"+",
|
|
812
|
-
collaborators.length - 5
|
|
813
|
-
] })
|
|
814
|
-
] });
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// src/editor/markdown-document-editor.tsx
|
|
818
|
-
import { Suspense as Suspense3, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2 } from "react";
|
|
819
|
-
|
|
820
|
-
// src/editor/markdown-conversion.ts
|
|
821
|
-
import { marked } from "marked";
|
|
822
|
-
import TurndownService from "turndown";
|
|
823
|
-
var turndown = new TurndownService({
|
|
824
|
-
bulletListMarker: "-",
|
|
825
|
-
codeBlockStyle: "fenced",
|
|
826
|
-
emDelimiter: "*",
|
|
827
|
-
headingStyle: "atx"
|
|
828
|
-
});
|
|
829
|
-
function markdownToHtml(markdown) {
|
|
830
|
-
return String(marked.parse(markdown, { async: false, gfm: true }));
|
|
831
|
-
}
|
|
832
|
-
function htmlToMarkdown(html) {
|
|
833
|
-
return turndown.turndown(html);
|
|
834
|
-
}
|
|
835
|
-
function normalizeMarkdown(markdown) {
|
|
836
|
-
return markdown.replace(/\r\n/g, "\n").trimEnd();
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// src/editor/markdown-document-editor.tsx
|
|
840
|
-
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
841
|
-
function createMarkdownDocumentEditor(peers) {
|
|
842
|
-
const { EditorContent, useEditor } = peers.react;
|
|
843
|
-
const StarterKit = peers.starterKit.default;
|
|
844
|
-
return function MarkdownDocumentEditor2({
|
|
845
|
-
value = "",
|
|
846
|
-
placeholder = "Start writing...",
|
|
847
|
-
readOnly = false,
|
|
848
|
-
autoFocus = false,
|
|
849
|
-
className,
|
|
850
|
-
contentClassName,
|
|
851
|
-
onChange,
|
|
852
|
-
onReady
|
|
853
|
-
}) {
|
|
854
|
-
const initialHtml = useMemo3(() => markdownToHtml(value), []);
|
|
855
|
-
const lastAppliedMarkdownRef = useRef2(normalizeMarkdown(value));
|
|
856
|
-
const editor = useEditor({
|
|
857
|
-
extensions: [
|
|
858
|
-
StarterKit.configure({
|
|
859
|
-
codeBlock: {
|
|
860
|
-
HTMLAttributes: {
|
|
861
|
-
class: "hljs"
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
})
|
|
865
|
-
],
|
|
866
|
-
content: initialHtml,
|
|
867
|
-
editable: !readOnly,
|
|
868
|
-
autofocus: autoFocus,
|
|
869
|
-
editorProps: {
|
|
870
|
-
attributes: {
|
|
871
|
-
class: cn(
|
|
872
|
-
"prose prose-sm sm:prose-base max-w-none focus:outline-none",
|
|
873
|
-
"prose-headings:text-foreground prose-p:text-foreground prose-li:text-foreground",
|
|
874
|
-
"prose-strong:text-foreground prose-code:text-foreground prose-pre:text-foreground",
|
|
875
|
-
contentClassName
|
|
876
|
-
),
|
|
877
|
-
"data-placeholder": placeholder
|
|
878
|
-
}
|
|
879
|
-
},
|
|
880
|
-
onUpdate: ({ editor: currentEditor }) => {
|
|
881
|
-
const nextMarkdown = normalizeMarkdown(htmlToMarkdown(currentEditor.getHTML()));
|
|
882
|
-
lastAppliedMarkdownRef.current = nextMarkdown;
|
|
883
|
-
onChange?.(nextMarkdown, currentEditor);
|
|
884
|
-
},
|
|
885
|
-
onCreate: ({ editor: currentEditor }) => {
|
|
886
|
-
onReady?.(currentEditor);
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
useEffect3(() => {
|
|
890
|
-
if (editor) {
|
|
891
|
-
editor.setEditable(!readOnly);
|
|
892
|
-
}
|
|
893
|
-
}, [editor, readOnly]);
|
|
894
|
-
useEffect3(() => {
|
|
895
|
-
if (!editor) {
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
const normalizedValue = normalizeMarkdown(value);
|
|
899
|
-
if (normalizedValue === lastAppliedMarkdownRef.current) {
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
editor.commands.setContent(markdownToHtml(value), { emitUpdate: false });
|
|
903
|
-
lastAppliedMarkdownRef.current = normalizedValue;
|
|
904
|
-
}, [editor, value]);
|
|
905
|
-
return /* @__PURE__ */ jsxs2(
|
|
906
|
-
"div",
|
|
907
|
-
{
|
|
908
|
-
className: cn(
|
|
909
|
-
"flex min-h-[14rem] w-full flex-col overflow-hidden rounded-lg border border-border bg-background",
|
|
910
|
-
className
|
|
911
|
-
),
|
|
912
|
-
children: [
|
|
913
|
-
/* @__PURE__ */ jsx5(
|
|
914
|
-
EditorToolbar,
|
|
915
|
-
{
|
|
916
|
-
editor,
|
|
917
|
-
className: "border-border bg-card px-2 py-2"
|
|
918
|
-
}
|
|
919
|
-
),
|
|
920
|
-
/* @__PURE__ */ jsx5("div", { className: "min-h-0 flex-1 overflow-auto px-5 py-4", children: /* @__PURE__ */ jsx5(EditorContent, { editor }) }),
|
|
921
|
-
/* @__PURE__ */ jsx5("style", { children: `
|
|
922
|
-
.ProseMirror p.is-editor-empty:first-child::before {
|
|
923
|
-
content: attr(data-placeholder);
|
|
924
|
-
float: left;
|
|
925
|
-
color: hsl(var(--muted-foreground));
|
|
926
|
-
pointer-events: none;
|
|
927
|
-
height: 0;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
.ProseMirror pre {
|
|
931
|
-
background: hsl(var(--muted));
|
|
932
|
-
border: 1px solid hsl(var(--border));
|
|
933
|
-
border-radius: 0.75rem;
|
|
934
|
-
padding: 0.875rem 1rem;
|
|
935
|
-
overflow-x: auto;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
.ProseMirror code {
|
|
939
|
-
background: hsl(var(--muted));
|
|
940
|
-
border-radius: 0.35rem;
|
|
941
|
-
padding: 0.12rem 0.3rem;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
.ProseMirror pre code {
|
|
945
|
-
background: transparent;
|
|
946
|
-
padding: 0;
|
|
947
|
-
}
|
|
948
|
-
` })
|
|
949
|
-
]
|
|
950
|
-
}
|
|
951
|
-
);
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
var lazyMarkdownDocumentEditor = retryableLazyEditor(
|
|
955
|
-
async () => createMarkdownDocumentEditor(await loadDocumentEditorPeers())
|
|
956
|
-
);
|
|
957
|
-
function MarkdownDocumentEditor(props) {
|
|
958
|
-
const LazyMarkdownDocumentEditor = lazyMarkdownDocumentEditor();
|
|
959
|
-
return /* @__PURE__ */ jsx5(
|
|
960
|
-
Suspense3,
|
|
961
|
-
{
|
|
962
|
-
fallback: /* @__PURE__ */ jsx5(EditorLoadingPlaceholder, { className: cn("min-h-[14rem]", props.className) }),
|
|
963
|
-
children: /* @__PURE__ */ jsx5(LazyMarkdownDocumentEditor, { ...props })
|
|
964
|
-
}
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// src/editor/use-editor.ts
|
|
969
|
-
import { useCallback as useCallback2, useEffect as useEffect4, useState as useState2 } from "react";
|
|
970
|
-
function useEditorConnection() {
|
|
971
|
-
const { connectionState, isSynced, connect, disconnect } = useEditorContext();
|
|
972
|
-
const isConnected = connectionState === "connected" || connectionState === "synced";
|
|
973
|
-
const isConnecting = connectionState === "connecting";
|
|
974
|
-
const isDisconnected = connectionState === "disconnected";
|
|
975
|
-
return {
|
|
976
|
-
/** Current connection state string */
|
|
977
|
-
state: connectionState,
|
|
978
|
-
/** Whether connected to the server (connected or synced) */
|
|
979
|
-
isConnected,
|
|
980
|
-
/** Whether currently attempting to connect */
|
|
981
|
-
isConnecting,
|
|
982
|
-
/** Whether disconnected from the server */
|
|
983
|
-
isDisconnected,
|
|
984
|
-
/** Whether the document is synced with the server */
|
|
985
|
-
isSynced,
|
|
986
|
-
/** Connect to the collaboration server */
|
|
987
|
-
connect,
|
|
988
|
-
/** Disconnect from the collaboration server */
|
|
989
|
-
disconnect
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
function useCollaborators() {
|
|
993
|
-
const { collaborators } = useEditorContext();
|
|
994
|
-
const count = collaborators.length;
|
|
995
|
-
const hasOthers = count > 0;
|
|
996
|
-
return {
|
|
997
|
-
/** List of active collaborators (excluding self) */
|
|
998
|
-
collaborators,
|
|
999
|
-
/** Number of other collaborators */
|
|
1000
|
-
count,
|
|
1001
|
-
/** Whether there are other collaborators present */
|
|
1002
|
-
hasOthers
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
function useCollaboratorPresence(userId) {
|
|
1006
|
-
const { collaborators } = useEditorContext();
|
|
1007
|
-
return collaborators.find((c) => c.user.userId === userId) ?? null;
|
|
1008
|
-
}
|
|
1009
|
-
function useYjsState(key, initialValue) {
|
|
1010
|
-
const { doc } = useEditorContext();
|
|
1011
|
-
const [value, setLocalValue] = useState2(initialValue);
|
|
1012
|
-
const metaMap = doc.getMap("metadata");
|
|
1013
|
-
useEffect4(() => {
|
|
1014
|
-
const updateValue = () => {
|
|
1015
|
-
const stored = metaMap.get(key);
|
|
1016
|
-
if (stored !== void 0) {
|
|
1017
|
-
setLocalValue(stored);
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
updateValue();
|
|
1021
|
-
metaMap.observe(updateValue);
|
|
1022
|
-
return () => {
|
|
1023
|
-
metaMap.unobserve(updateValue);
|
|
1024
|
-
};
|
|
1025
|
-
}, [metaMap, key]);
|
|
1026
|
-
const setValue = useCallback2(
|
|
1027
|
-
(newValue) => {
|
|
1028
|
-
doc.transact(() => {
|
|
1029
|
-
metaMap.set(key, newValue);
|
|
1030
|
-
});
|
|
1031
|
-
},
|
|
1032
|
-
[doc, metaMap, key]
|
|
1033
|
-
);
|
|
1034
|
-
return [value, setValue];
|
|
1035
|
-
}
|
|
1036
|
-
function useDocumentChanges(onSave) {
|
|
1037
|
-
const { doc } = useEditorContext();
|
|
1038
|
-
const [isDirty, setIsDirty] = useState2(false);
|
|
1039
|
-
const [isSaving, setIsSaving] = useState2(false);
|
|
1040
|
-
const [lastSaved, setLastSaved] = useState2(null);
|
|
1041
|
-
useEffect4(() => {
|
|
1042
|
-
const handleUpdate = () => {
|
|
1043
|
-
setIsDirty(true);
|
|
1044
|
-
};
|
|
1045
|
-
doc.on("update", handleUpdate);
|
|
1046
|
-
return () => {
|
|
1047
|
-
doc.off("update", handleUpdate);
|
|
1048
|
-
};
|
|
1049
|
-
}, [doc]);
|
|
1050
|
-
const save = useCallback2(async () => {
|
|
1051
|
-
if (!onSave || isSaving) return;
|
|
1052
|
-
setIsSaving(true);
|
|
1053
|
-
try {
|
|
1054
|
-
await onSave();
|
|
1055
|
-
setIsDirty(false);
|
|
1056
|
-
setLastSaved(/* @__PURE__ */ new Date());
|
|
1057
|
-
} finally {
|
|
1058
|
-
setIsSaving(false);
|
|
1059
|
-
}
|
|
1060
|
-
}, [onSave, isSaving]);
|
|
1061
|
-
return {
|
|
1062
|
-
/** Whether there are unsaved changes */
|
|
1063
|
-
isDirty,
|
|
1064
|
-
/** Whether save is in progress */
|
|
1065
|
-
isSaving,
|
|
1066
|
-
/** When the document was last saved */
|
|
1067
|
-
lastSaved,
|
|
1068
|
-
/** Save the document */
|
|
1069
|
-
save
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
function useAwareness() {
|
|
1073
|
-
const { provider } = useEditorContext();
|
|
1074
|
-
const [localState, setLocalStateValue] = useState2(
|
|
1075
|
-
{}
|
|
1076
|
-
);
|
|
1077
|
-
const awareness = provider?.awareness;
|
|
1078
|
-
useEffect4(() => {
|
|
1079
|
-
if (!awareness) return;
|
|
1080
|
-
const updateState = () => {
|
|
1081
|
-
setLocalStateValue(awareness.getLocalState() ?? {});
|
|
1082
|
-
};
|
|
1083
|
-
updateState();
|
|
1084
|
-
awareness.on("change", updateState);
|
|
1085
|
-
return () => {
|
|
1086
|
-
awareness.off("change", updateState);
|
|
1087
|
-
};
|
|
1088
|
-
}, [awareness]);
|
|
1089
|
-
const setLocalState = useCallback2(
|
|
1090
|
-
(state) => {
|
|
1091
|
-
if (!awareness) return;
|
|
1092
|
-
const current = awareness.getLocalState() ?? {};
|
|
1093
|
-
awareness.setLocalState({ ...current, ...state });
|
|
1094
|
-
},
|
|
1095
|
-
[awareness]
|
|
1096
|
-
);
|
|
1097
|
-
const setLocalStateField = useCallback2(
|
|
1098
|
-
(field, value) => {
|
|
1099
|
-
if (!awareness) return;
|
|
1100
|
-
awareness.setLocalStateField(field, value);
|
|
1101
|
-
},
|
|
1102
|
-
[awareness]
|
|
1103
|
-
);
|
|
1104
|
-
return {
|
|
1105
|
-
/** Current local awareness state */
|
|
1106
|
-
localState,
|
|
1107
|
-
/** Set the entire local state (merges with existing) */
|
|
1108
|
-
setLocalState,
|
|
1109
|
-
/** Set a single field in the local state */
|
|
1110
|
-
setLocalStateField,
|
|
1111
|
-
/** The raw awareness instance */
|
|
1112
|
-
awareness
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// src/editor/document-editor-pane.tsx
|
|
1117
|
-
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1118
|
-
function connectionTone(state) {
|
|
1119
|
-
switch (state) {
|
|
1120
|
-
case "synced":
|
|
1121
|
-
return "text-[var(--surface-success-text)] border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]";
|
|
1122
|
-
case "connected":
|
|
1123
|
-
case "connecting":
|
|
1124
|
-
return "text-[var(--surface-info-text)] border-[var(--surface-info-border)] bg-[var(--surface-info-bg)]";
|
|
1125
|
-
case "disconnected":
|
|
1126
|
-
default:
|
|
1127
|
-
return "text-[var(--surface-warning-text)] border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]";
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
function connectionLabel(state) {
|
|
1131
|
-
switch (state) {
|
|
1132
|
-
case "synced":
|
|
1133
|
-
return "Live synced";
|
|
1134
|
-
case "connected":
|
|
1135
|
-
return "Connected";
|
|
1136
|
-
case "connecting":
|
|
1137
|
-
return "Connecting";
|
|
1138
|
-
case "disconnected":
|
|
1139
|
-
default:
|
|
1140
|
-
return "Offline";
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
function connectionDescription(state, collaborators, readOnly) {
|
|
1144
|
-
if (readOnly) {
|
|
1145
|
-
return state === "disconnected" ? "Live access is paused. You can keep reading while the editor reconnects." : "You are viewing the live document in read-only mode.";
|
|
1146
|
-
}
|
|
1147
|
-
switch (state) {
|
|
1148
|
-
case "synced":
|
|
1149
|
-
return collaborators > 0 ? `You and ${collaborators} collaborator${collaborators === 1 ? "" : "s"} are editing the same document.` : "You are editing the live document. Changes sync automatically.";
|
|
1150
|
-
case "connected":
|
|
1151
|
-
case "connecting":
|
|
1152
|
-
return "Connecting the live document. Local edits stay in place while sync catches up.";
|
|
1153
|
-
case "disconnected":
|
|
1154
|
-
default:
|
|
1155
|
-
return "Live updates are paused. You can keep editing and reconnect when the transport is healthy again.";
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
function CollaborativeDocumentSurface({
|
|
1159
|
-
markdown,
|
|
1160
|
-
placeholder,
|
|
1161
|
-
autoFocus,
|
|
1162
|
-
readOnly,
|
|
1163
|
-
className,
|
|
1164
|
-
onChange
|
|
1165
|
-
}) {
|
|
1166
|
-
const { state } = useEditorConnection();
|
|
1167
|
-
const { collaborators } = useCollaborators();
|
|
1168
|
-
const initialContent = useMemo4(() => markdownToHtml(markdown), [markdown]);
|
|
1169
|
-
const collaboratorCount = collaborators.length + 1;
|
|
1170
|
-
return /* @__PURE__ */ jsxs3("div", { className: cn("flex h-full min-h-0 flex-col gap-3", className), children: [
|
|
1171
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-[var(--radius-lg)] border border-border bg-card px-3 py-2", children: [
|
|
1172
|
-
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 space-y-2", children: [
|
|
1173
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1174
|
-
/* @__PURE__ */ jsxs3(
|
|
1175
|
-
"span",
|
|
1176
|
-
{
|
|
1177
|
-
className: cn(
|
|
1178
|
-
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 font-medium",
|
|
1179
|
-
connectionTone(state)
|
|
1180
|
-
),
|
|
1181
|
-
children: [
|
|
1182
|
-
state === "disconnected" ? /* @__PURE__ */ jsx6(WifiOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx6(Wifi, { className: "h-3.5 w-3.5" }),
|
|
1183
|
-
connectionLabel(state)
|
|
1184
|
-
]
|
|
1185
|
-
}
|
|
1186
|
-
),
|
|
1187
|
-
/* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2.5 py-1", children: [
|
|
1188
|
-
/* @__PURE__ */ jsx6(Users, { className: "h-3.5 w-3.5" }),
|
|
1189
|
-
collaborators.length === 0 ? "Solo editing" : `${collaboratorCount} active`
|
|
1190
|
-
] })
|
|
1191
|
-
] }),
|
|
1192
|
-
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: connectionDescription(state, collaborators.length, readOnly) })
|
|
1193
|
-
] }),
|
|
1194
|
-
/* @__PURE__ */ jsx6(CollaboratorsList, { collaborators })
|
|
1195
|
-
] }),
|
|
1196
|
-
/* @__PURE__ */ jsx6(
|
|
1197
|
-
TiptapEditor,
|
|
1198
|
-
{
|
|
1199
|
-
initialContent,
|
|
1200
|
-
placeholder,
|
|
1201
|
-
autoFocus,
|
|
1202
|
-
readOnly,
|
|
1203
|
-
className: cn("h-full min-h-[16rem]", className),
|
|
1204
|
-
onUpdate: (editor) => {
|
|
1205
|
-
onChange?.(normalizeMarkdown(htmlToMarkdown(editor.getHTML())));
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
)
|
|
1209
|
-
] });
|
|
1210
|
-
}
|
|
1211
|
-
function DocumentEditorPane({
|
|
1212
|
-
eyebrow,
|
|
1213
|
-
title,
|
|
1214
|
-
subtitle,
|
|
1215
|
-
meta,
|
|
1216
|
-
headerActions,
|
|
1217
|
-
footer,
|
|
1218
|
-
className,
|
|
1219
|
-
contentClassName,
|
|
1220
|
-
headerClassName,
|
|
1221
|
-
hideTitleBlock,
|
|
1222
|
-
tabs,
|
|
1223
|
-
toolbar,
|
|
1224
|
-
markdown = "",
|
|
1225
|
-
mode,
|
|
1226
|
-
defaultMode = "preview",
|
|
1227
|
-
onModeChange,
|
|
1228
|
-
backend = "local",
|
|
1229
|
-
placeholder = "Start writing...",
|
|
1230
|
-
autoFocus = false,
|
|
1231
|
-
readOnly = false,
|
|
1232
|
-
onChange,
|
|
1233
|
-
onSave,
|
|
1234
|
-
saving = false,
|
|
1235
|
-
saveLabel = "Save changes",
|
|
1236
|
-
previewClassName,
|
|
1237
|
-
editorClassName,
|
|
1238
|
-
collaboration
|
|
1239
|
-
}) {
|
|
1240
|
-
const [draft, setDraft] = useState3(markdown);
|
|
1241
|
-
const [uncontrolledMode, setUncontrolledMode] = useState3(defaultMode);
|
|
1242
|
-
const activeMode = mode ?? uncontrolledMode;
|
|
1243
|
-
const isCollaborative = backend === "collaborative" && Boolean(collaboration);
|
|
1244
|
-
const isDirty = normalizeMarkdown(draft) !== normalizeMarkdown(markdown);
|
|
1245
|
-
const saveStateLabel = readOnly ? "Read only" : isCollaborative ? isDirty ? "Snapshot pending" : "Live document current" : isDirty ? "Unsaved changes" : "Saved";
|
|
1246
|
-
useEffect5(() => {
|
|
1247
|
-
setDraft(markdown);
|
|
1248
|
-
}, [markdown]);
|
|
1249
|
-
useEffect5(() => {
|
|
1250
|
-
if (mode === void 0) {
|
|
1251
|
-
setUncontrolledMode(defaultMode);
|
|
1252
|
-
}
|
|
1253
|
-
}, [defaultMode, mode]);
|
|
1254
|
-
const setMode = (nextMode) => {
|
|
1255
|
-
if (mode === void 0) {
|
|
1256
|
-
setUncontrolledMode(nextMode);
|
|
1257
|
-
}
|
|
1258
|
-
onModeChange?.(nextMode);
|
|
1259
|
-
};
|
|
1260
|
-
const handleChange = (nextMarkdown) => {
|
|
1261
|
-
setDraft(nextMarkdown);
|
|
1262
|
-
onChange?.(nextMarkdown);
|
|
1263
|
-
};
|
|
1264
|
-
const editorToolbar = /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
1265
|
-
/* @__PURE__ */ jsxs3(
|
|
1266
|
-
TabsList,
|
|
1267
|
-
{
|
|
1268
|
-
variant: "underline",
|
|
1269
|
-
className: "h-auto gap-4 border-0 bg-transparent p-0 text-muted-foreground",
|
|
1270
|
-
children: [
|
|
1271
|
-
/* @__PURE__ */ jsx6(
|
|
1272
|
-
TabsTrigger,
|
|
1273
|
-
{
|
|
1274
|
-
value: "preview",
|
|
1275
|
-
variant: "underline",
|
|
1276
|
-
className: "pb-2 data-[state=active]:border-primary data-[state=active]:text-foreground",
|
|
1277
|
-
children: "Preview"
|
|
1278
|
-
}
|
|
1279
|
-
),
|
|
1280
|
-
/* @__PURE__ */ jsxs3(
|
|
1281
|
-
TabsTrigger,
|
|
1282
|
-
{
|
|
1283
|
-
value: "edit",
|
|
1284
|
-
variant: "underline",
|
|
1285
|
-
className: "flex items-center gap-2 pb-2 data-[state=active]:border-primary data-[state=active]:text-foreground",
|
|
1286
|
-
children: [
|
|
1287
|
-
/* @__PURE__ */ jsx6(PencilLine, { className: "h-3.5 w-3.5" }),
|
|
1288
|
-
isCollaborative ? "Live edit" : "Edit"
|
|
1289
|
-
]
|
|
1290
|
-
}
|
|
1291
|
-
)
|
|
1292
|
-
]
|
|
1293
|
-
}
|
|
1294
|
-
),
|
|
1295
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1296
|
-
toolbar,
|
|
1297
|
-
/* @__PURE__ */ jsx6("span", { className: "rounded-full border border-border bg-card px-2.5 py-1 font-medium", children: isCollaborative ? "Live document" : "Local draft" }),
|
|
1298
|
-
/* @__PURE__ */ jsx6("span", { className: "rounded-full border border-border bg-background px-2.5 py-1", children: saveStateLabel }),
|
|
1299
|
-
onSave && !readOnly && /* @__PURE__ */ jsxs3(
|
|
1300
|
-
"button",
|
|
1301
|
-
{
|
|
1302
|
-
type: "button",
|
|
1303
|
-
onClick: () => void onSave(draft),
|
|
1304
|
-
disabled: saving || !isDirty,
|
|
1305
|
-
className: "inline-flex items-center gap-2 rounded-[var(--radius-full)] border border-border bg-card px-3 py-1.5 text-xs font-semibold text-foreground transition-colors hover:border-primary/40 hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
1306
|
-
children: [
|
|
1307
|
-
/* @__PURE__ */ jsx6(Save, { className: "h-3.5 w-3.5" }),
|
|
1308
|
-
saving ? "Saving..." : saveLabel
|
|
1309
|
-
]
|
|
1310
|
-
}
|
|
1311
|
-
)
|
|
1312
|
-
] })
|
|
1313
|
-
] });
|
|
1314
|
-
const preview = /* @__PURE__ */ jsx6(
|
|
1315
|
-
"div",
|
|
1316
|
-
{
|
|
1317
|
-
className: cn(
|
|
1318
|
-
// The outer margin is the card's gutter (rather than padding on the tab
|
|
1319
|
-
// panel) so `previewClassName` can govern the whole box — gutter, border,
|
|
1320
|
-
// surface, and inner padding — e.g. to render the document full-bleed.
|
|
1321
|
-
"m-4 rounded-[var(--radius-lg)] border border-border bg-card p-5",
|
|
1322
|
-
previewClassName
|
|
1323
|
-
),
|
|
1324
|
-
children: /* @__PURE__ */ jsx6(Markdown, { children: draft })
|
|
1325
|
-
}
|
|
1326
|
-
);
|
|
1327
|
-
const localEditor = /* @__PURE__ */ jsx6(
|
|
1328
|
-
MarkdownDocumentEditor,
|
|
1329
|
-
{
|
|
1330
|
-
value: draft,
|
|
1331
|
-
placeholder,
|
|
1332
|
-
autoFocus,
|
|
1333
|
-
readOnly,
|
|
1334
|
-
onChange: (nextMarkdown) => {
|
|
1335
|
-
handleChange(nextMarkdown);
|
|
1336
|
-
},
|
|
1337
|
-
className: editorClassName
|
|
1338
|
-
}
|
|
1339
|
-
);
|
|
1340
|
-
const collaborativeEditor = collaboration ? /* @__PURE__ */ jsx6(EditorProvider, { ...collaboration, children: /* @__PURE__ */ jsx6(
|
|
1341
|
-
CollaborativeDocumentSurface,
|
|
1342
|
-
{
|
|
1343
|
-
markdown: draft,
|
|
1344
|
-
placeholder,
|
|
1345
|
-
autoFocus,
|
|
1346
|
-
readOnly,
|
|
1347
|
-
className: editorClassName,
|
|
1348
|
-
onChange: handleChange
|
|
1349
|
-
}
|
|
1350
|
-
) }, collaboration.documentName) : localEditor;
|
|
1351
|
-
return /* @__PURE__ */ jsx6(
|
|
1352
|
-
Tabs,
|
|
1353
|
-
{
|
|
1354
|
-
value: activeMode,
|
|
1355
|
-
onValueChange: (nextValue) => setMode(nextValue),
|
|
1356
|
-
className: "h-full",
|
|
1357
|
-
children: /* @__PURE__ */ jsxs3(
|
|
1358
|
-
ArtifactPane,
|
|
1359
|
-
{
|
|
1360
|
-
eyebrow,
|
|
1361
|
-
title,
|
|
1362
|
-
subtitle,
|
|
1363
|
-
meta,
|
|
1364
|
-
headerActions,
|
|
1365
|
-
footer,
|
|
1366
|
-
tabs,
|
|
1367
|
-
className,
|
|
1368
|
-
contentClassName,
|
|
1369
|
-
headerClassName,
|
|
1370
|
-
toolbar: editorToolbar,
|
|
1371
|
-
hideTitleBlock,
|
|
1372
|
-
children: [
|
|
1373
|
-
/* @__PURE__ */ jsx6(TabsContent, { value: "preview", className: "mt-0 h-full", children: preview }),
|
|
1374
|
-
/* @__PURE__ */ jsx6(TabsContent, { value: "edit", className: "mt-0 h-full px-4 py-4", children: isCollaborative ? collaborativeEditor : localEditor })
|
|
1375
|
-
]
|
|
1376
|
-
}
|
|
1377
|
-
)
|
|
1378
|
-
}
|
|
1379
|
-
);
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
export {
|
|
1383
|
-
EditorProvider,
|
|
1384
|
-
useEditorContext,
|
|
1385
|
-
EditorToolbar,
|
|
1386
|
-
TiptapEditor,
|
|
1387
|
-
CollaboratorsList,
|
|
1388
|
-
useEditorConnection,
|
|
1389
|
-
useCollaborators,
|
|
1390
|
-
useCollaboratorPresence,
|
|
1391
|
-
useYjsState,
|
|
1392
|
-
useDocumentChanges,
|
|
1393
|
-
useAwareness,
|
|
1394
|
-
DocumentEditorPane
|
|
1395
|
-
};
|