@braincrew-lab/langchain-canvas 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ChartRenderer-FX6ZN4NC.js → ChartRenderer-AAWVGKV5.js} +20 -10
- package/dist/{DocumentRenderer-KJ6FTGKH.js → DocumentRenderer-NBRBPYU6.js} +2 -2
- package/dist/PdfRenderer-DPQT4E7O.js +97 -0
- package/dist/SlidesRenderer-6ZGHXTQX.js +877 -0
- package/dist/{TableRenderer-Y4JFRSLU.js → TableRenderer-VKDD3CB6.js} +120 -2
- package/dist/{chunk-KKLWKR5G.js → chunk-S54GJDSJ.js} +36 -20
- package/dist/{chunk-TERWLUW3.js → chunk-UL5F66PN.js} +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +810 -72
- package/dist/styles.css +141 -0
- package/package.json +1 -1
- package/dist/SlidesRenderer-SK5VQDIL.js +0 -492
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
3
3
|
import { resolveElements } from './chunk-6L3AL6W4.js';
|
|
4
|
-
export { useArtifactPatch } from './chunk-
|
|
5
|
-
import { useCanvasStoreApi, useCanvasStore } from './chunk-
|
|
6
|
-
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-
|
|
4
|
+
export { useArtifactPatch } from './chunk-UL5F66PN.js';
|
|
5
|
+
import { useCanvasStoreApi, useCanvasStore } from './chunk-S54GJDSJ.js';
|
|
6
|
+
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-S54GJDSJ.js';
|
|
7
7
|
import { createContext, lazy, useRef, useCallback, useEffect, useContext, useState, useMemo, Suspense, Component } from 'react';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
@@ -157,16 +157,47 @@ var INSPECTOR_SCRIPT = `
|
|
|
157
157
|
for (var j = 0; j < marked.length; j++) scrub(marked[j]);
|
|
158
158
|
parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
|
|
159
159
|
}
|
|
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
|
+
}
|
|
160
171
|
function newBlock(tag) {
|
|
161
172
|
var el = document.createElement(tag);
|
|
162
173
|
if (tag === "img") {
|
|
163
174
|
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";
|
|
164
175
|
el.alt = "image"; el.style.maxWidth = "100%";
|
|
165
176
|
}
|
|
166
|
-
else if (tag === "button")
|
|
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
|
+
}
|
|
167
189
|
else if (tag === "hr") { /* no content */ }
|
|
168
190
|
else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
|
|
169
|
-
else
|
|
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
|
+
}
|
|
170
201
|
return el;
|
|
171
202
|
}
|
|
172
203
|
// Floating rich-text toolbar shown while a text element is being edited.
|
|
@@ -263,16 +294,31 @@ var INSPECTOR_SCRIPT = `
|
|
|
263
294
|
// into absolute positioning inside its own parent, and its final spot + size are
|
|
264
295
|
// stored as percentages of that parent \u2014 so it stays put proportionally across
|
|
265
296
|
// responsive breakpoints, instead of a fixed pixel offset that drifts off.
|
|
266
|
-
var dragEls = null, dragStart = null, dragBases = null,
|
|
297
|
+
var dragEls = null, dragStart = null, dragBases = null, parentMutated = false;
|
|
267
298
|
|
|
268
299
|
function ensurePositioned(parent) {
|
|
269
|
-
if (!parent || parent === document.
|
|
270
|
-
|
|
300
|
+
if (!parent || parent === document.documentElement) return;
|
|
301
|
+
// body included: a static body makes absolute children resolve against the
|
|
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);
|
|
271
316
|
}
|
|
272
317
|
// Pull each element out into absolute positioning at its current spot (no visual
|
|
273
318
|
// jump), so it can then be moved freely.
|
|
274
319
|
function beginFreeDrag(els) {
|
|
275
320
|
dragBases = [];
|
|
321
|
+
parentMutated = false;
|
|
276
322
|
for (var i = 0; i < els.length; i++) {
|
|
277
323
|
var el = els[i], parent = el.parentElement || document.body;
|
|
278
324
|
ensurePositioned(parent);
|
|
@@ -295,13 +341,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
295
341
|
}
|
|
296
342
|
// Commit the current position (in px, as set live by moveFree) as % of the
|
|
297
343
|
// parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
|
|
298
|
-
// only if the parent has collapsed to zero on that axis.
|
|
344
|
+
// only if the parent has collapsed to zero on that axis. The vertical axis is
|
|
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.
|
|
299
348
|
function commitFree() {
|
|
300
349
|
for (var i = 0; i < dragBases.length; i++) {
|
|
301
350
|
var b = dragBases[i], pr = b.parent.getBoundingClientRect();
|
|
302
351
|
var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
|
|
303
352
|
b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
|
|
304
|
-
b.el.style.top =
|
|
353
|
+
b.el.style.top = Math.round(curTop) + "px";
|
|
305
354
|
if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
|
|
306
355
|
}
|
|
307
356
|
}
|
|
@@ -458,8 +507,8 @@ var INSPECTOR_SCRIPT = `
|
|
|
458
507
|
positionResize();
|
|
459
508
|
// The new position is already shown in the iframe with every cid intact,
|
|
460
509
|
// so persist without a reload (no flicker): one element \u2192 node_edit,
|
|
461
|
-
// several \u2192 a self-applied doc_edit.
|
|
462
|
-
|
|
510
|
+
// several (or a repositioned parent) \u2192 a self-applied doc_edit.
|
|
511
|
+
commitDrag(els);
|
|
463
512
|
}
|
|
464
513
|
dragBases = null;
|
|
465
514
|
return;
|
|
@@ -510,7 +559,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
510
559
|
// Pointer left the frame mid-drag: commit the move at its last position so
|
|
511
560
|
// it isn't lost (the element is already placed absolutely in the iframe).
|
|
512
561
|
var els = dragEls; dragEls = null;
|
|
513
|
-
if (moved && dragBases) { commitFree();
|
|
562
|
+
if (moved && dragBases) { commitFree(); commitDrag(els); }
|
|
514
563
|
dragBases = null;
|
|
515
564
|
}
|
|
516
565
|
if (dragging) {
|
|
@@ -584,6 +633,12 @@ var INSPECTOR_SCRIPT = `
|
|
|
584
633
|
if (d.type === "set_src") { var ei = byCid(d.cid); if (ei) { ei.setAttribute("src", d.value); emitEdit(ei); } return; }
|
|
585
634
|
if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
|
|
586
635
|
|
|
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
|
+
}
|
|
587
642
|
// Structural edits \u2014 mutate the tree, then persist the whole document.
|
|
588
643
|
if (d.type === "insert") {
|
|
589
644
|
var block = newBlock(d.block || "p");
|
|
@@ -591,7 +646,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
591
646
|
if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
|
|
592
647
|
anchor.parentNode.insertBefore(block, anchor.nextSibling);
|
|
593
648
|
} else {
|
|
594
|
-
|
|
649
|
+
insertRoot().appendChild(block);
|
|
595
650
|
}
|
|
596
651
|
emitDoc();
|
|
597
652
|
return;
|
|
@@ -599,7 +654,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
599
654
|
if (d.type === "insert_html") {
|
|
600
655
|
// A built-in section template (trusted markup from the toolbar).
|
|
601
656
|
var anc = d.cid ? byCid(d.cid) : null;
|
|
602
|
-
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode :
|
|
657
|
+
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : insertRoot();
|
|
603
658
|
var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
|
|
604
659
|
var frag = document.createElement("div");
|
|
605
660
|
frag.innerHTML = d.html || "";
|
|
@@ -614,7 +669,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
614
669
|
var cids = d.cids || [];
|
|
615
670
|
for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
|
|
616
671
|
if (members.length < 2) return;
|
|
617
|
-
|
|
672
|
+
// Next free id is derived from the document, not a counter \u2014 the counter
|
|
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);
|
|
618
682
|
for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
|
|
619
683
|
clearSelected();
|
|
620
684
|
emitDoc();
|
|
@@ -777,7 +841,7 @@ function useCanvasReplay() {
|
|
|
777
841
|
api.getState().applyEvent(event);
|
|
778
842
|
}
|
|
779
843
|
} finally {
|
|
780
|
-
api.getState().setStreaming(false);
|
|
844
|
+
if (abortRef.current === controller) api.getState().setStreaming(false);
|
|
781
845
|
}
|
|
782
846
|
},
|
|
783
847
|
[api]
|
|
@@ -957,7 +1021,29 @@ var slides = {
|
|
|
957
1021
|
{ type: "done" }
|
|
958
1022
|
]
|
|
959
1023
|
};
|
|
960
|
-
var
|
|
1024
|
+
var PDF_DATA_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggODA+PnN0cmVhbQpCVCAvRjEgMjggVGYgNzIgNzAwIFRkIChsYW5nY2hhaW4tY2FudmFzIFBERiB2aWV3ZXIpIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKNSAwIG9iajw8L1R5cGUvRm9udC9TdWJ0eXBlL1R5cGUxL0Jhc2VGb250L0hlbHZldGljYT4+ZW5kb2JqCnhyZWYKMCA2CnRyYWlsZXI8PC9TaXplIDYvUm9vdCAxIDAgUj4+CiUlRU9GCg==";
|
|
1025
|
+
var pdf = {
|
|
1026
|
+
id: "pdf",
|
|
1027
|
+
title: "PDF viewer",
|
|
1028
|
+
description: "A PDF artifact shown in the browser's built-in viewer \u2014 zero dependencies.",
|
|
1029
|
+
events: [
|
|
1030
|
+
{ type: "message.delta", messageId: "m-pdf", text: "Here's the signed report as a PDF." },
|
|
1031
|
+
{
|
|
1032
|
+
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
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
{ type: "canvas.status", id: "report-pdf", status: "complete" },
|
|
1043
|
+
{ type: "done" }
|
|
1044
|
+
]
|
|
1045
|
+
};
|
|
1046
|
+
var scenarios = [htmlPage, document2, chart, table, slides, pdf];
|
|
961
1047
|
var RegistryContext = createContext({});
|
|
962
1048
|
function CanvasRegistryProvider({ registry, children }) {
|
|
963
1049
|
return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
|
|
@@ -1103,40 +1189,202 @@ function display(value) {
|
|
|
1103
1189
|
if (value == null) return "";
|
|
1104
1190
|
if (typeof value === "object") {
|
|
1105
1191
|
if (value.result != null) return display(value.result);
|
|
1192
|
+
if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
|
|
1106
1193
|
if (typeof value.text === "string") return value.text;
|
|
1107
1194
|
if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
|
|
1108
1195
|
return "";
|
|
1109
1196
|
}
|
|
1110
1197
|
return String(value);
|
|
1111
1198
|
}
|
|
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
|
+
}
|
|
1112
1279
|
function formatNumber(value, numFmt) {
|
|
1113
|
-
const
|
|
1114
|
-
if (!
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1280
|
+
const raw = numFmt && numFmt !== "General" ? numFmt : "";
|
|
1281
|
+
if (!raw) return String(value);
|
|
1282
|
+
const sections = splitSections(raw);
|
|
1283
|
+
const negSection = value < 0 && sections.length > 1 ? sections[1] : void 0;
|
|
1284
|
+
const section = negSection ?? (value === 0 && sections[2] ? sections[2] : sections[0]);
|
|
1285
|
+
const { skeleton, currency, currencyTrails } = numberSkeleton(section);
|
|
1286
|
+
if (!/[0#]/.test(skeleton)) return String(value);
|
|
1287
|
+
const decimals = (skeleton.match(/\.([0#]+)/)?.[1] ?? "").length;
|
|
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})`;
|
|
1121
1300
|
return out;
|
|
1122
1301
|
}
|
|
1302
|
+
var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
1123
1303
|
function formatDate(d, numFmt) {
|
|
1124
|
-
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
|
|
1304
|
+
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? splitSections(numFmt)[0] : "yyyy-mm-dd";
|
|
1125
1305
|
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;
|
|
1126
1311
|
const map = {
|
|
1127
|
-
yyyy: String(d.
|
|
1128
|
-
yy: p(d.
|
|
1129
|
-
mmmm: d.toLocaleString("en-US", { month: "long" }),
|
|
1130
|
-
mmm: d.toLocaleString("en-US", { month: "short" }),
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
ss: p(d.getSeconds())
|
|
1312
|
+
yyyy: () => String(d.getUTCFullYear()),
|
|
1313
|
+
yy: () => p(d.getUTCFullYear() % 100),
|
|
1314
|
+
mmmm: () => d.toLocaleString("en-US", { month: "long", timeZone: "UTC" }),
|
|
1315
|
+
mmm: () => d.toLocaleString("en-US", { month: "short", timeZone: "UTC" }),
|
|
1316
|
+
dd: () => p(d.getUTCDate()),
|
|
1317
|
+
d: () => String(d.getUTCDate()),
|
|
1318
|
+
hh: () => hourText(2),
|
|
1319
|
+
h: () => hourText(1),
|
|
1320
|
+
ss: () => p(d.getUTCSeconds()),
|
|
1321
|
+
s: () => String(d.getUTCSeconds())
|
|
1138
1322
|
};
|
|
1139
|
-
|
|
1323
|
+
const month = (t) => t === "mm" ? p(d.getUTCMonth() + 1) : String(d.getUTCMonth() + 1);
|
|
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;
|
|
1140
1388
|
}
|
|
1141
1389
|
function cellValue(cell) {
|
|
1142
1390
|
const raw = cell.value;
|
|
@@ -1335,6 +1583,7 @@ function cellVal(v) {
|
|
|
1335
1583
|
if (typeof v === "number" || typeof v === "string") return v;
|
|
1336
1584
|
if (typeof v === "object") {
|
|
1337
1585
|
if (v.result != null) return cellVal(v.result);
|
|
1586
|
+
if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
|
|
1338
1587
|
if (typeof v.text === "string") return v.text;
|
|
1339
1588
|
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
1340
1589
|
}
|
|
@@ -1377,8 +1626,416 @@ function flatten(ws) {
|
|
|
1377
1626
|
return { columns, rows };
|
|
1378
1627
|
}
|
|
1379
1628
|
|
|
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
|
+
|
|
1380
2025
|
// src/io/importers.ts
|
|
1381
|
-
var IMPORTABLE_EXTENSIONS = [
|
|
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
|
+
];
|
|
1382
2039
|
var extensionOf = (name) => {
|
|
1383
2040
|
const dot = name.lastIndexOf(".");
|
|
1384
2041
|
return dot === -1 ? "" : name.slice(dot).toLowerCase();
|
|
@@ -1419,6 +2076,18 @@ async function importFile(file) {
|
|
|
1419
2076
|
const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
|
|
1420
2077
|
return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
|
|
1421
2078
|
}
|
|
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
|
+
}
|
|
1422
2091
|
default:
|
|
1423
2092
|
throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
|
|
1424
2093
|
}
|
|
@@ -1426,6 +2095,14 @@ async function importFile(file) {
|
|
|
1426
2095
|
function artifact(id, type, title, data) {
|
|
1427
2096
|
return { id, type, title, version: 1, status: "complete", data };
|
|
1428
2097
|
}
|
|
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
|
+
}
|
|
1429
2106
|
function importJson(text, id, title) {
|
|
1430
2107
|
let parsed;
|
|
1431
2108
|
try {
|
|
@@ -1743,8 +2420,8 @@ function checkA11y(html) {
|
|
|
1743
2420
|
if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
|
|
1744
2421
|
return issues;
|
|
1745
2422
|
}
|
|
1746
|
-
var
|
|
1747
|
-
var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
2423
|
+
var SLIDE_H = 720;
|
|
2424
|
+
var SCROLL_FIX = "<style data-lcx>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
1748
2425
|
function withScrollableBody(html) {
|
|
1749
2426
|
const i = html.lastIndexOf("</body>");
|
|
1750
2427
|
return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
|
|
@@ -1752,7 +2429,7 @@ function withScrollableBody(html) {
|
|
|
1752
2429
|
function useSlideFit(ratio, boxRef) {
|
|
1753
2430
|
const [scale, setScale] = useState(1);
|
|
1754
2431
|
const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
|
|
1755
|
-
const
|
|
2432
|
+
const width = rw && rh ? Math.round(SLIDE_H * rw / rh) : 1280;
|
|
1756
2433
|
useEffect(() => {
|
|
1757
2434
|
if (!ratio) return;
|
|
1758
2435
|
const el = boxRef.current;
|
|
@@ -1760,14 +2437,14 @@ function useSlideFit(ratio, boxRef) {
|
|
|
1760
2437
|
const fit = () => {
|
|
1761
2438
|
const w = el.clientWidth;
|
|
1762
2439
|
if (w <= 40) return;
|
|
1763
|
-
setScale(Math.min(1, (w - 40) /
|
|
2440
|
+
setScale(Math.min(1, (w - 40) / width));
|
|
1764
2441
|
};
|
|
1765
2442
|
fit();
|
|
1766
2443
|
const ro = new ResizeObserver(fit);
|
|
1767
2444
|
ro.observe(el);
|
|
1768
2445
|
return () => ro.disconnect();
|
|
1769
|
-
}, [ratio,
|
|
1770
|
-
return { scale, width
|
|
2446
|
+
}, [ratio, width]);
|
|
2447
|
+
return { scale, width, height: SLIDE_H };
|
|
1771
2448
|
}
|
|
1772
2449
|
function HtmlRenderer({ artifact: artifact2 }) {
|
|
1773
2450
|
const iframeRef = useRef(null);
|
|
@@ -2096,31 +2773,48 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2096
2773
|
sandbox: "allow-scripts allow-popups allow-modals",
|
|
2097
2774
|
style: { width: DEVICES.find((d) => d.id === device).width }
|
|
2098
2775
|
}
|
|
2099
|
-
) }) : /* @__PURE__ */ jsx(
|
|
2100
|
-
"textarea",
|
|
2101
|
-
{
|
|
2102
|
-
className: "cv-html-code",
|
|
2103
|
-
defaultValue: artifact2.data.html,
|
|
2104
|
-
spellCheck: false,
|
|
2105
|
-
onBlur: (e) => commitCode(e.target.value),
|
|
2106
|
-
"aria-label": "HTML source"
|
|
2107
|
-
},
|
|
2108
|
-
artifact2.data.html
|
|
2109
|
-
)
|
|
2776
|
+
) }) : /* @__PURE__ */ jsx(CodePane, { html: artifact2.data.html, onCommit: commitCode })
|
|
2110
2777
|
] });
|
|
2111
2778
|
}
|
|
2779
|
+
function CodePane({ html, onCommit }) {
|
|
2780
|
+
const [draft, setDraft] = useState(html);
|
|
2781
|
+
const dirty = useRef(false);
|
|
2782
|
+
useEffect(() => {
|
|
2783
|
+
if (!dirty.current) setDraft(html);
|
|
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);
|
|
2799
|
+
},
|
|
2800
|
+
"aria-label": "HTML source"
|
|
2801
|
+
}
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2112
2804
|
|
|
2113
2805
|
// src/components/renderers/index.ts
|
|
2114
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
2115
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
2116
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
2117
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
2806
|
+
var ChartRenderer = lazy(() => import('./ChartRenderer-AAWVGKV5.js').then((m) => ({ default: m.ChartRenderer })));
|
|
2807
|
+
var DocumentRenderer = lazy(() => import('./DocumentRenderer-NBRBPYU6.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
2808
|
+
var TableRenderer = lazy(() => import('./TableRenderer-VKDD3CB6.js').then((m) => ({ default: m.TableRenderer })));
|
|
2809
|
+
var SlidesRenderer = lazy(() => import('./SlidesRenderer-6ZGHXTQX.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2810
|
+
var PdfRenderer = lazy(() => import('./PdfRenderer-DPQT4E7O.js').then((m) => ({ default: m.PdfRenderer })));
|
|
2118
2811
|
var builtinRenderers = {
|
|
2119
2812
|
html: HtmlRenderer,
|
|
2120
2813
|
document: DocumentRenderer,
|
|
2121
2814
|
chart: ChartRenderer,
|
|
2122
2815
|
table: TableRenderer,
|
|
2123
|
-
slides: SlidesRenderer
|
|
2816
|
+
slides: SlidesRenderer,
|
|
2817
|
+
pdf: PdfRenderer
|
|
2124
2818
|
};
|
|
2125
2819
|
|
|
2126
2820
|
// src/export/download.ts
|
|
@@ -2181,11 +2875,34 @@ ${renderedHtml}
|
|
|
2181
2875
|
</html>`;
|
|
2182
2876
|
}
|
|
2183
2877
|
function tableToCsv(data) {
|
|
2878
|
+
if (data.sheet?.length) return fortuneToCsv(data.sheet[0]);
|
|
2184
2879
|
const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
|
|
2185
2880
|
const body = data.rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
|
|
2186
2881
|
return `${header}
|
|
2187
2882
|
${body}`;
|
|
2188
2883
|
}
|
|
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
|
+
}
|
|
2189
2906
|
async function tableToXlsx(data) {
|
|
2190
2907
|
const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
|
|
2191
2908
|
const workbook = new Workbook();
|
|
@@ -2373,6 +3090,8 @@ function printToPdf(html) {
|
|
|
2373
3090
|
document.body.appendChild(iframe);
|
|
2374
3091
|
}
|
|
2375
3092
|
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, """);
|
|
2376
3095
|
function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
2377
3096
|
const [open, setOpen] = useState(false);
|
|
2378
3097
|
const stem = slugify(artifact2.title);
|
|
@@ -2414,7 +3133,8 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2414
3133
|
return h == null ? null : toStandaloneHtml(artifact2.title, h);
|
|
2415
3134
|
})();
|
|
2416
3135
|
if (html == null) return;
|
|
2417
|
-
const
|
|
3136
|
+
const wrapper = `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml2(artifact2.title)}</title><style>html,body{margin:0;height:100%}iframe{display:block;width:100%;height:100%;border:0}</style></head><body><iframe sandbox="allow-scripts allow-popups allow-modals" srcdoc="${escapeAttr2(html)}"></iframe></body></html>`;
|
|
3137
|
+
const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
|
|
2418
3138
|
window.open(url, "_blank", "noopener");
|
|
2419
3139
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
2420
3140
|
setOpen(false);
|
|
@@ -2861,7 +3581,8 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
2861
3581
|
function ArtifactView({ artifact: artifact2, versions }) {
|
|
2862
3582
|
const [viewIndex, setViewIndex] = useState(null);
|
|
2863
3583
|
const bodyRef = useRef(null);
|
|
2864
|
-
const shown = viewIndex === null ? artifact2 : versions[viewIndex];
|
|
3584
|
+
const shown = viewIndex === null ? artifact2 : versions[Math.min(viewIndex, versions.length - 1)] ?? artifact2;
|
|
3585
|
+
const isHistory = viewIndex !== null && shown !== artifact2;
|
|
2865
3586
|
const Renderer = useRenderer(shown.type);
|
|
2866
3587
|
const getRenderedHtml = () => {
|
|
2867
3588
|
const node = bodyRef.current;
|
|
@@ -2890,11 +3611,26 @@ function ArtifactView({ artifact: artifact2, versions }) {
|
|
|
2890
3611
|
/* @__PURE__ */ jsx(ExportMenu, { artifact: shown, getRenderedHtml })
|
|
2891
3612
|
] })
|
|
2892
3613
|
] }),
|
|
2893
|
-
/* @__PURE__ */
|
|
2894
|
-
"
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
3614
|
+
/* @__PURE__ */ jsxs(
|
|
3615
|
+
"div",
|
|
3616
|
+
{
|
|
3617
|
+
className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${isHistory ? " cv-body--history" : ""}`,
|
|
3618
|
+
ref: bodyRef,
|
|
3619
|
+
"aria-readonly": isHistory || void 0,
|
|
3620
|
+
children: [
|
|
3621
|
+
isHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-note", role: "status", children: [
|
|
3622
|
+
"Viewing v",
|
|
3623
|
+
Math.min(viewIndex + 1, versions.length),
|
|
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
|
+
]
|
|
3632
|
+
}
|
|
3633
|
+
)
|
|
2898
3634
|
] });
|
|
2899
3635
|
}
|
|
2900
3636
|
function UndoRedo() {
|
|
@@ -2976,7 +3712,8 @@ var TYPE_META = {
|
|
|
2976
3712
|
document: { icon: "\u{1F4C4}", label: "Word document" },
|
|
2977
3713
|
chart: { icon: "\u{1F4CA}", label: "Chart" },
|
|
2978
3714
|
table: { icon: "\u{1F522}", label: "Excel sheet" },
|
|
2979
|
-
slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" }
|
|
3715
|
+
slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
|
|
3716
|
+
pdf: { icon: "\u{1F4D5}", label: "PDF" }
|
|
2980
3717
|
};
|
|
2981
3718
|
var KIND_META = {
|
|
2982
3719
|
web: TYPE_META.html,
|
|
@@ -2987,7 +3724,8 @@ var KIND_META = {
|
|
|
2987
3724
|
table: TYPE_META.table,
|
|
2988
3725
|
sheet: TYPE_META.table,
|
|
2989
3726
|
slide: TYPE_META.slides,
|
|
2990
|
-
slides: TYPE_META.slides
|
|
3727
|
+
slides: TYPE_META.slides,
|
|
3728
|
+
pdf: TYPE_META.pdf
|
|
2991
3729
|
};
|
|
2992
3730
|
function resolveCardMeta(artifact2) {
|
|
2993
3731
|
const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
|
|
@@ -3012,4 +3750,4 @@ function ArtifactCard({ artifactId }) {
|
|
|
3012
3750
|
] });
|
|
3013
3751
|
}
|
|
3014
3752
|
|
|
3015
|
-
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasStream, useRenderer, withInspector };
|
|
3753
|
+
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, PdfRenderer, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasStream, useRenderer, withInspector };
|