@june-ai/june-ui 0.2.1 → 0.2.3
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/README.md +598 -31
- package/dist/chunk-HFHRSNIL.js +174 -0
- package/dist/document-engine-C6ILFMRP.js +12 -0
- package/dist/index.cjs +1475 -98
- package/dist/index.d.cts +254 -10
- package/dist/index.d.ts +254 -10
- package/dist/index.js +1214 -75
- package/dist/june.css +140 -13
- package/package.json +10 -2
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
6
11
|
var __export = (target, all) => {
|
|
7
12
|
for (var name in all)
|
|
8
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -15,63 +20,261 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
20
|
}
|
|
16
21
|
return to;
|
|
17
22
|
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
18
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
32
|
|
|
33
|
+
// src/errors/errors.ts
|
|
34
|
+
function isJuneError(value) {
|
|
35
|
+
return value instanceof JuneError;
|
|
36
|
+
}
|
|
37
|
+
function toJuneError(error, fallbackCode, message) {
|
|
38
|
+
if (error instanceof JuneError) return error;
|
|
39
|
+
const text = message ?? (error instanceof Error ? error.message : String(error));
|
|
40
|
+
return new JuneError(fallbackCode, text, { cause: error });
|
|
41
|
+
}
|
|
42
|
+
var JuneError;
|
|
43
|
+
var init_errors = __esm({
|
|
44
|
+
"src/errors/errors.ts"() {
|
|
45
|
+
"use strict";
|
|
46
|
+
JuneError = class _JuneError extends Error {
|
|
47
|
+
constructor(code2, message, options = {}) {
|
|
48
|
+
super(message);
|
|
49
|
+
Object.setPrototypeOf(this, _JuneError.prototype);
|
|
50
|
+
this.name = "JuneError";
|
|
51
|
+
this.code = code2;
|
|
52
|
+
this.details = options.details;
|
|
53
|
+
if (options.cause !== void 0) {
|
|
54
|
+
this.cause = options.cause;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// src/components/document-editor/document-extensions.ts
|
|
62
|
+
function createDocumentExtensions() {
|
|
63
|
+
return [
|
|
64
|
+
import_starter_kit.default.configure({
|
|
65
|
+
heading: { levels: [1, 2, 3] },
|
|
66
|
+
// Tiptap v3 StarterKit already includes Link and Underline
|
|
67
|
+
link: { openOnClick: false, autolink: true, defaultProtocol: "https" }
|
|
68
|
+
}),
|
|
69
|
+
import_extension_text_align.default.configure({ types: ["heading", "paragraph"] }),
|
|
70
|
+
import_extension_table.TableKit,
|
|
71
|
+
import_markdown.Markdown
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
var import_extension_table, import_extension_text_align, import_markdown, import_starter_kit;
|
|
75
|
+
var init_document_extensions = __esm({
|
|
76
|
+
"src/components/document-editor/document-extensions.ts"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
import_extension_table = require("@tiptap/extension-table");
|
|
79
|
+
import_extension_text_align = __toESM(require("@tiptap/extension-text-align"), 1);
|
|
80
|
+
import_markdown = require("@tiptap/markdown");
|
|
81
|
+
import_starter_kit = __toESM(require("@tiptap/starter-kit"), 1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/components/document-editor/document-utils.ts
|
|
86
|
+
function isDocJSON(value) {
|
|
87
|
+
if (!value || typeof value !== "object") return false;
|
|
88
|
+
const v = value;
|
|
89
|
+
return v.type === "doc" && (v.content === void 0 || Array.isArray(v.content));
|
|
90
|
+
}
|
|
91
|
+
function looksLikeDocJSON(text) {
|
|
92
|
+
return /^\s*\{\s*"type"\s*:\s*"doc"/.test(text);
|
|
93
|
+
}
|
|
94
|
+
function tryParseDocJSON(text) {
|
|
95
|
+
try {
|
|
96
|
+
const value = JSON.parse(text);
|
|
97
|
+
return isDocJSON(value) ? value : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function docKey(doc) {
|
|
103
|
+
let key = keyCache.get(doc);
|
|
104
|
+
if (key === void 0) {
|
|
105
|
+
key = JSON.stringify(doc);
|
|
106
|
+
keyCache.set(doc, key);
|
|
107
|
+
}
|
|
108
|
+
return key;
|
|
109
|
+
}
|
|
110
|
+
function plainText(node) {
|
|
111
|
+
const n = node;
|
|
112
|
+
if (typeof n.text === "string") return n.text;
|
|
113
|
+
return (n.content ?? []).map(plainText).join(" ");
|
|
114
|
+
}
|
|
115
|
+
function countWords(text) {
|
|
116
|
+
const trimmed = text.trim();
|
|
117
|
+
return trimmed ? trimmed.split(/\s+/).length : 0;
|
|
118
|
+
}
|
|
119
|
+
function wordCountOf(doc) {
|
|
120
|
+
let count = wordCache.get(doc);
|
|
121
|
+
if (count === void 0) {
|
|
122
|
+
count = countWords(plainText(doc));
|
|
123
|
+
wordCache.set(doc, count);
|
|
124
|
+
}
|
|
125
|
+
return count;
|
|
126
|
+
}
|
|
127
|
+
function slugify(value) {
|
|
128
|
+
return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
129
|
+
}
|
|
130
|
+
function debounce(fn, wait) {
|
|
131
|
+
let timer = null;
|
|
132
|
+
const run = () => {
|
|
133
|
+
timer = null;
|
|
134
|
+
fn();
|
|
135
|
+
};
|
|
136
|
+
const debounced = (() => {
|
|
137
|
+
if (timer !== null) clearTimeout(timer);
|
|
138
|
+
timer = setTimeout(run, wait);
|
|
139
|
+
});
|
|
140
|
+
debounced.cancel = () => {
|
|
141
|
+
if (timer !== null) {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
timer = null;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
debounced.flush = () => {
|
|
147
|
+
if (timer !== null) {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
run();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return debounced;
|
|
153
|
+
}
|
|
154
|
+
function sanitizeHref(input) {
|
|
155
|
+
const value = input.trim();
|
|
156
|
+
if (!value) return null;
|
|
157
|
+
if (/^(https?:|mailto:|tel:)/i.test(value)) return value;
|
|
158
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return null;
|
|
159
|
+
if (value.startsWith("/") || value.startsWith("#")) return value;
|
|
160
|
+
return `https://${value}`;
|
|
161
|
+
}
|
|
162
|
+
var emptyDoc, keyCache, wordCache;
|
|
163
|
+
var init_document_utils = __esm({
|
|
164
|
+
"src/components/document-editor/document-utils.ts"() {
|
|
165
|
+
"use strict";
|
|
166
|
+
emptyDoc = () => ({ type: "doc", content: [{ type: "paragraph" }] });
|
|
167
|
+
keyCache = /* @__PURE__ */ new WeakMap();
|
|
168
|
+
wordCache = /* @__PURE__ */ new WeakMap();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/components/document-editor/document-engine.ts
|
|
173
|
+
var document_engine_exports = {};
|
|
174
|
+
__export(document_engine_exports, {
|
|
175
|
+
docToHtml: () => docToHtml,
|
|
176
|
+
docToMarkdown: () => docToMarkdown,
|
|
177
|
+
docToText: () => docToText,
|
|
178
|
+
parseDocumentContent: () => parseDocumentContent
|
|
179
|
+
});
|
|
180
|
+
function scratchEditor() {
|
|
181
|
+
if (!scratch || scratch.isDestroyed) {
|
|
182
|
+
scratch = new import_core.Editor({ extensions: createDocumentExtensions(), content: "" });
|
|
183
|
+
}
|
|
184
|
+
return scratch;
|
|
185
|
+
}
|
|
186
|
+
function parseDocumentContent(text) {
|
|
187
|
+
const editor = scratchEditor();
|
|
188
|
+
const trimmed = text.trim();
|
|
189
|
+
try {
|
|
190
|
+
if (looksLikeDocJSON(trimmed)) {
|
|
191
|
+
const json = tryParseDocJSON(trimmed);
|
|
192
|
+
if (!json) throw new Error("The document JSON could not be parsed");
|
|
193
|
+
editor.schema.nodeFromJSON(json).check();
|
|
194
|
+
editor.commands.setContent(json, { emitUpdate: false });
|
|
195
|
+
} else {
|
|
196
|
+
editor.commands.setContent(trimmed, { contentType: "markdown", emitUpdate: false });
|
|
197
|
+
}
|
|
198
|
+
return editor.getJSON();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw toJuneError(error, "DOCUMENT_ERROR", "The document content is invalid");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function load(doc) {
|
|
204
|
+
const editor = scratchEditor();
|
|
205
|
+
editor.commands.setContent(doc, { emitUpdate: false });
|
|
206
|
+
return editor;
|
|
207
|
+
}
|
|
208
|
+
var import_core, scratch, docToHtml, docToMarkdown, docToText;
|
|
209
|
+
var init_document_engine = __esm({
|
|
210
|
+
"src/components/document-editor/document-engine.ts"() {
|
|
211
|
+
"use strict";
|
|
212
|
+
import_core = require("@tiptap/core");
|
|
213
|
+
init_errors();
|
|
214
|
+
init_document_extensions();
|
|
215
|
+
init_document_utils();
|
|
216
|
+
scratch = null;
|
|
217
|
+
docToHtml = (doc) => load(doc).getHTML();
|
|
218
|
+
docToMarkdown = (doc) => load(doc).getMarkdown();
|
|
219
|
+
docToText = (doc) => load(doc).getText({ blockSeparator: "\n\n" });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
20
223
|
// src/index.ts
|
|
21
224
|
var index_exports = {};
|
|
22
225
|
__export(index_exports, {
|
|
226
|
+
ALL_EXPORT_FORMATS: () => ALL_EXPORT_FORMATS,
|
|
227
|
+
DocumentEditor: () => DocumentEditor,
|
|
228
|
+
JuneDocumentContext: () => JuneDocumentContext,
|
|
229
|
+
JuneDocumentEditorComponent: () => JuneDocumentEditorComponent,
|
|
23
230
|
JuneError: () => JuneError,
|
|
24
231
|
JuneErrorBoundary: () => JuneErrorBoundary,
|
|
25
232
|
JuneRenderer: () => JuneRenderer,
|
|
233
|
+
JuneRuntimeContext: () => JuneRuntimeContext,
|
|
26
234
|
JuneThemeProvider: () => JuneThemeProvider,
|
|
27
235
|
JuneUI: () => JuneUI,
|
|
28
236
|
consumeStream: () => consumeStream,
|
|
237
|
+
copyDocumentToClipboard: () => copyDocumentToClipboard,
|
|
29
238
|
createActionHandler: () => createActionHandler,
|
|
239
|
+
createDocumentStore: () => createDocumentStore,
|
|
30
240
|
createJuneLibrary: () => createJuneLibrary,
|
|
31
241
|
darkTheme: () => darkTheme,
|
|
32
242
|
defaultParseActionEvent: () => defaultParseActionEvent,
|
|
33
243
|
defineJuneAction: () => defineJuneAction,
|
|
34
244
|
defineJuneComponent: () => defineJuneComponent,
|
|
245
|
+
documentPromptOptions: () => documentPromptOptions,
|
|
246
|
+
downloadResult: () => downloadResult,
|
|
247
|
+
exportDocument: () => exportDocument,
|
|
35
248
|
fetchStream: () => fetchStream,
|
|
249
|
+
getDefaultDocumentStore: () => getDefaultDocumentStore,
|
|
250
|
+
getDocumentEditorInstructions: () => getDocumentEditorInstructions,
|
|
251
|
+
getDocumentEditorRules: () => getDocumentEditorRules,
|
|
36
252
|
isJuneError: () => isJuneError,
|
|
37
253
|
jsonFieldExtractor: () => jsonFieldExtractor,
|
|
254
|
+
juneLibrary: () => juneLibrary,
|
|
38
255
|
lightTheme: () => lightTheme,
|
|
256
|
+
listDocumentExporters: () => listDocumentExporters,
|
|
257
|
+
mergeDocuments: () => mergeDocuments,
|
|
258
|
+
registerDocumentExporter: () => registerDocumentExporter,
|
|
39
259
|
runAction: () => runAction,
|
|
260
|
+
serializeDocumentsForAgent: () => serializeDocumentsForAgent,
|
|
40
261
|
sseToText: () => sseToText,
|
|
41
262
|
toJuneError: () => toJuneError,
|
|
263
|
+
useDocumentOptions: () => useDocumentOptions,
|
|
264
|
+
useJuneRuntime: () => useJuneRuntime,
|
|
42
265
|
useJuneStream: () => useJuneStream,
|
|
43
266
|
useJuneTheme: () => useJuneTheme,
|
|
44
|
-
useResolvedTheme: () => useResolvedTheme
|
|
267
|
+
useResolvedTheme: () => useResolvedTheme,
|
|
268
|
+
withDocumentEditor: () => withDocumentEditor
|
|
45
269
|
});
|
|
46
270
|
module.exports = __toCommonJS(index_exports);
|
|
47
271
|
|
|
48
272
|
// src/renderer/renderer.tsx
|
|
49
273
|
var import_react_lang = require("@openuidev/react-lang");
|
|
50
|
-
var
|
|
51
|
-
|
|
52
|
-
// src/errors/errors.ts
|
|
53
|
-
var JuneError = class _JuneError extends Error {
|
|
54
|
-
constructor(code, message, options = {}) {
|
|
55
|
-
super(message);
|
|
56
|
-
Object.setPrototypeOf(this, _JuneError.prototype);
|
|
57
|
-
this.name = "JuneError";
|
|
58
|
-
this.code = code;
|
|
59
|
-
this.details = options.details;
|
|
60
|
-
if (options.cause !== void 0) {
|
|
61
|
-
this.cause = options.cause;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
function isJuneError(value) {
|
|
66
|
-
return value instanceof JuneError;
|
|
67
|
-
}
|
|
68
|
-
function toJuneError(error, fallbackCode, message) {
|
|
69
|
-
if (error instanceof JuneError) return error;
|
|
70
|
-
const text = message ?? (error instanceof Error ? error.message : String(error));
|
|
71
|
-
return new JuneError(fallbackCode, text, { cause: error });
|
|
72
|
-
}
|
|
274
|
+
var import_react5 = require("react");
|
|
73
275
|
|
|
74
276
|
// src/actions/action-handler.ts
|
|
277
|
+
init_errors();
|
|
75
278
|
function firstString(...values) {
|
|
76
279
|
for (const v of values) if (typeof v === "string" && v.length > 0) return v;
|
|
77
280
|
return null;
|
|
@@ -133,28 +336,44 @@ function createActionHandler(options) {
|
|
|
133
336
|
};
|
|
134
337
|
}
|
|
135
338
|
|
|
136
|
-
// src/
|
|
137
|
-
var
|
|
339
|
+
// src/components/document-editor/document-context.ts
|
|
340
|
+
var import_react = require("react");
|
|
341
|
+
var JuneDocumentContext = (0, import_react.createContext)({});
|
|
342
|
+
function useDocumentOptions() {
|
|
343
|
+
return (0, import_react.useContext)(JuneDocumentContext);
|
|
344
|
+
}
|
|
345
|
+
function useDocumentValue(store, id, selector) {
|
|
346
|
+
const read = () => selector(store.get(id));
|
|
347
|
+
return (0, import_react.useSyncExternalStore)(store.subscribe, read, read);
|
|
348
|
+
}
|
|
138
349
|
|
|
139
350
|
// src/core/context.ts
|
|
140
|
-
var
|
|
141
|
-
var JuneContext = (0,
|
|
351
|
+
var import_react2 = require("react");
|
|
352
|
+
var JuneContext = (0, import_react2.createContext)({
|
|
142
353
|
mode: "light",
|
|
143
354
|
theme: "light"
|
|
144
355
|
});
|
|
145
356
|
function useJuneTheme() {
|
|
146
|
-
return (0,
|
|
357
|
+
return (0, import_react2.useContext)(JuneContext);
|
|
358
|
+
}
|
|
359
|
+
var JuneRuntimeContext = (0, import_react2.createContext)({ isStreaming: false });
|
|
360
|
+
function useJuneRuntime() {
|
|
361
|
+
return (0, import_react2.useContext)(JuneRuntimeContext);
|
|
147
362
|
}
|
|
148
363
|
|
|
364
|
+
// src/renderer/renderer.tsx
|
|
365
|
+
init_errors();
|
|
366
|
+
|
|
367
|
+
// src/themes/theme-provider.tsx
|
|
368
|
+
var import_react3 = require("react");
|
|
369
|
+
|
|
149
370
|
// src/themes/dark.ts
|
|
150
371
|
var darkTheme = {
|
|
151
372
|
"--june-foreground": "#f3f4f6",
|
|
152
373
|
"--june-muted": "#9ca3af",
|
|
153
374
|
"--june-border": "#1f2937",
|
|
154
375
|
"--june-accent": "#818cf8",
|
|
155
|
-
"--june-danger": "#f87171"
|
|
156
|
-
"--june-radius": "8px",
|
|
157
|
-
"--june-font-family": "system-ui, -apple-system, 'Segoe UI', sans-serif"
|
|
376
|
+
"--june-danger": "#f87171"
|
|
158
377
|
};
|
|
159
378
|
|
|
160
379
|
// src/themes/light.ts
|
|
@@ -163,9 +382,7 @@ var lightTheme = {
|
|
|
163
382
|
"--june-muted": "#6b7280",
|
|
164
383
|
"--june-border": "#e5e7eb",
|
|
165
384
|
"--june-accent": "#4f46e5",
|
|
166
|
-
"--june-danger": "#b91c1c"
|
|
167
|
-
"--june-radius": "8px",
|
|
168
|
-
"--june-font-family": "system-ui, -apple-system, 'Segoe UI', sans-serif"
|
|
385
|
+
"--june-danger": "#b91c1c"
|
|
169
386
|
};
|
|
170
387
|
|
|
171
388
|
// src/themes/theme-provider.tsx
|
|
@@ -186,27 +403,37 @@ function getServerSnapshot() {
|
|
|
186
403
|
return "light";
|
|
187
404
|
}
|
|
188
405
|
function useResolvedTheme(mode) {
|
|
189
|
-
const system = (0,
|
|
406
|
+
const system = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
|
|
190
407
|
return mode === "system" ? system : mode;
|
|
191
408
|
}
|
|
192
409
|
function JuneThemeProvider({
|
|
193
|
-
mode
|
|
410
|
+
mode,
|
|
194
411
|
vars,
|
|
195
412
|
className,
|
|
196
413
|
style,
|
|
197
414
|
children
|
|
198
415
|
}) {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
416
|
+
const explicit = mode !== void 0;
|
|
417
|
+
const resolved = useResolvedTheme(mode ?? "system");
|
|
418
|
+
const mergedStyle = (0, import_react3.useMemo)(() => {
|
|
419
|
+
if (!explicit && !vars && !style) return void 0;
|
|
420
|
+
const base = explicit ? resolved === "dark" ? darkTheme : lightTheme : void 0;
|
|
421
|
+
return {
|
|
422
|
+
...base,
|
|
423
|
+
...vars,
|
|
424
|
+
...explicit ? { colorScheme: resolved } : {},
|
|
425
|
+
...style
|
|
426
|
+
};
|
|
427
|
+
}, [explicit, resolved, vars, style]);
|
|
428
|
+
const context = (0, import_react3.useMemo)(
|
|
429
|
+
() => ({ mode: mode ?? "system", theme: resolved }),
|
|
430
|
+
[mode, resolved]
|
|
431
|
+
);
|
|
205
432
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JuneContext.Provider, { value: context, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
206
433
|
"div",
|
|
207
434
|
{
|
|
208
435
|
className: className ? `june-ui-root ${className}` : "june-ui-root",
|
|
209
|
-
"data-june-theme": resolved,
|
|
436
|
+
"data-june-theme": explicit ? resolved : void 0,
|
|
210
437
|
style: mergedStyle,
|
|
211
438
|
children
|
|
212
439
|
}
|
|
@@ -622,12 +849,12 @@ function splitResponse(source, options = {}) {
|
|
|
622
849
|
}
|
|
623
850
|
|
|
624
851
|
// src/renderer/error-boundary.tsx
|
|
625
|
-
var
|
|
852
|
+
var import_react4 = require("react");
|
|
626
853
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
627
854
|
function keysChanged(a = [], b = []) {
|
|
628
855
|
return a.length !== b.length || a.some((v, i) => !Object.is(v, b[i]));
|
|
629
856
|
}
|
|
630
|
-
var JuneErrorBoundary = class extends
|
|
857
|
+
var JuneErrorBoundary = class extends import_react4.Component {
|
|
631
858
|
constructor() {
|
|
632
859
|
super(...arguments);
|
|
633
860
|
this.state = { error: null };
|
|
@@ -665,7 +892,8 @@ function JuneRenderer(props) {
|
|
|
665
892
|
isStreaming = false,
|
|
666
893
|
mixedContent = true,
|
|
667
894
|
renderText,
|
|
668
|
-
|
|
895
|
+
repair = true,
|
|
896
|
+
theme,
|
|
669
897
|
themeVars,
|
|
670
898
|
className,
|
|
671
899
|
style,
|
|
@@ -675,29 +903,16 @@ function JuneRenderer(props) {
|
|
|
675
903
|
onAction,
|
|
676
904
|
onError,
|
|
677
905
|
onJuneError,
|
|
906
|
+
documents,
|
|
678
907
|
loadingFallback = null,
|
|
679
908
|
emptyFallback = null,
|
|
680
909
|
errorFallback,
|
|
681
910
|
resetKeys,
|
|
682
911
|
...rendererProps
|
|
683
912
|
} = props;
|
|
684
|
-
const latest = (0,
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
parseActionEvent,
|
|
688
|
-
onAction,
|
|
689
|
-
onError,
|
|
690
|
-
onJuneError
|
|
691
|
-
});
|
|
692
|
-
latest.current = {
|
|
693
|
-
actions,
|
|
694
|
-
confirmAction,
|
|
695
|
-
parseActionEvent,
|
|
696
|
-
onAction,
|
|
697
|
-
onError,
|
|
698
|
-
onJuneError
|
|
699
|
-
};
|
|
700
|
-
const handleAction = (0, import_react4.useCallback)((event) => {
|
|
913
|
+
const latest = (0, import_react5.useRef)({ actions, confirmAction, parseActionEvent, onAction, onError, onJuneError });
|
|
914
|
+
latest.current = { actions, confirmAction, parseActionEvent, onAction, onError, onJuneError };
|
|
915
|
+
const handleAction = (0, import_react5.useCallback)((event) => {
|
|
701
916
|
const l = latest.current;
|
|
702
917
|
l.onAction?.(event);
|
|
703
918
|
if (l.actions) {
|
|
@@ -709,21 +924,21 @@ function JuneRenderer(props) {
|
|
|
709
924
|
});
|
|
710
925
|
}
|
|
711
926
|
}, []);
|
|
712
|
-
const handleParserErrors = (0,
|
|
713
|
-
const
|
|
927
|
+
const handleParserErrors = (0, import_react5.useCallback)((errors) => {
|
|
928
|
+
const list2 = Array.isArray(errors) ? errors : [];
|
|
714
929
|
const l = latest.current;
|
|
715
|
-
l.onError?.(
|
|
716
|
-
const real =
|
|
930
|
+
l.onError?.(list2);
|
|
931
|
+
const real = list2.filter((e) => e?.severity !== "warning");
|
|
717
932
|
if (real.length > 0) {
|
|
718
933
|
l.onJuneError?.(
|
|
719
934
|
new JuneError("PARSE_ERROR", `OpenUI reported ${real.length} error(s)`, { details: real })
|
|
720
935
|
);
|
|
721
936
|
}
|
|
722
|
-
if (
|
|
723
|
-
console.warn("[june-ui] OpenUI reported errors:",
|
|
937
|
+
if (list2.length > 0 && !l.onError && !l.onJuneError) {
|
|
938
|
+
console.warn("[june-ui] OpenUI reported errors:", list2);
|
|
724
939
|
}
|
|
725
940
|
}, []);
|
|
726
|
-
const handleBoundaryError = (0,
|
|
941
|
+
const handleBoundaryError = (0, import_react5.useCallback)((error, info) => {
|
|
727
942
|
latest.current.onJuneError?.(
|
|
728
943
|
new JuneError("RENDER_ERROR", error.message, {
|
|
729
944
|
cause: error,
|
|
@@ -731,21 +946,32 @@ function JuneRenderer(props) {
|
|
|
731
946
|
})
|
|
732
947
|
);
|
|
733
948
|
}, []);
|
|
734
|
-
const
|
|
949
|
+
const runtime = (0, import_react5.useMemo)(() => ({ isStreaming }), [isStreaming]);
|
|
950
|
+
const documentOptions = (0, import_react5.useMemo)(
|
|
951
|
+
() => ({
|
|
952
|
+
...documents,
|
|
953
|
+
onError: (error) => {
|
|
954
|
+
documents?.onError?.(error);
|
|
955
|
+
latest.current.onJuneError?.(error);
|
|
956
|
+
}
|
|
957
|
+
}),
|
|
958
|
+
[documents]
|
|
959
|
+
);
|
|
960
|
+
const parts = (0, import_react5.useMemo)(
|
|
735
961
|
() => mixedContent && response ? splitResponse(response, { streaming: isStreaming }) : null,
|
|
736
962
|
[mixedContent, response, isStreaming]
|
|
737
963
|
);
|
|
738
|
-
const uiText = (0,
|
|
964
|
+
const uiText = (0, import_react5.useMemo)(() => {
|
|
739
965
|
const raw = parts && !parts.pure ? parts.ui : response;
|
|
740
|
-
if (!mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0) return raw;
|
|
966
|
+
if (!repair || !mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0) return raw;
|
|
741
967
|
return normalizeProgram(raw);
|
|
742
|
-
}, [parts, response, mixedContent, isStreaming]);
|
|
968
|
+
}, [parts, response, mixedContent, isStreaming, repair]);
|
|
743
969
|
const hasUi = typeof uiText === "string" && uiText.trim().length > 0;
|
|
744
970
|
const before = parts?.before ?? [];
|
|
745
971
|
const after = parts?.after ?? [];
|
|
746
972
|
const hasText = before.length + after.length > 0;
|
|
747
|
-
const text = (value, index, key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
748
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneThemeProvider, { mode: theme, vars: themeVars, className, style, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
973
|
+
const text = (value, index, key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Fragment, { children: renderText ? renderText(value, index) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "june-text", children: value }) }, key);
|
|
974
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneThemeProvider, { mode: theme, vars: themeVars, className, style, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneDocumentContext.Provider, { value: documentOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
749
975
|
JuneErrorBoundary,
|
|
750
976
|
{
|
|
751
977
|
fallback: errorFallback,
|
|
@@ -768,11 +994,12 @@ function JuneRenderer(props) {
|
|
|
768
994
|
!hasUi && !hasText && (isStreaming ? loadingFallback : emptyFallback)
|
|
769
995
|
]
|
|
770
996
|
}
|
|
771
|
-
) });
|
|
997
|
+
) }) }) });
|
|
772
998
|
}
|
|
773
999
|
|
|
774
1000
|
// src/components/library.ts
|
|
775
1001
|
var import_react_lang2 = require("@openuidev/react-lang");
|
|
1002
|
+
init_errors();
|
|
776
1003
|
var defineJuneComponent = import_react_lang2.defineComponent;
|
|
777
1004
|
function createJuneLibrary(options) {
|
|
778
1005
|
const { allow, deny, ...rest } = options;
|
|
@@ -814,6 +1041,7 @@ function createJuneLibrary(options) {
|
|
|
814
1041
|
}
|
|
815
1042
|
|
|
816
1043
|
// src/actions/define-action.ts
|
|
1044
|
+
init_errors();
|
|
817
1045
|
function defineJuneAction(definition) {
|
|
818
1046
|
if (typeof definition.handler !== "function") {
|
|
819
1047
|
throw new JuneError("ACTION_VALIDATION_ERROR", "defineJuneAction requires a handler function");
|
|
@@ -822,9 +1050,17 @@ function defineJuneAction(definition) {
|
|
|
822
1050
|
}
|
|
823
1051
|
|
|
824
1052
|
// src/streaming/use-june-stream.ts
|
|
825
|
-
var
|
|
1053
|
+
var import_react6 = require("react");
|
|
1054
|
+
init_errors();
|
|
1055
|
+
|
|
1056
|
+
// src/streaming/fetch-adapter.ts
|
|
1057
|
+
init_errors();
|
|
1058
|
+
|
|
1059
|
+
// src/streaming/sse-adapter.ts
|
|
1060
|
+
init_errors();
|
|
826
1061
|
|
|
827
1062
|
// src/streaming/stream-handler.ts
|
|
1063
|
+
init_errors();
|
|
828
1064
|
function isReadableStream(source) {
|
|
829
1065
|
return typeof source.getReader === "function";
|
|
830
1066
|
}
|
|
@@ -995,20 +1231,20 @@ var INITIAL = {
|
|
|
995
1231
|
error: null
|
|
996
1232
|
};
|
|
997
1233
|
function useJuneStream(options = {}) {
|
|
998
|
-
const [state, setState] = (0,
|
|
999
|
-
const optionsRef = (0,
|
|
1234
|
+
const [state, setState] = (0, import_react6.useState)(INITIAL);
|
|
1235
|
+
const optionsRef = (0, import_react6.useRef)(options);
|
|
1000
1236
|
optionsRef.current = options;
|
|
1001
|
-
const controllerRef = (0,
|
|
1002
|
-
const runIdRef = (0,
|
|
1003
|
-
const textRef = (0,
|
|
1004
|
-
const timerRef = (0,
|
|
1005
|
-
const clearTimer = (0,
|
|
1237
|
+
const controllerRef = (0, import_react6.useRef)(null);
|
|
1238
|
+
const runIdRef = (0, import_react6.useRef)(0);
|
|
1239
|
+
const textRef = (0, import_react6.useRef)("");
|
|
1240
|
+
const timerRef = (0, import_react6.useRef)(null);
|
|
1241
|
+
const clearTimer = (0, import_react6.useCallback)(() => {
|
|
1006
1242
|
if (timerRef.current !== null) {
|
|
1007
1243
|
clearTimeout(timerRef.current);
|
|
1008
1244
|
timerRef.current = null;
|
|
1009
1245
|
}
|
|
1010
1246
|
}, []);
|
|
1011
|
-
const schedule = (0,
|
|
1247
|
+
const schedule = (0, import_react6.useCallback)(() => {
|
|
1012
1248
|
if (timerRef.current !== null) return;
|
|
1013
1249
|
timerRef.current = setTimeout(() => {
|
|
1014
1250
|
timerRef.current = null;
|
|
@@ -1018,7 +1254,7 @@ function useJuneStream(options = {}) {
|
|
|
1018
1254
|
);
|
|
1019
1255
|
}, FLUSH_MS);
|
|
1020
1256
|
}, []);
|
|
1021
|
-
const start = (0,
|
|
1257
|
+
const start = (0, import_react6.useCallback)(
|
|
1022
1258
|
async (input) => {
|
|
1023
1259
|
controllerRef.current?.abort();
|
|
1024
1260
|
const controller = new AbortController();
|
|
@@ -1063,21 +1299,21 @@ function useJuneStream(options = {}) {
|
|
|
1063
1299
|
},
|
|
1064
1300
|
[clearTimer, schedule]
|
|
1065
1301
|
);
|
|
1066
|
-
const startFetch = (0,
|
|
1302
|
+
const startFetch = (0, import_react6.useCallback)(
|
|
1067
1303
|
(input, fetchOptions) => start((signal) => fetchStream(input, { ...fetchOptions, signal })),
|
|
1068
1304
|
[start]
|
|
1069
1305
|
);
|
|
1070
|
-
const abort = (0,
|
|
1306
|
+
const abort = (0, import_react6.useCallback)(() => {
|
|
1071
1307
|
controllerRef.current?.abort();
|
|
1072
1308
|
}, []);
|
|
1073
|
-
const reset = (0,
|
|
1309
|
+
const reset = (0, import_react6.useCallback)(() => {
|
|
1074
1310
|
controllerRef.current?.abort();
|
|
1075
1311
|
runIdRef.current++;
|
|
1076
1312
|
textRef.current = "";
|
|
1077
1313
|
clearTimer();
|
|
1078
1314
|
setState(INITIAL);
|
|
1079
1315
|
}, [clearTimer]);
|
|
1080
|
-
(0,
|
|
1316
|
+
(0, import_react6.useEffect)(
|
|
1081
1317
|
() => () => {
|
|
1082
1318
|
runIdRef.current++;
|
|
1083
1319
|
controllerRef.current?.abort();
|
|
@@ -1085,40 +1321,1181 @@ function useJuneStream(options = {}) {
|
|
|
1085
1321
|
},
|
|
1086
1322
|
[clearTimer]
|
|
1087
1323
|
);
|
|
1088
|
-
return (0,
|
|
1324
|
+
return (0, import_react6.useMemo)(
|
|
1089
1325
|
() => ({ ...state, start, startFetch, abort, reset }),
|
|
1090
1326
|
[state, start, startFetch, abort, reset]
|
|
1091
1327
|
);
|
|
1092
1328
|
}
|
|
1093
1329
|
|
|
1094
|
-
// src/core
|
|
1095
|
-
|
|
1330
|
+
// src/entry-core.ts
|
|
1331
|
+
init_errors();
|
|
1332
|
+
|
|
1333
|
+
// src/components/document-editor/document-component.tsx
|
|
1334
|
+
var import_react_lang3 = require("@openuidev/react-lang");
|
|
1335
|
+
var import_v4 = require("zod/v4");
|
|
1336
|
+
|
|
1337
|
+
// src/components/document-editor/document-draft.tsx
|
|
1096
1338
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1097
|
-
function
|
|
1098
|
-
|
|
1339
|
+
function DocumentDraft({ title, words, state }) {
|
|
1340
|
+
if (state === "empty") {
|
|
1341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "june-doc june-doc--draft", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-text", children: "No document content was received." }) });
|
|
1342
|
+
}
|
|
1343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "june-doc june-doc--draft", role: "status", "aria-live": "polite", children: [
|
|
1344
|
+
title ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-title", children: title }) : null,
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-text", children: state === "drafting" ? `Drafting document\u2026${words ? ` ${words} words so far` : ""}` : "Loading editor\u2026" }),
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "june-doc__pulse", "aria-hidden": "true", children: [
|
|
1347
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {}),
|
|
1348
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {}),
|
|
1349
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {})
|
|
1350
|
+
] })
|
|
1351
|
+
] });
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/components/document-editor/document-editor.tsx
|
|
1355
|
+
var import_react10 = require("@tiptap/react");
|
|
1356
|
+
var import_react11 = require("react");
|
|
1357
|
+
init_errors();
|
|
1358
|
+
|
|
1359
|
+
// src/components/document-editor/document-content.tsx
|
|
1360
|
+
var import_react7 = require("@tiptap/react");
|
|
1361
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1362
|
+
function DocumentContent({ editor }) {
|
|
1363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "june-doc__content", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react7.EditorContent, { editor }) });
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// src/components/document-editor/document-editor.tsx
|
|
1367
|
+
init_document_engine();
|
|
1368
|
+
|
|
1369
|
+
// src/components/document-editor/document-export.ts
|
|
1370
|
+
init_errors();
|
|
1371
|
+
init_document_utils();
|
|
1372
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1373
|
+
var PRINT_CSS = 'body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;line-height:1.6;color:#111827;max-width:780px;margin:32px auto;padding:0 20px}h1,h2,h3{line-height:1.25}table{border-collapse:collapse;width:100%}th,td{border:1px solid #d1d5db;padding:6px 10px;text-align:left}blockquote{border-left:4px solid #d1d5db;margin:1em 0;padding-left:1em;color:#4b5563}pre,code{font-family:ui-monospace,Menlo,monospace;background:#f3f4f6}pre{padding:12px;overflow:auto}@media print{body{margin:0;max-width:none}}';
|
|
1374
|
+
function buildHtmlDocument(title, bodyHtml) {
|
|
1375
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(
|
|
1376
|
+
title
|
|
1377
|
+
)}</title><style>${PRINT_CSS}</style></head><body><h1>${escapeHtml(title)}</h1>${bodyHtml}</body></html>`;
|
|
1378
|
+
}
|
|
1379
|
+
var textResult = (text, filename, mimeType) => ({
|
|
1380
|
+
filename,
|
|
1381
|
+
mimeType,
|
|
1382
|
+
text,
|
|
1383
|
+
blob: new Blob([text], { type: `${mimeType};charset=utf-8` })
|
|
1384
|
+
});
|
|
1385
|
+
var exportHtml = async (ctx) => {
|
|
1386
|
+
const { docToHtml: docToHtml2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
1387
|
+
return textResult(
|
|
1388
|
+
buildHtmlDocument(ctx.title, docToHtml2(ctx.json)),
|
|
1389
|
+
`${ctx.basename}.html`,
|
|
1390
|
+
"text/html"
|
|
1391
|
+
);
|
|
1392
|
+
};
|
|
1393
|
+
var exportMarkdown = async (ctx) => {
|
|
1394
|
+
const { docToMarkdown: docToMarkdown2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
1395
|
+
return textResult(
|
|
1396
|
+
`# ${ctx.title}
|
|
1397
|
+
|
|
1398
|
+
${docToMarkdown2(ctx.json)}
|
|
1399
|
+
`,
|
|
1400
|
+
`${ctx.basename}.md`,
|
|
1401
|
+
"text/markdown"
|
|
1402
|
+
);
|
|
1403
|
+
};
|
|
1404
|
+
var exportTxt = async (ctx) => {
|
|
1405
|
+
const { docToText: docToText2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
1406
|
+
return textResult(`${ctx.title}
|
|
1407
|
+
|
|
1408
|
+
${docToText2(ctx.json)}
|
|
1409
|
+
`, `${ctx.basename}.txt`, "text/plain");
|
|
1410
|
+
};
|
|
1411
|
+
var ORDERED_REF = "june-ordered";
|
|
1412
|
+
var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
1413
|
+
function runs(d, node, fmt) {
|
|
1414
|
+
const out = [];
|
|
1415
|
+
for (const child of node.content ?? []) {
|
|
1416
|
+
if (child.type === "hardBreak") {
|
|
1417
|
+
out.push(new d.TextRun({ break: 1 }));
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (child.type !== "text" || !child.text) continue;
|
|
1421
|
+
const marks = child.marks ?? [];
|
|
1422
|
+
const has = (type) => marks.some((m) => m.type === type);
|
|
1423
|
+
const link = marks.find((m) => m.type === "link");
|
|
1424
|
+
const run = new d.TextRun({
|
|
1425
|
+
text: child.text,
|
|
1426
|
+
bold: fmt.bold || has("bold") || void 0,
|
|
1427
|
+
italics: has("italic") || void 0,
|
|
1428
|
+
underline: has("underline") || link ? {} : void 0,
|
|
1429
|
+
strike: has("strike") || void 0,
|
|
1430
|
+
font: has("code") ? "Courier New" : void 0,
|
|
1431
|
+
color: link ? "0563C1" : void 0
|
|
1432
|
+
});
|
|
1433
|
+
const href = typeof link?.attrs?.href === "string" ? sanitizeHref(link.attrs.href) : null;
|
|
1434
|
+
out.push(href ? new d.ExternalHyperlink({ link: href, children: [run] }) : run);
|
|
1435
|
+
}
|
|
1436
|
+
return out;
|
|
1437
|
+
}
|
|
1438
|
+
function paragraph(d, node, fmt, extra = {}) {
|
|
1439
|
+
const align = node.attrs?.textAlign;
|
|
1440
|
+
return new d.Paragraph({
|
|
1441
|
+
children: runs(d, node, fmt),
|
|
1442
|
+
alignment: align === "center" ? d.AlignmentType.CENTER : align === "right" ? d.AlignmentType.RIGHT : align === "justify" ? d.AlignmentType.JUSTIFIED : void 0,
|
|
1443
|
+
indent: fmt.indent ? { left: fmt.indent } : void 0,
|
|
1444
|
+
border: fmt.quote ? { left: { color: "9CA3AF", space: 8, style: d.BorderStyle.SINGLE, size: 12 } } : void 0,
|
|
1445
|
+
spacing: { after: 120 },
|
|
1446
|
+
...extra
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function list(d, node, fmt, level, state) {
|
|
1450
|
+
const ordered = node.type === "orderedList";
|
|
1451
|
+
const instance = ordered ? ++state.instance : 0;
|
|
1452
|
+
const out = [];
|
|
1453
|
+
for (const item of node.content ?? []) {
|
|
1454
|
+
let first = true;
|
|
1455
|
+
for (const child of item.content ?? []) {
|
|
1456
|
+
if (child.type === "bulletList" || child.type === "orderedList") {
|
|
1457
|
+
out.push(...list(d, child, fmt, level + 1, state));
|
|
1458
|
+
} else if (child.type === "paragraph" && first) {
|
|
1459
|
+
first = false;
|
|
1460
|
+
const lvl = Math.min(level, 2);
|
|
1461
|
+
out.push(
|
|
1462
|
+
paragraph(
|
|
1463
|
+
d,
|
|
1464
|
+
child,
|
|
1465
|
+
fmt,
|
|
1466
|
+
ordered ? { numbering: { reference: ORDERED_REF, level: lvl, instance } } : { bullet: { level: lvl } }
|
|
1467
|
+
)
|
|
1468
|
+
);
|
|
1469
|
+
} else {
|
|
1470
|
+
out.push(...blocks(d, [child], { ...fmt, indent: (fmt.indent ?? 0) + 720 * (level + 1) }, state));
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return out;
|
|
1475
|
+
}
|
|
1476
|
+
function code(d, node) {
|
|
1477
|
+
const lines = (node.content ?? []).map((c) => c.text ?? "").join("").split("\n");
|
|
1478
|
+
return new d.Paragraph({
|
|
1479
|
+
children: lines.map((line, i) => new d.TextRun({ text: line, font: "Courier New", break: i ? 1 : void 0 })),
|
|
1480
|
+
spacing: { after: 120 },
|
|
1481
|
+
shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6" }
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
function table(d, node, state) {
|
|
1485
|
+
const rows = (node.content ?? []).map(
|
|
1486
|
+
(row) => new d.TableRow({
|
|
1487
|
+
children: (row.content ?? []).map((cell) => {
|
|
1488
|
+
const header = cell.type === "tableHeader";
|
|
1489
|
+
const children = blocks(d, cell.content, { bold: header }, state);
|
|
1490
|
+
return new d.TableCell({
|
|
1491
|
+
children: children.length ? children : [new d.Paragraph("")],
|
|
1492
|
+
shading: header ? { type: d.ShadingType.CLEAR, fill: "F3F4F6" } : void 0
|
|
1493
|
+
});
|
|
1494
|
+
})
|
|
1495
|
+
})
|
|
1496
|
+
);
|
|
1497
|
+
if (rows.length === 0) return new d.Paragraph("");
|
|
1498
|
+
return new d.Table({ rows, width: { size: 100, type: d.WidthType.PERCENTAGE } });
|
|
1499
|
+
}
|
|
1500
|
+
function blocks(d, nodes, fmt, state) {
|
|
1501
|
+
const headings = [d.HeadingLevel.HEADING_1, d.HeadingLevel.HEADING_2, d.HeadingLevel.HEADING_3];
|
|
1502
|
+
const out = [];
|
|
1503
|
+
for (const node of nodes ?? []) {
|
|
1504
|
+
switch (node.type) {
|
|
1505
|
+
case "paragraph":
|
|
1506
|
+
out.push(paragraph(d, node, fmt));
|
|
1507
|
+
break;
|
|
1508
|
+
case "heading": {
|
|
1509
|
+
const level = Math.min(Math.max(Number(node.attrs?.level) || 1, 1), 3);
|
|
1510
|
+
out.push(paragraph(d, node, fmt, { heading: headings[level - 1] }));
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
case "bulletList":
|
|
1514
|
+
case "orderedList":
|
|
1515
|
+
out.push(...list(d, node, fmt, 0, state));
|
|
1516
|
+
break;
|
|
1517
|
+
case "blockquote":
|
|
1518
|
+
out.push(...blocks(d, node.content, { ...fmt, quote: true, indent: (fmt.indent ?? 0) + 360 }, state));
|
|
1519
|
+
break;
|
|
1520
|
+
case "codeBlock":
|
|
1521
|
+
out.push(code(d, node));
|
|
1522
|
+
break;
|
|
1523
|
+
case "horizontalRule":
|
|
1524
|
+
out.push(
|
|
1525
|
+
new d.Paragraph({
|
|
1526
|
+
border: { bottom: { color: "9CA3AF", space: 1, style: d.BorderStyle.SINGLE, size: 6 } },
|
|
1527
|
+
spacing: { after: 120 }
|
|
1528
|
+
})
|
|
1529
|
+
);
|
|
1530
|
+
break;
|
|
1531
|
+
case "table":
|
|
1532
|
+
out.push(table(d, node, state));
|
|
1533
|
+
break;
|
|
1534
|
+
default:
|
|
1535
|
+
if (node.content) out.push(...blocks(d, node.content, fmt, state));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
var exportDocx = async (ctx) => {
|
|
1541
|
+
const d = await import("docx");
|
|
1542
|
+
const state = { instance: 0 };
|
|
1543
|
+
const children = [
|
|
1544
|
+
new d.Paragraph({ text: ctx.title, heading: d.HeadingLevel.TITLE }),
|
|
1545
|
+
...blocks(d, ctx.json.content, {}, state)
|
|
1546
|
+
];
|
|
1547
|
+
const doc = new d.Document({
|
|
1548
|
+
title: ctx.title,
|
|
1549
|
+
numbering: {
|
|
1550
|
+
config: [
|
|
1551
|
+
{
|
|
1552
|
+
reference: ORDERED_REF,
|
|
1553
|
+
levels: [0, 1, 2].map((level) => ({
|
|
1554
|
+
level,
|
|
1555
|
+
format: d.LevelFormat.DECIMAL,
|
|
1556
|
+
text: `%${level + 1}.`,
|
|
1557
|
+
alignment: d.AlignmentType.START,
|
|
1558
|
+
style: { paragraph: { indent: { left: 720 * (level + 1), hanging: 360 } } }
|
|
1559
|
+
}))
|
|
1560
|
+
}
|
|
1561
|
+
]
|
|
1562
|
+
},
|
|
1563
|
+
sections: [{ children }]
|
|
1564
|
+
});
|
|
1565
|
+
const blob = await d.Packer.toBlob(doc);
|
|
1566
|
+
return { filename: `${ctx.basename}.docx`, mimeType: DOCX_MIME, blob };
|
|
1567
|
+
};
|
|
1568
|
+
function printHtml(html) {
|
|
1569
|
+
return new Promise((resolve, reject) => {
|
|
1570
|
+
const frame = document.createElement("iframe");
|
|
1571
|
+
frame.setAttribute("aria-hidden", "true");
|
|
1572
|
+
frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0;";
|
|
1573
|
+
const cleanup = () => setTimeout(() => frame.remove(), 500);
|
|
1574
|
+
frame.onload = () => {
|
|
1575
|
+
try {
|
|
1576
|
+
const win = frame.contentWindow;
|
|
1577
|
+
if (!win) throw new Error("The print frame is unavailable");
|
|
1578
|
+
win.addEventListener("afterprint", cleanup);
|
|
1579
|
+
win.focus();
|
|
1580
|
+
win.print();
|
|
1581
|
+
setTimeout(cleanup, 12e4);
|
|
1582
|
+
resolve();
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
cleanup();
|
|
1585
|
+
reject(error);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
frame.srcdoc = html;
|
|
1589
|
+
document.body.appendChild(frame);
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
var exportPdf = async (ctx) => {
|
|
1593
|
+
const { docToHtml: docToHtml2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
1594
|
+
await printHtml(buildHtmlDocument(ctx.title, docToHtml2(ctx.json)));
|
|
1595
|
+
return { filename: `${ctx.basename}.pdf`, mimeType: "application/pdf", handled: true };
|
|
1596
|
+
};
|
|
1597
|
+
var registry = /* @__PURE__ */ new Map([
|
|
1598
|
+
["html", exportHtml],
|
|
1599
|
+
["markdown", exportMarkdown],
|
|
1600
|
+
["txt", exportTxt],
|
|
1601
|
+
["docx", exportDocx],
|
|
1602
|
+
["pdf", exportPdf]
|
|
1603
|
+
]);
|
|
1604
|
+
function registerDocumentExporter(format, exporter) {
|
|
1605
|
+
registry.set(format, exporter);
|
|
1606
|
+
}
|
|
1607
|
+
function listDocumentExporters() {
|
|
1608
|
+
return [...registry.keys()];
|
|
1609
|
+
}
|
|
1610
|
+
async function exportDocument(format, doc) {
|
|
1611
|
+
const exporter = registry.get(format);
|
|
1612
|
+
if (!exporter) throw new JuneError("EXPORT_ERROR", `No exporter is registered for "${format}"`);
|
|
1613
|
+
try {
|
|
1614
|
+
return await exporter({ ...doc, basename: slugify(doc.title) || "document" });
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
throw toJuneError(error, "EXPORT_ERROR", `Exporting ${format} failed`);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function downloadResult(result) {
|
|
1620
|
+
if (!result.blob) return;
|
|
1621
|
+
const url = URL.createObjectURL(result.blob);
|
|
1622
|
+
const link = document.createElement("a");
|
|
1623
|
+
link.href = url;
|
|
1624
|
+
link.download = result.filename;
|
|
1625
|
+
document.body.appendChild(link);
|
|
1626
|
+
link.click();
|
|
1627
|
+
link.remove();
|
|
1628
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
1629
|
+
}
|
|
1630
|
+
async function copyDocumentToClipboard(doc) {
|
|
1631
|
+
const { docToHtml: docToHtml2, docToText: docToText2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
1632
|
+
const html = docToHtml2(doc.json);
|
|
1633
|
+
const text = `${doc.title}
|
|
1634
|
+
|
|
1635
|
+
${docToText2(doc.json)}`;
|
|
1636
|
+
try {
|
|
1637
|
+
if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
|
|
1638
|
+
await navigator.clipboard.write([
|
|
1639
|
+
new ClipboardItem({
|
|
1640
|
+
"text/html": new Blob([html], { type: "text/html" }),
|
|
1641
|
+
"text/plain": new Blob([text], { type: "text/plain" })
|
|
1642
|
+
})
|
|
1643
|
+
]);
|
|
1644
|
+
return true;
|
|
1645
|
+
}
|
|
1646
|
+
await navigator.clipboard.writeText(text);
|
|
1647
|
+
return true;
|
|
1648
|
+
} catch {
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/components/document-editor/document-editor.tsx
|
|
1654
|
+
init_document_extensions();
|
|
1655
|
+
|
|
1656
|
+
// src/components/document-editor/document-merge.ts
|
|
1657
|
+
init_document_utils();
|
|
1658
|
+
var MAX_CELLS = 4e6;
|
|
1659
|
+
function matchIndices(a, b) {
|
|
1660
|
+
const n = a.length;
|
|
1661
|
+
const m = b.length;
|
|
1662
|
+
if (n * m > MAX_CELLS) return null;
|
|
1663
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
1664
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
1665
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
1666
|
+
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const matches = /* @__PURE__ */ new Map();
|
|
1670
|
+
let i = 0;
|
|
1671
|
+
let j = 0;
|
|
1672
|
+
while (i < n && j < m) {
|
|
1673
|
+
if (a[i] === b[j]) {
|
|
1674
|
+
matches.set(i, j);
|
|
1675
|
+
i++;
|
|
1676
|
+
j++;
|
|
1677
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) i++;
|
|
1678
|
+
else j++;
|
|
1679
|
+
}
|
|
1680
|
+
return matches;
|
|
1681
|
+
}
|
|
1682
|
+
var same = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
|
|
1683
|
+
function mergeBlocks(base, local, incoming) {
|
|
1684
|
+
if (same(local, base)) return { blocks: incoming, conflicts: 0 };
|
|
1685
|
+
if (same(incoming, base) || same(local, incoming)) return { blocks: local, conflicts: 0 };
|
|
1686
|
+
const ml = matchIndices(base, local);
|
|
1687
|
+
const mn = matchIndices(base, incoming);
|
|
1688
|
+
if (!ml || !mn) return { blocks: null, conflicts: 1 };
|
|
1689
|
+
const anchors = [...ml.keys()].filter((i) => mn.has(i)).sort((x, y) => x - y);
|
|
1690
|
+
const out = [];
|
|
1691
|
+
let conflicts = 0;
|
|
1692
|
+
let pb = -1;
|
|
1693
|
+
let pl = -1;
|
|
1694
|
+
let pn = -1;
|
|
1695
|
+
const region = (bEnd, lEnd, nEnd) => {
|
|
1696
|
+
const B = base.slice(pb + 1, bEnd);
|
|
1697
|
+
const L = local.slice(pl + 1, lEnd);
|
|
1698
|
+
const N = incoming.slice(pn + 1, nEnd);
|
|
1699
|
+
if (B.length === L.length && L.length === N.length) {
|
|
1700
|
+
for (let k = 0; k < B.length; k++) {
|
|
1701
|
+
if (L[k] === B[k]) out.push(N[k]);
|
|
1702
|
+
else if (N[k] === B[k] || N[k] === L[k]) out.push(L[k]);
|
|
1703
|
+
else {
|
|
1704
|
+
conflicts++;
|
|
1705
|
+
out.push(L[k]);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (same(L, B)) out.push(...N);
|
|
1711
|
+
else if (same(N, B) || same(L, N)) out.push(...L);
|
|
1712
|
+
else {
|
|
1713
|
+
conflicts++;
|
|
1714
|
+
out.push(...L);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
for (const a of anchors) {
|
|
1718
|
+
const la = ml.get(a);
|
|
1719
|
+
const na = mn.get(a);
|
|
1720
|
+
region(a, la, na);
|
|
1721
|
+
out.push(local[la]);
|
|
1722
|
+
pb = a;
|
|
1723
|
+
pl = la;
|
|
1724
|
+
pn = na;
|
|
1725
|
+
}
|
|
1726
|
+
region(base.length, local.length, incoming.length);
|
|
1727
|
+
return { blocks: out, conflicts };
|
|
1728
|
+
}
|
|
1729
|
+
function mergeDocuments(base, local, incoming) {
|
|
1730
|
+
const toBlocks = (d) => (d.content ?? []).map((node) => JSON.stringify(node));
|
|
1731
|
+
const { blocks: blocks2, conflicts } = mergeBlocks(toBlocks(base), toBlocks(local), toBlocks(incoming));
|
|
1732
|
+
if (!blocks2) return { doc: local, conflicts: Math.max(1, conflicts) };
|
|
1733
|
+
if (blocks2.length === 0) return { doc: emptyDoc(), conflicts };
|
|
1734
|
+
return { doc: { type: "doc", content: blocks2.map((b) => JSON.parse(b)) }, conflicts };
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/components/document-editor/document-store.ts
|
|
1738
|
+
init_document_utils();
|
|
1739
|
+
var incomingKey = (doc) => JSON.stringify([doc.title, docKey(doc.json)]);
|
|
1740
|
+
function createDocumentStore() {
|
|
1741
|
+
const records = /* @__PURE__ */ new Map();
|
|
1742
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1743
|
+
const emit = () => listeners.forEach((listener) => listener());
|
|
1744
|
+
const put = (record) => {
|
|
1745
|
+
records.set(record.id, record);
|
|
1746
|
+
emit();
|
|
1747
|
+
};
|
|
1748
|
+
const fresh2 = (doc) => ({
|
|
1749
|
+
id: doc.id,
|
|
1750
|
+
title: doc.title,
|
|
1751
|
+
documentType: doc.documentType,
|
|
1752
|
+
status: doc.status,
|
|
1753
|
+
json: doc.json,
|
|
1754
|
+
baseJson: doc.json,
|
|
1755
|
+
baseTitle: doc.title,
|
|
1756
|
+
lastIncomingKey: incomingKey(doc),
|
|
1757
|
+
revision: 0,
|
|
1758
|
+
origin: "external",
|
|
1759
|
+
pending: null,
|
|
1760
|
+
updatedAt: Date.now()
|
|
1761
|
+
});
|
|
1762
|
+
return {
|
|
1763
|
+
get: (id) => records.get(id),
|
|
1764
|
+
list: () => [...records.values()],
|
|
1765
|
+
subscribe: (listener) => {
|
|
1766
|
+
listeners.add(listener);
|
|
1767
|
+
return () => {
|
|
1768
|
+
listeners.delete(listener);
|
|
1769
|
+
};
|
|
1770
|
+
},
|
|
1771
|
+
receive(incoming) {
|
|
1772
|
+
const current = records.get(incoming.id);
|
|
1773
|
+
if (!current) {
|
|
1774
|
+
put(fresh2(incoming));
|
|
1775
|
+
return "created";
|
|
1776
|
+
}
|
|
1777
|
+
const key = incomingKey(incoming);
|
|
1778
|
+
if (current.lastIncomingKey === key) return "unchanged";
|
|
1779
|
+
const userEditedBody = docKey(current.json) !== docKey(current.baseJson);
|
|
1780
|
+
const userEditedTitle = current.title !== current.baseTitle;
|
|
1781
|
+
const title = userEditedTitle ? current.title : incoming.title;
|
|
1782
|
+
const meta = {
|
|
1783
|
+
documentType: incoming.documentType ?? current.documentType,
|
|
1784
|
+
status: incoming.status ?? current.status
|
|
1785
|
+
};
|
|
1786
|
+
if (!userEditedBody) {
|
|
1787
|
+
put({
|
|
1788
|
+
...current,
|
|
1789
|
+
...meta,
|
|
1790
|
+
title,
|
|
1791
|
+
baseTitle: incoming.title,
|
|
1792
|
+
json: incoming.json,
|
|
1793
|
+
baseJson: incoming.json,
|
|
1794
|
+
lastIncomingKey: key,
|
|
1795
|
+
revision: current.revision + 1,
|
|
1796
|
+
origin: "external",
|
|
1797
|
+
pending: null,
|
|
1798
|
+
updatedAt: Date.now()
|
|
1799
|
+
});
|
|
1800
|
+
return "applied";
|
|
1801
|
+
}
|
|
1802
|
+
const { doc, conflicts } = mergeDocuments(current.baseJson, current.json, incoming.json);
|
|
1803
|
+
if (conflicts === 0) {
|
|
1804
|
+
put({
|
|
1805
|
+
...current,
|
|
1806
|
+
...meta,
|
|
1807
|
+
title,
|
|
1808
|
+
baseTitle: incoming.title,
|
|
1809
|
+
json: doc,
|
|
1810
|
+
baseJson: incoming.json,
|
|
1811
|
+
lastIncomingKey: key,
|
|
1812
|
+
revision: current.revision + 1,
|
|
1813
|
+
origin: "external",
|
|
1814
|
+
pending: null,
|
|
1815
|
+
updatedAt: Date.now()
|
|
1816
|
+
});
|
|
1817
|
+
return "merged";
|
|
1818
|
+
}
|
|
1819
|
+
put({
|
|
1820
|
+
...current,
|
|
1821
|
+
...meta,
|
|
1822
|
+
lastIncomingKey: key,
|
|
1823
|
+
pending: { json: incoming.json, title: incoming.title, merged: doc, conflicts }
|
|
1824
|
+
});
|
|
1825
|
+
return "conflict";
|
|
1826
|
+
},
|
|
1827
|
+
hydrate(doc) {
|
|
1828
|
+
put(fresh2(doc));
|
|
1829
|
+
},
|
|
1830
|
+
edit(id, json, origin) {
|
|
1831
|
+
const current = records.get(id);
|
|
1832
|
+
if (!current || docKey(current.json) === docKey(json)) return;
|
|
1833
|
+
put({ ...current, json, origin, revision: current.revision + 1, updatedAt: Date.now() });
|
|
1834
|
+
},
|
|
1835
|
+
setTitle(id, title) {
|
|
1836
|
+
const current = records.get(id);
|
|
1837
|
+
if (!current || current.title === title) return;
|
|
1838
|
+
put({ ...current, title, updatedAt: Date.now() });
|
|
1839
|
+
},
|
|
1840
|
+
resolve(id, choice) {
|
|
1841
|
+
const current = records.get(id);
|
|
1842
|
+
const pending = current?.pending;
|
|
1843
|
+
if (!current || !pending) return;
|
|
1844
|
+
if (choice === "keep") {
|
|
1845
|
+
put({ ...current, baseJson: pending.json, baseTitle: pending.title, pending: null });
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const json = choice === "merge" ? pending.merged : pending.json;
|
|
1849
|
+
const title = choice === "merge" ? current.title : pending.title;
|
|
1850
|
+
put({
|
|
1851
|
+
...current,
|
|
1852
|
+
title,
|
|
1853
|
+
json,
|
|
1854
|
+
baseJson: pending.json,
|
|
1855
|
+
baseTitle: pending.title,
|
|
1856
|
+
revision: current.revision + 1,
|
|
1857
|
+
origin: "external",
|
|
1858
|
+
pending: null,
|
|
1859
|
+
updatedAt: Date.now()
|
|
1860
|
+
});
|
|
1861
|
+
},
|
|
1862
|
+
remove(id) {
|
|
1863
|
+
if (records.delete(id)) emit();
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
var defaultStore = null;
|
|
1868
|
+
function getDefaultDocumentStore() {
|
|
1869
|
+
if (!defaultStore) defaultStore = createDocumentStore();
|
|
1870
|
+
return defaultStore;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// src/components/document-editor/document-title.tsx
|
|
1874
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1875
|
+
function DocumentTitle({ value, disabled, onChange }) {
|
|
1876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1877
|
+
"input",
|
|
1878
|
+
{
|
|
1879
|
+
className: "june-doc__title",
|
|
1880
|
+
"aria-label": "Document title",
|
|
1881
|
+
value,
|
|
1882
|
+
disabled,
|
|
1883
|
+
placeholder: "Untitled document",
|
|
1884
|
+
onChange: (e) => onChange(e.target.value)
|
|
1885
|
+
}
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// src/components/document-editor/document-toolbar.tsx
|
|
1890
|
+
var import_react8 = require("@tiptap/react");
|
|
1891
|
+
var import_react9 = require("react");
|
|
1892
|
+
init_document_utils();
|
|
1893
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1894
|
+
var FORMAT_LABELS = {
|
|
1895
|
+
html: "HTML",
|
|
1896
|
+
markdown: "Markdown",
|
|
1897
|
+
txt: "Plain text",
|
|
1898
|
+
docx: "Word (.docx)",
|
|
1899
|
+
pdf: "PDF (print)"
|
|
1900
|
+
};
|
|
1901
|
+
function Btn({ label, active, disabled, toggle, onClick, children }) {
|
|
1902
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1903
|
+
"button",
|
|
1904
|
+
{
|
|
1905
|
+
type: "button",
|
|
1906
|
+
className: "june-doc__btn",
|
|
1907
|
+
"aria-label": label,
|
|
1908
|
+
title: label,
|
|
1909
|
+
"aria-pressed": toggle ? !!active : void 0,
|
|
1910
|
+
"data-active": active || void 0,
|
|
1911
|
+
disabled,
|
|
1912
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1913
|
+
onClick,
|
|
1914
|
+
children
|
|
1915
|
+
}
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
function DocumentToolbar(props) {
|
|
1919
|
+
const { editor, editable, exportFormats, canSave, saveState, onExport, onCopy, onSave } = props;
|
|
1920
|
+
const [linkOpen, setLinkOpen] = (0, import_react9.useState)(false);
|
|
1921
|
+
const [linkValue, setLinkValue] = (0, import_react9.useState)("");
|
|
1922
|
+
const s = (0, import_react8.useEditorState)({
|
|
1923
|
+
editor,
|
|
1924
|
+
selector: ({ editor: ed }) => ed ? {
|
|
1925
|
+
bold: ed.isActive("bold"),
|
|
1926
|
+
italic: ed.isActive("italic"),
|
|
1927
|
+
underline: ed.isActive("underline"),
|
|
1928
|
+
strike: ed.isActive("strike"),
|
|
1929
|
+
bullet: ed.isActive("bulletList"),
|
|
1930
|
+
ordered: ed.isActive("orderedList"),
|
|
1931
|
+
quote: ed.isActive("blockquote"),
|
|
1932
|
+
link: ed.isActive("link"),
|
|
1933
|
+
linkHref: ed.getAttributes("link").href ?? "",
|
|
1934
|
+
h1: ed.isActive("heading", { level: 1 }),
|
|
1935
|
+
h2: ed.isActive("heading", { level: 2 }),
|
|
1936
|
+
h3: ed.isActive("heading", { level: 3 }),
|
|
1937
|
+
left: ed.isActive({ textAlign: "left" }),
|
|
1938
|
+
center: ed.isActive({ textAlign: "center" }),
|
|
1939
|
+
right: ed.isActive({ textAlign: "right" }),
|
|
1940
|
+
justify: ed.isActive({ textAlign: "justify" }),
|
|
1941
|
+
inTable: ed.isActive("table"),
|
|
1942
|
+
canUndo: ed.can().undo(),
|
|
1943
|
+
canRedo: ed.can().redo()
|
|
1944
|
+
} : null
|
|
1945
|
+
});
|
|
1946
|
+
const run = () => editor?.chain().focus();
|
|
1947
|
+
const off = !editor || !editable;
|
|
1948
|
+
const heading = s?.h1 ? "1" : s?.h2 ? "2" : s?.h3 ? "3" : "p";
|
|
1949
|
+
const openLink = () => {
|
|
1950
|
+
setLinkValue(s?.linkHref ?? "");
|
|
1951
|
+
setLinkOpen(true);
|
|
1952
|
+
};
|
|
1953
|
+
const applyLink = () => {
|
|
1954
|
+
const href = sanitizeHref(linkValue);
|
|
1955
|
+
if (href) run()?.extendMarkRange("link").setLink({ href }).run();
|
|
1956
|
+
setLinkOpen(false);
|
|
1957
|
+
};
|
|
1958
|
+
const removeLink = () => {
|
|
1959
|
+
run()?.extendMarkRange("link").unsetLink().run();
|
|
1960
|
+
setLinkOpen(false);
|
|
1961
|
+
};
|
|
1962
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__toolbar", role: "toolbar", "aria-label": "Document formatting", children: [
|
|
1963
|
+
editable ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1964
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
|
|
1965
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Undo", disabled: off || !s?.canUndo, onClick: () => run()?.undo().run(), children: "\u21B6" }),
|
|
1966
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Redo", disabled: off || !s?.canRedo, onClick: () => run()?.redo().run(), children: "\u21B7" })
|
|
1967
|
+
] }),
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "june-doc__group", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1969
|
+
"select",
|
|
1970
|
+
{
|
|
1971
|
+
className: "june-doc__select",
|
|
1972
|
+
"aria-label": "Text style",
|
|
1973
|
+
disabled: off,
|
|
1974
|
+
value: heading,
|
|
1975
|
+
onChange: (e) => {
|
|
1976
|
+
const v = e.target.value;
|
|
1977
|
+
if (v === "p") run()?.setParagraph().run();
|
|
1978
|
+
else run()?.toggleHeading({ level: Number(v) }).run();
|
|
1979
|
+
},
|
|
1980
|
+
children: [
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "p", children: "Paragraph" }),
|
|
1982
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "1", children: "Heading 1" }),
|
|
1983
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "2", children: "Heading 2" }),
|
|
1984
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "3", children: "Heading 3" })
|
|
1985
|
+
]
|
|
1986
|
+
}
|
|
1987
|
+
) }),
|
|
1988
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
|
|
1989
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Bold", toggle: true, active: s?.bold, disabled: off, onClick: () => run()?.toggleBold().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: "B" }) }),
|
|
1990
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Italic", toggle: true, active: s?.italic, disabled: off, onClick: () => run()?.toggleItalic().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { children: "I" }) }),
|
|
1991
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Underline", toggle: true, active: s?.underline, disabled: off, onClick: () => run()?.toggleUnderline().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("u", { children: "U" }) }),
|
|
1992
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Strikethrough", toggle: true, active: s?.strike, disabled: off, onClick: () => run()?.toggleStrike().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("s", { children: "S" }) })
|
|
1993
|
+
] }),
|
|
1994
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
|
|
1995
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Bullet list", toggle: true, active: s?.bullet, disabled: off, onClick: () => run()?.toggleBulletList().run(), children: "\u2022 List" }),
|
|
1996
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Numbered list", toggle: true, active: s?.ordered, disabled: off, onClick: () => run()?.toggleOrderedList().run(), children: "1. List" }),
|
|
1997
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Blockquote", toggle: true, active: s?.quote, disabled: off, onClick: () => run()?.toggleBlockquote().run(), children: "Quote" }),
|
|
1998
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Link", toggle: true, active: s?.link, disabled: off, onClick: openLink, children: "Link" })
|
|
1999
|
+
] }),
|
|
2000
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
|
|
2001
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align left", toggle: true, active: s?.left, disabled: off, onClick: () => run()?.setTextAlign("left").run(), children: "L" }),
|
|
2002
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align center", toggle: true, active: s?.center, disabled: off, onClick: () => run()?.setTextAlign("center").run(), children: "C" }),
|
|
2003
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align right", toggle: true, active: s?.right, disabled: off, onClick: () => run()?.setTextAlign("right").run(), children: "R" }),
|
|
2004
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Justify", toggle: true, active: s?.justify, disabled: off, onClick: () => run()?.setTextAlign("justify").run(), children: "J" })
|
|
2005
|
+
] }),
|
|
2006
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
|
|
2007
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Insert table", disabled: off, onClick: () => run()?.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), children: "Table" }),
|
|
2008
|
+
s?.inTable ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2009
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Add row below", disabled: off, onClick: () => run()?.addRowAfter().run(), children: "+Row" }),
|
|
2010
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Add column right", disabled: off, onClick: () => run()?.addColumnAfter().run(), children: "+Col" }),
|
|
2011
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete row", disabled: off, onClick: () => run()?.deleteRow().run(), children: "\u2212Row" }),
|
|
2012
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete column", disabled: off, onClick: () => run()?.deleteColumn().run(), children: "\u2212Col" }),
|
|
2013
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete table", disabled: off, onClick: () => run()?.deleteTable().run(), children: "Del table" })
|
|
2014
|
+
] }) : null
|
|
2015
|
+
] })
|
|
2016
|
+
] }) : null,
|
|
2017
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group june-doc__group--end", children: [
|
|
2018
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Copy document", onClick: onCopy, children: "Copy" }),
|
|
2019
|
+
exportFormats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2020
|
+
"select",
|
|
2021
|
+
{
|
|
2022
|
+
className: "june-doc__select",
|
|
2023
|
+
"aria-label": "Export document",
|
|
2024
|
+
value: "",
|
|
2025
|
+
onChange: (e) => e.target.value && onExport(e.target.value),
|
|
2026
|
+
children: [
|
|
2027
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "Export\u2026" }),
|
|
2028
|
+
exportFormats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: f, children: FORMAT_LABELS[f] ?? f }, f))
|
|
2029
|
+
]
|
|
2030
|
+
}
|
|
2031
|
+
) : null,
|
|
2032
|
+
canSave ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Save", disabled: saveState === "saving" || saveState === "saved" || saveState === "idle", onClick: onSave, children: "Save" }) : null
|
|
2033
|
+
] }),
|
|
2034
|
+
linkOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__linkbar", children: [
|
|
2035
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2036
|
+
"input",
|
|
2037
|
+
{
|
|
2038
|
+
"aria-label": "Link address",
|
|
2039
|
+
className: "june-doc__input",
|
|
2040
|
+
placeholder: "https://example.com",
|
|
2041
|
+
value: linkValue,
|
|
2042
|
+
autoFocus: true,
|
|
2043
|
+
onChange: (e) => setLinkValue(e.target.value),
|
|
2044
|
+
onKeyDown: (e) => {
|
|
2045
|
+
if (e.key === "Enter") applyLink();
|
|
2046
|
+
if (e.key === "Escape") setLinkOpen(false);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
),
|
|
2050
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: applyLink, children: "Apply" }),
|
|
2051
|
+
s?.link ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: removeLink, children: "Remove" }) : null,
|
|
2052
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => setLinkOpen(false), children: "Cancel" })
|
|
2053
|
+
] }) : null
|
|
2054
|
+
] });
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// src/components/document-editor/document-types.ts
|
|
2058
|
+
var ALL_EXPORT_FORMATS = [
|
|
2059
|
+
"html",
|
|
2060
|
+
"markdown",
|
|
2061
|
+
"txt",
|
|
2062
|
+
"docx",
|
|
2063
|
+
"pdf"
|
|
2064
|
+
];
|
|
2065
|
+
|
|
2066
|
+
// src/components/document-editor/document-editor.tsx
|
|
2067
|
+
init_document_utils();
|
|
2068
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2069
|
+
var snapshotOf = (r) => ({
|
|
2070
|
+
documentId: r.id,
|
|
2071
|
+
title: r.title,
|
|
2072
|
+
documentType: r.documentType,
|
|
2073
|
+
status: r.status,
|
|
2074
|
+
json: r.json
|
|
2075
|
+
});
|
|
2076
|
+
var SAVE_LABEL = {
|
|
2077
|
+
idle: "",
|
|
2078
|
+
dirty: "Unsaved changes",
|
|
2079
|
+
saving: "Saving\u2026",
|
|
2080
|
+
saved: "Saved",
|
|
2081
|
+
error: "Save failed"
|
|
2082
|
+
};
|
|
2083
|
+
function DocumentEditorImpl(props) {
|
|
2084
|
+
const {
|
|
2085
|
+
documentId,
|
|
2086
|
+
title: titleProp = "Untitled document",
|
|
2087
|
+
content,
|
|
2088
|
+
documentType,
|
|
2089
|
+
status = "draft",
|
|
2090
|
+
editable = true,
|
|
2091
|
+
exportFormats,
|
|
2092
|
+
theme = "auto",
|
|
2093
|
+
className
|
|
2094
|
+
} = props;
|
|
2095
|
+
const ctxOptions = useDocumentOptions();
|
|
2096
|
+
const options = (0, import_react11.useMemo)(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
|
|
2097
|
+
const optionsRef = (0, import_react11.useRef)(options);
|
|
2098
|
+
optionsRef.current = options;
|
|
2099
|
+
const store = (0, import_react11.useMemo)(() => options.store ?? getDefaultDocumentStore(), [options.store]);
|
|
2100
|
+
const instanceId = (0, import_react11.useId)();
|
|
2101
|
+
const { theme: contextTheme } = useJuneTheme();
|
|
2102
|
+
const resolvedTheme = theme === "auto" ? contextTheme : theme;
|
|
2103
|
+
const contentKey = typeof content === "string" ? content : content ? JSON.stringify(content) : "";
|
|
2104
|
+
const parsed = (0, import_react11.useMemo)(() => {
|
|
2105
|
+
if (!contentKey) return { json: null, error: null };
|
|
2106
|
+
try {
|
|
2107
|
+
return { json: parseDocumentContent(contentKey), error: null };
|
|
2108
|
+
} catch (e) {
|
|
2109
|
+
return { json: null, error: toJuneError(e, "DOCUMENT_ERROR") };
|
|
2110
|
+
}
|
|
2111
|
+
}, [contentKey]);
|
|
2112
|
+
(0, import_react11.useEffect)(() => {
|
|
2113
|
+
if (parsed.error) optionsRef.current.onError?.(parsed.error);
|
|
2114
|
+
}, [parsed.error]);
|
|
2115
|
+
const title = useDocumentValue(store, documentId, (r) => r?.title ?? titleProp);
|
|
2116
|
+
const pending = useDocumentValue(store, documentId, (r) => r?.pending ?? null);
|
|
2117
|
+
const words = useDocumentValue(store, documentId, (r) => r ? wordCountOf(r.json) : 0);
|
|
2118
|
+
const recordStatus = useDocumentValue(store, documentId, (r) => r?.status ?? status);
|
|
2119
|
+
const [saveState, setSaveState] = (0, import_react11.useState)("idle");
|
|
2120
|
+
const [notice, setNotice] = (0, import_react11.useState)(null);
|
|
2121
|
+
const lastSavedKey = (0, import_react11.useRef)("");
|
|
2122
|
+
const seenRevision = (0, import_react11.useRef)(store.get(documentId)?.revision ?? 0);
|
|
2123
|
+
const readyRef = (0, import_react11.useRef)(false);
|
|
2124
|
+
const flash = (0, import_react11.useCallback)((message) => {
|
|
2125
|
+
setNotice(message);
|
|
2126
|
+
}, []);
|
|
2127
|
+
(0, import_react11.useEffect)(() => {
|
|
2128
|
+
if (!notice) return;
|
|
2129
|
+
const t = setTimeout(() => setNotice(null), 3e3);
|
|
2130
|
+
return () => clearTimeout(t);
|
|
2131
|
+
}, [notice]);
|
|
2132
|
+
const emitChange = (0, import_react11.useCallback)(
|
|
2133
|
+
(source) => {
|
|
2134
|
+
const record = store.get(documentId);
|
|
2135
|
+
if (record) optionsRef.current.onDocumentChange?.({ ...snapshotOf(record), source });
|
|
2136
|
+
},
|
|
2137
|
+
[store, documentId]
|
|
2138
|
+
);
|
|
2139
|
+
const saveRef = (0, import_react11.useRef)(async () => {
|
|
2140
|
+
});
|
|
2141
|
+
const autosave = (0, import_react11.useMemo)(() => {
|
|
2142
|
+
const delay = options.autosaveDelay ?? 1500;
|
|
2143
|
+
return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
|
|
2144
|
+
}, [options.autosaveDelay]);
|
|
2145
|
+
(0, import_react11.useEffect)(() => () => autosave?.flush(), [autosave]);
|
|
2146
|
+
saveRef.current = async () => {
|
|
2147
|
+
const record = store.get(documentId);
|
|
2148
|
+
const onSave = optionsRef.current.onSave;
|
|
2149
|
+
if (!record || !onSave) return;
|
|
2150
|
+
const key = JSON.stringify([record.title, docKey(record.json)]);
|
|
2151
|
+
if (key === lastSavedKey.current) {
|
|
2152
|
+
setSaveState("saved");
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
setSaveState("saving");
|
|
2156
|
+
try {
|
|
2157
|
+
await onSave({
|
|
2158
|
+
...snapshotOf(record),
|
|
2159
|
+
html: docToHtml(record.json),
|
|
2160
|
+
markdown: docToMarkdown(record.json)
|
|
2161
|
+
});
|
|
2162
|
+
lastSavedKey.current = key;
|
|
2163
|
+
const latest = store.get(documentId);
|
|
2164
|
+
const latestKey = latest ? JSON.stringify([latest.title, docKey(latest.json)]) : key;
|
|
2165
|
+
setSaveState(latestKey === key ? "saved" : "dirty");
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
setSaveState("error");
|
|
2168
|
+
optionsRef.current.onError?.(toJuneError(error, "DOCUMENT_ERROR", "Saving the document failed"));
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
const markDirty = (0, import_react11.useCallback)(() => {
|
|
2172
|
+
if (!optionsRef.current.onSave) return;
|
|
2173
|
+
setSaveState("dirty");
|
|
2174
|
+
autosave?.();
|
|
2175
|
+
}, [autosave]);
|
|
2176
|
+
const [initialJson] = (0, import_react11.useState)(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
|
|
2177
|
+
const extensions = (0, import_react11.useMemo)(() => createDocumentExtensions(), []);
|
|
2178
|
+
const onUpdateRef = (0, import_react11.useRef)(() => {
|
|
2179
|
+
});
|
|
2180
|
+
onUpdateRef.current = (json) => {
|
|
2181
|
+
store.edit(documentId, json, instanceId);
|
|
2182
|
+
emitChange("user");
|
|
2183
|
+
markDirty();
|
|
2184
|
+
};
|
|
2185
|
+
const editor = (0, import_react10.useEditor)({
|
|
2186
|
+
extensions,
|
|
2187
|
+
content: initialJson,
|
|
2188
|
+
editable,
|
|
2189
|
+
immediatelyRender: false,
|
|
2190
|
+
editorProps: {
|
|
2191
|
+
attributes: {
|
|
2192
|
+
class: "june-doc__prose",
|
|
2193
|
+
role: "textbox",
|
|
2194
|
+
"aria-multiline": "true",
|
|
2195
|
+
"aria-label": "Document content"
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
onUpdate: ({ editor: ed }) => onUpdateRef.current(ed.getJSON())
|
|
2199
|
+
});
|
|
2200
|
+
(0, import_react11.useEffect)(() => {
|
|
2201
|
+
editor?.setEditable(editable);
|
|
2202
|
+
}, [editor, editable]);
|
|
2203
|
+
(0, import_react11.useEffect)(() => {
|
|
2204
|
+
if (!parsed.json) return;
|
|
2205
|
+
const outcome = store.receive({
|
|
2206
|
+
id: documentId,
|
|
2207
|
+
title: titleProp,
|
|
2208
|
+
json: parsed.json,
|
|
2209
|
+
documentType,
|
|
2210
|
+
status
|
|
2211
|
+
});
|
|
2212
|
+
if (outcome === "applied" || outcome === "merged") {
|
|
2213
|
+
emitChange(outcome === "applied" ? "ai" : "merge");
|
|
2214
|
+
markDirty();
|
|
2215
|
+
} else if (outcome === "conflict") {
|
|
2216
|
+
const record = store.get(documentId);
|
|
2217
|
+
optionsRef.current.onConflict?.({
|
|
2218
|
+
documentId,
|
|
2219
|
+
title: record?.title ?? titleProp,
|
|
2220
|
+
conflicts: record?.pending?.conflicts ?? 1
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
}, [store, documentId, titleProp, parsed.json, documentType, status, emitChange, markDirty]);
|
|
2224
|
+
(0, import_react11.useEffect)(() => {
|
|
2225
|
+
if (!editor) return;
|
|
2226
|
+
const apply = () => {
|
|
2227
|
+
const record = store.get(documentId);
|
|
2228
|
+
if (!record || record.revision === seenRevision.current) return;
|
|
2229
|
+
seenRevision.current = record.revision;
|
|
2230
|
+
if (record.origin === instanceId) return;
|
|
2231
|
+
const { from, to } = editor.state.selection;
|
|
2232
|
+
editor.commands.setContent(record.json, { emitUpdate: false });
|
|
2233
|
+
if (editor.isFocused) {
|
|
2234
|
+
const size = editor.state.doc.content.size;
|
|
2235
|
+
editor.commands.setTextSelection({ from: Math.min(from, size), to: Math.min(to, size) });
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
apply();
|
|
2239
|
+
return store.subscribe(apply);
|
|
2240
|
+
}, [editor, store, documentId, instanceId]);
|
|
2241
|
+
(0, import_react11.useEffect)(() => {
|
|
2242
|
+
if (!editor || readyRef.current) return;
|
|
2243
|
+
const record = store.get(documentId);
|
|
2244
|
+
if (!record) return;
|
|
2245
|
+
readyRef.current = true;
|
|
2246
|
+
lastSavedKey.current = JSON.stringify([record.title, docKey(record.json)]);
|
|
2247
|
+
optionsRef.current.onDocumentReady?.(snapshotOf(record));
|
|
2248
|
+
}, [editor, store, documentId, title]);
|
|
2249
|
+
const handleTitle = (value) => {
|
|
2250
|
+
store.setTitle(documentId, value);
|
|
2251
|
+
emitChange("user");
|
|
2252
|
+
markDirty();
|
|
2253
|
+
};
|
|
2254
|
+
const resolve = (choice) => {
|
|
2255
|
+
store.resolve(documentId, choice);
|
|
2256
|
+
if (choice !== "keep") {
|
|
2257
|
+
emitChange(choice === "merge" ? "merge" : "resolve");
|
|
2258
|
+
markDirty();
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
const handleExport = async (format) => {
|
|
2262
|
+
const record = store.get(documentId);
|
|
2263
|
+
if (!record) return;
|
|
2264
|
+
try {
|
|
2265
|
+
const result = await exportDocument(format, {
|
|
2266
|
+
documentId,
|
|
2267
|
+
title: record.title,
|
|
2268
|
+
json: record.json
|
|
2269
|
+
// always the latest editor content
|
|
2270
|
+
});
|
|
2271
|
+
if (result.blob) downloadResult(result);
|
|
2272
|
+
optionsRef.current.onExport?.({ documentId, format, ...result });
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
optionsRef.current.onError?.(toJuneError(error, "EXPORT_ERROR"));
|
|
2275
|
+
flash("Export failed");
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
const handleCopy = async () => {
|
|
2279
|
+
const record = store.get(documentId);
|
|
2280
|
+
if (!record) return;
|
|
2281
|
+
flash(await copyDocumentToClipboard({ title: record.title, json: record.json }) ? "Copied" : "Copy failed");
|
|
2282
|
+
};
|
|
2283
|
+
const formats = (exportFormats ?? [...ALL_EXPORT_FORMATS]).filter((f) => listDocumentExporters().includes(f));
|
|
2284
|
+
if (parsed.error && !store.get(documentId)) {
|
|
2285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc june-doc--error", "data-theme": resolvedTheme, role: "alert", children: [
|
|
2286
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "june-doc__draft-text", children: "This document could not be displayed." }),
|
|
2287
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("details", { children: [
|
|
2288
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("summary", { children: "Show raw content" }),
|
|
2289
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { className: "june-doc__raw", children: contentKey })
|
|
2290
|
+
] })
|
|
2291
|
+
] });
|
|
2292
|
+
}
|
|
2293
|
+
if (!editor) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentDraft, { title, state: "loading" });
|
|
2294
|
+
const isDraft = recordStatus === "draft";
|
|
2295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2296
|
+
"div",
|
|
2297
|
+
{
|
|
2298
|
+
className: className ? `june-doc ${className}` : "june-doc",
|
|
2299
|
+
"data-theme": resolvedTheme,
|
|
2300
|
+
"data-status": recordStatus,
|
|
2301
|
+
onKeyDown: (e) => {
|
|
2302
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s" && options.onSave) {
|
|
2303
|
+
e.preventDefault();
|
|
2304
|
+
void saveRef.current();
|
|
2305
|
+
}
|
|
2306
|
+
},
|
|
2307
|
+
children: [
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__header", children: [
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentTitle, { value: title, disabled: !editable, onChange: handleTitle }),
|
|
2310
|
+
isDraft ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "june-doc__badge", children: "AI draft" }) : null
|
|
2311
|
+
] }),
|
|
2312
|
+
pending ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__banner", role: "status", children: [
|
|
2313
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
2314
|
+
"The assistant suggested changes that overlap your edits (",
|
|
2315
|
+
pending.conflicts,
|
|
2316
|
+
" ",
|
|
2317
|
+
pending.conflicts === 1 ? "section" : "sections",
|
|
2318
|
+
")."
|
|
2319
|
+
] }),
|
|
2320
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "june-doc__banner-actions", children: [
|
|
2321
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("merge"), children: "Merge, keep my edits" }),
|
|
2322
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("replace"), children: "Use AI version" }),
|
|
2323
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("keep"), children: "Keep mine" })
|
|
2324
|
+
] })
|
|
2325
|
+
] }) : null,
|
|
2326
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2327
|
+
DocumentToolbar,
|
|
2328
|
+
{
|
|
2329
|
+
editor,
|
|
2330
|
+
editable,
|
|
2331
|
+
exportFormats: formats,
|
|
2332
|
+
canSave: !!options.onSave,
|
|
2333
|
+
saveState,
|
|
2334
|
+
onExport: (f) => void handleExport(f),
|
|
2335
|
+
onCopy: () => void handleCopy(),
|
|
2336
|
+
onSave: () => void saveRef.current()
|
|
2337
|
+
}
|
|
2338
|
+
),
|
|
2339
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentContent, { editor }),
|
|
2340
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__footer", "aria-live": "polite", children: [
|
|
2341
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
2342
|
+
words,
|
|
2343
|
+
" words"
|
|
2344
|
+
] }),
|
|
2345
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: notice ?? SAVE_LABEL[saveState] })
|
|
2346
|
+
] })
|
|
2347
|
+
]
|
|
2348
|
+
}
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// src/components/document-editor/document-component.tsx
|
|
2353
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2354
|
+
var DOCUMENT_COMPONENT_DESCRIPTION = 'An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?). "content" is the full document body as one Markdown string. Reuse documentId when revising the same document. Use it only when the user asks to create or revise a document.';
|
|
2355
|
+
var documentProps = import_v4.z.object({
|
|
2356
|
+
documentId: import_v4.z.string().describe("Stable id. Reuse it when revising the same document."),
|
|
2357
|
+
title: import_v4.z.string().describe("Document title. Do not repeat it as a heading in content."),
|
|
2358
|
+
content: import_v4.z.string().describe("The whole document body as Markdown."),
|
|
2359
|
+
documentType: import_v4.z.string().optional().describe("Kind of document, such as proposal, contract or invoice."),
|
|
2360
|
+
status: import_v4.z.string().optional().describe('Use "draft" for generated documents.'),
|
|
2361
|
+
editable: import_v4.z.boolean().optional().describe("Whether the user can edit. Defaults to true."),
|
|
2362
|
+
exportFormats: import_v4.z.array(import_v4.z.string()).optional().describe("Export formats to offer, such as html, markdown, docx.")
|
|
2363
|
+
});
|
|
2364
|
+
function DocumentEditor(props) {
|
|
2365
|
+
const { isStreaming } = useJuneRuntime();
|
|
2366
|
+
if (props.generating || isStreaming) {
|
|
2367
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentDraft, { title: props.title, state: "drafting" });
|
|
2368
|
+
}
|
|
2369
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentEditorImpl, { ...props });
|
|
2370
|
+
}
|
|
2371
|
+
function OpenUIDocument({ props }) {
|
|
2372
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2373
|
+
DocumentEditor,
|
|
2374
|
+
{
|
|
2375
|
+
documentId: props.documentId,
|
|
2376
|
+
title: props.title,
|
|
2377
|
+
content: props.content,
|
|
2378
|
+
documentType: props.documentType,
|
|
2379
|
+
status: props.status,
|
|
2380
|
+
editable: props.editable,
|
|
2381
|
+
exportFormats: props.exportFormats
|
|
2382
|
+
}
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
var JuneDocumentEditorComponent = (0, import_react_lang3.defineComponent)({
|
|
2386
|
+
name: "DocumentEditor",
|
|
2387
|
+
description: DOCUMENT_COMPONENT_DESCRIPTION,
|
|
2388
|
+
props: documentProps,
|
|
2389
|
+
component: OpenUIDocument
|
|
2390
|
+
});
|
|
2391
|
+
function withDocumentEditor(library) {
|
|
2392
|
+
return library.extend({ components: { add: [JuneDocumentEditorComponent] } });
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
// src/components/document-editor/document-instructions.ts
|
|
2396
|
+
var DEFAULT_TYPES = [
|
|
2397
|
+
"proposal",
|
|
2398
|
+
"report",
|
|
2399
|
+
"email",
|
|
2400
|
+
"letter",
|
|
2401
|
+
"invoice",
|
|
2402
|
+
"contract",
|
|
2403
|
+
"NDA",
|
|
2404
|
+
"memo",
|
|
2405
|
+
"meeting notes"
|
|
2406
|
+
];
|
|
2407
|
+
function getDocumentEditorRules(options = {}) {
|
|
2408
|
+
const name = options.componentName ?? "DocumentEditor";
|
|
2409
|
+
const types = (options.documentTypes ?? DEFAULT_TYPES).join(", ");
|
|
2410
|
+
return [
|
|
2411
|
+
`WHEN TO USE ${name}: use it only when the user asks you to create, draft, write, prepare, generate or revise an editable document (${types}, or similar). Put the finished document inside ${name}.`,
|
|
2412
|
+
`WHEN NOT TO USE ${name}: for explanations ("explain what an NDA is"), factual answers, comparisons, brainstorming and general questions, answer with normal text and the other components. Never open ${name} for those.`,
|
|
2413
|
+
`${name}(documentId, title, content, documentType?, status?, editable?, exportFormats?): "content" is the whole document body as Markdown inside ONE double-quoted string. Write line breaks as \\n and escape double quotes as \\". Supported Markdown: headings (#, ##, ###), paragraphs, **bold**, *italic*, lists, > quotes, [links](https://...), and tables.`,
|
|
2414
|
+
`Do not repeat the title as a heading inside "content"; the title is shown separately. Structure the document with headings, short paragraphs and tables where useful. Keep a professional tone.`,
|
|
2415
|
+
`EDITING: when the user asks to change an existing document (add a section, rewrite a part, change the title), write ${name} again with the SAME documentId and the COMPLETE updated content, starting from the current document text you were given. Copy every unchanged section exactly as it was. Change only what was requested. The app merges your version with the user's own edits.`,
|
|
2416
|
+
`If the user asks for a document but leaves out details, write the document anyway and use clear placeholders such as [CLIENT NAME], [DATE], [AMOUNT]. Do not stop to ask unless the request is impossible to interpret.`,
|
|
2417
|
+
`Never invent legal, financial or business facts, prices, dates, names or figures. Use placeholders instead. Set status to "draft" for generated documents, and for legal or financial documents say in one short sentence outside the component that the document is a draft that should be reviewed by a professional.`,
|
|
2418
|
+
`You may add one short sentence before or after the component. Do not repeat the document text outside the component.`,
|
|
2419
|
+
...options.extraRules ?? []
|
|
2420
|
+
];
|
|
2421
|
+
}
|
|
2422
|
+
function getDocumentEditorInstructions(options = {}) {
|
|
2423
|
+
const rules = getDocumentEditorRules(options);
|
|
2424
|
+
return ["Document drafting:", ...rules.map((rule, i) => `${i + 1}. ${rule}`)].join("\n");
|
|
2425
|
+
}
|
|
2426
|
+
var EXAMPLE_NEW = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |", "proposal", "draft")';
|
|
2427
|
+
var EXAMPLE_EDIT = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |\\n| Delivery | [AMOUNT] |", "proposal", "draft")';
|
|
2428
|
+
function documentPromptOptions(options = {}) {
|
|
2429
|
+
return {
|
|
2430
|
+
additionalRules: getDocumentEditorRules(options),
|
|
2431
|
+
examples: [EXAMPLE_NEW, EXAMPLE_EDIT]
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/components/document-editor/document-agent.ts
|
|
2436
|
+
var attr = (value) => value.replace(/"/g, """);
|
|
2437
|
+
async function serializeDocumentsForAgent(options = {}) {
|
|
2438
|
+
const store = options.store ?? getDefaultDocumentStore();
|
|
2439
|
+
const { docToMarkdown: docToMarkdown2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
|
|
2440
|
+
return store.list().filter((r) => !options.ids || options.ids.includes(r.id)).map(
|
|
2441
|
+
(r) => `<document id="${attr(r.id)}" title="${attr(r.title)}"${r.documentType ? ` type="${attr(r.documentType)}"` : ""}>
|
|
2442
|
+
${docToMarkdown2(r.json)}
|
|
2443
|
+
</document>`
|
|
2444
|
+
).join("\n\n");
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// src/components/june-library.ts
|
|
2448
|
+
var import_react_ui = require("@openuidev/react-ui");
|
|
2449
|
+
var juneLibrary = withDocumentEditor(import_react_ui.openuiLibrary);
|
|
2450
|
+
|
|
2451
|
+
// src/core/JuneUI.tsx
|
|
2452
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2453
|
+
function JuneUI({ library = juneLibrary, ...props }) {
|
|
2454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(JuneRenderer, { library, ...props });
|
|
1099
2455
|
}
|
|
1100
2456
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1101
2457
|
0 && (module.exports = {
|
|
2458
|
+
ALL_EXPORT_FORMATS,
|
|
2459
|
+
DocumentEditor,
|
|
2460
|
+
JuneDocumentContext,
|
|
2461
|
+
JuneDocumentEditorComponent,
|
|
1102
2462
|
JuneError,
|
|
1103
2463
|
JuneErrorBoundary,
|
|
1104
2464
|
JuneRenderer,
|
|
2465
|
+
JuneRuntimeContext,
|
|
1105
2466
|
JuneThemeProvider,
|
|
1106
2467
|
JuneUI,
|
|
1107
2468
|
consumeStream,
|
|
2469
|
+
copyDocumentToClipboard,
|
|
1108
2470
|
createActionHandler,
|
|
2471
|
+
createDocumentStore,
|
|
1109
2472
|
createJuneLibrary,
|
|
1110
2473
|
darkTheme,
|
|
1111
2474
|
defaultParseActionEvent,
|
|
1112
2475
|
defineJuneAction,
|
|
1113
2476
|
defineJuneComponent,
|
|
2477
|
+
documentPromptOptions,
|
|
2478
|
+
downloadResult,
|
|
2479
|
+
exportDocument,
|
|
1114
2480
|
fetchStream,
|
|
2481
|
+
getDefaultDocumentStore,
|
|
2482
|
+
getDocumentEditorInstructions,
|
|
2483
|
+
getDocumentEditorRules,
|
|
1115
2484
|
isJuneError,
|
|
1116
2485
|
jsonFieldExtractor,
|
|
2486
|
+
juneLibrary,
|
|
1117
2487
|
lightTheme,
|
|
2488
|
+
listDocumentExporters,
|
|
2489
|
+
mergeDocuments,
|
|
2490
|
+
registerDocumentExporter,
|
|
1118
2491
|
runAction,
|
|
2492
|
+
serializeDocumentsForAgent,
|
|
1119
2493
|
sseToText,
|
|
1120
2494
|
toJuneError,
|
|
2495
|
+
useDocumentOptions,
|
|
2496
|
+
useJuneRuntime,
|
|
1121
2497
|
useJuneStream,
|
|
1122
2498
|
useJuneTheme,
|
|
1123
|
-
useResolvedTheme
|
|
2499
|
+
useResolvedTheme,
|
|
2500
|
+
withDocumentEditor
|
|
1124
2501
|
});
|