@bendyline/docblocks-react 2.7.7 → 2.7.8
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/THIRD_PARTY_NOTICES.txt +205 -35
- package/dist/{AiDiagrams-7KBAOXPB.js → AiDiagrams-2IKKSBAU.js} +5 -3
- package/dist/{AiEditorAssistant-4B2PFFOT.js → AiEditorAssistant-ANYGVG7P.js} +46 -6
- package/dist/AiImagePanel-OXWEFOBC.js +314 -0
- package/dist/{AiStudyDialog-JPVFFZBB.js → AiStudyDialog-T3FY74HY.js} +2 -2
- package/dist/{AudioExportDialog-EICCBSSE.js → AudioExportDialog-CBMSLQBS.js} +1 -1
- package/dist/DocumentConflictDialog-4R4QMKBT.js +217 -0
- package/dist/{ExportDialog-EYDGTJ3S.js → ExportDialog-FNFD53SY.js} +2 -2
- package/dist/{ExportToolbarControls-2XY2CKGH.js → ExportToolbarControls-ZOYH4I36.js} +108 -71
- package/dist/{GitUI-F3CNBURE.js → GitUI-3FANM7I3.js} +88 -155
- package/dist/{KeyboardShortcutsDialog-HVKDFP5Z.js → KeyboardShortcutsDialog-7XJTDIX2.js} +1 -1
- package/dist/{PresentationMaker-VYQKNL3L.js → PresentationMaker-COLTVMTS.js} +3 -3
- package/dist/{QuickOpenDialog-3QEX7RZG.js → QuickOpenDialog-U2FWCEPA.js} +1 -1
- package/dist/{Settings-CfhY489G.d.ts → Settings-ButIB78y.d.ts} +20 -4
- package/dist/{ShareDialog-PF7D7ORP.js → ShareDialog-WOWHI6YI.js} +1 -1
- package/dist/{SpeechToolbarControl-E6OSJGHL.js → SpeechToolbarControl-BZW6IVZM.js} +1 -1
- package/dist/{TextFromNarration-ME6FAYWO.js → TextFromNarration-6BYTUKVZ.js} +2 -2
- package/dist/chunk-27E2OON2.js +90 -0
- package/dist/{chunk-5O5YTOXG.js → chunk-5YMULR6A.js} +13 -1
- package/dist/{chunk-5OIHB3SR.js → chunk-A7BK6QFL.js} +362 -25
- package/dist/{chunk-T2PA7QEF.js → chunk-BGA5UVKL.js} +58 -17
- package/dist/{chunk-N7AKLJPY.js → chunk-CXLXGFG2.js} +16 -8
- package/dist/{chunk-7CSBRAMT.js → chunk-F7TR5EKX.js} +8 -6
- package/dist/chunk-HIKMOKBQ.js +37 -0
- package/dist/chunk-OW6JWRAA.js +66 -0
- package/dist/{chunk-BNKIPCXO.js → chunk-QXBV3G6T.js} +5 -3
- package/dist/{chunk-7HBKKXRV.js → chunk-TN7BY5N3.js} +722 -281
- package/dist/{chunk-VJPGZCFS.js → chunk-UYJCL6OW.js} +4 -2
- package/dist/{chunk-G63YXKBC.js → chunk-WDNE5TI3.js} +13 -2
- package/dist/{chunk-KBUHWZCF.js → chunk-YX275NPE.js} +1 -1
- package/dist/editor.js +3 -3
- package/dist/export/index.d.ts +16 -162
- package/dist/export/index.js +6 -4
- package/dist/image-save-D18dJrUr.d.ts +182 -0
- package/dist/index.d.ts +15 -11
- package/dist/index.js +1154 -964
- package/dist/{run-export-SPNNH32M.js → run-export-C3PLLWLS.js} +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +3 -2
- package/package.json +8 -8
- package/src/styles/docblocks.css +302 -10
- package/dist/DocumentConflictDialog-DZ2BV2IH.js +0 -111
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import {
|
|
2
|
+
imageAltText,
|
|
3
|
+
imageMarkdown,
|
|
4
|
+
saveGeneratedImage
|
|
5
|
+
} from "./chunk-HIKMOKBQ.js";
|
|
6
|
+
import {
|
|
7
|
+
imageAccessSentence,
|
|
8
|
+
imageProgressFraction,
|
|
9
|
+
imageProgressText,
|
|
10
|
+
useImageAccess
|
|
11
|
+
} from "./chunk-OW6JWRAA.js";
|
|
12
|
+
|
|
13
|
+
// src/Ai/AiImagePanel.tsx
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
|
+
import { useEditorContext } from "@bendyline/squisq-editor-react";
|
|
16
|
+
import { AI_IMAGE_ASPECTS } from "@bendyline/docblocks/host";
|
|
17
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
+
var ASPECT_LABELS = {
|
|
19
|
+
"1:1": "Square",
|
|
20
|
+
"4:3": "Landscape",
|
|
21
|
+
"3:4": "Portrait",
|
|
22
|
+
"16:9": "Wide",
|
|
23
|
+
"9:16": "Tall"
|
|
24
|
+
};
|
|
25
|
+
var PREFILL_CHARACTERS = 600;
|
|
26
|
+
function imagesOf(ai) {
|
|
27
|
+
return ai.images;
|
|
28
|
+
}
|
|
29
|
+
function AiImagePanel({ ai, onClose }) {
|
|
30
|
+
const images = imagesOf(ai);
|
|
31
|
+
const access = useImageAccess(images);
|
|
32
|
+
const { getSelection, insertBlockAfterCursor, mediaProvider, bumpMediaRevision } = useEditorContext();
|
|
33
|
+
const [prompt, setPrompt] = useState(() => {
|
|
34
|
+
const selection = getSelection();
|
|
35
|
+
const text = selection && !selection.empty ? selection.text.trim() : "";
|
|
36
|
+
return text.slice(0, PREFILL_CHARACTERS);
|
|
37
|
+
});
|
|
38
|
+
const [aspect, setAspect] = useState("1:1");
|
|
39
|
+
const [model, setModel] = useState(null);
|
|
40
|
+
const [models, setModels] = useState(null);
|
|
41
|
+
const [modelsError, setModelsError] = useState(null);
|
|
42
|
+
const [results, setResults] = useState([]);
|
|
43
|
+
const [notice, setNotice] = useState(null);
|
|
44
|
+
const [allowing, setAllowing] = useState(false);
|
|
45
|
+
const handle = useRef(null);
|
|
46
|
+
const urls = useRef(/* @__PURE__ */ new Set());
|
|
47
|
+
const counter = useRef(0);
|
|
48
|
+
const ready = access?.kind === "ready";
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!images) return;
|
|
51
|
+
let live = true;
|
|
52
|
+
void images.getPreferences().then((preferences) => {
|
|
53
|
+
if (!live) return;
|
|
54
|
+
setAspect(preferences.aspect);
|
|
55
|
+
setModel(preferences.model);
|
|
56
|
+
}).catch(() => void 0);
|
|
57
|
+
return () => {
|
|
58
|
+
live = false;
|
|
59
|
+
};
|
|
60
|
+
}, [images]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!images || !ready) return;
|
|
63
|
+
let live = true;
|
|
64
|
+
void images.models().then((result) => {
|
|
65
|
+
if (!live) return;
|
|
66
|
+
if (result.ok) {
|
|
67
|
+
setModels(result.value);
|
|
68
|
+
setModelsError(null);
|
|
69
|
+
} else {
|
|
70
|
+
setModelsError(result.error.message);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return () => {
|
|
74
|
+
live = false;
|
|
75
|
+
};
|
|
76
|
+
}, [images, ready]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const owned = urls.current;
|
|
79
|
+
return () => {
|
|
80
|
+
handle.current?.cancel();
|
|
81
|
+
for (const url of owned) URL.revokeObjectURL(url);
|
|
82
|
+
owned.clear();
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
const installed = (models ?? []).filter((entry) => entry.state === "installed");
|
|
86
|
+
const chosen = installed.find((entry) => entry.id === model) ?? installed[0] ?? null;
|
|
87
|
+
const rendering = results.some((result) => result.status === "rendering");
|
|
88
|
+
const update = useCallback((key, change) => {
|
|
89
|
+
setResults(
|
|
90
|
+
(current) => current.map((result) => result.key === key ? { ...result, ...change } : result)
|
|
91
|
+
);
|
|
92
|
+
}, []);
|
|
93
|
+
const render = (text, shape) => {
|
|
94
|
+
if (!images || !chosen || !text.trim() || handle.current) return;
|
|
95
|
+
counter.current += 1;
|
|
96
|
+
const key = `image-${String(counter.current)}`;
|
|
97
|
+
setNotice(null);
|
|
98
|
+
setResults((current) => [
|
|
99
|
+
{
|
|
100
|
+
key,
|
|
101
|
+
prompt: text.trim(),
|
|
102
|
+
aspect: shape,
|
|
103
|
+
model: chosen.id,
|
|
104
|
+
status: "rendering",
|
|
105
|
+
progress: null,
|
|
106
|
+
image: null,
|
|
107
|
+
url: null,
|
|
108
|
+
alt: imageAltText(text),
|
|
109
|
+
error: null,
|
|
110
|
+
inserted: false
|
|
111
|
+
},
|
|
112
|
+
...current
|
|
113
|
+
]);
|
|
114
|
+
const started = images.generate(
|
|
115
|
+
{ prompt: text.trim(), aspect: shape, model: chosen.id },
|
|
116
|
+
(event) => {
|
|
117
|
+
if (event.kind === "progress") update(key, { progress: event.progress });
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
handle.current = started;
|
|
121
|
+
void started.done.then((outcome) => {
|
|
122
|
+
if (handle.current === started) handle.current = null;
|
|
123
|
+
if (outcome.ok) {
|
|
124
|
+
const url = URL.createObjectURL(new Blob([outcome.value.data], { type: "image/png" }));
|
|
125
|
+
urls.current.add(url);
|
|
126
|
+
update(key, { status: "done", image: outcome.value, url, progress: null });
|
|
127
|
+
} else {
|
|
128
|
+
update(key, {
|
|
129
|
+
status: outcome.error.code === "cancelled" ? "stopped" : "failed",
|
|
130
|
+
error: outcome.error.code === "cancelled" ? null : outcome.error.message,
|
|
131
|
+
progress: null
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const chooseAspect = (next) => {
|
|
137
|
+
setAspect(next);
|
|
138
|
+
void images?.setPreferences({ aspect: next }).catch(() => void 0);
|
|
139
|
+
};
|
|
140
|
+
const chooseModel = (next) => {
|
|
141
|
+
setModel(next);
|
|
142
|
+
void images?.setPreferences({ model: next }).catch(() => void 0);
|
|
143
|
+
};
|
|
144
|
+
const insert = async (result) => {
|
|
145
|
+
if (!result.image) return;
|
|
146
|
+
if (!mediaProvider) {
|
|
147
|
+
setNotice("Save the document in a folder to insert images.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const path = await saveGeneratedImage(mediaProvider, result.image, result.prompt);
|
|
152
|
+
bumpMediaRevision?.();
|
|
153
|
+
if (!insertBlockAfterCursor(imageMarkdown(path, result.alt || result.prompt))) {
|
|
154
|
+
setNotice("Switch to Write or Source view, place the cursor, then insert again.");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
update(result.key, { inserted: true });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
setNotice(
|
|
160
|
+
`DocBlocks could not save the image beside the document. ${error instanceof Error ? error.message : ""}`.trim()
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const discard = (result) => {
|
|
165
|
+
if (result.url) {
|
|
166
|
+
URL.revokeObjectURL(result.url);
|
|
167
|
+
urls.current.delete(result.url);
|
|
168
|
+
}
|
|
169
|
+
setResults((current) => current.filter((entry) => entry.key !== result.key));
|
|
170
|
+
};
|
|
171
|
+
const allow = async () => {
|
|
172
|
+
if (!images) return;
|
|
173
|
+
setAllowing(true);
|
|
174
|
+
const outcome = await images.requestAccess();
|
|
175
|
+
setAllowing(false);
|
|
176
|
+
if (!outcome.ok) setNotice(outcome.error.message);
|
|
177
|
+
};
|
|
178
|
+
const blocked = imageAccessSentence(access);
|
|
179
|
+
const modelLabel = (id) => (models ?? []).find((entry) => entry.id === id)?.label ?? "the image model";
|
|
180
|
+
return /* @__PURE__ */ jsxs("aside", { className: "db-ai-review-panel db-ai-image-panel", "aria-label": "Generate image", children: [
|
|
181
|
+
/* @__PURE__ */ jsxs("div", { className: "db-ai-review-header", children: [
|
|
182
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
183
|
+
/* @__PURE__ */ jsx("h2", { children: "Generate image" }),
|
|
184
|
+
chosen && /* @__PURE__ */ jsx("span", { className: "db-ai-review-count", children: chosen.label })
|
|
185
|
+
] }),
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
type: "button",
|
|
190
|
+
className: "db-ai-review-close",
|
|
191
|
+
onClick: onClose,
|
|
192
|
+
"aria-label": "Close Generate image",
|
|
193
|
+
children: "\xD7"
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
] }),
|
|
197
|
+
blocked && /* @__PURE__ */ jsxs("div", { className: "db-ai-review-empty", role: "status", children: [
|
|
198
|
+
access?.kind === "awaiting-approval" && access.verificationCode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
199
|
+
"In Gezel, approve DocBlocks and enter this code:",
|
|
200
|
+
/* @__PURE__ */ jsx("span", { className: "db-ai-image-code", children: access.verificationCode })
|
|
201
|
+
] }) : blocked,
|
|
202
|
+
access?.kind === "needs-approval" && /* @__PURE__ */ jsx("div", { className: "db-ai-illustrate-bar db-ai-image-allow", children: /* @__PURE__ */ jsx("button", { type: "button", disabled: allowing, onClick: () => void allow(), children: "Allow image generation\u2026" }) })
|
|
203
|
+
] }),
|
|
204
|
+
ready && models !== null && installed.length === 0 && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty", role: "status", children: "No image model is installed yet. Download one under Settings, AI assistance." }),
|
|
205
|
+
ready && modelsError && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: modelsError }),
|
|
206
|
+
ready && installed.length > 0 && /* @__PURE__ */ jsxs("section", { className: "db-ai-image-form", "aria-label": "Describe the image", children: [
|
|
207
|
+
/* @__PURE__ */ jsxs("label", { className: "db-ai-field", children: [
|
|
208
|
+
"Description",
|
|
209
|
+
/* @__PURE__ */ jsx(
|
|
210
|
+
"textarea",
|
|
211
|
+
{
|
|
212
|
+
value: prompt,
|
|
213
|
+
onChange: (event) => setPrompt(event.target.value),
|
|
214
|
+
placeholder: "A watercolor lighthouse on a cliff at dusk",
|
|
215
|
+
maxLength: 4e3
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
] }),
|
|
219
|
+
/* @__PURE__ */ jsx("div", { className: "db-ai-image-aspects", role: "radiogroup", "aria-label": "Shape", children: AI_IMAGE_ASPECTS.map((shape) => /* @__PURE__ */ jsx(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
type: "button",
|
|
223
|
+
role: "radio",
|
|
224
|
+
"aria-checked": aspect === shape,
|
|
225
|
+
onClick: () => chooseAspect(shape),
|
|
226
|
+
children: ASPECT_LABELS[shape]
|
|
227
|
+
},
|
|
228
|
+
shape
|
|
229
|
+
)) }),
|
|
230
|
+
installed.length > 1 && /* @__PURE__ */ jsxs("label", { className: "db-ai-field db-ai-field--inline", children: [
|
|
231
|
+
"Model",
|
|
232
|
+
/* @__PURE__ */ jsx(
|
|
233
|
+
"select",
|
|
234
|
+
{
|
|
235
|
+
value: chosen?.id ?? "",
|
|
236
|
+
onChange: (event) => chooseModel(event.target.value),
|
|
237
|
+
children: installed.map((entry) => /* @__PURE__ */ jsx("option", { value: entry.id, children: entry.label }, entry.id))
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsx("div", { className: "db-ai-illustrate-bar db-ai-image-actions", children: rendering ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => handle.current?.cancel(), children: "Stop" }) : /* @__PURE__ */ jsx(
|
|
242
|
+
"button",
|
|
243
|
+
{
|
|
244
|
+
type: "button",
|
|
245
|
+
className: "db-ai-image-generate",
|
|
246
|
+
disabled: !prompt.trim(),
|
|
247
|
+
onClick: () => render(prompt, aspect),
|
|
248
|
+
children: "Generate"
|
|
249
|
+
}
|
|
250
|
+
) })
|
|
251
|
+
] }),
|
|
252
|
+
notice && /* @__PURE__ */ jsx("div", { className: "db-ai-review-empty db-ai-error", role: "alert", children: notice }),
|
|
253
|
+
results.length > 0 && /* @__PURE__ */ jsx("ol", { className: "db-ai-review-list db-ai-image-results", children: results.map((result) => {
|
|
254
|
+
const fraction = imageProgressFraction(result.progress);
|
|
255
|
+
return /* @__PURE__ */ jsxs("li", { className: "db-ai-review-finding db-ai-image-result", children: [
|
|
256
|
+
/* @__PURE__ */ jsx("p", { className: "db-ai-image-prompt", children: result.prompt }),
|
|
257
|
+
result.status === "rendering" && /* @__PURE__ */ jsxs("div", { className: "db-ai-illustrate-progress", role: "status", children: [
|
|
258
|
+
/* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
|
|
259
|
+
imageProgressText(result.progress, modelLabel(result.model)),
|
|
260
|
+
fraction !== null && /* @__PURE__ */ jsx("progress", { max: 1, value: fraction })
|
|
261
|
+
] }),
|
|
262
|
+
result.status === "failed" && /* @__PURE__ */ jsx("p", { className: "db-ai-error", role: "alert", children: result.error }),
|
|
263
|
+
result.status === "stopped" && /* @__PURE__ */ jsx("p", { className: "db-ai-note", children: "Stopped." }),
|
|
264
|
+
result.status === "done" && result.url && result.image && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"img",
|
|
267
|
+
{
|
|
268
|
+
className: "db-ai-image-preview",
|
|
269
|
+
src: result.url,
|
|
270
|
+
alt: result.alt,
|
|
271
|
+
width: result.image.width,
|
|
272
|
+
height: result.image.height
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsxs("label", { className: "db-ai-field db-ai-image-alt", children: [
|
|
276
|
+
"Description for readers",
|
|
277
|
+
/* @__PURE__ */ jsx(
|
|
278
|
+
"input",
|
|
279
|
+
{
|
|
280
|
+
value: result.alt,
|
|
281
|
+
onChange: (event) => update(result.key, { alt: event.target.value }),
|
|
282
|
+
maxLength: 160
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
] })
|
|
286
|
+
] }),
|
|
287
|
+
/* @__PURE__ */ jsxs("div", { className: "db-ai-illustrate-bar db-ai-image-result-actions", children: [
|
|
288
|
+
result.status === "done" && /* @__PURE__ */ jsx(
|
|
289
|
+
"button",
|
|
290
|
+
{
|
|
291
|
+
type: "button",
|
|
292
|
+
disabled: result.inserted,
|
|
293
|
+
onClick: () => void insert(result),
|
|
294
|
+
children: result.inserted ? "Inserted" : "Insert"
|
|
295
|
+
}
|
|
296
|
+
),
|
|
297
|
+
result.status !== "rendering" && /* @__PURE__ */ jsx(
|
|
298
|
+
"button",
|
|
299
|
+
{
|
|
300
|
+
type: "button",
|
|
301
|
+
disabled: rendering || !chosen,
|
|
302
|
+
onClick: () => render(result.prompt, result.aspect),
|
|
303
|
+
children: result.status === "done" ? "Regenerate" : "Try again"
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
result.status !== "rendering" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => discard(result), children: "Discard" })
|
|
307
|
+
] })
|
|
308
|
+
] }, result.key);
|
|
309
|
+
}) })
|
|
310
|
+
] });
|
|
311
|
+
}
|
|
312
|
+
export {
|
|
313
|
+
AiImagePanel
|
|
314
|
+
};
|
|
@@ -30,14 +30,14 @@ import {
|
|
|
30
30
|
extractNumbers,
|
|
31
31
|
sanitizeGeneratedMarkdown,
|
|
32
32
|
stripReasoning
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-CXLXGFG2.js";
|
|
34
34
|
import {
|
|
35
35
|
unavailableSentence,
|
|
36
36
|
useAiStatus
|
|
37
37
|
} from "./chunk-K4I3NJFL.js";
|
|
38
38
|
import {
|
|
39
39
|
Dialog
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-UYJCL6OW.js";
|
|
41
41
|
|
|
42
42
|
// src/Ai/AiStudyDialog.tsx
|
|
43
43
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitDiffView
|
|
3
|
+
} from "./chunk-27E2OON2.js";
|
|
4
|
+
import {
|
|
5
|
+
Dialog
|
|
6
|
+
} from "./chunk-UYJCL6OW.js";
|
|
7
|
+
|
|
8
|
+
// src/DocBlocksShell/DocumentConflictDialog.tsx
|
|
9
|
+
import { Component, useEffect, useMemo, useState } from "react";
|
|
10
|
+
import { resolveFileKind } from "@bendyline/squisq-editor-react";
|
|
11
|
+
import { MediaContext } from "@bendyline/squisq-react";
|
|
12
|
+
import { LinearDocView } from "@bendyline/squisq-react/page";
|
|
13
|
+
import { markdownToDoc, resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
14
|
+
import { parseMarkdown } from "@bendyline/squisq/markdown";
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
var DIFF_VIEWS = [
|
|
17
|
+
{ value: "inline", label: "Inline" },
|
|
18
|
+
{ value: "side-by-side", label: "Side by side" },
|
|
19
|
+
{ value: "changes", label: "Changes only" }
|
|
20
|
+
];
|
|
21
|
+
function formattedTime(value) {
|
|
22
|
+
if (value === null || value === void 0) return null;
|
|
23
|
+
const date = new Date(value);
|
|
24
|
+
return Number.isFinite(date.getTime()) ? date.toLocaleString() : null;
|
|
25
|
+
}
|
|
26
|
+
function VersionText({ label, content }) {
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
"textarea",
|
|
29
|
+
{
|
|
30
|
+
className: "db-conflict-preview",
|
|
31
|
+
"aria-label": `${label} preview`,
|
|
32
|
+
value: content,
|
|
33
|
+
readOnly: true,
|
|
34
|
+
spellCheck: false
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
var RenderBoundary = class extends Component {
|
|
39
|
+
constructor() {
|
|
40
|
+
super(...arguments);
|
|
41
|
+
this.state = { failed: false, resetKey: this.props.resetKey };
|
|
42
|
+
}
|
|
43
|
+
static getDerivedStateFromError() {
|
|
44
|
+
return { failed: true };
|
|
45
|
+
}
|
|
46
|
+
static getDerivedStateFromProps(props, state) {
|
|
47
|
+
return props.resetKey === state.resetKey ? null : { failed: false, resetKey: props.resetKey };
|
|
48
|
+
}
|
|
49
|
+
render() {
|
|
50
|
+
return this.state.failed ? this.props.fallback : this.props.children;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function RenderedVersion({
|
|
54
|
+
label,
|
|
55
|
+
content,
|
|
56
|
+
defaultThemeId
|
|
57
|
+
}) {
|
|
58
|
+
const rendered = useMemo(() => {
|
|
59
|
+
try {
|
|
60
|
+
const doc = markdownToDoc(parseMarkdown(content));
|
|
61
|
+
return { doc, theme: resolveThemeForDoc(doc, doc.themeId ? void 0 : defaultThemeId) };
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}, [content, defaultThemeId]);
|
|
66
|
+
const text = /* @__PURE__ */ jsx(VersionText, { label, content });
|
|
67
|
+
if (!rendered) return text;
|
|
68
|
+
return /* @__PURE__ */ jsx(RenderBoundary, { resetKey: content, fallback: text, children: /* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: "db-conflict-preview db-conflict-preview--rendered",
|
|
72
|
+
role: "region",
|
|
73
|
+
"aria-label": `${label} preview`,
|
|
74
|
+
tabIndex: 0,
|
|
75
|
+
style: { background: rendered.theme.colors.background },
|
|
76
|
+
children: /* @__PURE__ */ jsx(
|
|
77
|
+
LinearDocView,
|
|
78
|
+
{
|
|
79
|
+
doc: rendered.doc,
|
|
80
|
+
theme: rendered.theme,
|
|
81
|
+
variant: "document",
|
|
82
|
+
showCover: false,
|
|
83
|
+
thinMargins: true,
|
|
84
|
+
animationsEnabled: false
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
) });
|
|
89
|
+
}
|
|
90
|
+
function DocumentConflictDialog({
|
|
91
|
+
conflict,
|
|
92
|
+
fileName,
|
|
93
|
+
theme,
|
|
94
|
+
defaultThemeId,
|
|
95
|
+
mediaProvider,
|
|
96
|
+
readSavedFile,
|
|
97
|
+
onUseLocal,
|
|
98
|
+
onUseSaved,
|
|
99
|
+
onClose
|
|
100
|
+
}) {
|
|
101
|
+
const [savedFile, setSavedFile] = useState(null);
|
|
102
|
+
const [busy, setBusy] = useState(false);
|
|
103
|
+
const [error, setError] = useState(null);
|
|
104
|
+
const [diffView, setDiffView] = useState("inline");
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
let disposed = false;
|
|
107
|
+
void readSavedFile().then(
|
|
108
|
+
(file) => {
|
|
109
|
+
if (!disposed) setSavedFile(file);
|
|
110
|
+
},
|
|
111
|
+
() => {
|
|
112
|
+
if (!disposed) setSavedFile(null);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
return () => {
|
|
116
|
+
disposed = true;
|
|
117
|
+
};
|
|
118
|
+
}, [readSavedFile, conflict.targetKey, conflict.externalContent, conflict.externalVersion]);
|
|
119
|
+
const capturedAt = formattedTime(conflict.recoveredDraftCapturedAt);
|
|
120
|
+
const savedAt = savedFile?.content === conflict.externalContent ? formattedTime(savedFile?.lastModified) : null;
|
|
121
|
+
const localLabel = conflict.recoveredDraft ? "Recovery draft" : "Your unsaved version";
|
|
122
|
+
const fileKind = resolveFileKind(fileName);
|
|
123
|
+
const version = (label, content) => fileKind.mode === "markdown" ? /* @__PURE__ */ jsx(RenderedVersion, { label, content, defaultThemeId }) : /* @__PURE__ */ jsx(VersionText, { label, content });
|
|
124
|
+
const choose = async (resolve) => {
|
|
125
|
+
if (busy) return;
|
|
126
|
+
setBusy(true);
|
|
127
|
+
setError(null);
|
|
128
|
+
try {
|
|
129
|
+
if (await resolve()) onClose();
|
|
130
|
+
else setError("Could not resolve this conflict. Both versions are still available.");
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
setError(cause instanceof Error ? cause.message : "Could not resolve this conflict.");
|
|
133
|
+
} finally {
|
|
134
|
+
setBusy(false);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return /* @__PURE__ */ jsxs(
|
|
138
|
+
Dialog,
|
|
139
|
+
{
|
|
140
|
+
title: "Compare document versions",
|
|
141
|
+
size: "full",
|
|
142
|
+
className: "db-conflict-dialog",
|
|
143
|
+
onClose: () => {
|
|
144
|
+
if (!busy) onClose();
|
|
145
|
+
},
|
|
146
|
+
closeOnBackdrop: !busy,
|
|
147
|
+
footer: /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: onClose, children: "Decide later" }),
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
150
|
+
conflict.recoveredDraft ? "This draft was recovered from a previous session. The saved file may contain newer edits." : "The saved file changed while you had unsaved edits.",
|
|
151
|
+
" ",
|
|
152
|
+
"Compare the two versions before choosing. Neither version changes until you choose."
|
|
153
|
+
] }),
|
|
154
|
+
/* @__PURE__ */ jsx(MediaContext.Provider, { value: mediaProvider ?? null, children: /* @__PURE__ */ jsxs(
|
|
155
|
+
"div",
|
|
156
|
+
{
|
|
157
|
+
className: diffView === "side-by-side" ? "db-conflict-columns db-conflict-columns--wide-diff" : "db-conflict-columns",
|
|
158
|
+
"aria-busy": busy,
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": localLabel, children: [
|
|
161
|
+
/* @__PURE__ */ jsx("h3", { children: localLabel }),
|
|
162
|
+
/* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.recoveredDraft ? capturedAt ? `Recovery copy captured: ${capturedAt}` : "Recovery capture time unavailable" : "Not saved in this session" }),
|
|
163
|
+
version(localLabel, conflict.localContent),
|
|
164
|
+
/* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: "Replaces the saved file with the draft shown above." }),
|
|
165
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseLocal), children: conflict.recoveredDraft ? "Replace file with draft" : "Keep mine" })
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsxs("section", { className: "db-conflict-diff", "aria-label": "Differences", children: [
|
|
168
|
+
/* @__PURE__ */ jsxs("div", { className: "db-conflict-diff-header", children: [
|
|
169
|
+
/* @__PURE__ */ jsx("h3", { children: "Differences" }),
|
|
170
|
+
/* @__PURE__ */ jsxs("label", { className: "db-conflict-diff-view", children: [
|
|
171
|
+
/* @__PURE__ */ jsx("span", { children: "View" }),
|
|
172
|
+
/* @__PURE__ */ jsx(
|
|
173
|
+
"select",
|
|
174
|
+
{
|
|
175
|
+
value: diffView,
|
|
176
|
+
onChange: (event) => setDiffView(event.currentTarget.value),
|
|
177
|
+
children: DIFF_VIEWS.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
] })
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ jsxs("p", { className: "db-conflict-metadata", children: [
|
|
183
|
+
"\u2212 Only in ",
|
|
184
|
+
localLabel.toLowerCase(),
|
|
185
|
+
" \xB7 + Only in the saved file"
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsx(
|
|
188
|
+
GitDiffView,
|
|
189
|
+
{
|
|
190
|
+
original: conflict.localContent,
|
|
191
|
+
modified: conflict.externalContent ?? "",
|
|
192
|
+
fileName,
|
|
193
|
+
language: fileKind.language,
|
|
194
|
+
theme,
|
|
195
|
+
layout: diffView === "side-by-side" ? "side-by-side" : "inline",
|
|
196
|
+
changesOnly: diffView === "changes"
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
] }),
|
|
200
|
+
/* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": "Saved file", children: [
|
|
201
|
+
/* @__PURE__ */ jsx("h3", { children: "Saved file" }),
|
|
202
|
+
/* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.externalContent === null ? "File was deleted outside DocBlocks" : savedAt ? `File last modified: ${savedAt}` : "File modification time unavailable" }),
|
|
203
|
+
conflict.externalContent === null ? /* @__PURE__ */ jsx("div", { className: "db-conflict-preview", children: "The saved file no longer exists." }) : version("Saved file", conflict.externalContent),
|
|
204
|
+
/* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: conflict.externalContent === null ? "Discards the draft and closes this document." : "Discards the draft and opens the saved file shown above." }),
|
|
205
|
+
/* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseSaved), children: conflict.externalContent === null ? "Accept deletion" : "Use saved file" })
|
|
206
|
+
] })
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
) }),
|
|
210
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
export {
|
|
216
|
+
DocumentConflictDialog as default
|
|
217
|
+
};
|