@braincrew-lab/langchain-canvas 0.2.0 → 0.4.9
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/dist/{ChartRenderer-AAWVGKV5.js → ChartRenderer-JGRJ23OB.js} +10 -20
- package/dist/{DocumentRenderer-NBRBPYU6.js → DocumentRenderer-ZQDQMX7X.js} +8 -4
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-56R3TNWN.js +496 -0
- package/dist/{TableRenderer-VKDD3CB6.js → TableRenderer-3ESF577F.js} +49 -193
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-S54GJDSJ.js → chunk-EHW446VF.js} +62 -47
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-UL5F66PN.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/chunk-SGOPRUQ4.js +182 -0
- package/dist/formula-27TCEZI5.js +2 -0
- package/dist/formula-cli.d.ts +2 -0
- package/dist/formula-cli.js +37 -0
- package/dist/index.d.ts +201 -289
- package/dist/index.js +393 -860
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +125 -100
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +40 -5
- package/dist/PdfRenderer-DPQT4E7O.js +0 -97
- package/dist/SlidesRenderer-6ZGHXTQX.js +0 -877
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
|
|
2
3
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
3
4
|
import { resolveElements } from './chunk-6L3AL6W4.js';
|
|
4
|
-
export {
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
|
|
5
|
+
export { useAssetUrl } from './chunk-FTNRRJ3K.js';
|
|
6
|
+
import { inlineArtifactAssets, inlineHtmlAssets } from './chunk-7T5DRR3F.js';
|
|
7
|
+
export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
|
|
8
|
+
export { useArtifactPatch } from './chunk-K2UZAYW2.js';
|
|
9
|
+
import { useCanvasStoreApi, useCanvasStore } from './chunk-EHW446VF.js';
|
|
10
|
+
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-EHW446VF.js';
|
|
11
|
+
import { createContext, lazy, useMemo, useRef, useCallback, useEffect, useContext, useState, Suspense, Component } from 'react';
|
|
8
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
13
|
|
|
10
14
|
// src/client/sse-client.ts
|
|
@@ -61,9 +65,10 @@ function parseFrame(frame) {
|
|
|
61
65
|
|
|
62
66
|
// src/client/inspector.ts
|
|
63
67
|
var INSPECTOR_MARK = "langchain-canvas";
|
|
64
|
-
function withInspector(html) {
|
|
68
|
+
function withInspector(html, assetBaseUrl) {
|
|
65
69
|
let out = withViewport(html);
|
|
66
|
-
const
|
|
70
|
+
const config = assetBaseUrl ? `<script data-lcx>window.__LCX_ASSET_BASE=${JSON.stringify(assetBaseUrl)}</script>` : "";
|
|
71
|
+
const injection = `<style data-lcx>${INSPECTOR_CSS}</style>${config}<script data-lcx>${INSPECTOR_SCRIPT}</script>`;
|
|
67
72
|
const marker = "</body>";
|
|
68
73
|
const at = out.lastIndexOf(marker);
|
|
69
74
|
out = at === -1 ? out + injection : out.slice(0, at) + injection + out.slice(at);
|
|
@@ -131,6 +136,33 @@ var INSPECTOR_SCRIPT = `
|
|
|
131
136
|
el.removeAttribute("contenteditable");
|
|
132
137
|
if (el.classList) { el.classList.remove("lcx-hover"); el.classList.remove("lcx-selected"); }
|
|
133
138
|
if (el.getAttribute && el.getAttribute("class") === "") el.removeAttribute("class");
|
|
139
|
+
// A display-resolved asset src goes back to its stored relative form.
|
|
140
|
+
var orig = el.getAttribute && el.getAttribute("data-lcx-src");
|
|
141
|
+
if (orig) { el.setAttribute("src", orig); el.removeAttribute("data-lcx-src"); }
|
|
142
|
+
}
|
|
143
|
+
// --- canvas-asset references: resolve for display, keep the source relative ---
|
|
144
|
+
var ASSET_BASE = window.__LCX_ASSET_BASE || "";
|
|
145
|
+
var ASSET_REF = /^(?:\\.\\.?\\/)*(?:assets|sources)\\//;
|
|
146
|
+
// Leading ./ and ../ fold onto the canvas root (assets/ and sources/ exist
|
|
147
|
+
// only there) \u2014 same lenient reading as canvasAssets.normalizeAssetReference.
|
|
148
|
+
function foldAssetRef(s) {
|
|
149
|
+
while (s.lastIndexOf("./", 0) === 0 || s.lastIndexOf("../", 0) === 0) {
|
|
150
|
+
s = s.lastIndexOf("./", 0) === 0 ? s.slice(2) : s.slice(3);
|
|
151
|
+
}
|
|
152
|
+
return s;
|
|
153
|
+
}
|
|
154
|
+
function rewriteAssetSrcs() {
|
|
155
|
+
if (!ASSET_BASE) return;
|
|
156
|
+
var imgs = document.querySelectorAll("img[src]");
|
|
157
|
+
for (var i = 0; i < imgs.length; i++) {
|
|
158
|
+
var el = imgs[i];
|
|
159
|
+
if (el.hasAttribute("data-lcx-src")) continue;
|
|
160
|
+
var src = el.getAttribute("src") || "";
|
|
161
|
+
if (ASSET_REF.test(src)) {
|
|
162
|
+
el.setAttribute("data-lcx-src", src);
|
|
163
|
+
el.setAttribute("src", ASSET_BASE + encodeURIComponent(foldAssetRef(src)));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
134
166
|
}
|
|
135
167
|
function emitEdit(el) {
|
|
136
168
|
// Serialize the *canonical* HTML \u2014 strip the inspector's own injected
|
|
@@ -138,7 +170,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
138
170
|
var cid = el.getAttribute("data-cid");
|
|
139
171
|
var clone = el.cloneNode(true);
|
|
140
172
|
scrub(clone);
|
|
141
|
-
var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected") : [];
|
|
173
|
+
var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected") : [];
|
|
142
174
|
for (var i = 0; i < inner.length; i++) scrub(inner[i]);
|
|
143
175
|
parent.postMessage({ source: MARK, type: "node_edit", cid: cid, html: clone.outerHTML }, "*");
|
|
144
176
|
}
|
|
@@ -153,51 +185,20 @@ var INSPECTOR_SCRIPT = `
|
|
|
153
185
|
var clone = document.documentElement.cloneNode(true);
|
|
154
186
|
var injected = clone.querySelectorAll("[data-lcx]");
|
|
155
187
|
for (var i = 0; i < injected.length; i++) injected[i].parentNode && injected[i].parentNode.removeChild(injected[i]);
|
|
156
|
-
var marked = clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected");
|
|
188
|
+
var marked = clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected");
|
|
157
189
|
for (var j = 0; j < marked.length; j++) scrub(marked[j]);
|
|
158
190
|
parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
|
|
159
191
|
}
|
|
160
|
-
// Copy the visual style of an existing element onto a new block, so inserts
|
|
161
|
-
// match the page's own design system instead of landing as bare UA-styled
|
|
162
|
-
// tags (a default grey <button> on a dark page reads as broken).
|
|
163
|
-
function adoptStyleFrom(el, sample) {
|
|
164
|
-
if (!sample) return false;
|
|
165
|
-
var cs = window.getComputedStyle(sample);
|
|
166
|
-
var props = ["background-color", "color", "border", "border-radius", "padding",
|
|
167
|
-
"font-family", "font-size", "font-weight", "letter-spacing", "box-shadow", "cursor"];
|
|
168
|
-
for (var i = 0; i < props.length; i++) el.style.setProperty(props[i], cs.getPropertyValue(props[i]));
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
192
|
function newBlock(tag) {
|
|
172
193
|
var el = document.createElement(tag);
|
|
173
194
|
if (tag === "img") {
|
|
174
195
|
el.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270'%3E%3Crect width='100%25' height='100%25' fill='%23e5e7eb'/%3E%3Cpath d='M190 155l40-45 35 40 25-25 40 45z' fill='%23c3c8d0'/%3E%3Ccircle cx='300' cy='105' r='16' fill='%23c3c8d0'/%3E%3C/svg%3E";
|
|
175
196
|
el.alt = "image"; el.style.maxWidth = "100%";
|
|
176
197
|
}
|
|
177
|
-
else if (tag === "button")
|
|
178
|
-
el.textContent = "Button";
|
|
179
|
-
// Match an existing button (skipping editor chrome); else a clean accent default.
|
|
180
|
-
var sampleBtn = null;
|
|
181
|
-
var btns = document.querySelectorAll("button");
|
|
182
|
-
for (var sb = 0; sb < btns.length; sb++) {
|
|
183
|
-
if (!btns[sb].closest("[data-lcx]")) { sampleBtn = btns[sb]; break; }
|
|
184
|
-
}
|
|
185
|
-
if (!adoptStyleFrom(el, sampleBtn)) {
|
|
186
|
-
el.style.cssText = "padding:10px 18px;border:0;border-radius:9px;background:#6366f1;color:#fff;font:600 15px/1.2 inherit;cursor:pointer";
|
|
187
|
-
}
|
|
188
|
-
}
|
|
198
|
+
else if (tag === "button") el.textContent = "Button";
|
|
189
199
|
else if (tag === "hr") { /* no content */ }
|
|
190
200
|
else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
|
|
191
|
-
else
|
|
192
|
-
el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
|
|
193
|
-
// Headings/text adopt an existing peer's look too (font, colour) so they
|
|
194
|
-
// don't appear in default serif-black on a styled page.
|
|
195
|
-
adoptStyleFrom(el, document.querySelector(tag === "p" ? "main p, section p, div p" : tag));
|
|
196
|
-
el.style.removeProperty("border");
|
|
197
|
-
el.style.removeProperty("box-shadow");
|
|
198
|
-
el.style.removeProperty("cursor");
|
|
199
|
-
el.style.removeProperty("background-color");
|
|
200
|
-
}
|
|
201
|
+
else el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
|
|
201
202
|
return el;
|
|
202
203
|
}
|
|
203
204
|
// Floating rich-text toolbar shown while a text element is being edited.
|
|
@@ -287,6 +288,15 @@ var INSPECTOR_SCRIPT = `
|
|
|
287
288
|
}
|
|
288
289
|
function start() {
|
|
289
290
|
assign(document.body, "e");
|
|
291
|
+
// Resolve asset references now and after any change (insert_html, set_src,
|
|
292
|
+
// duplicate). Idempotent: rewritten images carry data-lcx-src and are
|
|
293
|
+
// skipped, so the observer settles after one pass.
|
|
294
|
+
rewriteAssetSrcs();
|
|
295
|
+
if (ASSET_BASE) {
|
|
296
|
+
new MutationObserver(rewriteAssetSrcs).observe(document.documentElement, {
|
|
297
|
+
subtree: true, childList: true, attributes: true, attributeFilter: ["src"]
|
|
298
|
+
});
|
|
299
|
+
}
|
|
290
300
|
var hovered = null;
|
|
291
301
|
var selected = []; // currently highlighted elements
|
|
292
302
|
var marquee = null, sx = 0, sy = 0, dragging = false, moved = false, suppressClick = false;
|
|
@@ -294,31 +304,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
294
304
|
// into absolute positioning inside its own parent, and its final spot + size are
|
|
295
305
|
// stored as percentages of that parent \u2014 so it stays put proportionally across
|
|
296
306
|
// responsive breakpoints, instead of a fixed pixel offset that drifts off.
|
|
297
|
-
var dragEls = null, dragStart = null, dragBases = null,
|
|
307
|
+
var dragEls = null, dragStart = null, dragBases = null, groupSeq = 0;
|
|
298
308
|
|
|
299
309
|
function ensurePositioned(parent) {
|
|
300
|
-
if (!parent || parent === document.documentElement) return;
|
|
301
|
-
|
|
302
|
-
// initial containing block (html), so a body margin or html padding shifts
|
|
303
|
-
// every free-dragged element \u2014 relative pins them to the body box itself.
|
|
304
|
-
if (window.getComputedStyle(parent).position === "static") {
|
|
305
|
-
parent.style.position = "relative";
|
|
306
|
-
parentMutated = true; // the parent must be persisted too, not just the child
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// Persist a completed free-drag. A single element normally commits as a cheap
|
|
310
|
-
// node_edit \u2014 but when its parent was pulled to position:relative that parent
|
|
311
|
-
// lives only in the live DOM, so a node-only patch would store an absolute
|
|
312
|
-
// child inside a still-static parent (it jumps on the next reload). In that
|
|
313
|
-
// case persist the whole document instead.
|
|
314
|
-
function commitDrag(els) {
|
|
315
|
-
if (els.length === 1 && !parentMutated) emitEdit(els[0]); else emitDoc(true);
|
|
310
|
+
if (!parent || parent === document.body || parent === document.documentElement) return;
|
|
311
|
+
if (window.getComputedStyle(parent).position === "static") parent.style.position = "relative";
|
|
316
312
|
}
|
|
317
313
|
// Pull each element out into absolute positioning at its current spot (no visual
|
|
318
314
|
// jump), so it can then be moved freely.
|
|
319
315
|
function beginFreeDrag(els) {
|
|
320
316
|
dragBases = [];
|
|
321
|
-
parentMutated = false;
|
|
322
317
|
for (var i = 0; i < els.length; i++) {
|
|
323
318
|
var el = els[i], parent = el.parentElement || document.body;
|
|
324
319
|
ensurePositioned(parent);
|
|
@@ -341,16 +336,13 @@ var INSPECTOR_SCRIPT = `
|
|
|
341
336
|
}
|
|
342
337
|
// Commit the current position (in px, as set live by moveFree) as % of the
|
|
343
338
|
// parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
|
|
344
|
-
// only if the parent has collapsed to zero on that axis.
|
|
345
|
-
// an exception: a container's *height* is content-driven and reflows once the
|
|
346
|
-
// dragged element leaves the flow, so a top stored as % of the old height
|
|
347
|
-
// lands somewhere else after reload \u2014 top always commits in px.
|
|
339
|
+
// only if the parent has collapsed to zero on that axis.
|
|
348
340
|
function commitFree() {
|
|
349
341
|
for (var i = 0; i < dragBases.length; i++) {
|
|
350
342
|
var b = dragBases[i], pr = b.parent.getBoundingClientRect();
|
|
351
343
|
var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
|
|
352
344
|
b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
|
|
353
|
-
b.el.style.top =
|
|
345
|
+
b.el.style.top = pr.height ? ((curTop / pr.height) * 100).toFixed(3) + "%" : curTop + "px";
|
|
354
346
|
if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
|
|
355
347
|
}
|
|
356
348
|
}
|
|
@@ -507,8 +499,8 @@ var INSPECTOR_SCRIPT = `
|
|
|
507
499
|
positionResize();
|
|
508
500
|
// The new position is already shown in the iframe with every cid intact,
|
|
509
501
|
// so persist without a reload (no flicker): one element \u2192 node_edit,
|
|
510
|
-
// several
|
|
511
|
-
|
|
502
|
+
// several \u2192 a self-applied doc_edit.
|
|
503
|
+
if (els.length === 1) emitEdit(els[0]); else emitDoc(true);
|
|
512
504
|
}
|
|
513
505
|
dragBases = null;
|
|
514
506
|
return;
|
|
@@ -559,7 +551,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
559
551
|
// Pointer left the frame mid-drag: commit the move at its last position so
|
|
560
552
|
// it isn't lost (the element is already placed absolutely in the iframe).
|
|
561
553
|
var els = dragEls; dragEls = null;
|
|
562
|
-
if (moved && dragBases) { commitFree();
|
|
554
|
+
if (moved && dragBases) { commitFree(); if (els.length === 1) emitEdit(els[0]); else emitDoc(true); }
|
|
563
555
|
dragBases = null;
|
|
564
556
|
}
|
|
565
557
|
if (dragging) {
|
|
@@ -630,15 +622,20 @@ var INSPECTOR_SCRIPT = `
|
|
|
630
622
|
if (root && d.style) { for (var sk in d.style) { try { root.style[sk] = d.style[sk]; } catch (_e) {} } emitDoc(); }
|
|
631
623
|
return;
|
|
632
624
|
}
|
|
633
|
-
if (d.type === "set_src") {
|
|
625
|
+
if (d.type === "set_src") {
|
|
626
|
+
var ei = byCid(d.cid);
|
|
627
|
+
if (ei) {
|
|
628
|
+
// Drop stale asset bookkeeping first, or scrub would restore the old
|
|
629
|
+
// src over the new one. The observer re-resolves if the new value is
|
|
630
|
+
// itself an asset reference.
|
|
631
|
+
ei.removeAttribute("data-lcx-src");
|
|
632
|
+
ei.setAttribute("src", d.value);
|
|
633
|
+
emitEdit(ei);
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
634
637
|
if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
|
|
635
638
|
|
|
636
|
-
// No-selection inserts land in the slide root when there is one \u2014 a slide
|
|
637
|
-
// document is body > .slide-container (fixed 720px), so appending to body
|
|
638
|
-
// puts content below the visible slide where it silently never shows.
|
|
639
|
-
function insertRoot() {
|
|
640
|
-
return document.querySelector(".slide-container") || document.body;
|
|
641
|
-
}
|
|
642
639
|
// Structural edits \u2014 mutate the tree, then persist the whole document.
|
|
643
640
|
if (d.type === "insert") {
|
|
644
641
|
var block = newBlock(d.block || "p");
|
|
@@ -646,7 +643,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
646
643
|
if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
|
|
647
644
|
anchor.parentNode.insertBefore(block, anchor.nextSibling);
|
|
648
645
|
} else {
|
|
649
|
-
|
|
646
|
+
document.body.appendChild(block);
|
|
650
647
|
}
|
|
651
648
|
emitDoc();
|
|
652
649
|
return;
|
|
@@ -654,7 +651,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
654
651
|
if (d.type === "insert_html") {
|
|
655
652
|
// A built-in section template (trusted markup from the toolbar).
|
|
656
653
|
var anc = d.cid ? byCid(d.cid) : null;
|
|
657
|
-
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode :
|
|
654
|
+
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : document.body;
|
|
658
655
|
var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
|
|
659
656
|
var frag = document.createElement("div");
|
|
660
657
|
frag.innerHTML = d.html || "";
|
|
@@ -669,16 +666,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
669
666
|
var cids = d.cids || [];
|
|
670
667
|
for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
|
|
671
668
|
if (members.length < 2) return;
|
|
672
|
-
|
|
673
|
-
// reset on every reload, so a second group session reused "g0" and merged
|
|
674
|
-
// with the previously-persisted group.
|
|
675
|
-
var maxGid = -1;
|
|
676
|
-
var existing = document.querySelectorAll("[data-group-id]");
|
|
677
|
-
for (var q = 0; q < existing.length; q++) {
|
|
678
|
-
var mm = /^g(d+)$/.exec(existing[q].getAttribute("data-group-id") || "");
|
|
679
|
-
if (mm && Number(mm[1]) > maxGid) maxGid = Number(mm[1]);
|
|
680
|
-
}
|
|
681
|
-
var gid = "g" + (maxGid + 1);
|
|
669
|
+
var gid = "g" + (groupSeq++);
|
|
682
670
|
for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
|
|
683
671
|
clearSelected();
|
|
684
672
|
emitDoc();
|
|
@@ -735,8 +723,41 @@ async function* mockStream(events, options = {}) {
|
|
|
735
723
|
function sleep(ms) {
|
|
736
724
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
737
725
|
}
|
|
738
|
-
|
|
726
|
+
|
|
727
|
+
// src/transports/sse.ts
|
|
728
|
+
function sseTransport(options = {}) {
|
|
739
729
|
const endpoint = options.endpoint ?? "/api/chat";
|
|
730
|
+
return {
|
|
731
|
+
stream(request) {
|
|
732
|
+
const streamOptions = { signal: request.signal, headers: options.headers };
|
|
733
|
+
return streamChat(
|
|
734
|
+
endpoint,
|
|
735
|
+
{ threadId: request.threadId, message: request.message, selections: request.selections },
|
|
736
|
+
streamOptions
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/transports/mock.ts
|
|
743
|
+
function mockTransport(script, fallback, options = {}) {
|
|
744
|
+
return {
|
|
745
|
+
stream(request) {
|
|
746
|
+
const events = script(request.message);
|
|
747
|
+
if (events) {
|
|
748
|
+
return mockStream(events, { delayMs: options.delayMs ?? 60, signal: request.signal });
|
|
749
|
+
}
|
|
750
|
+
if (fallback) return fallback.stream(request);
|
|
751
|
+
return mockStream([], { delayMs: 0 });
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function useCanvasStream(options = {}) {
|
|
756
|
+
const { transport: customTransport, endpoint, mock } = options;
|
|
757
|
+
const transport = useMemo(() => {
|
|
758
|
+
const base = customTransport ?? sseTransport({ endpoint });
|
|
759
|
+
return mock ? mockTransport(mock, base) : base;
|
|
760
|
+
}, [customTransport, endpoint, mock]);
|
|
740
761
|
const threadIdRef = useRef(options.threadId ?? crypto.randomUUID());
|
|
741
762
|
const abortRef = useRef(null);
|
|
742
763
|
const api = useCanvasStoreApi();
|
|
@@ -775,12 +796,12 @@ function useCanvasStream(options = {}) {
|
|
|
775
796
|
const controller = new AbortController();
|
|
776
797
|
abortRef.current = controller;
|
|
777
798
|
try {
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
);
|
|
799
|
+
const stream = transport.stream({
|
|
800
|
+
threadId: threadIdRef.current,
|
|
801
|
+
message: text,
|
|
802
|
+
selections: withSelections,
|
|
803
|
+
signal: controller.signal
|
|
804
|
+
});
|
|
784
805
|
for await (const event of stream) {
|
|
785
806
|
enqueue(event);
|
|
786
807
|
}
|
|
@@ -793,7 +814,7 @@ function useCanvasStream(options = {}) {
|
|
|
793
814
|
api.getState().setStreaming(false);
|
|
794
815
|
}
|
|
795
816
|
},
|
|
796
|
-
[api,
|
|
817
|
+
[api, transport, enqueue, flush]
|
|
797
818
|
);
|
|
798
819
|
const stop = useCallback(() => abortRef.current?.abort(), []);
|
|
799
820
|
const reset = useCallback(() => api.getState().reset(), [api]);
|
|
@@ -841,7 +862,7 @@ function useCanvasReplay() {
|
|
|
841
862
|
api.getState().applyEvent(event);
|
|
842
863
|
}
|
|
843
864
|
} finally {
|
|
844
|
-
|
|
865
|
+
api.getState().setStreaming(false);
|
|
845
866
|
}
|
|
846
867
|
},
|
|
847
868
|
[api]
|
|
@@ -850,6 +871,39 @@ function useCanvasReplay() {
|
|
|
850
871
|
const reset = useCallback(() => api.getState().reset(), [api]);
|
|
851
872
|
return { play, stop, reset, canvas, isPlaying };
|
|
852
873
|
}
|
|
874
|
+
var DEFAULT_DEBOUNCE_MS = 800;
|
|
875
|
+
function useCanvasSave(onSave, debounceMs = DEFAULT_DEBOUNCE_MS) {
|
|
876
|
+
const timers = useRef(/* @__PURE__ */ new Map());
|
|
877
|
+
const latest = useRef(/* @__PURE__ */ new Map());
|
|
878
|
+
const handler = useRef(onSave);
|
|
879
|
+
handler.current = onSave;
|
|
880
|
+
const enabled = Boolean(onSave);
|
|
881
|
+
useEffect(() => {
|
|
882
|
+
const pending = timers.current;
|
|
883
|
+
return () => pending.forEach((t) => clearTimeout(t));
|
|
884
|
+
}, []);
|
|
885
|
+
return useMemo(() => {
|
|
886
|
+
if (!enabled) return null;
|
|
887
|
+
return (artifact2) => {
|
|
888
|
+
latest.current.set(artifact2.id, artifact2);
|
|
889
|
+
const existing = timers.current.get(artifact2.id);
|
|
890
|
+
if (existing) clearTimeout(existing);
|
|
891
|
+
timers.current.set(
|
|
892
|
+
artifact2.id,
|
|
893
|
+
setTimeout(() => {
|
|
894
|
+
timers.current.delete(artifact2.id);
|
|
895
|
+
const current = latest.current.get(artifact2.id);
|
|
896
|
+
if (!current || !handler.current) return;
|
|
897
|
+
void handler.current({
|
|
898
|
+
artifactId: current.id,
|
|
899
|
+
artifact: current,
|
|
900
|
+
baseRevision: typeof current.meta?.revision === "string" ? current.meta.revision : null
|
|
901
|
+
});
|
|
902
|
+
}, debounceMs)
|
|
903
|
+
);
|
|
904
|
+
};
|
|
905
|
+
}, [enabled, debounceMs]);
|
|
906
|
+
}
|
|
853
907
|
|
|
854
908
|
// src/fixtures/scenarios.ts
|
|
855
909
|
var PRICING_HTML = `<!doctype html>
|
|
@@ -1021,29 +1075,32 @@ var slides = {
|
|
|
1021
1075
|
{ type: "done" }
|
|
1022
1076
|
]
|
|
1023
1077
|
};
|
|
1024
|
-
var
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1078
|
+
var VERSIONED_HTML = `<!doctype html><html><body style="font-family:sans-serif;padding:40px">
|
|
1079
|
+
<h1>Coffee history</h1><p>From the Ethiopian highlands to the espresso bar.</p>
|
|
1080
|
+
</body></html>`;
|
|
1081
|
+
var versions = {
|
|
1082
|
+
id: "versions",
|
|
1083
|
+
title: "Version history",
|
|
1084
|
+
description: "An agent builds a page, a user edit and an agent edit each land as described commits \u2014 open the version rail to browse and restore-view snapshots.",
|
|
1029
1085
|
events: [
|
|
1030
|
-
{ type: "message.delta", messageId: "
|
|
1086
|
+
{ type: "message.delta", messageId: "m1", text: "Built the page \u2014 every change now lands in the version history." },
|
|
1087
|
+
{ type: "message.end", messageId: "m1" },
|
|
1031
1088
|
{
|
|
1032
1089
|
type: "canvas.create",
|
|
1033
|
-
artifact: {
|
|
1034
|
-
id: "report-pdf",
|
|
1035
|
-
type: "pdf",
|
|
1036
|
-
title: "Signed report",
|
|
1037
|
-
version: 1,
|
|
1038
|
-
status: "complete",
|
|
1039
|
-
data: { src: PDF_DATA_URL, filename: "signed-report.pdf" }
|
|
1040
|
-
}
|
|
1090
|
+
artifact: { id: "page", type: "html", title: "Coffee history", version: 1, status: "streaming", data: { html: VERSIONED_HTML } }
|
|
1041
1091
|
},
|
|
1042
|
-
{ type: "canvas.status", id: "
|
|
1092
|
+
{ type: "canvas.status", id: "page", status: "complete" },
|
|
1093
|
+
{ type: "canvas.commit", id: "page", description: "Create page", revision: "v1" },
|
|
1094
|
+
// A human tweaks the headline by hand, then saves — one described commit.
|
|
1095
|
+
{ type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee") } },
|
|
1096
|
+
{ type: "canvas.commit", id: "page", description: "Manual edit: 1 change", revision: "v2" },
|
|
1097
|
+
// The agent applies a targeted follow-up edit on the current state.
|
|
1098
|
+
{ type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee").replace("espresso bar", "third-wave caf\xE9") } },
|
|
1099
|
+
{ type: "canvas.commit", id: "page", description: "Update closing phrase", revision: "v3" },
|
|
1043
1100
|
{ type: "done" }
|
|
1044
1101
|
]
|
|
1045
1102
|
};
|
|
1046
|
-
var scenarios = [htmlPage, document2, chart, table, slides,
|
|
1103
|
+
var scenarios = [htmlPage, document2, chart, table, slides, versions];
|
|
1047
1104
|
var RegistryContext = createContext({});
|
|
1048
1105
|
function CanvasRegistryProvider({ registry, children }) {
|
|
1049
1106
|
return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
|
|
@@ -1189,202 +1246,40 @@ function display(value) {
|
|
|
1189
1246
|
if (value == null) return "";
|
|
1190
1247
|
if (typeof value === "object") {
|
|
1191
1248
|
if (value.result != null) return display(value.result);
|
|
1192
|
-
if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
|
|
1193
1249
|
if (typeof value.text === "string") return value.text;
|
|
1194
1250
|
if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
|
|
1195
1251
|
return "";
|
|
1196
1252
|
}
|
|
1197
1253
|
return String(value);
|
|
1198
1254
|
}
|
|
1199
|
-
function splitSections(fmt) {
|
|
1200
|
-
const sections = [];
|
|
1201
|
-
let cur = "";
|
|
1202
|
-
for (let i = 0; i < fmt.length; i++) {
|
|
1203
|
-
const ch = fmt[i];
|
|
1204
|
-
if (ch === '"') {
|
|
1205
|
-
cur += ch;
|
|
1206
|
-
i++;
|
|
1207
|
-
while (i < fmt.length && fmt[i] !== '"') cur += fmt[i++];
|
|
1208
|
-
if (i < fmt.length) cur += fmt[i];
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
if (ch === "[") {
|
|
1212
|
-
cur += ch;
|
|
1213
|
-
i++;
|
|
1214
|
-
while (i < fmt.length && fmt[i] !== "]") cur += fmt[i++];
|
|
1215
|
-
if (i < fmt.length) cur += fmt[i];
|
|
1216
|
-
continue;
|
|
1217
|
-
}
|
|
1218
|
-
if (ch === "\\") {
|
|
1219
|
-
cur += ch + (fmt[i + 1] ?? "");
|
|
1220
|
-
i++;
|
|
1221
|
-
continue;
|
|
1222
|
-
}
|
|
1223
|
-
if (ch === ";") {
|
|
1224
|
-
sections.push(cur);
|
|
1225
|
-
cur = "";
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
cur += ch;
|
|
1229
|
-
}
|
|
1230
|
-
sections.push(cur);
|
|
1231
|
-
return sections;
|
|
1232
|
-
}
|
|
1233
|
-
function numberSkeleton(fmt) {
|
|
1234
|
-
let currency = "";
|
|
1235
|
-
let currencyTrails = false;
|
|
1236
|
-
let skeleton = "";
|
|
1237
|
-
let seenDigit = false;
|
|
1238
|
-
const found = (sym) => {
|
|
1239
|
-
if (currency) return;
|
|
1240
|
-
currency = sym;
|
|
1241
|
-
currencyTrails = seenDigit;
|
|
1242
|
-
};
|
|
1243
|
-
for (let i = 0; i < fmt.length; i++) {
|
|
1244
|
-
const ch = fmt[i];
|
|
1245
|
-
if (ch === '"') {
|
|
1246
|
-
let lit = "";
|
|
1247
|
-
i++;
|
|
1248
|
-
while (i < fmt.length && fmt[i] !== '"') lit += fmt[i++];
|
|
1249
|
-
if (/[$₩€£¥]/.test(lit)) found(lit);
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
if (ch === "\\") {
|
|
1253
|
-
const c = fmt[i + 1] ?? "";
|
|
1254
|
-
if (/[$₩€£¥]/.test(c)) found(c);
|
|
1255
|
-
i++;
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
if (ch === "[") {
|
|
1259
|
-
const end = fmt.indexOf("]", i);
|
|
1260
|
-
const group = fmt.slice(i + 1, end === -1 ? void 0 : end);
|
|
1261
|
-
const cur = /^\$(.*?)-/.exec(group)?.[1] ?? (group.startsWith("$") ? group.slice(1) : "");
|
|
1262
|
-
if (cur) found(cur);
|
|
1263
|
-
i = end === -1 ? fmt.length : end;
|
|
1264
|
-
continue;
|
|
1265
|
-
}
|
|
1266
|
-
if (ch === "_" || ch === "*") {
|
|
1267
|
-
i++;
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
if (/[$₩€£¥]/.test(ch)) {
|
|
1271
|
-
found(ch);
|
|
1272
|
-
continue;
|
|
1273
|
-
}
|
|
1274
|
-
if (ch === "0" || ch === "#") seenDigit = true;
|
|
1275
|
-
skeleton += ch;
|
|
1276
|
-
}
|
|
1277
|
-
return { skeleton, currency, currencyTrails };
|
|
1278
|
-
}
|
|
1279
1255
|
function formatNumber(value, numFmt) {
|
|
1280
|
-
const
|
|
1281
|
-
if (!
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const thousands = /[#0],[#0]/.test(skeleton);
|
|
1289
|
-
const n = negSection ? Math.abs(value) : value;
|
|
1290
|
-
const percents = (skeleton.match(/%/g) ?? []).length;
|
|
1291
|
-
const scaled = percents ? n * 100 ** percents : n;
|
|
1292
|
-
let out = thousands ? scaled.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : scaled.toFixed(decimals);
|
|
1293
|
-
if (percents) out += "%";
|
|
1294
|
-
if (currency) {
|
|
1295
|
-
if (currencyTrails) out = `${out}${currency}`;
|
|
1296
|
-
else if (out.startsWith("-")) out = `-${currency}${out.slice(1)}`;
|
|
1297
|
-
else out = `${currency}${out}`;
|
|
1298
|
-
}
|
|
1299
|
-
if (negSection && /\(/.test(skeleton)) out = `(${out})`;
|
|
1256
|
+
const fmt = numFmt && numFmt !== "General" ? numFmt : "";
|
|
1257
|
+
if (!fmt) return String(value);
|
|
1258
|
+
const decimals = (fmt.match(/\.([0#]+)/)?.[1] ?? "").length;
|
|
1259
|
+
if (fmt.includes("%")) return `${(value * 100).toFixed(decimals)}%`;
|
|
1260
|
+
const thousands = /[#0],[#0]/.test(fmt);
|
|
1261
|
+
let out = thousands ? value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : value.toFixed(decimals);
|
|
1262
|
+
const currency = fmt.match(/[$₩€£¥]/);
|
|
1263
|
+
if (currency) out = value < 0 ? `-${currency[0]}${out.slice(1)}` : `${currency[0]}${out}`;
|
|
1300
1264
|
return out;
|
|
1301
1265
|
}
|
|
1302
|
-
var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
1303
1266
|
function formatDate(d, numFmt) {
|
|
1304
|
-
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ?
|
|
1267
|
+
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
|
|
1305
1268
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
1306
|
-
const meridiem = /AM\/PM|A\/P/i.test(fmt);
|
|
1307
|
-
const H = d.getUTCHours();
|
|
1308
|
-
const hour12 = H % 12 === 0 ? 12 : H % 12;
|
|
1309
|
-
const hourText = (width) => meridiem ? p(hour12, width) : p(H, width);
|
|
1310
|
-
const elapsedMs = d.getTime() - EXCEL_EPOCH_MS;
|
|
1311
1269
|
const map = {
|
|
1312
|
-
yyyy:
|
|
1313
|
-
yy:
|
|
1314
|
-
mmmm:
|
|
1315
|
-
mmm:
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1270
|
+
yyyy: String(d.getFullYear()),
|
|
1271
|
+
yy: p(d.getFullYear() % 100),
|
|
1272
|
+
mmmm: d.toLocaleString("en-US", { month: "long" }),
|
|
1273
|
+
mmm: d.toLocaleString("en-US", { month: "short" }),
|
|
1274
|
+
mm: p(d.getMonth() + 1),
|
|
1275
|
+
m: String(d.getMonth() + 1),
|
|
1276
|
+
dd: p(d.getDate()),
|
|
1277
|
+
d: String(d.getDate()),
|
|
1278
|
+
hh: p(d.getHours()),
|
|
1279
|
+
h: String(d.getHours()),
|
|
1280
|
+
ss: p(d.getSeconds())
|
|
1322
1281
|
};
|
|
1323
|
-
|
|
1324
|
-
const minute = (t) => t === "mm" ? p(d.getUTCMinutes()) : String(d.getUTCMinutes());
|
|
1325
|
-
const TOKEN = /AM\/PM|A\/P|yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss|s/iy;
|
|
1326
|
-
let out = "";
|
|
1327
|
-
let lastWasHours = false;
|
|
1328
|
-
for (let i = 0; i < fmt.length; ) {
|
|
1329
|
-
const ch = fmt[i];
|
|
1330
|
-
if (ch === '"') {
|
|
1331
|
-
i++;
|
|
1332
|
-
while (i < fmt.length && fmt[i] !== '"') out += fmt[i++];
|
|
1333
|
-
i++;
|
|
1334
|
-
continue;
|
|
1335
|
-
}
|
|
1336
|
-
if (ch === "\\") {
|
|
1337
|
-
out += fmt[i + 1] ?? "";
|
|
1338
|
-
i += 2;
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
if (ch === "[") {
|
|
1342
|
-
const end = fmt.indexOf("]", i);
|
|
1343
|
-
const group = fmt.slice(i + 1, end === -1 ? fmt.length : end);
|
|
1344
|
-
i = end === -1 ? fmt.length : end + 1;
|
|
1345
|
-
if (/^h+$/i.test(group)) {
|
|
1346
|
-
out += p(Math.floor(elapsedMs / 36e5), group.length);
|
|
1347
|
-
lastWasHours = true;
|
|
1348
|
-
} else if (/^m+$/i.test(group)) out += p(Math.floor(elapsedMs / 6e4), group.length);
|
|
1349
|
-
else if (/^s+$/i.test(group)) out += p(Math.floor(elapsedMs / 1e3), group.length);
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
if (ch === "_") {
|
|
1353
|
-
out += " ";
|
|
1354
|
-
i += 2;
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
if (ch === "*") {
|
|
1358
|
-
i += 2;
|
|
1359
|
-
continue;
|
|
1360
|
-
}
|
|
1361
|
-
TOKEN.lastIndex = i;
|
|
1362
|
-
const m = TOKEN.exec(fmt);
|
|
1363
|
-
if (m) {
|
|
1364
|
-
const tok = m[0].toLowerCase();
|
|
1365
|
-
i += m[0].length;
|
|
1366
|
-
if (tok === "am/pm") {
|
|
1367
|
-
out += H < 12 ? "AM" : "PM";
|
|
1368
|
-
continue;
|
|
1369
|
-
}
|
|
1370
|
-
if (tok === "a/p") {
|
|
1371
|
-
out += H < 12 ? "A" : "P";
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1374
|
-
if (tok === "m" || tok === "mm") {
|
|
1375
|
-
const minutes = lastWasHours || /^[\s:.,\-]*s/i.test(fmt.slice(i));
|
|
1376
|
-
out += minutes ? minute(tok) : month(tok);
|
|
1377
|
-
lastWasHours = false;
|
|
1378
|
-
continue;
|
|
1379
|
-
}
|
|
1380
|
-
lastWasHours = tok === "h" || tok === "hh";
|
|
1381
|
-
out += map[tok]();
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
out += ch;
|
|
1385
|
-
i++;
|
|
1386
|
-
}
|
|
1387
|
-
return out;
|
|
1282
|
+
return fmt.replace(/yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss/g, (t) => map[t] ?? t);
|
|
1388
1283
|
}
|
|
1389
1284
|
function cellValue(cell) {
|
|
1390
1285
|
const raw = cell.value;
|
|
@@ -1583,7 +1478,6 @@ function cellVal(v) {
|
|
|
1583
1478
|
if (typeof v === "number" || typeof v === "string") return v;
|
|
1584
1479
|
if (typeof v === "object") {
|
|
1585
1480
|
if (v.result != null) return cellVal(v.result);
|
|
1586
|
-
if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
|
|
1587
1481
|
if (typeof v.text === "string") return v.text;
|
|
1588
1482
|
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
1589
1483
|
}
|
|
@@ -1626,416 +1520,8 @@ function flatten(ws) {
|
|
|
1626
1520
|
return { columns, rows };
|
|
1627
1521
|
}
|
|
1628
1522
|
|
|
1629
|
-
// src/io/hwpx.ts
|
|
1630
|
-
var EOCD_SIG = 101010256;
|
|
1631
|
-
var CDIR_SIG = 33639248;
|
|
1632
|
-
var LOCAL_SIG = 67324752;
|
|
1633
|
-
async function inflateRaw(bytes) {
|
|
1634
|
-
if (typeof DecompressionStream === "undefined") {
|
|
1635
|
-
throw new Error("\uC774 \uBE0C\uB77C\uC6B0\uC800\uB294 \uC555\uCD95 \uD574\uC81C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (DecompressionStream unavailable).");
|
|
1636
|
-
}
|
|
1637
|
-
const ds = new DecompressionStream("deflate-raw");
|
|
1638
|
-
const writer = ds.writable.getWriter();
|
|
1639
|
-
const wrote = writer.write(bytes).then(() => writer.close());
|
|
1640
|
-
const reader = ds.readable.getReader();
|
|
1641
|
-
const chunks = [];
|
|
1642
|
-
let total = 0;
|
|
1643
|
-
for (; ; ) {
|
|
1644
|
-
const { done, value } = await reader.read();
|
|
1645
|
-
if (done) break;
|
|
1646
|
-
chunks.push(value);
|
|
1647
|
-
total += value.length;
|
|
1648
|
-
}
|
|
1649
|
-
await wrote;
|
|
1650
|
-
const out = new Uint8Array(total);
|
|
1651
|
-
let pos = 0;
|
|
1652
|
-
for (const c of chunks) {
|
|
1653
|
-
out.set(c, pos);
|
|
1654
|
-
pos += c.length;
|
|
1655
|
-
}
|
|
1656
|
-
return out;
|
|
1657
|
-
}
|
|
1658
|
-
async function readZip(buffer) {
|
|
1659
|
-
const view = new DataView(buffer);
|
|
1660
|
-
const bytes = new Uint8Array(buffer);
|
|
1661
|
-
let eocd = -1;
|
|
1662
|
-
const floor = Math.max(0, buffer.byteLength - 22 - 65535);
|
|
1663
|
-
for (let i = buffer.byteLength - 22; i >= floor; i--) {
|
|
1664
|
-
if (view.getUint32(i, true) === EOCD_SIG) {
|
|
1665
|
-
eocd = i;
|
|
1666
|
-
break;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
if (eocd === -1) throw new Error("ZIP \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (no end-of-central-directory).");
|
|
1670
|
-
const count = view.getUint16(eocd + 10, true);
|
|
1671
|
-
let offset = view.getUint32(eocd + 16, true);
|
|
1672
|
-
const entries = /* @__PURE__ */ new Map();
|
|
1673
|
-
const decoder = new TextDecoder();
|
|
1674
|
-
for (let i = 0; i < count; i++) {
|
|
1675
|
-
if (offset + 46 > buffer.byteLength || view.getUint32(offset, true) !== CDIR_SIG) {
|
|
1676
|
-
throw new Error("ZIP \uC911\uC559 \uB514\uB809\uD130\uB9AC\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt central directory).");
|
|
1677
|
-
}
|
|
1678
|
-
const method = view.getUint16(offset + 10, true);
|
|
1679
|
-
const compressedSize = view.getUint32(offset + 20, true);
|
|
1680
|
-
const uncompressedSize = view.getUint32(offset + 24, true);
|
|
1681
|
-
const nameLen = view.getUint16(offset + 28, true);
|
|
1682
|
-
const extraLen = view.getUint16(offset + 30, true);
|
|
1683
|
-
const commentLen = view.getUint16(offset + 32, true);
|
|
1684
|
-
const localOffset = view.getUint32(offset + 42, true);
|
|
1685
|
-
const name = decoder.decode(bytes.subarray(offset + 46, offset + 46 + nameLen));
|
|
1686
|
-
offset += 46 + nameLen + extraLen + commentLen;
|
|
1687
|
-
if (compressedSize === 4294967295 || uncompressedSize === 4294967295) {
|
|
1688
|
-
throw new Error("Zip64 \uD615\uC2DD\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (Zip64 not supported).");
|
|
1689
|
-
}
|
|
1690
|
-
if (name.endsWith("/")) continue;
|
|
1691
|
-
if (localOffset + 30 > buffer.byteLength || view.getUint32(localOffset, true) !== LOCAL_SIG) {
|
|
1692
|
-
throw new Error("ZIP \uB85C\uCEEC \uD5E4\uB354\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt local header).");
|
|
1693
|
-
}
|
|
1694
|
-
const localNameLen = view.getUint16(localOffset + 26, true);
|
|
1695
|
-
const localExtraLen = view.getUint16(localOffset + 28, true);
|
|
1696
|
-
const start = localOffset + 30 + localNameLen + localExtraLen;
|
|
1697
|
-
const data = bytes.subarray(start, start + compressedSize);
|
|
1698
|
-
if (method === 0) entries.set(name, data);
|
|
1699
|
-
else if (method === 8) entries.set(name, await inflateRaw(data));
|
|
1700
|
-
else throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC555\uCD95 \uBC29\uC2DD\uC785\uB2C8\uB2E4 (compression method ${method}).`);
|
|
1701
|
-
}
|
|
1702
|
-
return entries;
|
|
1703
|
-
}
|
|
1704
|
-
function paragraphText(p) {
|
|
1705
|
-
let out = "";
|
|
1706
|
-
const walk = (el) => {
|
|
1707
|
-
for (const child of Array.from(el.children)) {
|
|
1708
|
-
const tag = child.localName;
|
|
1709
|
-
if (tag === "tbl") continue;
|
|
1710
|
-
if (tag === "t") out += child.textContent ?? "";
|
|
1711
|
-
else if (tag === "lineBreak") out += "\n";
|
|
1712
|
-
else walk(child);
|
|
1713
|
-
}
|
|
1714
|
-
};
|
|
1715
|
-
walk(p);
|
|
1716
|
-
return out;
|
|
1717
|
-
}
|
|
1718
|
-
function tableToMarkdown(tbl) {
|
|
1719
|
-
const rows = [];
|
|
1720
|
-
for (const tr of Array.from(tbl.getElementsByTagNameNS("*", "tr"))) {
|
|
1721
|
-
if (tr.closest("tbl") !== tbl) continue;
|
|
1722
|
-
const cells = [];
|
|
1723
|
-
for (const tc of Array.from(tr.children)) {
|
|
1724
|
-
if (tc.localName !== "tc") continue;
|
|
1725
|
-
const text = Array.from(tc.getElementsByTagNameNS("*", "p")).map((p) => paragraphText(p).trim()).filter(Boolean).join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
1726
|
-
cells.push(text);
|
|
1727
|
-
}
|
|
1728
|
-
if (cells.length) rows.push(cells);
|
|
1729
|
-
}
|
|
1730
|
-
if (!rows.length) return "";
|
|
1731
|
-
const width = Math.max(...rows.map((r) => r.length));
|
|
1732
|
-
const pad = (r) => Array.from({ length: width }, (_, i) => r[i] ?? "");
|
|
1733
|
-
const line = (r) => `| ${pad(r).join(" | ")} |`;
|
|
1734
|
-
const [head, ...body] = rows;
|
|
1735
|
-
return [line(head), `| ${Array(width).fill("---").join(" | ")} |`, ...body.map(line)].join("\n");
|
|
1736
|
-
}
|
|
1737
|
-
function sectionToMarkdown(xml) {
|
|
1738
|
-
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
1739
|
-
if (doc.querySelector("parsererror")) {
|
|
1740
|
-
throw new Error("HWPX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid section XML).");
|
|
1741
|
-
}
|
|
1742
|
-
const blocks = [];
|
|
1743
|
-
const walk = (el) => {
|
|
1744
|
-
for (const child of Array.from(el.children)) {
|
|
1745
|
-
const tag = child.localName;
|
|
1746
|
-
if (tag === "p") {
|
|
1747
|
-
const text = paragraphText(child).trim();
|
|
1748
|
-
if (text) blocks.push(text);
|
|
1749
|
-
for (const tbl of Array.from(child.getElementsByTagNameNS("*", "tbl"))) {
|
|
1750
|
-
if ((tbl.parentElement?.closest("tbl") ?? null) === null) {
|
|
1751
|
-
const md = tableToMarkdown(tbl);
|
|
1752
|
-
if (md) blocks.push(md);
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
} else if (tag !== "tbl") {
|
|
1756
|
-
walk(child);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
};
|
|
1760
|
-
walk(doc.documentElement);
|
|
1761
|
-
return blocks;
|
|
1762
|
-
}
|
|
1763
|
-
async function hwpxToMarkdown(buffer) {
|
|
1764
|
-
const entries = await readZip(buffer);
|
|
1765
|
-
const sections = [...entries.keys()].filter((n) => /^Contents\/section\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)/.exec(a)?.[1] ?? 0) - Number(/(\d+)/.exec(b)?.[1] ?? 0));
|
|
1766
|
-
if (!sections.length) {
|
|
1767
|
-
throw new Error("HWPX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(section XML)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (no Contents/section*.xml).");
|
|
1768
|
-
}
|
|
1769
|
-
const decoder = new TextDecoder();
|
|
1770
|
-
const blocks = [];
|
|
1771
|
-
for (const name of sections) {
|
|
1772
|
-
blocks.push(...sectionToMarkdown(decoder.decode(entries.get(name))));
|
|
1773
|
-
}
|
|
1774
|
-
return blocks.join("\n\n").trim();
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
// src/io/hwp.ts
|
|
1778
|
-
var ERR = {
|
|
1779
|
-
notCfb: "CFB(OLE) \uCEE8\uD14C\uC774\uB108 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC62C\uBC14\uB978 HWP \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4. / Not a CFB (OLE) compound file container \u2014 not a valid HWP file.",
|
|
1780
|
-
notHwp: '"HWP Document File" \uC2DC\uADF8\uB2C8\uCC98\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWP \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. / Missing "HWP Document File" signature \u2014 not an HWP document.',
|
|
1781
|
-
encrypted: "\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uD55C\uAE00\uC5D0\uC11C \uC554\uD638\uB97C \uD574\uC81C\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / This HWP document is password-encrypted \u2014 remove the password in Hangul and try again.",
|
|
1782
|
-
drm: "\uBC30\uD3EC\uC6A9(DRM) HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uBCF8\uBB38\uC774 \uB09C\uB3C5\uD654\uB418\uC5B4 \uC788\uC5B4 \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / This is a distribution (DRM) HWP document \u2014 its body text is obfuscated and cannot be extracted.",
|
|
1783
|
-
legacy: (version) => `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP \uBC84\uC804\uC785\uB2C8\uB2E4 (${version} < 5.0) \u2014 \uD55C\uAE00\uC5D0\uC11C HWP 5.0 \uC774\uC0C1\uC73C\uB85C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / Unsupported HWP version (${version} < 5.0) \u2014 re-save as HWP 5.0+ in Hangul and try again.`,
|
|
1784
|
-
corrupt: (detail) => `\uC190\uC0C1\uB41C HWP \uD30C\uC77C\uC785\uB2C8\uB2E4 (${detail}). / Corrupt HWP file (${detail}).`,
|
|
1785
|
-
noInflate: "\uC774 \uD658\uACBD\uC5D0\uC11C\uB294 DecompressionStream\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uC555\uCD95\uB41C HWP\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / DecompressionStream is unavailable in this environment, so compressed HWP files cannot be opened."
|
|
1786
|
-
};
|
|
1787
|
-
var ENDOFCHAIN = 4294967294;
|
|
1788
|
-
var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
1789
|
-
function parseCfb(bytes) {
|
|
1790
|
-
if (bytes.length < 512 || CFB_SIGNATURE.some((b, i) => bytes[i] !== b)) {
|
|
1791
|
-
throw new Error(ERR.notCfb);
|
|
1792
|
-
}
|
|
1793
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1794
|
-
const sectorSize = 1 << view.getUint16(30, true);
|
|
1795
|
-
const miniSectorSize = 1 << view.getUint16(32, true);
|
|
1796
|
-
const numFatSectors = view.getUint32(44, true);
|
|
1797
|
-
const firstDirSector = view.getUint32(48, true);
|
|
1798
|
-
const miniCutoff = view.getUint32(56, true);
|
|
1799
|
-
const firstMiniFatSector = view.getUint32(60, true);
|
|
1800
|
-
const firstDifatSector = view.getUint32(68, true);
|
|
1801
|
-
const totalSectors = Math.max(0, Math.ceil((bytes.length - 512) / sectorSize));
|
|
1802
|
-
const sectorBytes = (sector) => {
|
|
1803
|
-
const start = 512 + sector * sectorSize;
|
|
1804
|
-
if (sector >= totalSectors || start >= bytes.length) {
|
|
1805
|
-
throw new Error(ERR.corrupt("sector out of range"));
|
|
1806
|
-
}
|
|
1807
|
-
return bytes.subarray(start, start + sectorSize);
|
|
1808
|
-
};
|
|
1809
|
-
const fatSectorIds = [];
|
|
1810
|
-
for (let i = 0; i < 109 && fatSectorIds.length < numFatSectors; i++) {
|
|
1811
|
-
fatSectorIds.push(view.getUint32(76 + i * 4, true));
|
|
1812
|
-
}
|
|
1813
|
-
let difatSector = firstDifatSector;
|
|
1814
|
-
let difatHops = 0;
|
|
1815
|
-
while (difatSector !== ENDOFCHAIN && difatSector < 4294967290) {
|
|
1816
|
-
if (difatHops++ > totalSectors) throw new Error(ERR.corrupt("DIFAT chain loops"));
|
|
1817
|
-
const s = sectorBytes(difatSector);
|
|
1818
|
-
const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
|
|
1819
|
-
const perSector = sectorSize / 4 - 1;
|
|
1820
|
-
for (let i = 0; i < perSector && fatSectorIds.length < numFatSectors; i++) {
|
|
1821
|
-
fatSectorIds.push(sv.getUint32(i * 4, true));
|
|
1822
|
-
}
|
|
1823
|
-
difatSector = sv.getUint32(sectorSize - 4, true);
|
|
1824
|
-
}
|
|
1825
|
-
const fat = new Uint32Array(fatSectorIds.length * (sectorSize / 4));
|
|
1826
|
-
fatSectorIds.forEach((id, idx) => {
|
|
1827
|
-
const s = sectorBytes(id);
|
|
1828
|
-
const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
|
|
1829
|
-
for (let i = 0; i < sectorSize / 4; i++) fat[idx * (sectorSize / 4) + i] = sv.getUint32(i * 4, true);
|
|
1830
|
-
});
|
|
1831
|
-
const readChain = (start, size) => {
|
|
1832
|
-
const out = new Uint8Array(size);
|
|
1833
|
-
let sector = start;
|
|
1834
|
-
let written = 0;
|
|
1835
|
-
let hops = 0;
|
|
1836
|
-
while (sector !== ENDOFCHAIN && written < size) {
|
|
1837
|
-
if (hops++ > totalSectors || sector >= fat.length) throw new Error(ERR.corrupt("FAT chain loops"));
|
|
1838
|
-
const chunk = sectorBytes(sector);
|
|
1839
|
-
out.set(chunk.subarray(0, Math.min(sectorSize, size - written)), written);
|
|
1840
|
-
written += sectorSize;
|
|
1841
|
-
sector = fat[sector];
|
|
1842
|
-
}
|
|
1843
|
-
return out;
|
|
1844
|
-
};
|
|
1845
|
-
const dirSectors = [];
|
|
1846
|
-
let dirSector = firstDirSector;
|
|
1847
|
-
let dirHops = 0;
|
|
1848
|
-
while (dirSector !== ENDOFCHAIN) {
|
|
1849
|
-
if (dirHops++ > totalSectors || dirSector >= fat.length) throw new Error(ERR.corrupt("directory chain loops"));
|
|
1850
|
-
dirSectors.push(sectorBytes(dirSector));
|
|
1851
|
-
dirSector = fat[dirSector];
|
|
1852
|
-
}
|
|
1853
|
-
const entries = [];
|
|
1854
|
-
for (const sector of dirSectors) {
|
|
1855
|
-
const sv = new DataView(sector.buffer, sector.byteOffset, sector.byteLength);
|
|
1856
|
-
for (let off = 0; off + 128 <= sector.length; off += 128) {
|
|
1857
|
-
const nameLen = sv.getUint16(off + 64, true);
|
|
1858
|
-
const type = sv.getUint8(off + 66);
|
|
1859
|
-
if (type === 0 || nameLen < 2 || nameLen > 64) continue;
|
|
1860
|
-
let name = "";
|
|
1861
|
-
for (let i = 0; i < nameLen - 2; i += 2) name += String.fromCharCode(sv.getUint16(off + i, true));
|
|
1862
|
-
entries.push({
|
|
1863
|
-
name,
|
|
1864
|
-
type,
|
|
1865
|
-
startSector: sv.getUint32(off + 116, true),
|
|
1866
|
-
// Size is a uint64, but HWP streams are far below 4 GB — the low half suffices.
|
|
1867
|
-
size: sv.getUint32(off + 120, true)
|
|
1868
|
-
});
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
const root = entries.find((e) => e.type === 5);
|
|
1872
|
-
if (!root) throw new Error(ERR.corrupt("missing root directory entry"));
|
|
1873
|
-
let miniStream = null;
|
|
1874
|
-
let miniFat = null;
|
|
1875
|
-
const lazyMini = () => {
|
|
1876
|
-
if (miniStream && miniFat) return;
|
|
1877
|
-
miniStream = readChain(root.startSector, root.size);
|
|
1878
|
-
const miniFatSectors = Math.ceil(root.size / miniSectorSize) + 1;
|
|
1879
|
-
const raw = readChain(firstMiniFatSector, miniFatSectors * 4 + sectorSize);
|
|
1880
|
-
const rv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
1881
|
-
miniFat = new Uint32Array(Math.floor(raw.length / 4));
|
|
1882
|
-
for (let i = 0; i < miniFat.length; i++) miniFat[i] = rv.getUint32(i * 4, true);
|
|
1883
|
-
};
|
|
1884
|
-
const readMiniChain = (start, size) => {
|
|
1885
|
-
lazyMini();
|
|
1886
|
-
const out = new Uint8Array(size);
|
|
1887
|
-
let sector = start;
|
|
1888
|
-
let written = 0;
|
|
1889
|
-
let hops = 0;
|
|
1890
|
-
const maxMini = Math.ceil(miniStream.length / miniSectorSize);
|
|
1891
|
-
while (sector !== ENDOFCHAIN && written < size) {
|
|
1892
|
-
if (hops++ > maxMini || sector >= miniFat.length || sector >= maxMini) {
|
|
1893
|
-
throw new Error(ERR.corrupt("mini FAT chain loops"));
|
|
1894
|
-
}
|
|
1895
|
-
const at = sector * miniSectorSize;
|
|
1896
|
-
out.set(miniStream.subarray(at, at + Math.min(miniSectorSize, size - written)), written);
|
|
1897
|
-
written += miniSectorSize;
|
|
1898
|
-
sector = miniFat[sector];
|
|
1899
|
-
}
|
|
1900
|
-
return out;
|
|
1901
|
-
};
|
|
1902
|
-
return {
|
|
1903
|
-
entries,
|
|
1904
|
-
readStream: (entry) => entry.size < miniCutoff && entry.type !== 5 ? readMiniChain(entry.startSector, entry.size) : readChain(entry.startSector, entry.size)
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
var HWP_SIGNATURE = "HWP Document File";
|
|
1908
|
-
function parseFileHeader(bytes) {
|
|
1909
|
-
if (bytes.length < 40) throw new Error(ERR.notHwp);
|
|
1910
|
-
let sig = "";
|
|
1911
|
-
for (let i = 0; i < HWP_SIGNATURE.length; i++) sig += String.fromCharCode(bytes[i]);
|
|
1912
|
-
if (sig !== HWP_SIGNATURE) throw new Error(ERR.notHwp);
|
|
1913
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1914
|
-
const version = view.getUint32(32, true);
|
|
1915
|
-
const major = version >>> 24 & 255;
|
|
1916
|
-
if (major < 5) {
|
|
1917
|
-
throw new Error(ERR.legacy(`${major}.${version >>> 16 & 255}`));
|
|
1918
|
-
}
|
|
1919
|
-
const flags = view.getUint32(36, true);
|
|
1920
|
-
if (flags & 2) throw new Error(ERR.encrypted);
|
|
1921
|
-
if (flags & 4) throw new Error(ERR.drm);
|
|
1922
|
-
return { compressed: (flags & 1) !== 0 };
|
|
1923
|
-
}
|
|
1924
|
-
var HWPTAG_PARA_TEXT = 16 + 51;
|
|
1925
|
-
function decodeParaText(view, offset, byteLength) {
|
|
1926
|
-
const paragraphs = [];
|
|
1927
|
-
let text = "";
|
|
1928
|
-
const count = byteLength >> 1;
|
|
1929
|
-
for (let i = 0; i < count; i++) {
|
|
1930
|
-
const code = view.getUint16(offset + i * 2, true);
|
|
1931
|
-
if (code >= 32) {
|
|
1932
|
-
text += String.fromCharCode(code);
|
|
1933
|
-
continue;
|
|
1934
|
-
}
|
|
1935
|
-
switch (code) {
|
|
1936
|
-
case 13:
|
|
1937
|
-
paragraphs.push(text);
|
|
1938
|
-
text = "";
|
|
1939
|
-
break;
|
|
1940
|
-
case 10:
|
|
1941
|
-
text += "\n";
|
|
1942
|
-
break;
|
|
1943
|
-
case 0:
|
|
1944
|
-
case 24:
|
|
1945
|
-
case 25:
|
|
1946
|
-
case 26:
|
|
1947
|
-
case 27:
|
|
1948
|
-
case 28:
|
|
1949
|
-
case 29:
|
|
1950
|
-
case 30:
|
|
1951
|
-
case 31:
|
|
1952
|
-
break;
|
|
1953
|
-
// 1-WCHAR char controls with no text representation
|
|
1954
|
-
case 9:
|
|
1955
|
-
i += 7;
|
|
1956
|
-
text += " ";
|
|
1957
|
-
break;
|
|
1958
|
-
default:
|
|
1959
|
-
i += 7;
|
|
1960
|
-
break;
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
if (text) paragraphs.push(text);
|
|
1964
|
-
return paragraphs;
|
|
1965
|
-
}
|
|
1966
|
-
function extractSectionParagraphs(section) {
|
|
1967
|
-
const view = new DataView(section.buffer, section.byteOffset, section.byteLength);
|
|
1968
|
-
const paragraphs = [];
|
|
1969
|
-
let off = 0;
|
|
1970
|
-
while (off + 4 <= section.length) {
|
|
1971
|
-
const header = view.getUint32(off, true);
|
|
1972
|
-
off += 4;
|
|
1973
|
-
const tagId = header & 1023;
|
|
1974
|
-
let size = header >>> 20 & 4095;
|
|
1975
|
-
if (size === 4095) {
|
|
1976
|
-
if (off + 4 > section.length) throw new Error(ERR.corrupt("truncated record header"));
|
|
1977
|
-
size = view.getUint32(off, true);
|
|
1978
|
-
off += 4;
|
|
1979
|
-
}
|
|
1980
|
-
if (off + size > section.length) throw new Error(ERR.corrupt("record overruns section"));
|
|
1981
|
-
if (tagId === HWPTAG_PARA_TEXT) paragraphs.push(...decodeParaText(view, off, size));
|
|
1982
|
-
off += size;
|
|
1983
|
-
}
|
|
1984
|
-
return paragraphs;
|
|
1985
|
-
}
|
|
1986
|
-
async function inflateRaw2(data) {
|
|
1987
|
-
if (typeof DecompressionStream === "undefined") throw new Error(ERR.noInflate);
|
|
1988
|
-
const stream = new DecompressionStream("deflate-raw");
|
|
1989
|
-
const writer = stream.writable.getWriter();
|
|
1990
|
-
const writing = writer.write(data).then(() => writer.close());
|
|
1991
|
-
writing.catch(() => {
|
|
1992
|
-
});
|
|
1993
|
-
const reader = stream.readable.getReader();
|
|
1994
|
-
const chunks = [];
|
|
1995
|
-
let total = 0;
|
|
1996
|
-
for (; ; ) {
|
|
1997
|
-
const { done, value } = await reader.read();
|
|
1998
|
-
if (done) break;
|
|
1999
|
-
chunks.push(value);
|
|
2000
|
-
total += value.length;
|
|
2001
|
-
}
|
|
2002
|
-
const out = new Uint8Array(total);
|
|
2003
|
-
let at = 0;
|
|
2004
|
-
for (const chunk of chunks) {
|
|
2005
|
-
out.set(chunk, at);
|
|
2006
|
-
at += chunk.length;
|
|
2007
|
-
}
|
|
2008
|
-
return out;
|
|
2009
|
-
}
|
|
2010
|
-
async function hwpToText(buffer) {
|
|
2011
|
-
const cfb = parseCfb(new Uint8Array(buffer));
|
|
2012
|
-
const headerEntry = cfb.entries.find((e) => e.type === 2 && e.name === "FileHeader");
|
|
2013
|
-
if (!headerEntry) throw new Error(ERR.notHwp);
|
|
2014
|
-
const header = parseFileHeader(cfb.readStream(headerEntry));
|
|
2015
|
-
const sections = cfb.entries.map((e) => ({ entry: e, match: e.type === 2 ? /^Section(\d+)$/.exec(e.name) : null })).filter((s) => s.match !== null).sort((a, b) => Number(a.match[1]) - Number(b.match[1]));
|
|
2016
|
-
const paragraphs = [];
|
|
2017
|
-
for (const { entry } of sections) {
|
|
2018
|
-
let bytes = cfb.readStream(entry);
|
|
2019
|
-
if (header.compressed) bytes = await inflateRaw2(bytes);
|
|
2020
|
-
paragraphs.push(...extractSectionParagraphs(bytes).filter((p) => p.length > 0));
|
|
2021
|
-
}
|
|
2022
|
-
return paragraphs.join("\n\n").replace(/\s+$/, "");
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
1523
|
// src/io/importers.ts
|
|
2026
|
-
var IMPORTABLE_EXTENSIONS = [
|
|
2027
|
-
".csv",
|
|
2028
|
-
".md",
|
|
2029
|
-
".markdown",
|
|
2030
|
-
".txt",
|
|
2031
|
-
".html",
|
|
2032
|
-
".htm",
|
|
2033
|
-
".json",
|
|
2034
|
-
".xlsx",
|
|
2035
|
-
".pdf",
|
|
2036
|
-
".hwpx",
|
|
2037
|
-
".hwp"
|
|
2038
|
-
];
|
|
1524
|
+
var IMPORTABLE_EXTENSIONS = [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
|
|
2039
1525
|
var extensionOf = (name) => {
|
|
2040
1526
|
const dot = name.lastIndexOf(".");
|
|
2041
1527
|
return dot === -1 ? "" : name.slice(dot).toLowerCase();
|
|
@@ -2076,18 +1562,6 @@ async function importFile(file) {
|
|
|
2076
1562
|
const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
|
|
2077
1563
|
return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
|
|
2078
1564
|
}
|
|
2079
|
-
case ".pdf": {
|
|
2080
|
-
const src = await fileToDataUrl(file);
|
|
2081
|
-
return toEvents(artifact(id, "pdf", title, { src, filename: file.name }));
|
|
2082
|
-
}
|
|
2083
|
-
case ".hwpx": {
|
|
2084
|
-
const content = await hwpxToMarkdown(await file.arrayBuffer());
|
|
2085
|
-
return toEvents(artifact(id, "document", title, { format: "markdown", content }));
|
|
2086
|
-
}
|
|
2087
|
-
case ".hwp": {
|
|
2088
|
-
const content = await hwpToText(await file.arrayBuffer());
|
|
2089
|
-
return toEvents(artifact(id, "document", title, { format: "markdown", content }));
|
|
2090
|
-
}
|
|
2091
1565
|
default:
|
|
2092
1566
|
throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
|
|
2093
1567
|
}
|
|
@@ -2095,14 +1569,6 @@ async function importFile(file) {
|
|
|
2095
1569
|
function artifact(id, type, title, data) {
|
|
2096
1570
|
return { id, type, title, version: 1, status: "complete", data };
|
|
2097
1571
|
}
|
|
2098
|
-
function fileToDataUrl(file) {
|
|
2099
|
-
return new Promise((resolve, reject) => {
|
|
2100
|
-
const reader = new FileReader();
|
|
2101
|
-
reader.onload = () => resolve(reader.result);
|
|
2102
|
-
reader.onerror = () => reject(reader.error ?? new Error("Failed to read file."));
|
|
2103
|
-
reader.readAsDataURL(file);
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
1572
|
function importJson(text, id, title) {
|
|
2107
1573
|
let parsed;
|
|
2108
1574
|
try {
|
|
@@ -2420,8 +1886,8 @@ function checkA11y(html) {
|
|
|
2420
1886
|
if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
|
|
2421
1887
|
return issues;
|
|
2422
1888
|
}
|
|
2423
|
-
var
|
|
2424
|
-
var SCROLL_FIX = "<style
|
|
1889
|
+
var SLIDE_W = 1280;
|
|
1890
|
+
var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
2425
1891
|
function withScrollableBody(html) {
|
|
2426
1892
|
const i = html.lastIndexOf("</body>");
|
|
2427
1893
|
return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
|
|
@@ -2429,7 +1895,7 @@ function withScrollableBody(html) {
|
|
|
2429
1895
|
function useSlideFit(ratio, boxRef) {
|
|
2430
1896
|
const [scale, setScale] = useState(1);
|
|
2431
1897
|
const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
|
|
2432
|
-
const
|
|
1898
|
+
const height = rw && rh ? Math.round(SLIDE_W * rh / rw) : 720;
|
|
2433
1899
|
useEffect(() => {
|
|
2434
1900
|
if (!ratio) return;
|
|
2435
1901
|
const el = boxRef.current;
|
|
@@ -2437,14 +1903,14 @@ function useSlideFit(ratio, boxRef) {
|
|
|
2437
1903
|
const fit = () => {
|
|
2438
1904
|
const w = el.clientWidth;
|
|
2439
1905
|
if (w <= 40) return;
|
|
2440
|
-
setScale(Math.min(1, (w - 40) /
|
|
1906
|
+
setScale(Math.min(1, (w - 40) / SLIDE_W));
|
|
2441
1907
|
};
|
|
2442
1908
|
fit();
|
|
2443
1909
|
const ro = new ResizeObserver(fit);
|
|
2444
1910
|
ro.observe(el);
|
|
2445
1911
|
return () => ro.disconnect();
|
|
2446
|
-
}, [ratio,
|
|
2447
|
-
return { scale, width, height
|
|
1912
|
+
}, [ratio, height]);
|
|
1913
|
+
return { scale, width: SLIDE_W, height };
|
|
2448
1914
|
}
|
|
2449
1915
|
function HtmlRenderer({ artifact: artifact2 }) {
|
|
2450
1916
|
const iframeRef = useRef(null);
|
|
@@ -2457,6 +1923,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2457
1923
|
const sendIframeCommand = useCanvasStore((s) => s.sendIframeCommand);
|
|
2458
1924
|
const selections = useCanvasStore((s) => s.selections);
|
|
2459
1925
|
const iframeCommand = useCanvasStore((s) => s.iframeCommand);
|
|
1926
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
2460
1927
|
const [device, setDevice] = useState("desktop");
|
|
2461
1928
|
const [mode, setMode] = useState("design");
|
|
2462
1929
|
const [a11y, setA11y] = useState(null);
|
|
@@ -2465,11 +1932,11 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2465
1932
|
const isFixedSlide = Boolean(artifact2.meta?.ratio);
|
|
2466
1933
|
const srcDoc = useMemo(() => {
|
|
2467
1934
|
if (mode === "design" && artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
|
|
2468
|
-
const base = withInspector(artifact2.data.html);
|
|
1935
|
+
const base = withInspector(artifact2.data.html, assetBaseUrl ?? void 0);
|
|
2469
1936
|
srcDocRef.current = isFixedSlide ? base : withScrollableBody(base);
|
|
2470
1937
|
lastSelfHtml.current = null;
|
|
2471
1938
|
return srcDocRef.current;
|
|
2472
|
-
}, [artifact2.data.html, mode, isFixedSlide]);
|
|
1939
|
+
}, [artifact2.data.html, mode, isFixedSlide, assetBaseUrl]);
|
|
2473
1940
|
const selected = selections.filter((s) => s.artifactId === artifact2.id);
|
|
2474
1941
|
const single = selected.length === 1 ? selected[0] : null;
|
|
2475
1942
|
const outline = useMemo(() => {
|
|
@@ -2773,48 +2240,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2773
2240
|
sandbox: "allow-scripts allow-popups allow-modals",
|
|
2774
2241
|
style: { width: DEVICES.find((d) => d.id === device).width }
|
|
2775
2242
|
}
|
|
2776
|
-
) }) : /* @__PURE__ */ jsx(
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
}, [html]);
|
|
2785
|
-
return /* @__PURE__ */ jsx(
|
|
2786
|
-
"textarea",
|
|
2787
|
-
{
|
|
2788
|
-
className: "cv-html-code",
|
|
2789
|
-
value: draft,
|
|
2790
|
-
spellCheck: false,
|
|
2791
|
-
onChange: (e) => {
|
|
2792
|
-
dirty.current = true;
|
|
2793
|
-
setDraft(e.target.value);
|
|
2794
|
-
},
|
|
2795
|
-
onBlur: () => {
|
|
2796
|
-
if (!dirty.current) return;
|
|
2797
|
-
dirty.current = false;
|
|
2798
|
-
onCommit(draft);
|
|
2243
|
+
) }) : /* @__PURE__ */ jsx(
|
|
2244
|
+
"textarea",
|
|
2245
|
+
{
|
|
2246
|
+
className: "cv-html-code",
|
|
2247
|
+
defaultValue: artifact2.data.html,
|
|
2248
|
+
spellCheck: false,
|
|
2249
|
+
onBlur: (e) => commitCode(e.target.value),
|
|
2250
|
+
"aria-label": "HTML source"
|
|
2799
2251
|
},
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
);
|
|
2252
|
+
artifact2.data.html
|
|
2253
|
+
)
|
|
2254
|
+
] });
|
|
2803
2255
|
}
|
|
2804
2256
|
|
|
2805
2257
|
// src/components/renderers/index.ts
|
|
2806
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
2807
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
2808
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
2809
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
2810
|
-
var
|
|
2258
|
+
var ChartRenderer = lazy(() => import('./ChartRenderer-JGRJ23OB.js').then((m) => ({ default: m.ChartRenderer })));
|
|
2259
|
+
var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZQDQMX7X.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
2260
|
+
var TableRenderer = lazy(() => import('./TableRenderer-3ESF577F.js').then((m) => ({ default: m.TableRenderer })));
|
|
2261
|
+
var SlidesRenderer = lazy(() => import('./SlidesRenderer-56R3TNWN.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2262
|
+
var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
|
|
2811
2263
|
var builtinRenderers = {
|
|
2812
2264
|
html: HtmlRenderer,
|
|
2813
2265
|
document: DocumentRenderer,
|
|
2814
2266
|
chart: ChartRenderer,
|
|
2815
2267
|
table: TableRenderer,
|
|
2816
2268
|
slides: SlidesRenderer,
|
|
2817
|
-
|
|
2269
|
+
file: FileRenderer
|
|
2818
2270
|
};
|
|
2819
2271
|
|
|
2820
2272
|
// src/export/download.ts
|
|
@@ -2875,43 +2327,38 @@ ${renderedHtml}
|
|
|
2875
2327
|
</html>`;
|
|
2876
2328
|
}
|
|
2877
2329
|
function tableToCsv(data) {
|
|
2878
|
-
|
|
2330
|
+
const rows = data.sheet?.length ? projectSheetIntoRows(data.columns, data.rows, data.sheet) : data.rows;
|
|
2879
2331
|
const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
|
|
2880
|
-
const body =
|
|
2332
|
+
const body = rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
|
|
2881
2333
|
return `${header}
|
|
2882
2334
|
${body}`;
|
|
2883
2335
|
}
|
|
2884
|
-
function fortuneToCsv(sheet) {
|
|
2885
|
-
let maxR = -1;
|
|
2886
|
-
let maxC = -1;
|
|
2887
|
-
const grid = /* @__PURE__ */ new Map();
|
|
2888
|
-
for (const cell of sheet.celldata ?? []) {
|
|
2889
|
-
const v = cell?.v;
|
|
2890
|
-
if (v == null) continue;
|
|
2891
|
-
if (typeof v === "object" && v.mc && v.v == null && v.m == null) continue;
|
|
2892
|
-
const value = typeof v === "object" ? v.v ?? v.m ?? "" : v;
|
|
2893
|
-
if (value === "" || value == null) continue;
|
|
2894
|
-
grid.set(`${cell.r},${cell.c}`, String(value));
|
|
2895
|
-
if (cell.r > maxR) maxR = cell.r;
|
|
2896
|
-
if (cell.c > maxC) maxC = cell.c;
|
|
2897
|
-
}
|
|
2898
|
-
const lines = [];
|
|
2899
|
-
for (let r = 0; r <= maxR; r++) {
|
|
2900
|
-
const row = [];
|
|
2901
|
-
for (let c = 0; c <= maxC; c++) row.push(csvCell(grid.get(`${r},${c}`) ?? ""));
|
|
2902
|
-
lines.push(row.join(","));
|
|
2903
|
-
}
|
|
2904
|
-
return lines.join("\n");
|
|
2905
|
-
}
|
|
2906
2336
|
async function tableToXlsx(data) {
|
|
2907
2337
|
const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
|
|
2908
2338
|
const workbook = new Workbook();
|
|
2909
2339
|
if (data.sheet?.length) {
|
|
2910
|
-
|
|
2340
|
+
const { computeFormulas } = await import('./formula-27TCEZI5.js');
|
|
2341
|
+
const merged = mergeRowsIntoSheet(
|
|
2342
|
+
data.columns,
|
|
2343
|
+
data.rows,
|
|
2344
|
+
data.sheet,
|
|
2345
|
+
await computeFormulas(data.columns, data.rows)
|
|
2346
|
+
);
|
|
2347
|
+
fortuneToWorkbook(workbook, merged);
|
|
2911
2348
|
} else {
|
|
2912
2349
|
const sheet = workbook.addWorksheet("Sheet1");
|
|
2913
2350
|
sheet.addRow(data.columns.map((c) => c.label ?? c.key));
|
|
2914
|
-
|
|
2351
|
+
const { computeFormulas } = await import('./formula-27TCEZI5.js');
|
|
2352
|
+
const results = await computeFormulas(data.columns, data.rows);
|
|
2353
|
+
data.rows.forEach((row, dataIdx) => {
|
|
2354
|
+
sheet.addRow(
|
|
2355
|
+
data.columns.map((c, colIdx) => {
|
|
2356
|
+
const v = row[c.key] ?? "";
|
|
2357
|
+
if (typeof v !== "string" || !v.startsWith("=")) return v;
|
|
2358
|
+
return { formula: v.slice(1), result: results.get(`${dataIdx + 1},${colIdx}`) };
|
|
2359
|
+
})
|
|
2360
|
+
);
|
|
2361
|
+
});
|
|
2915
2362
|
sheet.getRow(1).font = { bold: true };
|
|
2916
2363
|
}
|
|
2917
2364
|
return workbook.xlsx.writeBuffer();
|
|
@@ -2924,7 +2371,8 @@ function fortuneToWorkbook(workbook, sheets) {
|
|
|
2924
2371
|
const v = cell.v;
|
|
2925
2372
|
const value = v && typeof v === "object" ? v.v ?? v.m ?? null : v;
|
|
2926
2373
|
const xc = ws.getCell(cell.r + 1, cell.c + 1);
|
|
2927
|
-
|
|
2374
|
+
const formula = v && typeof v === "object" && typeof v.f === "string" ? v.f : null;
|
|
2375
|
+
xc.value = formula ? { formula: formula.replace(/^=/, ""), result: value ?? void 0 } : value;
|
|
2928
2376
|
if (v && typeof v === "object") {
|
|
2929
2377
|
if (v.bl) xc.font = { ...xc.font, bold: true };
|
|
2930
2378
|
if (v.it) xc.font = { ...xc.font, italic: true };
|
|
@@ -3090,62 +2538,62 @@ function printToPdf(html) {
|
|
|
3090
2538
|
document.body.appendChild(iframe);
|
|
3091
2539
|
}
|
|
3092
2540
|
var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
|
|
3093
|
-
var escapeHtml2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3094
|
-
var escapeAttr2 = (s) => escapeHtml2(s).replace(/"/g, """);
|
|
3095
2541
|
function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
3096
2542
|
const [open, setOpen] = useState(false);
|
|
3097
2543
|
const stem = slugify(artifact2.title);
|
|
3098
2544
|
const dataOptions = dataExporters[artifact2.type] ?? [];
|
|
3099
|
-
const
|
|
2545
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
2546
|
+
const prepare = () => inlineArtifactAssets(artifact2, assetBaseUrl);
|
|
2547
|
+
const prepareHtml = (html) => assetBaseUrl ? inlineHtmlAssets(html, assetBaseUrl) : Promise.resolve(html);
|
|
2548
|
+
const exportHtml = async () => {
|
|
3100
2549
|
if (artifact2.type === "html") {
|
|
3101
2550
|
const ratio = artifact2.meta?.ratio;
|
|
3102
|
-
const html =
|
|
2551
|
+
const html = (await prepare()).data.html;
|
|
3103
2552
|
downloadBlob(`${stem}.html`, "text/html", ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
3104
2553
|
} else {
|
|
3105
2554
|
const html = getRenderedHtml();
|
|
3106
2555
|
if (html == null) return;
|
|
3107
|
-
downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, html));
|
|
2556
|
+
downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, await prepareHtml(html)));
|
|
3108
2557
|
}
|
|
3109
2558
|
setOpen(false);
|
|
3110
2559
|
};
|
|
3111
2560
|
const exportData = async (option) => {
|
|
3112
|
-
const content = await option.build(
|
|
2561
|
+
const content = await option.build(await prepare());
|
|
3113
2562
|
downloadBlob(`${stem}.${option.extension}`, option.mime, content);
|
|
3114
2563
|
setOpen(false);
|
|
3115
2564
|
};
|
|
3116
|
-
const exportPdf = () => {
|
|
2565
|
+
const exportPdf = async () => {
|
|
3117
2566
|
if (artifact2.type === "slides") {
|
|
3118
|
-
printToPdf(slidesToPrintHtml(
|
|
2567
|
+
printToPdf(slidesToPrintHtml((await prepare()).data, artifact2.title));
|
|
3119
2568
|
} else if (artifact2.type === "html") {
|
|
3120
2569
|
const ratio = artifact2.meta?.ratio;
|
|
3121
|
-
const html =
|
|
2570
|
+
const html = (await prepare()).data.html;
|
|
3122
2571
|
printToPdf(ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
3123
2572
|
} else {
|
|
3124
2573
|
const html = getRenderedHtml();
|
|
3125
2574
|
if (html == null) return;
|
|
3126
|
-
printToPdf(toStandaloneHtml(artifact2.title, html));
|
|
2575
|
+
printToPdf(toStandaloneHtml(artifact2.title, await prepareHtml(html)));
|
|
3127
2576
|
}
|
|
3128
2577
|
setOpen(false);
|
|
3129
2578
|
};
|
|
3130
|
-
const openInTab = () => {
|
|
3131
|
-
const html = artifact2.type === "html" ?
|
|
2579
|
+
const openInTab = async () => {
|
|
2580
|
+
const html = artifact2.type === "html" ? (await prepare()).data.html : artifact2.type === "slides" ? slidesToPrintHtml((await prepare()).data, artifact2.title) : await (async () => {
|
|
3132
2581
|
const h = getRenderedHtml();
|
|
3133
|
-
return h == null ? null : toStandaloneHtml(artifact2.title, h);
|
|
2582
|
+
return h == null ? null : toStandaloneHtml(artifact2.title, await prepareHtml(h));
|
|
3134
2583
|
})();
|
|
3135
2584
|
if (html == null) return;
|
|
3136
|
-
const
|
|
3137
|
-
const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
|
|
2585
|
+
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
|
3138
2586
|
window.open(url, "_blank", "noopener");
|
|
3139
2587
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
3140
2588
|
setOpen(false);
|
|
3141
2589
|
};
|
|
3142
2590
|
const [copied, setCopied] = useState(false);
|
|
3143
2591
|
const copyHtml = async () => {
|
|
3144
|
-
const html = artifact2.type === "html" ?
|
|
2592
|
+
const html = artifact2.type === "html" ? (await prepare()).data.html : getRenderedHtml();
|
|
3145
2593
|
if (html == null) return;
|
|
3146
2594
|
try {
|
|
3147
2595
|
await navigator.clipboard.writeText(
|
|
3148
|
-
artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, html)
|
|
2596
|
+
artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, await prepareHtml(html))
|
|
3149
2597
|
);
|
|
3150
2598
|
setCopied(true);
|
|
3151
2599
|
setTimeout(() => setCopied(false), 1400);
|
|
@@ -3482,8 +2930,10 @@ function toHex2(value) {
|
|
|
3482
2930
|
if (!parts || parts.length < 3) return "#000000";
|
|
3483
2931
|
return "#" + parts.slice(0, 3).map((n) => Number(n).toString(16).padStart(2, "0")).join("");
|
|
3484
2932
|
}
|
|
3485
|
-
function useCanvasImport() {
|
|
2933
|
+
function useCanvasImport({ onImported } = {}) {
|
|
3486
2934
|
const api = useCanvasStoreApi();
|
|
2935
|
+
const imported = useRef(onImported);
|
|
2936
|
+
imported.current = onImported;
|
|
3487
2937
|
const importFiles = useCallback(
|
|
3488
2938
|
async (files) => {
|
|
3489
2939
|
let lastId = null;
|
|
@@ -3496,6 +2946,7 @@ function useCanvasImport() {
|
|
|
3496
2946
|
if (created && created.type === "canvas.create") {
|
|
3497
2947
|
lastId = created.artifact.id;
|
|
3498
2948
|
api.getState().setActiveArtifact(lastId);
|
|
2949
|
+
imported.current?.(created.artifact, file);
|
|
3499
2950
|
}
|
|
3500
2951
|
} catch (err) {
|
|
3501
2952
|
console.error("[langchain-canvas] import failed:", file.name, err);
|
|
@@ -3508,22 +2959,66 @@ function useCanvasImport() {
|
|
|
3508
2959
|
return { importFiles, canImport };
|
|
3509
2960
|
}
|
|
3510
2961
|
var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
|
|
3511
|
-
function Canvas({
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
2962
|
+
function Canvas({
|
|
2963
|
+
registry = builtinRenderers,
|
|
2964
|
+
emptyState,
|
|
2965
|
+
onEditElement,
|
|
2966
|
+
onUserEdit,
|
|
2967
|
+
onSave,
|
|
2968
|
+
onFilesOpened,
|
|
2969
|
+
onImported,
|
|
2970
|
+
assetBaseUrl
|
|
2971
|
+
}) {
|
|
2972
|
+
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(
|
|
2973
|
+
CanvasPanel,
|
|
2974
|
+
{
|
|
2975
|
+
emptyState,
|
|
2976
|
+
onEditElement,
|
|
2977
|
+
onUserEdit,
|
|
2978
|
+
onSave,
|
|
2979
|
+
onFilesOpened,
|
|
2980
|
+
onImported,
|
|
2981
|
+
assetBaseUrl
|
|
2982
|
+
}
|
|
2983
|
+
) });
|
|
2984
|
+
}
|
|
2985
|
+
function CanvasPanel({
|
|
2986
|
+
emptyState,
|
|
2987
|
+
onEditElement,
|
|
2988
|
+
onUserEdit,
|
|
2989
|
+
onSave,
|
|
2990
|
+
onFilesOpened,
|
|
2991
|
+
onImported,
|
|
2992
|
+
assetBaseUrl
|
|
2993
|
+
}) {
|
|
2994
|
+
const debouncedSave = useCanvasSave(onSave);
|
|
3515
2995
|
const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
|
|
3516
2996
|
const history = useCanvasStore((s) => s.canvas.history);
|
|
3517
2997
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
3518
2998
|
const selections = useCanvasStore((s) => s.selections);
|
|
3519
2999
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
3520
3000
|
const setOnUserEdit = useCanvasStore((s) => s.setOnUserEdit);
|
|
3521
|
-
const
|
|
3001
|
+
const setAssetBaseUrl = useCanvasStore((s) => s.setAssetBaseUrl);
|
|
3002
|
+
const { importFiles } = useCanvasImport({ onImported });
|
|
3522
3003
|
const [dropping, setDropping] = useState(false);
|
|
3523
3004
|
useEffect(() => {
|
|
3524
|
-
|
|
3005
|
+
setAssetBaseUrl(assetBaseUrl ?? null);
|
|
3006
|
+
}, [assetBaseUrl, setAssetBaseUrl]);
|
|
3007
|
+
const openFiles = (files) => {
|
|
3008
|
+
onFilesOpened?.(Array.from(files));
|
|
3009
|
+
void importFiles(files);
|
|
3010
|
+
};
|
|
3011
|
+
useEffect(() => {
|
|
3012
|
+
if (!onUserEdit && !debouncedSave) {
|
|
3013
|
+
setOnUserEdit(null);
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
setOnUserEdit((artifact2) => {
|
|
3017
|
+
onUserEdit?.(artifact2);
|
|
3018
|
+
debouncedSave?.(artifact2);
|
|
3019
|
+
});
|
|
3525
3020
|
return () => setOnUserEdit(null);
|
|
3526
|
-
}, [onUserEdit, setOnUserEdit]);
|
|
3021
|
+
}, [onUserEdit, debouncedSave, setOnUserEdit]);
|
|
3527
3022
|
useEffect(() => {
|
|
3528
3023
|
if (!selections.length) return;
|
|
3529
3024
|
const onKey = (e) => {
|
|
@@ -3546,17 +3041,17 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
3546
3041
|
onDrop: (e) => {
|
|
3547
3042
|
e.preventDefault();
|
|
3548
3043
|
setDropping(false);
|
|
3549
|
-
if (e.dataTransfer.files.length)
|
|
3044
|
+
if (e.dataTransfer.files.length) openFiles(e.dataTransfer.files);
|
|
3550
3045
|
}
|
|
3551
3046
|
};
|
|
3552
3047
|
const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "Drop to open on the canvas" }) : null;
|
|
3553
3048
|
if (!active) {
|
|
3554
3049
|
return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
|
|
3555
|
-
emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles:
|
|
3050
|
+
emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: openFiles, acceptAll: Boolean(onFilesOpened) }),
|
|
3556
3051
|
dropOverlay
|
|
3557
3052
|
] });
|
|
3558
3053
|
}
|
|
3559
|
-
const
|
|
3054
|
+
const versions2 = history[active.id] ?? [active];
|
|
3560
3055
|
const showSelection = Boolean(onEditElement) && selections.length > 0 && selections[0].artifactId === active.id;
|
|
3561
3056
|
return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas", ...dropProps, children: [
|
|
3562
3057
|
dropOverlay,
|
|
@@ -3571,18 +3066,18 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
3571
3066
|
},
|
|
3572
3067
|
id
|
|
3573
3068
|
)) }),
|
|
3574
|
-
/* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions }, active.id),
|
|
3069
|
+
/* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions: versions2 }, active.id),
|
|
3575
3070
|
showSelection && onEditElement && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3576
3071
|
active.type === "html" && selections.length === 1 && /* @__PURE__ */ jsx(StylePanel, { selection: selections[0] }, selections[0].cid),
|
|
3577
3072
|
/* @__PURE__ */ jsx(SelectionBar, { selections, onEdit: onEditElement, onClear: () => setSelections([]) })
|
|
3578
3073
|
] })
|
|
3579
3074
|
] });
|
|
3580
3075
|
}
|
|
3581
|
-
function ArtifactView({ artifact: artifact2, versions }) {
|
|
3076
|
+
function ArtifactView({ artifact: artifact2, versions: versions2 }) {
|
|
3582
3077
|
const [viewIndex, setViewIndex] = useState(null);
|
|
3583
3078
|
const bodyRef = useRef(null);
|
|
3584
|
-
const shown = viewIndex === null ? artifact2 :
|
|
3585
|
-
const
|
|
3079
|
+
const shown = viewIndex === null ? artifact2 : versions2[viewIndex];
|
|
3080
|
+
const viewingHistory = viewIndex !== null && viewIndex !== versions2.length - 1;
|
|
3586
3081
|
const Renderer = useRenderer(shown.type);
|
|
3587
3082
|
const getRenderedHtml = () => {
|
|
3588
3083
|
const node = bodyRef.current;
|
|
@@ -3600,35 +3095,36 @@ function ArtifactView({ artifact: artifact2, versions }) {
|
|
|
3600
3095
|
] }),
|
|
3601
3096
|
/* @__PURE__ */ jsxs("div", { className: "cv-header__actions", children: [
|
|
3602
3097
|
/* @__PURE__ */ jsx(UndoRedo, {}),
|
|
3603
|
-
|
|
3604
|
-
|
|
3098
|
+
versions2.length > 1 && /* @__PURE__ */ jsx(
|
|
3099
|
+
VersionHistory,
|
|
3605
3100
|
{
|
|
3606
|
-
|
|
3607
|
-
index: viewIndex ??
|
|
3608
|
-
onSelect: (i) => setViewIndex(i ===
|
|
3101
|
+
versions: versions2,
|
|
3102
|
+
index: viewIndex ?? versions2.length - 1,
|
|
3103
|
+
onSelect: (i) => setViewIndex(i === versions2.length - 1 ? null : i)
|
|
3609
3104
|
}
|
|
3610
3105
|
),
|
|
3611
3106
|
/* @__PURE__ */ jsx(ExportMenu, { artifact: shown, getRenderedHtml })
|
|
3612
3107
|
] })
|
|
3613
3108
|
] }),
|
|
3614
|
-
/* @__PURE__ */ jsxs(
|
|
3109
|
+
viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
|
|
3110
|
+
"Viewing v",
|
|
3111
|
+
(viewIndex ?? 0) + 1,
|
|
3112
|
+
" of ",
|
|
3113
|
+
versions2.length,
|
|
3114
|
+
" \u2014 read-only.",
|
|
3115
|
+
" ",
|
|
3116
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: "Back to latest" })
|
|
3117
|
+
] }),
|
|
3118
|
+
/* @__PURE__ */ jsx(
|
|
3615
3119
|
"div",
|
|
3616
3120
|
{
|
|
3617
|
-
className: `cv-body${shown.type === "table"
|
|
3121
|
+
className: `cv-body${shown.type === "table" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
|
|
3618
3122
|
ref: bodyRef,
|
|
3619
|
-
"
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
" (read-only) \u2014 select the latest version to edit"
|
|
3625
|
-
] }),
|
|
3626
|
-
Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
|
|
3627
|
-
"No renderer registered for type \u201C",
|
|
3628
|
-
shown.type,
|
|
3629
|
-
"\u201D."
|
|
3630
|
-
] })
|
|
3631
|
-
]
|
|
3123
|
+
children: Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
|
|
3124
|
+
"No renderer registered for type \u201C",
|
|
3125
|
+
shown.type,
|
|
3126
|
+
"\u201D."
|
|
3127
|
+
] })
|
|
3632
3128
|
}
|
|
3633
3129
|
)
|
|
3634
3130
|
] });
|
|
@@ -3657,32 +3153,70 @@ function UndoRedo() {
|
|
|
3657
3153
|
/* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "Redo (\u2318\u21E7Z)", "aria-label": "Redo", children: "\u21B7" })
|
|
3658
3154
|
] });
|
|
3659
3155
|
}
|
|
3660
|
-
function
|
|
3156
|
+
function VersionHistory({
|
|
3157
|
+
versions: versions2,
|
|
3158
|
+
index,
|
|
3159
|
+
onSelect
|
|
3160
|
+
}) {
|
|
3161
|
+
const [open, setOpen] = useState(false);
|
|
3162
|
+
const total = versions2.length;
|
|
3163
|
+
const pick = (i) => {
|
|
3164
|
+
setOpen(false);
|
|
3165
|
+
onSelect(i);
|
|
3166
|
+
};
|
|
3661
3167
|
return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": "Version history", children: [
|
|
3662
|
-
/* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () =>
|
|
3663
|
-
/* @__PURE__ */ jsxs(
|
|
3664
|
-
"
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3168
|
+
/* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": "Previous version", children: "\u2039" }),
|
|
3169
|
+
/* @__PURE__ */ jsxs(
|
|
3170
|
+
"button",
|
|
3171
|
+
{
|
|
3172
|
+
className: "cv-versions__label",
|
|
3173
|
+
"aria-expanded": open,
|
|
3174
|
+
"aria-label": "Open version history",
|
|
3175
|
+
onClick: () => setOpen((v) => !v),
|
|
3176
|
+
children: [
|
|
3177
|
+
"v",
|
|
3178
|
+
index + 1,
|
|
3179
|
+
" / ",
|
|
3180
|
+
total
|
|
3181
|
+
]
|
|
3182
|
+
}
|
|
3183
|
+
),
|
|
3669
3184
|
/* @__PURE__ */ jsx(
|
|
3670
3185
|
"button",
|
|
3671
3186
|
{
|
|
3672
3187
|
className: "cv-versions__nav",
|
|
3673
3188
|
disabled: index === total - 1,
|
|
3674
|
-
onClick: () =>
|
|
3189
|
+
onClick: () => pick(index + 1),
|
|
3675
3190
|
"aria-label": "Next version",
|
|
3676
3191
|
children: "\u203A"
|
|
3677
3192
|
}
|
|
3678
|
-
)
|
|
3193
|
+
),
|
|
3194
|
+
open && /* @__PURE__ */ jsx("ul", { className: "cv-versions__list", role: "listbox", "aria-label": "Versions", children: versions2.map((snapshot, i) => ({ snapshot, i })).reverse().map(({ snapshot, i }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
3195
|
+
"button",
|
|
3196
|
+
{
|
|
3197
|
+
role: "option",
|
|
3198
|
+
"aria-selected": i === index,
|
|
3199
|
+
className: i === index ? "is-current" : void 0,
|
|
3200
|
+
onClick: () => pick(i),
|
|
3201
|
+
children: [
|
|
3202
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-versions__v", children: [
|
|
3203
|
+
"v",
|
|
3204
|
+
i + 1
|
|
3205
|
+
] }),
|
|
3206
|
+
/* @__PURE__ */ jsx("span", { className: "cv-versions__desc", children: typeof snapshot.meta?.commitDescription === "string" ? snapshot.meta.commitDescription : "Snapshot" })
|
|
3207
|
+
]
|
|
3208
|
+
}
|
|
3209
|
+
) }, i)) })
|
|
3679
3210
|
] });
|
|
3680
3211
|
}
|
|
3681
3212
|
function StatusBadge({ status }) {
|
|
3682
3213
|
const label = status === "streaming" ? "Writing\u2026" : status === "error" ? "Error" : "Ready";
|
|
3683
3214
|
return /* @__PURE__ */ jsx("span", { className: `cv-badge cv-badge--${status}`, children: label });
|
|
3684
3215
|
}
|
|
3685
|
-
function EmptyState({
|
|
3216
|
+
function EmptyState({
|
|
3217
|
+
onOpenFiles,
|
|
3218
|
+
acceptAll = false
|
|
3219
|
+
}) {
|
|
3686
3220
|
const inputRef = useRef(null);
|
|
3687
3221
|
return /* @__PURE__ */ jsxs("div", { className: "cv-empty", children: [
|
|
3688
3222
|
/* @__PURE__ */ jsx("p", { className: "cv-empty__title", children: "Nothing on the canvas yet" }),
|
|
@@ -3694,7 +3228,7 @@ function EmptyState({ onOpenFiles }) {
|
|
|
3694
3228
|
{
|
|
3695
3229
|
ref: inputRef,
|
|
3696
3230
|
type: "file",
|
|
3697
|
-
accept: ACCEPT,
|
|
3231
|
+
accept: acceptAll ? void 0 : ACCEPT,
|
|
3698
3232
|
multiple: true,
|
|
3699
3233
|
hidden: true,
|
|
3700
3234
|
onChange: (e) => {
|
|
@@ -3703,7 +3237,7 @@ function EmptyState({ onOpenFiles }) {
|
|
|
3703
3237
|
}
|
|
3704
3238
|
}
|
|
3705
3239
|
),
|
|
3706
|
-
/* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
|
|
3240
|
+
/* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: acceptAll ? "Any file \u2014 tables and pages open here, the rest goes to the agent" : "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
|
|
3707
3241
|
] })
|
|
3708
3242
|
] });
|
|
3709
3243
|
}
|
|
@@ -3713,7 +3247,7 @@ var TYPE_META = {
|
|
|
3713
3247
|
chart: { icon: "\u{1F4CA}", label: "Chart" },
|
|
3714
3248
|
table: { icon: "\u{1F522}", label: "Excel sheet" },
|
|
3715
3249
|
slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
|
|
3716
|
-
|
|
3250
|
+
file: { icon: "\u{1F4CE}", label: "File" }
|
|
3717
3251
|
};
|
|
3718
3252
|
var KIND_META = {
|
|
3719
3253
|
web: TYPE_META.html,
|
|
@@ -3724,8 +3258,7 @@ var KIND_META = {
|
|
|
3724
3258
|
table: TYPE_META.table,
|
|
3725
3259
|
sheet: TYPE_META.table,
|
|
3726
3260
|
slide: TYPE_META.slides,
|
|
3727
|
-
slides: TYPE_META.slides
|
|
3728
|
-
pdf: TYPE_META.pdf
|
|
3261
|
+
slides: TYPE_META.slides
|
|
3729
3262
|
};
|
|
3730
3263
|
function resolveCardMeta(artifact2) {
|
|
3731
3264
|
const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
|
|
@@ -3750,4 +3283,4 @@ function ArtifactCard({ artifactId }) {
|
|
|
3750
3283
|
] });
|
|
3751
3284
|
}
|
|
3752
3285
|
|
|
3753
|
-
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK,
|
|
3286
|
+
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, FileRenderer, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, mockTransport, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, sseTransport, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasSave, useCanvasStream, useRenderer, withInspector };
|