@braincrew-lab/langchain-canvas 0.3.0 → 0.5.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/README.ko.md +11 -23
- package/README.md +11 -23
- package/dist/{ChartRenderer-ABRQ5YFN.js → ChartRenderer-JGRJ23OB.js} +39 -67
- package/dist/{DocumentRenderer-YTEMC3Z4.js → DocumentRenderer-ZQDQMX7X.js} +18 -19
- package/dist/FileRenderer-3ZHNORZJ.js +39 -0
- package/dist/SlidesRenderer-4TZNTSEL.js +514 -0
- package/dist/TableRenderer-3ESF577F.js +275 -0
- package/dist/chunk-7T5DRR3F.js +109 -0
- package/dist/{chunk-ZLXAWRUP.js → chunk-EHW446VF.js} +49 -50
- package/dist/chunk-FTNRRJ3K.js +13 -0
- package/dist/chunk-IFNRLN4Y.js +137 -0
- package/dist/{chunk-FSFOURG5.js → chunk-K2UZAYW2.js} +1 -1
- package/dist/{chunk-6L3AL6W4.js → chunk-SE6AP3A7.js} +21 -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 +166 -1002
- package/dist/index.js +361 -2526
- package/dist/langgraph/index.d.ts +68 -0
- package/dist/langgraph/index.js +111 -0
- package/dist/styles.css +59 -409
- package/dist/types-BfGP9R2I.d.ts +324 -0
- package/package.json +15 -4
- package/dist/PdfRenderer-GDA67MES.js +0 -98
- package/dist/SlidesRenderer-JNPXNTNJ.js +0 -964
- package/dist/TableRenderer-BJQE2HMO.js +0 -636
- package/dist/chunk-QMOJEGRH.js +0 -207
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
|
|
2
3
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
3
|
-
import { resolveElements } from './chunk-
|
|
4
|
-
export {
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { deckPage, resolveElements } from './chunk-SE6AP3A7.js';
|
|
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';
|
|
10
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
13
|
|
|
12
14
|
// src/client/sse-client.ts
|
|
@@ -63,9 +65,10 @@ function parseFrame(frame) {
|
|
|
63
65
|
|
|
64
66
|
// src/client/inspector.ts
|
|
65
67
|
var INSPECTOR_MARK = "langchain-canvas";
|
|
66
|
-
function withInspector(html) {
|
|
68
|
+
function withInspector(html, assetBaseUrl) {
|
|
67
69
|
let out = withViewport(html);
|
|
68
|
-
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>`;
|
|
69
72
|
const marker = "</body>";
|
|
70
73
|
const at = out.lastIndexOf(marker);
|
|
71
74
|
out = at === -1 ? out + injection : out.slice(0, at) + injection + out.slice(at);
|
|
@@ -133,6 +136,33 @@ var INSPECTOR_SCRIPT = `
|
|
|
133
136
|
el.removeAttribute("contenteditable");
|
|
134
137
|
if (el.classList) { el.classList.remove("lcx-hover"); el.classList.remove("lcx-selected"); }
|
|
135
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
|
+
}
|
|
136
166
|
}
|
|
137
167
|
function emitEdit(el) {
|
|
138
168
|
// Serialize the *canonical* HTML \u2014 strip the inspector's own injected
|
|
@@ -140,7 +170,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
140
170
|
var cid = el.getAttribute("data-cid");
|
|
141
171
|
var clone = el.cloneNode(true);
|
|
142
172
|
scrub(clone);
|
|
143
|
-
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") : [];
|
|
144
174
|
for (var i = 0; i < inner.length; i++) scrub(inner[i]);
|
|
145
175
|
parent.postMessage({ source: MARK, type: "node_edit", cid: cid, html: clone.outerHTML }, "*");
|
|
146
176
|
}
|
|
@@ -155,51 +185,20 @@ var INSPECTOR_SCRIPT = `
|
|
|
155
185
|
var clone = document.documentElement.cloneNode(true);
|
|
156
186
|
var injected = clone.querySelectorAll("[data-lcx]");
|
|
157
187
|
for (var i = 0; i < injected.length; i++) injected[i].parentNode && injected[i].parentNode.removeChild(injected[i]);
|
|
158
|
-
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");
|
|
159
189
|
for (var j = 0; j < marked.length; j++) scrub(marked[j]);
|
|
160
190
|
parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
|
|
161
191
|
}
|
|
162
|
-
// Copy the visual style of an existing element onto a new block, so inserts
|
|
163
|
-
// match the page's own design system instead of landing as bare UA-styled
|
|
164
|
-
// tags (a default grey <button> on a dark page reads as broken).
|
|
165
|
-
function adoptStyleFrom(el, sample) {
|
|
166
|
-
if (!sample) return false;
|
|
167
|
-
var cs = window.getComputedStyle(sample);
|
|
168
|
-
var props = ["background-color", "color", "border", "border-radius", "padding",
|
|
169
|
-
"font-family", "font-size", "font-weight", "letter-spacing", "box-shadow", "cursor"];
|
|
170
|
-
for (var i = 0; i < props.length; i++) el.style.setProperty(props[i], cs.getPropertyValue(props[i]));
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
192
|
function newBlock(tag) {
|
|
174
193
|
var el = document.createElement(tag);
|
|
175
194
|
if (tag === "img") {
|
|
176
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";
|
|
177
196
|
el.alt = "image"; el.style.maxWidth = "100%";
|
|
178
197
|
}
|
|
179
|
-
else if (tag === "button")
|
|
180
|
-
el.textContent = "Button";
|
|
181
|
-
// Match an existing button (skipping editor chrome); else a clean accent default.
|
|
182
|
-
var sampleBtn = null;
|
|
183
|
-
var btns = document.querySelectorAll("button");
|
|
184
|
-
for (var sb = 0; sb < btns.length; sb++) {
|
|
185
|
-
if (!btns[sb].closest("[data-lcx]")) { sampleBtn = btns[sb]; break; }
|
|
186
|
-
}
|
|
187
|
-
if (!adoptStyleFrom(el, sampleBtn)) {
|
|
188
|
-
el.style.cssText = "padding:10px 18px;border:0;border-radius:9px;background:#6366f1;color:#fff;font:600 15px/1.2 inherit;cursor:pointer";
|
|
189
|
-
}
|
|
190
|
-
}
|
|
198
|
+
else if (tag === "button") el.textContent = "Button";
|
|
191
199
|
else if (tag === "hr") { /* no content */ }
|
|
192
200
|
else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
|
|
193
|
-
else
|
|
194
|
-
el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
|
|
195
|
-
// Headings/text adopt an existing peer's look too (font, colour) so they
|
|
196
|
-
// don't appear in default serif-black on a styled page.
|
|
197
|
-
adoptStyleFrom(el, document.querySelector(tag === "p" ? "main p, section p, div p" : tag));
|
|
198
|
-
el.style.removeProperty("border");
|
|
199
|
-
el.style.removeProperty("box-shadow");
|
|
200
|
-
el.style.removeProperty("cursor");
|
|
201
|
-
el.style.removeProperty("background-color");
|
|
202
|
-
}
|
|
201
|
+
else el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
|
|
203
202
|
return el;
|
|
204
203
|
}
|
|
205
204
|
// Floating rich-text toolbar shown while a text element is being edited.
|
|
@@ -289,6 +288,15 @@ var INSPECTOR_SCRIPT = `
|
|
|
289
288
|
}
|
|
290
289
|
function start() {
|
|
291
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
|
+
}
|
|
292
300
|
var hovered = null;
|
|
293
301
|
var selected = []; // currently highlighted elements
|
|
294
302
|
var marquee = null, sx = 0, sy = 0, dragging = false, moved = false, suppressClick = false;
|
|
@@ -296,31 +304,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
296
304
|
// into absolute positioning inside its own parent, and its final spot + size are
|
|
297
305
|
// stored as percentages of that parent \u2014 so it stays put proportionally across
|
|
298
306
|
// responsive breakpoints, instead of a fixed pixel offset that drifts off.
|
|
299
|
-
var dragEls = null, dragStart = null, dragBases = null,
|
|
307
|
+
var dragEls = null, dragStart = null, dragBases = null, groupSeq = 0;
|
|
300
308
|
|
|
301
309
|
function ensurePositioned(parent) {
|
|
302
|
-
if (!parent || parent === document.documentElement) return;
|
|
303
|
-
|
|
304
|
-
// initial containing block (html), so a body margin or html padding shifts
|
|
305
|
-
// every free-dragged element \u2014 relative pins them to the body box itself.
|
|
306
|
-
if (window.getComputedStyle(parent).position === "static") {
|
|
307
|
-
parent.style.position = "relative";
|
|
308
|
-
parentMutated = true; // the parent must be persisted too, not just the child
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// Persist a completed free-drag. A single element normally commits as a cheap
|
|
312
|
-
// node_edit \u2014 but when its parent was pulled to position:relative that parent
|
|
313
|
-
// lives only in the live DOM, so a node-only patch would store an absolute
|
|
314
|
-
// child inside a still-static parent (it jumps on the next reload). In that
|
|
315
|
-
// case persist the whole document instead.
|
|
316
|
-
function commitDrag(els) {
|
|
317
|
-
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";
|
|
318
312
|
}
|
|
319
313
|
// Pull each element out into absolute positioning at its current spot (no visual
|
|
320
314
|
// jump), so it can then be moved freely.
|
|
321
315
|
function beginFreeDrag(els) {
|
|
322
316
|
dragBases = [];
|
|
323
|
-
parentMutated = false;
|
|
324
317
|
for (var i = 0; i < els.length; i++) {
|
|
325
318
|
var el = els[i], parent = el.parentElement || document.body;
|
|
326
319
|
ensurePositioned(parent);
|
|
@@ -343,16 +336,13 @@ var INSPECTOR_SCRIPT = `
|
|
|
343
336
|
}
|
|
344
337
|
// Commit the current position (in px, as set live by moveFree) as % of the
|
|
345
338
|
// parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
|
|
346
|
-
// only if the parent has collapsed to zero on that axis.
|
|
347
|
-
// an exception: a container's *height* is content-driven and reflows once the
|
|
348
|
-
// dragged element leaves the flow, so a top stored as % of the old height
|
|
349
|
-
// lands somewhere else after reload \u2014 top always commits in px.
|
|
339
|
+
// only if the parent has collapsed to zero on that axis.
|
|
350
340
|
function commitFree() {
|
|
351
341
|
for (var i = 0; i < dragBases.length; i++) {
|
|
352
342
|
var b = dragBases[i], pr = b.parent.getBoundingClientRect();
|
|
353
343
|
var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
|
|
354
344
|
b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
|
|
355
|
-
b.el.style.top =
|
|
345
|
+
b.el.style.top = pr.height ? ((curTop / pr.height) * 100).toFixed(3) + "%" : curTop + "px";
|
|
356
346
|
if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
|
|
357
347
|
}
|
|
358
348
|
}
|
|
@@ -509,8 +499,8 @@ var INSPECTOR_SCRIPT = `
|
|
|
509
499
|
positionResize();
|
|
510
500
|
// The new position is already shown in the iframe with every cid intact,
|
|
511
501
|
// so persist without a reload (no flicker): one element \u2192 node_edit,
|
|
512
|
-
// several
|
|
513
|
-
|
|
502
|
+
// several \u2192 a self-applied doc_edit.
|
|
503
|
+
if (els.length === 1) emitEdit(els[0]); else emitDoc(true);
|
|
514
504
|
}
|
|
515
505
|
dragBases = null;
|
|
516
506
|
return;
|
|
@@ -561,7 +551,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
561
551
|
// Pointer left the frame mid-drag: commit the move at its last position so
|
|
562
552
|
// it isn't lost (the element is already placed absolutely in the iframe).
|
|
563
553
|
var els = dragEls; dragEls = null;
|
|
564
|
-
if (moved && dragBases) { commitFree();
|
|
554
|
+
if (moved && dragBases) { commitFree(); if (els.length === 1) emitEdit(els[0]); else emitDoc(true); }
|
|
565
555
|
dragBases = null;
|
|
566
556
|
}
|
|
567
557
|
if (dragging) {
|
|
@@ -632,15 +622,20 @@ var INSPECTOR_SCRIPT = `
|
|
|
632
622
|
if (root && d.style) { for (var sk in d.style) { try { root.style[sk] = d.style[sk]; } catch (_e) {} } emitDoc(); }
|
|
633
623
|
return;
|
|
634
624
|
}
|
|
635
|
-
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
|
+
}
|
|
636
637
|
if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
|
|
637
638
|
|
|
638
|
-
// No-selection inserts land in the slide root when there is one \u2014 a slide
|
|
639
|
-
// document is body > .slide-container (fixed 720px), so appending to body
|
|
640
|
-
// puts content below the visible slide where it silently never shows.
|
|
641
|
-
function insertRoot() {
|
|
642
|
-
return document.querySelector(".slide-container") || document.body;
|
|
643
|
-
}
|
|
644
639
|
// Structural edits \u2014 mutate the tree, then persist the whole document.
|
|
645
640
|
if (d.type === "insert") {
|
|
646
641
|
var block = newBlock(d.block || "p");
|
|
@@ -648,7 +643,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
648
643
|
if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
|
|
649
644
|
anchor.parentNode.insertBefore(block, anchor.nextSibling);
|
|
650
645
|
} else {
|
|
651
|
-
|
|
646
|
+
document.body.appendChild(block);
|
|
652
647
|
}
|
|
653
648
|
emitDoc();
|
|
654
649
|
return;
|
|
@@ -656,7 +651,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
656
651
|
if (d.type === "insert_html") {
|
|
657
652
|
// A built-in section template (trusted markup from the toolbar).
|
|
658
653
|
var anc = d.cid ? byCid(d.cid) : null;
|
|
659
|
-
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;
|
|
660
655
|
var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
|
|
661
656
|
var frag = document.createElement("div");
|
|
662
657
|
frag.innerHTML = d.html || "";
|
|
@@ -671,16 +666,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
671
666
|
var cids = d.cids || [];
|
|
672
667
|
for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
|
|
673
668
|
if (members.length < 2) return;
|
|
674
|
-
|
|
675
|
-
// reset on every reload, so a second group session reused "g0" and merged
|
|
676
|
-
// with the previously-persisted group.
|
|
677
|
-
var maxGid = -1;
|
|
678
|
-
var existing = document.querySelectorAll("[data-group-id]");
|
|
679
|
-
for (var q = 0; q < existing.length; q++) {
|
|
680
|
-
var mm = /^g(d+)$/.exec(existing[q].getAttribute("data-group-id") || "");
|
|
681
|
-
if (mm && Number(mm[1]) > maxGid) maxGid = Number(mm[1]);
|
|
682
|
-
}
|
|
683
|
-
var gid = "g" + (maxGid + 1);
|
|
669
|
+
var gid = "g" + (groupSeq++);
|
|
684
670
|
for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
|
|
685
671
|
clearSelected();
|
|
686
672
|
emitDoc();
|
|
@@ -737,8 +723,41 @@ async function* mockStream(events, options = {}) {
|
|
|
737
723
|
function sleep(ms) {
|
|
738
724
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
739
725
|
}
|
|
740
|
-
|
|
726
|
+
|
|
727
|
+
// src/transports/sse.ts
|
|
728
|
+
function sseTransport(options = {}) {
|
|
741
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]);
|
|
742
761
|
const threadIdRef = useRef(options.threadId ?? crypto.randomUUID());
|
|
743
762
|
const abortRef = useRef(null);
|
|
744
763
|
const api = useCanvasStoreApi();
|
|
@@ -777,12 +796,12 @@ function useCanvasStream(options = {}) {
|
|
|
777
796
|
const controller = new AbortController();
|
|
778
797
|
abortRef.current = controller;
|
|
779
798
|
try {
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
);
|
|
799
|
+
const stream = transport.stream({
|
|
800
|
+
threadId: threadIdRef.current,
|
|
801
|
+
message: text,
|
|
802
|
+
selections: withSelections,
|
|
803
|
+
signal: controller.signal
|
|
804
|
+
});
|
|
786
805
|
for await (const event of stream) {
|
|
787
806
|
enqueue(event);
|
|
788
807
|
}
|
|
@@ -795,7 +814,7 @@ function useCanvasStream(options = {}) {
|
|
|
795
814
|
api.getState().setStreaming(false);
|
|
796
815
|
}
|
|
797
816
|
},
|
|
798
|
-
[api,
|
|
817
|
+
[api, transport, enqueue, flush]
|
|
799
818
|
);
|
|
800
819
|
const stop = useCallback(() => abortRef.current?.abort(), []);
|
|
801
820
|
const reset = useCallback(() => api.getState().reset(), [api]);
|
|
@@ -843,7 +862,7 @@ function useCanvasReplay() {
|
|
|
843
862
|
api.getState().applyEvent(event);
|
|
844
863
|
}
|
|
845
864
|
} finally {
|
|
846
|
-
|
|
865
|
+
api.getState().setStreaming(false);
|
|
847
866
|
}
|
|
848
867
|
},
|
|
849
868
|
[api]
|
|
@@ -1081,82 +1100,7 @@ var versions = {
|
|
|
1081
1100
|
{ type: "done" }
|
|
1082
1101
|
]
|
|
1083
1102
|
};
|
|
1084
|
-
var
|
|
1085
|
-
var pdf = {
|
|
1086
|
-
id: "pdf",
|
|
1087
|
-
title: "PDF viewer",
|
|
1088
|
-
description: "A PDF artifact shown in the browser's built-in viewer \u2014 zero dependencies.",
|
|
1089
|
-
events: [
|
|
1090
|
-
{ type: "message.delta", messageId: "m-pdf", text: "Here's the signed report as a PDF." },
|
|
1091
|
-
{
|
|
1092
|
-
type: "canvas.create",
|
|
1093
|
-
artifact: {
|
|
1094
|
-
id: "report-pdf",
|
|
1095
|
-
type: "pdf",
|
|
1096
|
-
title: "Signed report",
|
|
1097
|
-
version: 1,
|
|
1098
|
-
status: "complete",
|
|
1099
|
-
data: { src: PDF_DATA_URL, filename: "signed-report.pdf" }
|
|
1100
|
-
}
|
|
1101
|
-
},
|
|
1102
|
-
{ type: "canvas.status", id: "report-pdf", status: "complete" },
|
|
1103
|
-
{ type: "done" }
|
|
1104
|
-
]
|
|
1105
|
-
};
|
|
1106
|
-
var HWP_HTML = `<!doctype html>
|
|
1107
|
-
<html lang="ko"><head><meta charset="utf-8"><style>
|
|
1108
|
-
body{margin:0;background:#eceef1;font-family:"Malgun Gothic","\uB9D1\uC740 \uACE0\uB515","Apple SD Gothic Neo",AppleGothic,sans-serif;color:#1a1a1a}
|
|
1109
|
-
.page{max-width:794px;margin:24px auto;padding:96px 72px;background:#fff;box-shadow:0 2px 14px rgba(0,0,0,.12);line-height:1.7}
|
|
1110
|
-
h1{font-size:22pt;text-align:center;margin:0 0 8px}
|
|
1111
|
-
.stamp{text-align:center;color:#555;font-size:10.5pt;margin:0 0 36px}
|
|
1112
|
-
h2{font-size:14pt;border-bottom:2px solid #2f5597;padding-bottom:4px;margin:28px 0 12px;color:#2f5597}
|
|
1113
|
-
p{margin:0 0 10px;font-size:11pt;text-align:justify}
|
|
1114
|
-
table{border-collapse:collapse;width:100%;margin:12px 0;font-size:10.5pt}
|
|
1115
|
-
th{background:#dbe5f1;border:1px solid #666;padding:7px 10px}
|
|
1116
|
-
td{border:1px solid #666;padding:7px 10px}
|
|
1117
|
-
.sign{margin-top:48px;text-align:right;font-size:12pt}
|
|
1118
|
-
.red{color:#c00000;font-weight:700}
|
|
1119
|
-
</style></head>
|
|
1120
|
-
<body><div class="page">
|
|
1121
|
-
<h1>\uC0AC\uC5C5 \uC218\uD589 \uACC4\uD68D\uC11C</h1>
|
|
1122
|
-
<p class="stamp">\uBB38\uC11C\uBC88\uD638 BC-2026-041 \xB7 2026. 7. 29.</p>
|
|
1123
|
-
<h2>1. \uAC1C\uC694</h2>
|
|
1124
|
-
<p>\uBCF8 \uBB38\uC11C\uB294 <b>\uB300\uD654\uD615 \uC5D0\uC774\uC804\uD2B8 \uBE4C\uB354 \uAD6C\uCD95</b> \uC0AC\uC5C5\uC758 \uC218\uD589 \uACC4\uD68D\uC744 \uC815\uB9AC\uD55C \uAC83\uC785\uB2C8\uB2E4. <span class="red">\uD55C\uAE00(.hwp/.hwpx) \uD30C\uC77C\uC744 \uCE94\uBC84\uC2A4\uC5D0 \uB04C\uC5B4\uB2E4 \uB193\uC73C\uBA74</span> \uC774 \uBB38\uC11C\uCC98\uB7FC <u>\uC11C\uC2DD\uC774 \uBCF4\uC874\uB41C \uD398\uC774\uC9C0</u>\uB85C \uC5F4\uB9BD\uB2C8\uB2E4 \u2014 \uAE00\uAF34 \uD06C\uAE30\xB7\uC0C9\xB7\uC815\uB82C\xB7\uD45C \uD14C\uB450\uB9AC\xB7\uC774\uBBF8\uC9C0\uAE4C\uC9C0.</p>
|
|
1125
|
-
<h2>2. \uCD94\uC9C4 \uC77C\uC815</h2>
|
|
1126
|
-
<table>
|
|
1127
|
-
<tr><th>\uB2E8\uACC4</th><th>\uAE30\uAC04</th><th>\uC0B0\uCD9C\uBB3C</th></tr>
|
|
1128
|
-
<tr><td>\uCC29\uC218</td><td style="text-align:center">2026-07</td><td>\uC0AC\uC5C5\uC218\uD589\uACC4\uD68D\uC11C</td></tr>
|
|
1129
|
-
<tr><td>\uBD84\uC11D/\uC124\uACC4</td><td style="text-align:center">2026-07 ~ 2026-08</td><td>\uC694\uAD6C\uC0AC\uD56D \uC815\uC758\uC11C</td></tr>
|
|
1130
|
-
<tr><td>\uAD6C\uCD95</td><td style="text-align:center">2026-08 ~ 2026-11</td><td>\uAE30\uB2A5 \uAD6C\uD604</td></tr>
|
|
1131
|
-
<tr><td>\uAC80\uC218</td><td style="text-align:center">2026-12</td><td>\uAC80\uC218\uD655\uC778\uC11C</td></tr>
|
|
1132
|
-
</table>
|
|
1133
|
-
<h2>3. \uB0B4\uBCF4\uB0B4\uAE30</h2>
|
|
1134
|
-
<p>\uD3B8\uC9D1\uD55C \uBB38\uC11C\uB294 <b>\uB0B4\uBCF4\uB0B4\uAE30</b> \uBA54\uB274\uC5D0\uC11C PDF\xB7HTML\uB85C \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC5D0\uC774\uC804\uD2B8\uAC00 \uB9CC\uB4E0 \uBB38\uC11C(document)\uB294 <b>\uD55C\uAE00 (HWPX)</b>\uB85C\uB3C4 \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4.</p>
|
|
1135
|
-
<p class="sign">\uBE0C\uB808\uC778\uD06C\uB8E8 \uC8FC\uC2DD\uD68C\uC0AC</p>
|
|
1136
|
-
</div></body></html>`;
|
|
1137
|
-
var hwp = {
|
|
1138
|
-
id: "hwp",
|
|
1139
|
-
title: "\uD55C\uAE00 (HWP)",
|
|
1140
|
-
description: "A formatted Korean document \u2014 how .hwp/.hwpx files open on the canvas, styling preserved.",
|
|
1141
|
-
events: [
|
|
1142
|
-
{ type: "message.delta", messageId: "m-hwp", text: "\uD55C\uAE00 \uD30C\uC77C\uC744 \uC11C\uC2DD \uADF8\uB300\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4." },
|
|
1143
|
-
{
|
|
1144
|
-
type: "canvas.create",
|
|
1145
|
-
artifact: {
|
|
1146
|
-
id: "hwp-doc",
|
|
1147
|
-
type: "html",
|
|
1148
|
-
title: "\uC0AC\uC5C5 \uC218\uD589 \uACC4\uD68D\uC11C.hwpx",
|
|
1149
|
-
version: 1,
|
|
1150
|
-
status: "complete",
|
|
1151
|
-
meta: { kind: "doc", source: "hwpx" },
|
|
1152
|
-
data: { html: HWP_HTML }
|
|
1153
|
-
}
|
|
1154
|
-
},
|
|
1155
|
-
{ type: "canvas.status", id: "hwp-doc", status: "complete" },
|
|
1156
|
-
{ type: "done" }
|
|
1157
|
-
]
|
|
1158
|
-
};
|
|
1159
|
-
var scenarios = [htmlPage, document2, chart, table, slides, pdf, hwp, versions];
|
|
1103
|
+
var scenarios = [htmlPage, document2, chart, table, slides, versions];
|
|
1160
1104
|
var RegistryContext = createContext({});
|
|
1161
1105
|
function CanvasRegistryProvider({ registry, children }) {
|
|
1162
1106
|
return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
|
|
@@ -1302,202 +1246,40 @@ function display(value) {
|
|
|
1302
1246
|
if (value == null) return "";
|
|
1303
1247
|
if (typeof value === "object") {
|
|
1304
1248
|
if (value.result != null) return display(value.result);
|
|
1305
|
-
if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
|
|
1306
1249
|
if (typeof value.text === "string") return value.text;
|
|
1307
1250
|
if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
|
|
1308
1251
|
return "";
|
|
1309
1252
|
}
|
|
1310
1253
|
return String(value);
|
|
1311
1254
|
}
|
|
1312
|
-
function splitSections(fmt) {
|
|
1313
|
-
const sections = [];
|
|
1314
|
-
let cur = "";
|
|
1315
|
-
for (let i = 0; i < fmt.length; i++) {
|
|
1316
|
-
const ch = fmt[i];
|
|
1317
|
-
if (ch === '"') {
|
|
1318
|
-
cur += ch;
|
|
1319
|
-
i++;
|
|
1320
|
-
while (i < fmt.length && fmt[i] !== '"') cur += fmt[i++];
|
|
1321
|
-
if (i < fmt.length) cur += fmt[i];
|
|
1322
|
-
continue;
|
|
1323
|
-
}
|
|
1324
|
-
if (ch === "[") {
|
|
1325
|
-
cur += ch;
|
|
1326
|
-
i++;
|
|
1327
|
-
while (i < fmt.length && fmt[i] !== "]") cur += fmt[i++];
|
|
1328
|
-
if (i < fmt.length) cur += fmt[i];
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
if (ch === "\\") {
|
|
1332
|
-
cur += ch + (fmt[i + 1] ?? "");
|
|
1333
|
-
i++;
|
|
1334
|
-
continue;
|
|
1335
|
-
}
|
|
1336
|
-
if (ch === ";") {
|
|
1337
|
-
sections.push(cur);
|
|
1338
|
-
cur = "";
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
cur += ch;
|
|
1342
|
-
}
|
|
1343
|
-
sections.push(cur);
|
|
1344
|
-
return sections;
|
|
1345
|
-
}
|
|
1346
|
-
function numberSkeleton(fmt) {
|
|
1347
|
-
let currency = "";
|
|
1348
|
-
let currencyTrails = false;
|
|
1349
|
-
let skeleton = "";
|
|
1350
|
-
let seenDigit = false;
|
|
1351
|
-
const found = (sym) => {
|
|
1352
|
-
if (currency) return;
|
|
1353
|
-
currency = sym;
|
|
1354
|
-
currencyTrails = seenDigit;
|
|
1355
|
-
};
|
|
1356
|
-
for (let i = 0; i < fmt.length; i++) {
|
|
1357
|
-
const ch = fmt[i];
|
|
1358
|
-
if (ch === '"') {
|
|
1359
|
-
let lit = "";
|
|
1360
|
-
i++;
|
|
1361
|
-
while (i < fmt.length && fmt[i] !== '"') lit += fmt[i++];
|
|
1362
|
-
if (/[$₩€£¥]/.test(lit)) found(lit);
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
if (ch === "\\") {
|
|
1366
|
-
const c = fmt[i + 1] ?? "";
|
|
1367
|
-
if (/[$₩€£¥]/.test(c)) found(c);
|
|
1368
|
-
i++;
|
|
1369
|
-
continue;
|
|
1370
|
-
}
|
|
1371
|
-
if (ch === "[") {
|
|
1372
|
-
const end = fmt.indexOf("]", i);
|
|
1373
|
-
const group = fmt.slice(i + 1, end === -1 ? void 0 : end);
|
|
1374
|
-
const cur = /^\$(.*?)-/.exec(group)?.[1] ?? (group.startsWith("$") ? group.slice(1) : "");
|
|
1375
|
-
if (cur) found(cur);
|
|
1376
|
-
i = end === -1 ? fmt.length : end;
|
|
1377
|
-
continue;
|
|
1378
|
-
}
|
|
1379
|
-
if (ch === "_" || ch === "*") {
|
|
1380
|
-
i++;
|
|
1381
|
-
continue;
|
|
1382
|
-
}
|
|
1383
|
-
if (/[$₩€£¥]/.test(ch)) {
|
|
1384
|
-
found(ch);
|
|
1385
|
-
continue;
|
|
1386
|
-
}
|
|
1387
|
-
if (ch === "0" || ch === "#") seenDigit = true;
|
|
1388
|
-
skeleton += ch;
|
|
1389
|
-
}
|
|
1390
|
-
return { skeleton, currency, currencyTrails };
|
|
1391
|
-
}
|
|
1392
1255
|
function formatNumber(value, numFmt) {
|
|
1393
|
-
const
|
|
1394
|
-
if (!
|
|
1395
|
-
const
|
|
1396
|
-
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
const thousands = /[#0],[#0]/.test(skeleton);
|
|
1402
|
-
const n = negSection ? Math.abs(value) : value;
|
|
1403
|
-
const percents = (skeleton.match(/%/g) ?? []).length;
|
|
1404
|
-
const scaled = percents ? n * 100 ** percents : n;
|
|
1405
|
-
let out = thousands ? scaled.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : scaled.toFixed(decimals);
|
|
1406
|
-
if (percents) out += "%";
|
|
1407
|
-
if (currency) {
|
|
1408
|
-
if (currencyTrails) out = `${out}${currency}`;
|
|
1409
|
-
else if (out.startsWith("-")) out = `-${currency}${out.slice(1)}`;
|
|
1410
|
-
else out = `${currency}${out}`;
|
|
1411
|
-
}
|
|
1412
|
-
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}`;
|
|
1413
1264
|
return out;
|
|
1414
1265
|
}
|
|
1415
|
-
var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
1416
1266
|
function formatDate(d, numFmt) {
|
|
1417
|
-
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ?
|
|
1267
|
+
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
|
|
1418
1268
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
1419
|
-
const meridiem = /AM\/PM|A\/P/i.test(fmt);
|
|
1420
|
-
const H = d.getUTCHours();
|
|
1421
|
-
const hour12 = H % 12 === 0 ? 12 : H % 12;
|
|
1422
|
-
const hourText = (width) => meridiem ? p(hour12, width) : p(H, width);
|
|
1423
|
-
const elapsedMs = d.getTime() - EXCEL_EPOCH_MS;
|
|
1424
1269
|
const map = {
|
|
1425
|
-
yyyy:
|
|
1426
|
-
yy:
|
|
1427
|
-
mmmm:
|
|
1428
|
-
mmm:
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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())
|
|
1435
1281
|
};
|
|
1436
|
-
|
|
1437
|
-
const minute = (t) => t === "mm" ? p(d.getUTCMinutes()) : String(d.getUTCMinutes());
|
|
1438
|
-
const TOKEN = /AM\/PM|A\/P|yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss|s/iy;
|
|
1439
|
-
let out = "";
|
|
1440
|
-
let lastWasHours = false;
|
|
1441
|
-
for (let i = 0; i < fmt.length; ) {
|
|
1442
|
-
const ch = fmt[i];
|
|
1443
|
-
if (ch === '"') {
|
|
1444
|
-
i++;
|
|
1445
|
-
while (i < fmt.length && fmt[i] !== '"') out += fmt[i++];
|
|
1446
|
-
i++;
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
if (ch === "\\") {
|
|
1450
|
-
out += fmt[i + 1] ?? "";
|
|
1451
|
-
i += 2;
|
|
1452
|
-
continue;
|
|
1453
|
-
}
|
|
1454
|
-
if (ch === "[") {
|
|
1455
|
-
const end = fmt.indexOf("]", i);
|
|
1456
|
-
const group = fmt.slice(i + 1, end === -1 ? fmt.length : end);
|
|
1457
|
-
i = end === -1 ? fmt.length : end + 1;
|
|
1458
|
-
if (/^h+$/i.test(group)) {
|
|
1459
|
-
out += p(Math.floor(elapsedMs / 36e5), group.length);
|
|
1460
|
-
lastWasHours = true;
|
|
1461
|
-
} else if (/^m+$/i.test(group)) out += p(Math.floor(elapsedMs / 6e4), group.length);
|
|
1462
|
-
else if (/^s+$/i.test(group)) out += p(Math.floor(elapsedMs / 1e3), group.length);
|
|
1463
|
-
continue;
|
|
1464
|
-
}
|
|
1465
|
-
if (ch === "_") {
|
|
1466
|
-
out += " ";
|
|
1467
|
-
i += 2;
|
|
1468
|
-
continue;
|
|
1469
|
-
}
|
|
1470
|
-
if (ch === "*") {
|
|
1471
|
-
i += 2;
|
|
1472
|
-
continue;
|
|
1473
|
-
}
|
|
1474
|
-
TOKEN.lastIndex = i;
|
|
1475
|
-
const m = TOKEN.exec(fmt);
|
|
1476
|
-
if (m) {
|
|
1477
|
-
const tok = m[0].toLowerCase();
|
|
1478
|
-
i += m[0].length;
|
|
1479
|
-
if (tok === "am/pm") {
|
|
1480
|
-
out += H < 12 ? "AM" : "PM";
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
if (tok === "a/p") {
|
|
1484
|
-
out += H < 12 ? "A" : "P";
|
|
1485
|
-
continue;
|
|
1486
|
-
}
|
|
1487
|
-
if (tok === "m" || tok === "mm") {
|
|
1488
|
-
const minutes = lastWasHours || /^[\s:.,\-]*s/i.test(fmt.slice(i));
|
|
1489
|
-
out += minutes ? minute(tok) : month(tok);
|
|
1490
|
-
lastWasHours = false;
|
|
1491
|
-
continue;
|
|
1492
|
-
}
|
|
1493
|
-
lastWasHours = tok === "h" || tok === "hh";
|
|
1494
|
-
out += map[tok]();
|
|
1495
|
-
continue;
|
|
1496
|
-
}
|
|
1497
|
-
out += ch;
|
|
1498
|
-
i++;
|
|
1499
|
-
}
|
|
1500
|
-
return out;
|
|
1282
|
+
return fmt.replace(/yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss/g, (t) => map[t] ?? t);
|
|
1501
1283
|
}
|
|
1502
1284
|
function cellValue(cell) {
|
|
1503
1285
|
const raw = cell.value;
|
|
@@ -1696,7 +1478,6 @@ function cellVal(v) {
|
|
|
1696
1478
|
if (typeof v === "number" || typeof v === "string") return v;
|
|
1697
1479
|
if (typeof v === "object") {
|
|
1698
1480
|
if (v.result != null) return cellVal(v.result);
|
|
1699
|
-
if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
|
|
1700
1481
|
if (typeof v.text === "string") return v.text;
|
|
1701
1482
|
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
1702
1483
|
}
|
|
@@ -1739,1670 +1520,8 @@ function flatten(ws) {
|
|
|
1739
1520
|
return { columns, rows };
|
|
1740
1521
|
}
|
|
1741
1522
|
|
|
1742
|
-
// src/io/hwpx.ts
|
|
1743
|
-
var EOCD_SIG = 101010256;
|
|
1744
|
-
var CDIR_SIG = 33639248;
|
|
1745
|
-
var LOCAL_SIG = 67324752;
|
|
1746
|
-
async function inflateRaw(bytes) {
|
|
1747
|
-
if (typeof DecompressionStream === "undefined") {
|
|
1748
|
-
throw new Error("\uC774 \uBE0C\uB77C\uC6B0\uC800\uB294 \uC555\uCD95 \uD574\uC81C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (DecompressionStream unavailable).");
|
|
1749
|
-
}
|
|
1750
|
-
const ds = new DecompressionStream("deflate-raw");
|
|
1751
|
-
const writer = ds.writable.getWriter();
|
|
1752
|
-
const wrote = writer.write(bytes).then(() => writer.close());
|
|
1753
|
-
const reader = ds.readable.getReader();
|
|
1754
|
-
const chunks = [];
|
|
1755
|
-
let total = 0;
|
|
1756
|
-
for (; ; ) {
|
|
1757
|
-
const { done, value } = await reader.read();
|
|
1758
|
-
if (done) break;
|
|
1759
|
-
chunks.push(value);
|
|
1760
|
-
total += value.length;
|
|
1761
|
-
}
|
|
1762
|
-
await wrote;
|
|
1763
|
-
const out = new Uint8Array(total);
|
|
1764
|
-
let pos = 0;
|
|
1765
|
-
for (const c of chunks) {
|
|
1766
|
-
out.set(c, pos);
|
|
1767
|
-
pos += c.length;
|
|
1768
|
-
}
|
|
1769
|
-
return out;
|
|
1770
|
-
}
|
|
1771
|
-
async function readZip(buffer) {
|
|
1772
|
-
const view = new DataView(buffer);
|
|
1773
|
-
const bytes = new Uint8Array(buffer);
|
|
1774
|
-
let eocd = -1;
|
|
1775
|
-
const floor = Math.max(0, buffer.byteLength - 22 - 65535);
|
|
1776
|
-
for (let i = buffer.byteLength - 22; i >= floor; i--) {
|
|
1777
|
-
if (view.getUint32(i, true) === EOCD_SIG) {
|
|
1778
|
-
eocd = i;
|
|
1779
|
-
break;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
if (eocd === -1) throw new Error("ZIP \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (no end-of-central-directory).");
|
|
1783
|
-
const count = view.getUint16(eocd + 10, true);
|
|
1784
|
-
let offset = view.getUint32(eocd + 16, true);
|
|
1785
|
-
const entries = /* @__PURE__ */ new Map();
|
|
1786
|
-
const decoder = new TextDecoder();
|
|
1787
|
-
for (let i = 0; i < count; i++) {
|
|
1788
|
-
if (offset + 46 > buffer.byteLength || view.getUint32(offset, true) !== CDIR_SIG) {
|
|
1789
|
-
throw new Error("ZIP \uC911\uC559 \uB514\uB809\uD130\uB9AC\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt central directory).");
|
|
1790
|
-
}
|
|
1791
|
-
const method = view.getUint16(offset + 10, true);
|
|
1792
|
-
const compressedSize = view.getUint32(offset + 20, true);
|
|
1793
|
-
const uncompressedSize = view.getUint32(offset + 24, true);
|
|
1794
|
-
const nameLen = view.getUint16(offset + 28, true);
|
|
1795
|
-
const extraLen = view.getUint16(offset + 30, true);
|
|
1796
|
-
const commentLen = view.getUint16(offset + 32, true);
|
|
1797
|
-
const localOffset = view.getUint32(offset + 42, true);
|
|
1798
|
-
const name = decoder.decode(bytes.subarray(offset + 46, offset + 46 + nameLen));
|
|
1799
|
-
offset += 46 + nameLen + extraLen + commentLen;
|
|
1800
|
-
if (compressedSize === 4294967295 || uncompressedSize === 4294967295) {
|
|
1801
|
-
throw new Error("Zip64 \uD615\uC2DD\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (Zip64 not supported).");
|
|
1802
|
-
}
|
|
1803
|
-
if (name.endsWith("/")) continue;
|
|
1804
|
-
if (localOffset + 30 > buffer.byteLength || view.getUint32(localOffset, true) !== LOCAL_SIG) {
|
|
1805
|
-
throw new Error("ZIP \uB85C\uCEEC \uD5E4\uB354\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt local header).");
|
|
1806
|
-
}
|
|
1807
|
-
const localNameLen = view.getUint16(localOffset + 26, true);
|
|
1808
|
-
const localExtraLen = view.getUint16(localOffset + 28, true);
|
|
1809
|
-
const start = localOffset + 30 + localNameLen + localExtraLen;
|
|
1810
|
-
const data = bytes.subarray(start, start + compressedSize);
|
|
1811
|
-
if (method === 0) entries.set(name, data);
|
|
1812
|
-
else if (method === 8) entries.set(name, await inflateRaw(data));
|
|
1813
|
-
else throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC555\uCD95 \uBC29\uC2DD\uC785\uB2C8\uB2E4 (compression method ${method}).`);
|
|
1814
|
-
}
|
|
1815
|
-
return entries;
|
|
1816
|
-
}
|
|
1817
|
-
function parseXml(xml) {
|
|
1818
|
-
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
1819
|
-
return doc.querySelector("parsererror") ? null : doc;
|
|
1820
|
-
}
|
|
1821
|
-
var IMAGE_BASE64_BUDGET = 8 * 1024 * 1024;
|
|
1822
|
-
var MIME_BY_EXT = {
|
|
1823
|
-
png: "image/png",
|
|
1824
|
-
jpg: "image/jpeg",
|
|
1825
|
-
jpeg: "image/jpeg",
|
|
1826
|
-
gif: "image/gif",
|
|
1827
|
-
bmp: "image/bmp"
|
|
1828
|
-
};
|
|
1829
|
-
function bytesToBase642(bytes) {
|
|
1830
|
-
let bin = "";
|
|
1831
|
-
for (let i = 0; i < bytes.length; i += 32768) {
|
|
1832
|
-
bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + 32768)));
|
|
1833
|
-
}
|
|
1834
|
-
return btoa(bin);
|
|
1835
|
-
}
|
|
1836
|
-
var ImageResolver = class {
|
|
1837
|
-
// ref id → ZIP entry name
|
|
1838
|
-
constructor(entries) {
|
|
1839
|
-
this.entries = entries;
|
|
1840
|
-
this.omitted = 0;
|
|
1841
|
-
this.used = 0;
|
|
1842
|
-
this.index = /* @__PURE__ */ new Map();
|
|
1843
|
-
for (const name of entries.keys()) {
|
|
1844
|
-
const m = /^(?:Contents\/)?BinData\/([^/]+)$/i.exec(name);
|
|
1845
|
-
if (!m) continue;
|
|
1846
|
-
this.index.set(m[1], name);
|
|
1847
|
-
this.index.set(m[1].replace(/\.[^.]+$/, ""), name);
|
|
1848
|
-
}
|
|
1849
|
-
const hpfName = [...entries.keys()].find((n) => /(^|\/)content\.hpf$/i.test(n));
|
|
1850
|
-
const doc = hpfName ? parseXml(new TextDecoder().decode(entries.get(hpfName))) : null;
|
|
1851
|
-
if (!doc) return;
|
|
1852
|
-
for (const item of Array.from(doc.getElementsByTagNameNS("*", "item"))) {
|
|
1853
|
-
const id = item.getAttribute("id");
|
|
1854
|
-
const href = item.getAttribute("href");
|
|
1855
|
-
if (!id || !href) continue;
|
|
1856
|
-
const target = this.entries.has(href) ? href : this.entries.has(`Contents/${href}`) ? `Contents/${href}` : null;
|
|
1857
|
-
if (target) this.index.set(id, target);
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
/** Data URL for a binary item id, or null when unresolvable / over budget —
|
|
1861
|
-
* the shared resolution both the markdown and HTML emitters build on. */
|
|
1862
|
-
dataUrl(refId) {
|
|
1863
|
-
const name = this.index.get(refId);
|
|
1864
|
-
const bytes = name ? this.entries.get(name) : void 0;
|
|
1865
|
-
if (!name || !bytes) return null;
|
|
1866
|
-
const base64Length = Math.ceil(bytes.length / 3) * 4;
|
|
1867
|
-
if (this.used + base64Length > IMAGE_BASE64_BUDGET) {
|
|
1868
|
-
this.omitted++;
|
|
1869
|
-
return null;
|
|
1870
|
-
}
|
|
1871
|
-
this.used += base64Length;
|
|
1872
|
-
const ext = /\.([^.]+)$/.exec(name)?.[1]?.toLowerCase() ?? "";
|
|
1873
|
-
return `data:${MIME_BY_EXT[ext] ?? "image/png"};base64,${bytesToBase642(bytes)}`;
|
|
1874
|
-
}
|
|
1875
|
-
/** Markdown image block for a binary item id, or null when unresolvable /
|
|
1876
|
-
* over budget. */
|
|
1877
|
-
markdown(refId) {
|
|
1878
|
-
const url = this.dataUrl(refId);
|
|
1879
|
-
return url ? `` : null;
|
|
1880
|
-
}
|
|
1881
|
-
};
|
|
1882
|
-
function binaryItemRef(pic) {
|
|
1883
|
-
const refOf = (el) => {
|
|
1884
|
-
for (const attr of Array.from(el.attributes)) {
|
|
1885
|
-
if (/^(binaryitemidref|bin-item-id|binitemidref)$/i.test(attr.localName)) return attr.value || null;
|
|
1886
|
-
}
|
|
1887
|
-
return null;
|
|
1888
|
-
};
|
|
1889
|
-
const descendants = [pic, ...Array.from(pic.getElementsByTagNameNS("*", "*"))];
|
|
1890
|
-
for (const el of descendants) {
|
|
1891
|
-
if (el.localName === "img") {
|
|
1892
|
-
const ref = refOf(el);
|
|
1893
|
-
if (ref) return ref;
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
for (const el of descendants) {
|
|
1897
|
-
const ref = refOf(el);
|
|
1898
|
-
if (ref) return ref;
|
|
1899
|
-
}
|
|
1900
|
-
return null;
|
|
1901
|
-
}
|
|
1902
|
-
var HEADING_STYLE_NAME = /^(개요|Outline|Heading|제목)\s*([1-6])/i;
|
|
1903
|
-
function buildHeadingIndex(entries) {
|
|
1904
|
-
const byStyle = /* @__PURE__ */ new Map();
|
|
1905
|
-
const byParaPr = /* @__PURE__ */ new Map();
|
|
1906
|
-
const name = [...entries.keys()].find((n) => /^Contents\/header\.xml$/i.test(n));
|
|
1907
|
-
const doc = name ? parseXml(new TextDecoder().decode(entries.get(name))) : null;
|
|
1908
|
-
if (doc) {
|
|
1909
|
-
for (const style of Array.from(doc.getElementsByTagNameNS("*", "style"))) {
|
|
1910
|
-
const match = HEADING_STYLE_NAME.exec(style.getAttribute("name") ?? "");
|
|
1911
|
-
if (!match) continue;
|
|
1912
|
-
const level = Number(match[2]);
|
|
1913
|
-
const id = style.getAttribute("id");
|
|
1914
|
-
if (id !== null) byStyle.set(id, level);
|
|
1915
|
-
const paraPr = style.getAttribute("paraPrIDRef");
|
|
1916
|
-
if (paraPr !== null) byParaPr.set(paraPr, level);
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
return { byStyle, byParaPr };
|
|
1920
|
-
}
|
|
1921
|
-
function paragraphText(p) {
|
|
1922
|
-
let out = "";
|
|
1923
|
-
const walk = (el) => {
|
|
1924
|
-
for (const child of Array.from(el.children)) {
|
|
1925
|
-
const tag = child.localName;
|
|
1926
|
-
if (tag === "tbl") continue;
|
|
1927
|
-
if (tag === "t") out += child.textContent ?? "";
|
|
1928
|
-
else if (tag === "lineBreak") out += "\n";
|
|
1929
|
-
else walk(child);
|
|
1930
|
-
}
|
|
1931
|
-
};
|
|
1932
|
-
walk(p);
|
|
1933
|
-
return out;
|
|
1934
|
-
}
|
|
1935
|
-
async function openHwpx(buffer) {
|
|
1936
|
-
const entries = await readZip(buffer);
|
|
1937
|
-
const names = [...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));
|
|
1938
|
-
if (!names.length) {
|
|
1939
|
-
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).");
|
|
1940
|
-
}
|
|
1941
|
-
const decoder = new TextDecoder();
|
|
1942
|
-
return {
|
|
1943
|
-
entries,
|
|
1944
|
-
sections: names.map((n) => decoder.decode(entries.get(n))),
|
|
1945
|
-
headings: buildHeadingIndex(entries),
|
|
1946
|
-
images: new ImageResolver(entries)
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
function headingLevelOf(p, headings) {
|
|
1950
|
-
const styleId = p.getAttribute("styleIDRef");
|
|
1951
|
-
if (styleId !== null && headings.byStyle.has(styleId)) return headings.byStyle.get(styleId);
|
|
1952
|
-
const paraPrId = p.getAttribute("paraPrIDRef");
|
|
1953
|
-
return paraPrId !== null && headings.byParaPr.get(paraPrId) || 0;
|
|
1954
|
-
}
|
|
1955
|
-
function escapeHtml(text) {
|
|
1956
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1957
|
-
}
|
|
1958
|
-
var KOREAN_FONT_STACK = "'Malgun Gothic','\uB9D1\uC740 \uACE0\uB515','Apple SD Gothic Neo',AppleGothic,sans-serif";
|
|
1959
|
-
function koreanDocHtml(bodyHtml, title = "\uBB38\uC11C") {
|
|
1960
|
-
return `<!DOCTYPE html>
|
|
1961
|
-
<html lang="ko">
|
|
1962
|
-
<head>
|
|
1963
|
-
<meta charset="utf-8">
|
|
1964
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1965
|
-
<title>${escapeHtml(title)}</title>
|
|
1966
|
-
<style>
|
|
1967
|
-
body{margin:0;padding:32px 16px;background:#eceff1;color:#191919;font-family:${KOREAN_FONT_STACK};line-height:1.6;}
|
|
1968
|
-
.page{max-width:794px;margin:0 auto;background:#fff;padding:96px 72px;box-shadow:0 2px 12px rgba(0,0,0,.12);}
|
|
1969
|
-
.page p{margin:0 0 10px;}
|
|
1970
|
-
.page h1,.page h2,.page h3,.page h4,.page h5,.page h6{margin:18px 0 12px;line-height:1.35;}
|
|
1971
|
-
.page table{border-collapse:collapse;margin:0 0 10px;}
|
|
1972
|
-
.page td,.page th{border:1px solid #666;padding:6px 8px;vertical-align:top;}
|
|
1973
|
-
.page td p:last-child{margin-bottom:0;}
|
|
1974
|
-
.page img{max-width:100%;}
|
|
1975
|
-
</style>
|
|
1976
|
-
</head>
|
|
1977
|
-
<body>
|
|
1978
|
-
<div class="page">
|
|
1979
|
-
${bodyHtml}
|
|
1980
|
-
</div>
|
|
1981
|
-
</body>
|
|
1982
|
-
</html>`;
|
|
1983
|
-
}
|
|
1984
|
-
var ALIGN_CSS = {
|
|
1985
|
-
LEFT: "left",
|
|
1986
|
-
CENTER: "center",
|
|
1987
|
-
RIGHT: "right",
|
|
1988
|
-
JUSTIFY: "justify",
|
|
1989
|
-
BOTH: "justify",
|
|
1990
|
-
DISTRIBUTE: "justify",
|
|
1991
|
-
DISTRIBUTE_SPACE: "justify"
|
|
1992
|
-
};
|
|
1993
|
-
var HEX_COLOR = /^#[0-9a-f]{6}$/i;
|
|
1994
|
-
var heightToPx = (height) => Math.round(height / 100 * (4 / 3) * 100) / 100;
|
|
1995
|
-
function charPrCss(charPr, faceByLangId) {
|
|
1996
|
-
let bold = false;
|
|
1997
|
-
let italic = false;
|
|
1998
|
-
let underline = false;
|
|
1999
|
-
let face;
|
|
2000
|
-
for (const child of Array.from(charPr.children)) {
|
|
2001
|
-
const tag = child.localName;
|
|
2002
|
-
if (tag === "bold") bold = true;
|
|
2003
|
-
else if (tag === "italic") italic = true;
|
|
2004
|
-
else if (tag === "underline") {
|
|
2005
|
-
underline = (child.getAttribute("type") ?? "SOLID").toUpperCase() !== "NONE";
|
|
2006
|
-
} else if (tag === "fontRef") {
|
|
2007
|
-
const ref = child.getAttribute("hangul") ?? child.getAttribute("latin");
|
|
2008
|
-
if (ref !== null) face = faceByLangId.get(`HANGUL:${ref}`) ?? faceByLangId.get(`LATIN:${ref}`);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
const rules = [];
|
|
2012
|
-
if (face) rules.push(`font-family:'${face.replace(/'/g, "")}',${KOREAN_FONT_STACK}`);
|
|
2013
|
-
const height = Number(charPr.getAttribute("height"));
|
|
2014
|
-
if (Number.isFinite(height) && height > 0) rules.push(`font-size:${heightToPx(height)}px`);
|
|
2015
|
-
const color = charPr.getAttribute("textColor");
|
|
2016
|
-
if (color && HEX_COLOR.test(color) && color.toLowerCase() !== "#000000") rules.push(`color:${color}`);
|
|
2017
|
-
if (bold) rules.push("font-weight:bold");
|
|
2018
|
-
if (italic) rules.push("font-style:italic");
|
|
2019
|
-
if (underline) rules.push("text-decoration:underline");
|
|
2020
|
-
return rules.join(";");
|
|
2021
|
-
}
|
|
2022
|
-
function buildStyleIndex(entries) {
|
|
2023
|
-
const index = { charCss: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), fillColor: /* @__PURE__ */ new Map() };
|
|
2024
|
-
const name = [...entries.keys()].find((n) => /^Contents\/header\.xml$/i.test(n));
|
|
2025
|
-
const doc = name ? parseXml(new TextDecoder().decode(entries.get(name))) : null;
|
|
2026
|
-
if (!doc) return index;
|
|
2027
|
-
const faceByLangId = /* @__PURE__ */ new Map();
|
|
2028
|
-
for (const fontface of Array.from(doc.getElementsByTagNameNS("*", "fontface"))) {
|
|
2029
|
-
const lang = fontface.getAttribute("lang") ?? "";
|
|
2030
|
-
for (const font of Array.from(fontface.getElementsByTagNameNS("*", "font"))) {
|
|
2031
|
-
const id = font.getAttribute("id");
|
|
2032
|
-
const face = font.getAttribute("face");
|
|
2033
|
-
if (id !== null && face) faceByLangId.set(`${lang}:${id}`, face);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
for (const charPr of Array.from(doc.getElementsByTagNameNS("*", "charPr"))) {
|
|
2037
|
-
const id = charPr.getAttribute("id");
|
|
2038
|
-
if (id !== null) index.charCss.set(id, charPrCss(charPr, faceByLangId));
|
|
2039
|
-
}
|
|
2040
|
-
for (const paraPr of Array.from(doc.getElementsByTagNameNS("*", "paraPr"))) {
|
|
2041
|
-
const id = paraPr.getAttribute("id");
|
|
2042
|
-
if (id === null) continue;
|
|
2043
|
-
let raw = paraPr.getAttribute("align") ?? paraPr.getAttribute("alignment");
|
|
2044
|
-
for (const child of Array.from(paraPr.children)) {
|
|
2045
|
-
if (child.localName === "align") raw = child.getAttribute("horizontal") ?? child.getAttribute("align") ?? raw;
|
|
2046
|
-
}
|
|
2047
|
-
const css = raw ? ALIGN_CSS[raw.toUpperCase()] : void 0;
|
|
2048
|
-
if (css) index.paraAlign.set(id, css);
|
|
2049
|
-
}
|
|
2050
|
-
for (const borderFill of Array.from(doc.getElementsByTagNameNS("*", "borderFill"))) {
|
|
2051
|
-
const id = borderFill.getAttribute("id");
|
|
2052
|
-
const color = borderFill.getElementsByTagNameNS("*", "winBrush")[0]?.getAttribute("faceColor");
|
|
2053
|
-
if (id !== null && color && HEX_COLOR.test(color)) index.fillColor.set(id, color);
|
|
2054
|
-
}
|
|
2055
|
-
return index;
|
|
2056
|
-
}
|
|
2057
|
-
function paragraphInlineHtml(p, ctx) {
|
|
2058
|
-
const segments = [];
|
|
2059
|
-
const walk = (el, css) => {
|
|
2060
|
-
for (const child of Array.from(el.children)) {
|
|
2061
|
-
const tag = child.localName;
|
|
2062
|
-
if (tag === "tbl" || tag === "pic") continue;
|
|
2063
|
-
if (tag === "t") {
|
|
2064
|
-
const text = child.textContent ?? "";
|
|
2065
|
-
if (text) segments.push({ css, html: escapeHtml(text) });
|
|
2066
|
-
} else if (tag === "lineBreak") {
|
|
2067
|
-
segments.push({ css, html: "<br>" });
|
|
2068
|
-
} else if (tag === "run") {
|
|
2069
|
-
walk(child, ctx.charCss(child.getAttribute("charPrIDRef")));
|
|
2070
|
-
} else {
|
|
2071
|
-
walk(child, css);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
};
|
|
2075
|
-
walk(p, "");
|
|
2076
|
-
let out = "";
|
|
2077
|
-
for (let i = 0; i < segments.length; ) {
|
|
2078
|
-
const css = segments[i].css;
|
|
2079
|
-
let html = "";
|
|
2080
|
-
for (; i < segments.length && segments[i].css === css; i++) html += segments[i].html;
|
|
2081
|
-
out += css ? `<span style="${escapeHtml(css)}">${html}</span>` : html;
|
|
2082
|
-
}
|
|
2083
|
-
return out;
|
|
2084
|
-
}
|
|
2085
|
-
function paragraphToHtmlBlocks(p, blocks, ctx) {
|
|
2086
|
-
if (paragraphText(p).trim()) {
|
|
2087
|
-
const level = ctx.headingLevel(p);
|
|
2088
|
-
const align = ctx.paraAlign(p.getAttribute("paraPrIDRef"));
|
|
2089
|
-
const style = align ? ` style="text-align:${align}"` : "";
|
|
2090
|
-
const inline = paragraphInlineHtml(p, ctx);
|
|
2091
|
-
blocks.push(level ? `<h${level}${style}>${inline}</h${level}>` : `<p${style}>${inline}</p>`);
|
|
2092
|
-
}
|
|
2093
|
-
collectEmbeddedHtml(p, blocks, ctx);
|
|
2094
|
-
}
|
|
2095
|
-
function collectEmbeddedHtml(el, blocks, ctx) {
|
|
2096
|
-
for (const child of Array.from(el.children)) {
|
|
2097
|
-
if (child.localName === "tbl") {
|
|
2098
|
-
const html = tableToHtml(child, ctx);
|
|
2099
|
-
if (html) blocks.push(html);
|
|
2100
|
-
} else if (child.localName === "pic") {
|
|
2101
|
-
const img = ctx.imageTag(child);
|
|
2102
|
-
if (img) blocks.push(img);
|
|
2103
|
-
} else {
|
|
2104
|
-
collectEmbeddedHtml(child, blocks, ctx);
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
function tableToHtml(tbl, ctx) {
|
|
2109
|
-
let rowsHtml = "";
|
|
2110
|
-
for (const tr of Array.from(tbl.getElementsByTagNameNS("*", "tr"))) {
|
|
2111
|
-
if (tr.closest("tbl") !== tbl) continue;
|
|
2112
|
-
let cells = "";
|
|
2113
|
-
for (const tc of Array.from(tr.children)) {
|
|
2114
|
-
if (tc.localName !== "tc") continue;
|
|
2115
|
-
let colSpan = 1;
|
|
2116
|
-
let rowSpan = 1;
|
|
2117
|
-
const readSpan = (el) => {
|
|
2118
|
-
for (const attr of Array.from(el.attributes)) {
|
|
2119
|
-
if (/^colspan$/i.test(attr.localName)) colSpan = Math.max(1, Number(attr.value) || 1);
|
|
2120
|
-
else if (/^rowspan$/i.test(attr.localName)) rowSpan = Math.max(1, Number(attr.value) || 1);
|
|
2121
|
-
}
|
|
2122
|
-
};
|
|
2123
|
-
readSpan(tc);
|
|
2124
|
-
for (const child of Array.from(tc.children)) {
|
|
2125
|
-
if (child.localName === "cellSpan") readSpan(child);
|
|
2126
|
-
}
|
|
2127
|
-
const inner = [];
|
|
2128
|
-
const walkCell = (el) => {
|
|
2129
|
-
for (const child of Array.from(el.children)) {
|
|
2130
|
-
if (child.localName === "p") paragraphToHtmlBlocks(child, inner, ctx);
|
|
2131
|
-
else if (child.localName === "tbl") inner.push(tableToHtml(child, ctx));
|
|
2132
|
-
else walkCell(child);
|
|
2133
|
-
}
|
|
2134
|
-
};
|
|
2135
|
-
walkCell(tc);
|
|
2136
|
-
const bg = ctx.cellBackground(tc.getAttribute("borderFillIDRef"));
|
|
2137
|
-
const attrs = (colSpan > 1 ? ` colspan="${colSpan}"` : "") + (rowSpan > 1 ? ` rowspan="${rowSpan}"` : "") + (bg ? ` style="background:${bg}"` : "");
|
|
2138
|
-
cells += `<td${attrs}>${inner.join("")}</td>`;
|
|
2139
|
-
}
|
|
2140
|
-
if (cells) rowsHtml += `<tr>${cells}</tr>`;
|
|
2141
|
-
}
|
|
2142
|
-
return rowsHtml ? `<table>${rowsHtml}</table>` : "";
|
|
2143
|
-
}
|
|
2144
|
-
function sectionToHtml(xml, ctx) {
|
|
2145
|
-
const doc = parseXml(xml);
|
|
2146
|
-
if (!doc) {
|
|
2147
|
-
throw new Error("HWPX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid section XML).");
|
|
2148
|
-
}
|
|
2149
|
-
const blocks = [];
|
|
2150
|
-
const walk = (el) => {
|
|
2151
|
-
for (const child of Array.from(el.children)) {
|
|
2152
|
-
if (child.localName === "p") paragraphToHtmlBlocks(child, blocks, ctx);
|
|
2153
|
-
else if (child.localName !== "tbl") walk(child);
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
walk(doc.documentElement);
|
|
2157
|
-
return blocks;
|
|
2158
|
-
}
|
|
2159
|
-
async function hwpxToHtml(buffer) {
|
|
2160
|
-
const { entries, sections, headings, images } = await openHwpx(buffer);
|
|
2161
|
-
const styles2 = buildStyleIndex(entries);
|
|
2162
|
-
const ctx = {
|
|
2163
|
-
headingLevel: (p) => headingLevelOf(p, headings),
|
|
2164
|
-
charCss: (id) => id !== null ? styles2.charCss.get(id) ?? "" : "",
|
|
2165
|
-
paraAlign: (id) => id !== null ? styles2.paraAlign.get(id) ?? null : null,
|
|
2166
|
-
cellBackground: (id) => id !== null ? styles2.fillColor.get(id) ?? null : null,
|
|
2167
|
-
imageTag: (pic) => {
|
|
2168
|
-
const ref = binaryItemRef(pic);
|
|
2169
|
-
const url = ref ? images.dataUrl(ref) : null;
|
|
2170
|
-
return url ? `<img src="${url}" style="max-width:100%">` : null;
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2173
|
-
const blocks = [];
|
|
2174
|
-
for (const xml of sections) {
|
|
2175
|
-
blocks.push(...sectionToHtml(xml, ctx));
|
|
2176
|
-
}
|
|
2177
|
-
if (images.omitted > 0) {
|
|
2178
|
-
blocks.push(`<p>(\uC774\uBBF8\uC9C0 ${images.omitted}\uC7A5 \uC0DD\uB7B5 / ${images.omitted} images omitted)</p>`);
|
|
2179
|
-
}
|
|
2180
|
-
return koreanDocHtml(blocks.join("\n"));
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
// src/io/hwp.ts
|
|
2184
|
-
var ERR = {
|
|
2185
|
-
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.",
|
|
2186
|
-
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.',
|
|
2187
|
-
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.",
|
|
2188
|
-
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.",
|
|
2189
|
-
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.`,
|
|
2190
|
-
corrupt: (detail) => `\uC190\uC0C1\uB41C HWP \uD30C\uC77C\uC785\uB2C8\uB2E4 (${detail}). / Corrupt HWP file (${detail}).`,
|
|
2191
|
-
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."
|
|
2192
|
-
};
|
|
2193
|
-
var ENDOFCHAIN = 4294967294;
|
|
2194
|
-
var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
2195
|
-
function parseCfb(bytes) {
|
|
2196
|
-
if (bytes.length < 512 || CFB_SIGNATURE.some((b, i) => bytes[i] !== b)) {
|
|
2197
|
-
throw new Error(ERR.notCfb);
|
|
2198
|
-
}
|
|
2199
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2200
|
-
const sectorSize = 1 << view.getUint16(30, true);
|
|
2201
|
-
const miniSectorSize = 1 << view.getUint16(32, true);
|
|
2202
|
-
const numFatSectors = view.getUint32(44, true);
|
|
2203
|
-
const firstDirSector = view.getUint32(48, true);
|
|
2204
|
-
const miniCutoff = view.getUint32(56, true);
|
|
2205
|
-
const firstMiniFatSector = view.getUint32(60, true);
|
|
2206
|
-
const firstDifatSector = view.getUint32(68, true);
|
|
2207
|
-
const totalSectors = Math.max(0, Math.ceil((bytes.length - 512) / sectorSize));
|
|
2208
|
-
const sectorBytes = (sector) => {
|
|
2209
|
-
const start = 512 + sector * sectorSize;
|
|
2210
|
-
if (sector >= totalSectors || start >= bytes.length) {
|
|
2211
|
-
throw new Error(ERR.corrupt("sector out of range"));
|
|
2212
|
-
}
|
|
2213
|
-
return bytes.subarray(start, start + sectorSize);
|
|
2214
|
-
};
|
|
2215
|
-
const fatSectorIds = [];
|
|
2216
|
-
for (let i = 0; i < 109 && fatSectorIds.length < numFatSectors; i++) {
|
|
2217
|
-
fatSectorIds.push(view.getUint32(76 + i * 4, true));
|
|
2218
|
-
}
|
|
2219
|
-
let difatSector = firstDifatSector;
|
|
2220
|
-
let difatHops = 0;
|
|
2221
|
-
while (difatSector !== ENDOFCHAIN && difatSector < 4294967290) {
|
|
2222
|
-
if (difatHops++ > totalSectors) throw new Error(ERR.corrupt("DIFAT chain loops"));
|
|
2223
|
-
const s = sectorBytes(difatSector);
|
|
2224
|
-
const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
|
|
2225
|
-
const perSector = sectorSize / 4 - 1;
|
|
2226
|
-
for (let i = 0; i < perSector && fatSectorIds.length < numFatSectors; i++) {
|
|
2227
|
-
fatSectorIds.push(sv.getUint32(i * 4, true));
|
|
2228
|
-
}
|
|
2229
|
-
difatSector = sv.getUint32(sectorSize - 4, true);
|
|
2230
|
-
}
|
|
2231
|
-
const fat = new Uint32Array(fatSectorIds.length * (sectorSize / 4));
|
|
2232
|
-
fatSectorIds.forEach((id, idx) => {
|
|
2233
|
-
const s = sectorBytes(id);
|
|
2234
|
-
const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
|
|
2235
|
-
for (let i = 0; i < sectorSize / 4; i++) fat[idx * (sectorSize / 4) + i] = sv.getUint32(i * 4, true);
|
|
2236
|
-
});
|
|
2237
|
-
const readChain = (start, size) => {
|
|
2238
|
-
const out = new Uint8Array(size);
|
|
2239
|
-
let sector = start;
|
|
2240
|
-
let written = 0;
|
|
2241
|
-
let hops = 0;
|
|
2242
|
-
while (sector !== ENDOFCHAIN && written < size) {
|
|
2243
|
-
if (hops++ > totalSectors || sector >= fat.length) throw new Error(ERR.corrupt("FAT chain loops"));
|
|
2244
|
-
const chunk = sectorBytes(sector);
|
|
2245
|
-
out.set(chunk.subarray(0, Math.min(sectorSize, size - written)), written);
|
|
2246
|
-
written += sectorSize;
|
|
2247
|
-
sector = fat[sector];
|
|
2248
|
-
}
|
|
2249
|
-
return out;
|
|
2250
|
-
};
|
|
2251
|
-
const dirSectors = [];
|
|
2252
|
-
let dirSector = firstDirSector;
|
|
2253
|
-
let dirHops = 0;
|
|
2254
|
-
while (dirSector !== ENDOFCHAIN) {
|
|
2255
|
-
if (dirHops++ > totalSectors || dirSector >= fat.length) throw new Error(ERR.corrupt("directory chain loops"));
|
|
2256
|
-
dirSectors.push(sectorBytes(dirSector));
|
|
2257
|
-
dirSector = fat[dirSector];
|
|
2258
|
-
}
|
|
2259
|
-
const entries = [];
|
|
2260
|
-
for (const sector of dirSectors) {
|
|
2261
|
-
const sv = new DataView(sector.buffer, sector.byteOffset, sector.byteLength);
|
|
2262
|
-
for (let off = 0; off + 128 <= sector.length; off += 128) {
|
|
2263
|
-
const nameLen = sv.getUint16(off + 64, true);
|
|
2264
|
-
const type = sv.getUint8(off + 66);
|
|
2265
|
-
if (type === 0 || nameLen < 2 || nameLen > 64) continue;
|
|
2266
|
-
let name = "";
|
|
2267
|
-
for (let i = 0; i < nameLen - 2; i += 2) name += String.fromCharCode(sv.getUint16(off + i, true));
|
|
2268
|
-
entries.push({
|
|
2269
|
-
name,
|
|
2270
|
-
type,
|
|
2271
|
-
startSector: sv.getUint32(off + 116, true),
|
|
2272
|
-
// Size is a uint64, but HWP streams are far below 4 GB — the low half suffices.
|
|
2273
|
-
size: sv.getUint32(off + 120, true)
|
|
2274
|
-
});
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
const root = entries.find((e) => e.type === 5);
|
|
2278
|
-
if (!root) throw new Error(ERR.corrupt("missing root directory entry"));
|
|
2279
|
-
let miniStream = null;
|
|
2280
|
-
let miniFat = null;
|
|
2281
|
-
const lazyMini = () => {
|
|
2282
|
-
if (miniStream && miniFat) return;
|
|
2283
|
-
miniStream = readChain(root.startSector, root.size);
|
|
2284
|
-
const miniFatSectors = Math.ceil(root.size / miniSectorSize) + 1;
|
|
2285
|
-
const raw = readChain(firstMiniFatSector, miniFatSectors * 4 + sectorSize);
|
|
2286
|
-
const rv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
2287
|
-
miniFat = new Uint32Array(Math.floor(raw.length / 4));
|
|
2288
|
-
for (let i = 0; i < miniFat.length; i++) miniFat[i] = rv.getUint32(i * 4, true);
|
|
2289
|
-
};
|
|
2290
|
-
const readMiniChain = (start, size) => {
|
|
2291
|
-
lazyMini();
|
|
2292
|
-
const out = new Uint8Array(size);
|
|
2293
|
-
let sector = start;
|
|
2294
|
-
let written = 0;
|
|
2295
|
-
let hops = 0;
|
|
2296
|
-
const maxMini = Math.ceil(miniStream.length / miniSectorSize);
|
|
2297
|
-
while (sector !== ENDOFCHAIN && written < size) {
|
|
2298
|
-
if (hops++ > maxMini || sector >= miniFat.length || sector >= maxMini) {
|
|
2299
|
-
throw new Error(ERR.corrupt("mini FAT chain loops"));
|
|
2300
|
-
}
|
|
2301
|
-
const at = sector * miniSectorSize;
|
|
2302
|
-
out.set(miniStream.subarray(at, at + Math.min(miniSectorSize, size - written)), written);
|
|
2303
|
-
written += miniSectorSize;
|
|
2304
|
-
sector = miniFat[sector];
|
|
2305
|
-
}
|
|
2306
|
-
return out;
|
|
2307
|
-
};
|
|
2308
|
-
return {
|
|
2309
|
-
entries,
|
|
2310
|
-
readStream: (entry) => entry.size < miniCutoff && entry.type !== 5 ? readMiniChain(entry.startSector, entry.size) : readChain(entry.startSector, entry.size)
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
|
-
var HWP_SIGNATURE = "HWP Document File";
|
|
2314
|
-
function parseFileHeader(bytes) {
|
|
2315
|
-
if (bytes.length < 40) throw new Error(ERR.notHwp);
|
|
2316
|
-
let sig = "";
|
|
2317
|
-
for (let i = 0; i < HWP_SIGNATURE.length; i++) sig += String.fromCharCode(bytes[i]);
|
|
2318
|
-
if (sig !== HWP_SIGNATURE) throw new Error(ERR.notHwp);
|
|
2319
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2320
|
-
const version = view.getUint32(32, true);
|
|
2321
|
-
const major = version >>> 24 & 255;
|
|
2322
|
-
if (major < 5) {
|
|
2323
|
-
throw new Error(ERR.legacy(`${major}.${version >>> 16 & 255}`));
|
|
2324
|
-
}
|
|
2325
|
-
const flags = view.getUint32(36, true);
|
|
2326
|
-
if (flags & 2) throw new Error(ERR.encrypted);
|
|
2327
|
-
if (flags & 4) throw new Error(ERR.drm);
|
|
2328
|
-
return { compressed: (flags & 1) !== 0 };
|
|
2329
|
-
}
|
|
2330
|
-
var HWPTAG_PARA_TEXT = 16 + 51;
|
|
2331
|
-
var HWPTAG_CTRL_HEADER = 16 + 55;
|
|
2332
|
-
var HWPTAG_LIST_HEADER = 16 + 56;
|
|
2333
|
-
var HWPTAG_TABLE = 16 + 61;
|
|
2334
|
-
var CTRL_ID_TABLE = 1952607264;
|
|
2335
|
-
function decodeParaText(view, offset, byteLength) {
|
|
2336
|
-
const paragraphs = [];
|
|
2337
|
-
let text = "";
|
|
2338
|
-
const count = byteLength >> 1;
|
|
2339
|
-
for (let i = 0; i < count; i++) {
|
|
2340
|
-
const code = view.getUint16(offset + i * 2, true);
|
|
2341
|
-
if (code >= 32) {
|
|
2342
|
-
text += String.fromCharCode(code);
|
|
2343
|
-
continue;
|
|
2344
|
-
}
|
|
2345
|
-
switch (code) {
|
|
2346
|
-
case 13:
|
|
2347
|
-
paragraphs.push(text);
|
|
2348
|
-
text = "";
|
|
2349
|
-
break;
|
|
2350
|
-
case 10:
|
|
2351
|
-
text += "\n";
|
|
2352
|
-
break;
|
|
2353
|
-
case 0:
|
|
2354
|
-
case 24:
|
|
2355
|
-
case 25:
|
|
2356
|
-
case 26:
|
|
2357
|
-
case 27:
|
|
2358
|
-
case 28:
|
|
2359
|
-
case 29:
|
|
2360
|
-
case 30:
|
|
2361
|
-
case 31:
|
|
2362
|
-
break;
|
|
2363
|
-
// 1-WCHAR char controls with no text representation
|
|
2364
|
-
case 9:
|
|
2365
|
-
i += 7;
|
|
2366
|
-
text += " ";
|
|
2367
|
-
break;
|
|
2368
|
-
default:
|
|
2369
|
-
i += 7;
|
|
2370
|
-
break;
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
if (text) paragraphs.push(text);
|
|
2374
|
-
return paragraphs;
|
|
2375
|
-
}
|
|
2376
|
-
function parseRecords(section, view) {
|
|
2377
|
-
const records = [];
|
|
2378
|
-
let off = 0;
|
|
2379
|
-
while (off + 4 <= section.length) {
|
|
2380
|
-
const header = view.getUint32(off, true);
|
|
2381
|
-
off += 4;
|
|
2382
|
-
const tag = header & 1023;
|
|
2383
|
-
const level = header >>> 10 & 1023;
|
|
2384
|
-
let size = header >>> 20 & 4095;
|
|
2385
|
-
if (size === 4095) {
|
|
2386
|
-
if (off + 4 > section.length) throw new Error(ERR.corrupt("truncated record header"));
|
|
2387
|
-
size = view.getUint32(off, true);
|
|
2388
|
-
off += 4;
|
|
2389
|
-
}
|
|
2390
|
-
if (off + size > section.length) throw new Error(ERR.corrupt("record overruns section"));
|
|
2391
|
-
records.push({ tag, level, start: off, size });
|
|
2392
|
-
off += size;
|
|
2393
|
-
}
|
|
2394
|
-
return records;
|
|
2395
|
-
}
|
|
2396
|
-
function tableBlocks(view, subtree, ctrlLevel) {
|
|
2397
|
-
const tableRec = subtree.find((r) => r.tag === HWPTAG_TABLE);
|
|
2398
|
-
const cellLevel = ctrlLevel + 1;
|
|
2399
|
-
const cells = [];
|
|
2400
|
-
const flat = [];
|
|
2401
|
-
let current = null;
|
|
2402
|
-
let surprised = tableRec === void 0 || tableRec.size < 8;
|
|
2403
|
-
for (const rec of subtree) {
|
|
2404
|
-
if (rec.tag === HWPTAG_LIST_HEADER && rec.level === cellLevel) {
|
|
2405
|
-
current = [];
|
|
2406
|
-
cells.push(current);
|
|
2407
|
-
} else if (rec.tag === HWPTAG_PARA_TEXT) {
|
|
2408
|
-
const paragraphs = decodeParaText(view, rec.start, rec.size);
|
|
2409
|
-
flat.push(...paragraphs);
|
|
2410
|
-
if (current) current.push(...paragraphs);
|
|
2411
|
-
else surprised = true;
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
if (!surprised) {
|
|
2415
|
-
const nRows = view.getUint16(tableRec.start + 4, true);
|
|
2416
|
-
const nCols = view.getUint16(tableRec.start + 6, true);
|
|
2417
|
-
if (nRows >= 1 && nCols >= 1 && cells.length === nRows * nCols) {
|
|
2418
|
-
const texts = cells.map((c) => c.filter((p) => p.length > 0).join("\n"));
|
|
2419
|
-
const rows = Array.from({ length: nRows }, (_, r) => texts.slice(r * nCols, (r + 1) * nCols));
|
|
2420
|
-
return [{ kind: "table", rows }];
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
return flat.map((text) => ({ kind: "paragraph", text }));
|
|
2424
|
-
}
|
|
2425
|
-
function extractSectionBlocks(section) {
|
|
2426
|
-
const view = new DataView(section.buffer, section.byteOffset, section.byteLength);
|
|
2427
|
-
const records = parseRecords(section, view);
|
|
2428
|
-
const blocks = [];
|
|
2429
|
-
let i = 0;
|
|
2430
|
-
while (i < records.length) {
|
|
2431
|
-
const rec = records[i];
|
|
2432
|
-
if (rec.tag === HWPTAG_CTRL_HEADER && rec.size >= 4 && view.getUint32(rec.start, true) === CTRL_ID_TABLE) {
|
|
2433
|
-
let end = i + 1;
|
|
2434
|
-
while (end < records.length && records[end].level > rec.level) end++;
|
|
2435
|
-
blocks.push(...tableBlocks(view, records.slice(i + 1, end), rec.level));
|
|
2436
|
-
i = end;
|
|
2437
|
-
continue;
|
|
2438
|
-
}
|
|
2439
|
-
if (rec.tag === HWPTAG_PARA_TEXT) {
|
|
2440
|
-
for (const text of decodeParaText(view, rec.start, rec.size)) blocks.push({ kind: "paragraph", text });
|
|
2441
|
-
}
|
|
2442
|
-
i++;
|
|
2443
|
-
}
|
|
2444
|
-
return blocks;
|
|
2445
|
-
}
|
|
2446
|
-
function blocksToHtml(blocks) {
|
|
2447
|
-
const inline = (text) => escapeHtml(text).replace(/\n/g, "<br>");
|
|
2448
|
-
const out = [];
|
|
2449
|
-
for (const block of blocks) {
|
|
2450
|
-
if (block.kind === "paragraph") {
|
|
2451
|
-
if (block.text.length > 0) out.push(`<p>${inline(block.text)}</p>`);
|
|
2452
|
-
continue;
|
|
2453
|
-
}
|
|
2454
|
-
const rows = block.rows.map((row) => `<tr>${row.map((cell) => `<td>${inline(cell)}</td>`).join("")}</tr>`);
|
|
2455
|
-
out.push(`<table>${rows.join("")}</table>`);
|
|
2456
|
-
}
|
|
2457
|
-
return out;
|
|
2458
|
-
}
|
|
2459
|
-
async function inflateRaw2(data) {
|
|
2460
|
-
if (typeof DecompressionStream === "undefined") throw new Error(ERR.noInflate);
|
|
2461
|
-
const stream = new DecompressionStream("deflate-raw");
|
|
2462
|
-
const writer = stream.writable.getWriter();
|
|
2463
|
-
const writing = writer.write(data).then(() => writer.close());
|
|
2464
|
-
writing.catch(() => {
|
|
2465
|
-
});
|
|
2466
|
-
const reader = stream.readable.getReader();
|
|
2467
|
-
const chunks = [];
|
|
2468
|
-
let total = 0;
|
|
2469
|
-
for (; ; ) {
|
|
2470
|
-
const { done, value } = await reader.read();
|
|
2471
|
-
if (done) break;
|
|
2472
|
-
chunks.push(value);
|
|
2473
|
-
total += value.length;
|
|
2474
|
-
}
|
|
2475
|
-
const out = new Uint8Array(total);
|
|
2476
|
-
let at = 0;
|
|
2477
|
-
for (const chunk of chunks) {
|
|
2478
|
-
out.set(chunk, at);
|
|
2479
|
-
at += chunk.length;
|
|
2480
|
-
}
|
|
2481
|
-
return out;
|
|
2482
|
-
}
|
|
2483
|
-
async function extractBlocks(buffer) {
|
|
2484
|
-
const cfb = parseCfb(new Uint8Array(buffer));
|
|
2485
|
-
const headerEntry = cfb.entries.find((e) => e.type === 2 && e.name === "FileHeader");
|
|
2486
|
-
if (!headerEntry) throw new Error(ERR.notHwp);
|
|
2487
|
-
const header = parseFileHeader(cfb.readStream(headerEntry));
|
|
2488
|
-
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]));
|
|
2489
|
-
const blocks = [];
|
|
2490
|
-
for (const { entry } of sections) {
|
|
2491
|
-
let bytes = cfb.readStream(entry);
|
|
2492
|
-
if (header.compressed) bytes = await inflateRaw2(bytes);
|
|
2493
|
-
blocks.push(...extractSectionBlocks(bytes));
|
|
2494
|
-
}
|
|
2495
|
-
return blocks;
|
|
2496
|
-
}
|
|
2497
|
-
async function hwpToHtml(buffer) {
|
|
2498
|
-
return koreanDocHtml(blocksToHtml(await extractBlocks(buffer)).join("\n"));
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
// src/io/docx.ts
|
|
2502
|
-
function childOf(el, name) {
|
|
2503
|
-
for (const c of Array.from(el.children)) if (c.localName === name) return c;
|
|
2504
|
-
return null;
|
|
2505
|
-
}
|
|
2506
|
-
function attrOf(el, name) {
|
|
2507
|
-
for (const a of Array.from(el.attributes)) if (a.localName === name) return a.value;
|
|
2508
|
-
return null;
|
|
2509
|
-
}
|
|
2510
|
-
function toggleOn(rPr, name) {
|
|
2511
|
-
const prop = rPr && childOf(rPr, name);
|
|
2512
|
-
if (!prop) return false;
|
|
2513
|
-
const val = attrOf(prop, "val");
|
|
2514
|
-
return val !== "0" && val !== "false" && val !== "none";
|
|
2515
|
-
}
|
|
2516
|
-
function headingLevel(pPr) {
|
|
2517
|
-
const style = pPr && childOf(pPr, "pStyle");
|
|
2518
|
-
const id = style && attrOf(style, "val");
|
|
2519
|
-
const m = id && /^heading ?([1-6])$/i.exec(id);
|
|
2520
|
-
return m ? Number(m[1]) : 0;
|
|
2521
|
-
}
|
|
2522
|
-
function listLevel(pPr) {
|
|
2523
|
-
const numPr = pPr && childOf(pPr, "numPr");
|
|
2524
|
-
if (!numPr) return null;
|
|
2525
|
-
const ilvl = childOf(numPr, "ilvl");
|
|
2526
|
-
return Number(ilvl && attrOf(ilvl, "val")) || 0;
|
|
2527
|
-
}
|
|
2528
|
-
function runText(run) {
|
|
2529
|
-
let out = "";
|
|
2530
|
-
for (const c of Array.from(run.children)) {
|
|
2531
|
-
if (c.localName === "t") {
|
|
2532
|
-
const raw = c.textContent ?? "";
|
|
2533
|
-
out += attrOf(c, "space") === "preserve" ? raw : raw.trim();
|
|
2534
|
-
} else if (c.localName === "br") out += "\n";
|
|
2535
|
-
else if (c.localName === "tab") out += " ";
|
|
2536
|
-
}
|
|
2537
|
-
return out;
|
|
2538
|
-
}
|
|
2539
|
-
function paragraphSegments(p) {
|
|
2540
|
-
const segs = [];
|
|
2541
|
-
const walk = (el) => {
|
|
2542
|
-
for (const c of Array.from(el.children)) {
|
|
2543
|
-
if (c.localName === "pPr") continue;
|
|
2544
|
-
if (c.localName === "r") {
|
|
2545
|
-
const rPr = childOf(c, "rPr");
|
|
2546
|
-
const text = runText(c);
|
|
2547
|
-
if (text) segs.push({ text, bold: toggleOn(rPr, "b"), italic: toggleOn(rPr, "i") });
|
|
2548
|
-
} else walk(c);
|
|
2549
|
-
}
|
|
2550
|
-
};
|
|
2551
|
-
walk(p);
|
|
2552
|
-
return segs;
|
|
2553
|
-
}
|
|
2554
|
-
function renderSegments(segs) {
|
|
2555
|
-
const merged = [];
|
|
2556
|
-
for (const s of segs) {
|
|
2557
|
-
const last = merged[merged.length - 1];
|
|
2558
|
-
if (last && last.bold === s.bold && last.italic === s.italic) last.text += s.text;
|
|
2559
|
-
else merged.push({ ...s });
|
|
2560
|
-
}
|
|
2561
|
-
return merged.map(({ text, bold, italic }) => {
|
|
2562
|
-
if (!bold && !italic) return text;
|
|
2563
|
-
const [, lead, core, trail] = /^(\s*)([\s\S]*?)(\s*)$/.exec(text);
|
|
2564
|
-
if (!core) return text;
|
|
2565
|
-
const mark = bold && italic ? "***" : bold ? "**" : "*";
|
|
2566
|
-
return `${lead}${mark}${core}${mark}${trail}`;
|
|
2567
|
-
}).join("");
|
|
2568
|
-
}
|
|
2569
|
-
function paragraphToBlock(p) {
|
|
2570
|
-
const text = renderSegments(paragraphSegments(p)).trim();
|
|
2571
|
-
if (!text) return null;
|
|
2572
|
-
const pPr = childOf(p, "pPr");
|
|
2573
|
-
const heading = headingLevel(pPr);
|
|
2574
|
-
if (heading) return { kind: "other", text: `${"#".repeat(heading)} ${text}` };
|
|
2575
|
-
const level = listLevel(pPr);
|
|
2576
|
-
if (level !== null) return { kind: "list", text: `${" ".repeat(level)}- ${text}` };
|
|
2577
|
-
return { kind: "other", text };
|
|
2578
|
-
}
|
|
2579
|
-
function tableToMarkdown(tbl) {
|
|
2580
|
-
const rows = [];
|
|
2581
|
-
for (const tr of Array.from(tbl.children)) {
|
|
2582
|
-
if (tr.localName !== "tr") continue;
|
|
2583
|
-
const cells = [];
|
|
2584
|
-
for (const tc of Array.from(tr.children)) {
|
|
2585
|
-
if (tc.localName !== "tc") continue;
|
|
2586
|
-
const text = Array.from(tc.getElementsByTagNameNS("*", "p")).filter((p) => p.closest("tbl") === tbl).map((p) => renderSegments(paragraphSegments(p)).trim()).filter(Boolean).join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
2587
|
-
cells.push(text);
|
|
2588
|
-
}
|
|
2589
|
-
if (cells.length) rows.push(cells);
|
|
2590
|
-
}
|
|
2591
|
-
if (!rows.length) return "";
|
|
2592
|
-
const width = Math.max(...rows.map((r) => r.length));
|
|
2593
|
-
const pad = (r) => Array.from({ length: width }, (_, i) => r[i] ?? "");
|
|
2594
|
-
const line = (r) => `| ${pad(r).join(" | ")} |`;
|
|
2595
|
-
const [head, ...body] = rows;
|
|
2596
|
-
return [line(head), `| ${Array(width).fill("---").join(" | ")} |`, ...body.map(line)].join("\n");
|
|
2597
|
-
}
|
|
2598
|
-
function documentToMarkdown(doc) {
|
|
2599
|
-
const blocks = [];
|
|
2600
|
-
const walk = (el) => {
|
|
2601
|
-
for (const c of Array.from(el.children)) {
|
|
2602
|
-
if (c.localName === "p") {
|
|
2603
|
-
const block = paragraphToBlock(c);
|
|
2604
|
-
if (block) blocks.push(block);
|
|
2605
|
-
} else if (c.localName === "tbl") {
|
|
2606
|
-
const md = tableToMarkdown(c);
|
|
2607
|
-
if (md) blocks.push({ kind: "other", text: md });
|
|
2608
|
-
} else {
|
|
2609
|
-
walk(c);
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
};
|
|
2613
|
-
walk(doc.documentElement);
|
|
2614
|
-
const out = [];
|
|
2615
|
-
let prevKind = null;
|
|
2616
|
-
for (const { kind, text } of blocks) {
|
|
2617
|
-
if (kind === "list" && prevKind === "list") out[out.length - 1] += `
|
|
2618
|
-
${text}`;
|
|
2619
|
-
else out.push(text);
|
|
2620
|
-
prevKind = kind;
|
|
2621
|
-
}
|
|
2622
|
-
return out.join("\n\n").trim();
|
|
2623
|
-
}
|
|
2624
|
-
async function docxToMarkdown(buffer) {
|
|
2625
|
-
const entries = await readZip(buffer);
|
|
2626
|
-
const body = entries.get("word/document.xml");
|
|
2627
|
-
if (!body) {
|
|
2628
|
-
throw new Error("DOCX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(word/document.xml)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a DOCX document \u2014 missing word/document.xml).");
|
|
2629
|
-
}
|
|
2630
|
-
const doc = new DOMParser().parseFromString(new TextDecoder().decode(body), "application/xml");
|
|
2631
|
-
if (doc.querySelector("parsererror")) {
|
|
2632
|
-
throw new Error("DOCX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid document XML).");
|
|
2633
|
-
}
|
|
2634
|
-
return documentToMarkdown(doc);
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
// src/io/pptx.ts
|
|
2638
|
-
function parseXml2(xml) {
|
|
2639
|
-
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
2640
|
-
return doc.querySelector("parsererror") ? null : doc;
|
|
2641
|
-
}
|
|
2642
|
-
function childOf2(el, name) {
|
|
2643
|
-
for (const c of Array.from(el.children)) if (c.localName === name) return c;
|
|
2644
|
-
return null;
|
|
2645
|
-
}
|
|
2646
|
-
function attrOf2(el, name) {
|
|
2647
|
-
for (const a of Array.from(el.attributes)) if (a.localName === name) return a.value;
|
|
2648
|
-
return null;
|
|
2649
|
-
}
|
|
2650
|
-
var isOn = (value) => value === "1" || value === "true";
|
|
2651
|
-
function relIdOf(el) {
|
|
2652
|
-
for (const a of Array.from(el.attributes)) {
|
|
2653
|
-
if (a.localName === "id" && /relationships/i.test(a.namespaceURI ?? "")) return a.value;
|
|
2654
|
-
}
|
|
2655
|
-
for (const a of Array.from(el.attributes)) {
|
|
2656
|
-
if (/:id$/i.test(a.name)) return a.value;
|
|
2657
|
-
}
|
|
2658
|
-
return null;
|
|
2659
|
-
}
|
|
2660
|
-
function resolveTarget(baseDir, target) {
|
|
2661
|
-
if (target.startsWith("/")) return target.slice(1);
|
|
2662
|
-
const parts = baseDir ? baseDir.split("/") : [];
|
|
2663
|
-
for (const seg of target.split("/")) {
|
|
2664
|
-
if (seg === "..") parts.pop();
|
|
2665
|
-
else if (seg && seg !== ".") parts.push(seg);
|
|
2666
|
-
}
|
|
2667
|
-
return parts.join("/");
|
|
2668
|
-
}
|
|
2669
|
-
function relsOf(entries, partName) {
|
|
2670
|
-
const rels = /* @__PURE__ */ new Map();
|
|
2671
|
-
const slash = partName.lastIndexOf("/");
|
|
2672
|
-
const dir = partName.slice(0, slash);
|
|
2673
|
-
const relsPart = entries.get(`${dir}/_rels/${partName.slice(slash + 1)}.rels`);
|
|
2674
|
-
const doc = relsPart ? parseXml2(new TextDecoder().decode(relsPart)) : null;
|
|
2675
|
-
if (!doc) return rels;
|
|
2676
|
-
for (const rel of Array.from(doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
2677
|
-
const id = rel.getAttribute("Id");
|
|
2678
|
-
const target = rel.getAttribute("Target");
|
|
2679
|
-
if (id && target) rels.set(id, { type: rel.getAttribute("Type") ?? "", target: resolveTarget(dir, target) });
|
|
2680
|
-
}
|
|
2681
|
-
return rels;
|
|
2682
|
-
}
|
|
2683
|
-
function relOfType(rels, suffix) {
|
|
2684
|
-
for (const rel of rels.values()) if (rel.type.endsWith(suffix)) return rel;
|
|
2685
|
-
return void 0;
|
|
2686
|
-
}
|
|
2687
|
-
var DEFAULT_SLIDE_CX = 12192e3;
|
|
2688
|
-
var DEFAULT_SLIDE_CY = 6858e3;
|
|
2689
|
-
function emuOf(parent, child, attr) {
|
|
2690
|
-
const el = childOf2(parent, child);
|
|
2691
|
-
const n = el ? Number(attrOf2(el, attr)) : NaN;
|
|
2692
|
-
return Number.isFinite(n) ? n : null;
|
|
2693
|
-
}
|
|
2694
|
-
function xfrmOf(pr) {
|
|
2695
|
-
const xfrm = childOf2(pr, "xfrm");
|
|
2696
|
-
if (!xfrm) return null;
|
|
2697
|
-
const x = emuOf(xfrm, "off", "x");
|
|
2698
|
-
const y = emuOf(xfrm, "off", "y");
|
|
2699
|
-
const cx = emuOf(xfrm, "ext", "cx");
|
|
2700
|
-
const cy = emuOf(xfrm, "ext", "cy");
|
|
2701
|
-
if (x === null || y === null || cx === null || cy === null) return null;
|
|
2702
|
-
return { x, y, cx, cy, rot: Number(attrOf2(xfrm, "rot")) || 0 };
|
|
2703
|
-
}
|
|
2704
|
-
var IDENTITY_MAP = { tx: 0, ty: 0, sx: 1, sy: 1, chx: 0, chy: 0 };
|
|
2705
|
-
function groupChildMap(grp, map) {
|
|
2706
|
-
const pr = childOf2(grp, "grpSpPr");
|
|
2707
|
-
const xfrm = pr && childOf2(pr, "xfrm");
|
|
2708
|
-
if (!pr || !xfrm) return map;
|
|
2709
|
-
const off = xfrmOf(pr);
|
|
2710
|
-
const chx = emuOf(xfrm, "chOff", "x");
|
|
2711
|
-
const chy = emuOf(xfrm, "chOff", "y");
|
|
2712
|
-
const chCx = emuOf(xfrm, "chExt", "cx");
|
|
2713
|
-
const chCy = emuOf(xfrm, "chExt", "cy");
|
|
2714
|
-
if (!off || chx === null || chy === null || !chCx || !chCy || chCx < 0 || chCy < 0) return map;
|
|
2715
|
-
return {
|
|
2716
|
-
tx: map.tx + (off.x - map.chx) * map.sx,
|
|
2717
|
-
ty: map.ty + (off.y - map.chy) * map.sy,
|
|
2718
|
-
sx: map.sx * (off.cx / chCx),
|
|
2719
|
-
sy: map.sy * (off.cy / chCy),
|
|
2720
|
-
chx,
|
|
2721
|
-
chy
|
|
2722
|
-
};
|
|
2723
|
-
}
|
|
2724
|
-
var round2 = (n) => Math.round(n * 100) / 100;
|
|
2725
|
-
var pct = (value, total) => round2(value / total * 100);
|
|
2726
|
-
var DEFAULT_CLR_MAP = [
|
|
2727
|
-
["bg1", "lt1"],
|
|
2728
|
-
["tx1", "dk1"],
|
|
2729
|
-
["bg2", "lt2"],
|
|
2730
|
-
["tx2", "dk2"]
|
|
2731
|
-
];
|
|
2732
|
-
var clamp01 = (n) => Math.max(0, Math.min(1, n));
|
|
2733
|
-
var byteHex = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
2734
|
-
function rgbOf(hex) {
|
|
2735
|
-
return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
|
|
2736
|
-
}
|
|
2737
|
-
function rgbToHsl(r, g, b) {
|
|
2738
|
-
r /= 255;
|
|
2739
|
-
g /= 255;
|
|
2740
|
-
b /= 255;
|
|
2741
|
-
const max = Math.max(r, g, b);
|
|
2742
|
-
const min = Math.min(r, g, b);
|
|
2743
|
-
const l = (max + min) / 2;
|
|
2744
|
-
if (max === min) return [0, 0, l];
|
|
2745
|
-
const d = max - min;
|
|
2746
|
-
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2747
|
-
const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
|
|
2748
|
-
return [h, s, l];
|
|
2749
|
-
}
|
|
2750
|
-
function hslToRgb(h, s, l) {
|
|
2751
|
-
if (s === 0) return [l * 255, l * 255, l * 255];
|
|
2752
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
2753
|
-
const p = 2 * l - q;
|
|
2754
|
-
const channel = (t) => {
|
|
2755
|
-
if (t < 0) t += 1;
|
|
2756
|
-
if (t > 1) t -= 1;
|
|
2757
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
2758
|
-
if (t < 1 / 2) return q;
|
|
2759
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
2760
|
-
return p;
|
|
2761
|
-
};
|
|
2762
|
-
return [channel(h + 1 / 3) * 255, channel(h) * 255, channel(h - 1 / 3) * 255];
|
|
2763
|
-
}
|
|
2764
|
-
var ColorScheme = class {
|
|
2765
|
-
constructor(scheme, clrMap) {
|
|
2766
|
-
this.scheme = scheme;
|
|
2767
|
-
this.clrMap = clrMap;
|
|
2768
|
-
}
|
|
2769
|
-
resolve(clr) {
|
|
2770
|
-
let hex = null;
|
|
2771
|
-
if (clr.localName === "srgbClr") hex = attrOf2(clr, "val");
|
|
2772
|
-
else if (clr.localName === "sysClr") hex = attrOf2(clr, "lastClr");
|
|
2773
|
-
else if (clr.localName === "schemeClr") {
|
|
2774
|
-
const name = attrOf2(clr, "val") ?? "";
|
|
2775
|
-
hex = this.scheme.get(this.clrMap.get(name) ?? name) ?? null;
|
|
2776
|
-
}
|
|
2777
|
-
if (!hex || !/^[0-9a-fA-F]{6}$/.test(hex)) return void 0;
|
|
2778
|
-
let [r, g, b] = rgbOf(hex);
|
|
2779
|
-
for (const t of Array.from(clr.children)) {
|
|
2780
|
-
const raw = Number(attrOf2(t, "val"));
|
|
2781
|
-
if (!Number.isFinite(raw)) continue;
|
|
2782
|
-
const f = raw / 1e5;
|
|
2783
|
-
if (t.localName === "lumMod" || t.localName === "lumOff") {
|
|
2784
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
2785
|
-
[r, g, b] = hslToRgb(h, s, clamp01(t.localName === "lumMod" ? l * f : l + f));
|
|
2786
|
-
} else if (t.localName === "tint") {
|
|
2787
|
-
r = r * f + 255 * (1 - f);
|
|
2788
|
-
g = g * f + 255 * (1 - f);
|
|
2789
|
-
b = b * f + 255 * (1 - f);
|
|
2790
|
-
} else if (t.localName === "shade") {
|
|
2791
|
-
r *= f;
|
|
2792
|
-
g *= f;
|
|
2793
|
-
b *= f;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
return `#${byteHex(r)}${byteHex(g)}${byteHex(b)}`.toUpperCase();
|
|
2797
|
-
}
|
|
2798
|
-
};
|
|
2799
|
-
function solidFillColor(el, colors) {
|
|
2800
|
-
const fill = el && childOf2(el, "solidFill");
|
|
2801
|
-
if (!fill) return void 0;
|
|
2802
|
-
for (const c of Array.from(fill.children)) {
|
|
2803
|
-
const hex = colors.resolve(c);
|
|
2804
|
-
if (!hex) continue;
|
|
2805
|
-
const alphaEl = childOf2(c, "alpha");
|
|
2806
|
-
const alpha = alphaEl ? Number(attrOf2(alphaEl, "val")) : NaN;
|
|
2807
|
-
if (Number.isFinite(alpha) && alpha >= 0 && alpha < 1e5) {
|
|
2808
|
-
return hex + Math.round(alpha / 1e5 * 255).toString(16).padStart(2, "0").toUpperCase();
|
|
2809
|
-
}
|
|
2810
|
-
return hex;
|
|
2811
|
-
}
|
|
2812
|
-
return void 0;
|
|
2813
|
-
}
|
|
2814
|
-
function gradientFirstStop(el, colors) {
|
|
2815
|
-
const grad = el && childOf2(el, "gradFill");
|
|
2816
|
-
const lst = grad && childOf2(grad, "gsLst");
|
|
2817
|
-
const gs = lst && childOf2(lst, "gs");
|
|
2818
|
-
if (!gs) return void 0;
|
|
2819
|
-
for (const c of Array.from(gs.children)) {
|
|
2820
|
-
const hex = colors.resolve(c);
|
|
2821
|
-
if (hex) return hex;
|
|
2822
|
-
}
|
|
2823
|
-
return void 0;
|
|
2824
|
-
}
|
|
2825
|
-
function styleRefColor(sp, ref, colors) {
|
|
2826
|
-
const style = childOf2(sp, "style");
|
|
2827
|
-
const refEl = style && childOf2(style, ref);
|
|
2828
|
-
if (!refEl || attrOf2(refEl, "idx") === "0") return void 0;
|
|
2829
|
-
for (const c of Array.from(refEl.children)) {
|
|
2830
|
-
const hex = colors.resolve(c);
|
|
2831
|
-
if (hex) return hex;
|
|
2832
|
-
}
|
|
2833
|
-
return void 0;
|
|
2834
|
-
}
|
|
2835
|
-
function chainFor(entries, slideRels, cache) {
|
|
2836
|
-
const load = (name) => {
|
|
2837
|
-
if (!name) return null;
|
|
2838
|
-
let doc = cache.get(name);
|
|
2839
|
-
if (doc === void 0) {
|
|
2840
|
-
const part = entries.get(name);
|
|
2841
|
-
doc = part ? parseXml2(new TextDecoder().decode(part)) : null;
|
|
2842
|
-
cache.set(name, doc);
|
|
2843
|
-
}
|
|
2844
|
-
return doc;
|
|
2845
|
-
};
|
|
2846
|
-
const layoutName = relOfType(slideRels, "/slideLayout")?.target;
|
|
2847
|
-
const layoutDoc = load(layoutName);
|
|
2848
|
-
const masterName = layoutName ? relOfType(relsOf(entries, layoutName), "/slideMaster")?.target : void 0;
|
|
2849
|
-
const masterDoc = load(masterName);
|
|
2850
|
-
const themeDoc = masterName ? load(relOfType(relsOf(entries, masterName), "/theme")?.target) : null;
|
|
2851
|
-
const scheme = /* @__PURE__ */ new Map();
|
|
2852
|
-
const clrScheme = themeDoc?.getElementsByTagNameNS("*", "clrScheme")[0];
|
|
2853
|
-
for (const slot of Array.from(clrScheme?.children ?? [])) {
|
|
2854
|
-
const clr = slot.firstElementChild;
|
|
2855
|
-
const hex = clr?.localName === "sysClr" ? attrOf2(clr, "lastClr") : clr && attrOf2(clr, "val");
|
|
2856
|
-
if (hex && /^[0-9a-fA-F]{6}$/.test(hex)) scheme.set(slot.localName, hex);
|
|
2857
|
-
}
|
|
2858
|
-
const clrMap = new Map(DEFAULT_CLR_MAP);
|
|
2859
|
-
const clrMapEl = masterDoc?.getElementsByTagNameNS("*", "clrMap")[0];
|
|
2860
|
-
for (const a of Array.from(clrMapEl?.attributes ?? [])) clrMap.set(a.localName, a.value);
|
|
2861
|
-
const treeOf = (doc) => doc?.getElementsByTagNameNS("*", "spTree")[0] ?? null;
|
|
2862
|
-
return {
|
|
2863
|
-
colors: new ColorScheme(scheme, clrMap),
|
|
2864
|
-
layoutTree: treeOf(layoutDoc),
|
|
2865
|
-
masterTree: treeOf(masterDoc),
|
|
2866
|
-
layoutDoc,
|
|
2867
|
-
masterDoc,
|
|
2868
|
-
txStyles: masterDoc?.getElementsByTagNameNS("*", "txStyles")[0] ?? null
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
var MEDIA_BASE64_BUDGET = 12 * 1024 * 1024;
|
|
2872
|
-
var MIME_BY_EXT2 = {
|
|
2873
|
-
png: "image/png",
|
|
2874
|
-
jpg: "image/jpeg",
|
|
2875
|
-
jpeg: "image/jpeg",
|
|
2876
|
-
gif: "image/gif",
|
|
2877
|
-
bmp: "image/bmp",
|
|
2878
|
-
webp: "image/webp",
|
|
2879
|
-
svg: "image/svg+xml"
|
|
2880
|
-
};
|
|
2881
|
-
function bytesToBase643(bytes) {
|
|
2882
|
-
let bin = "";
|
|
2883
|
-
for (let i = 0; i < bytes.length; i += 32768) {
|
|
2884
|
-
bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + 32768)));
|
|
2885
|
-
}
|
|
2886
|
-
return btoa(bin);
|
|
2887
|
-
}
|
|
2888
|
-
var MediaResolver = class {
|
|
2889
|
-
constructor(entries) {
|
|
2890
|
-
this.entries = entries;
|
|
2891
|
-
this.used = 0;
|
|
2892
|
-
}
|
|
2893
|
-
dataUrl(entryName) {
|
|
2894
|
-
const bytes = this.entries.get(entryName);
|
|
2895
|
-
if (!bytes) return null;
|
|
2896
|
-
const base64Length = Math.ceil(bytes.length / 3) * 4;
|
|
2897
|
-
if (this.used + base64Length > MEDIA_BASE64_BUDGET) return null;
|
|
2898
|
-
this.used += base64Length;
|
|
2899
|
-
const ext = /\.([^.]+)$/.exec(entryName)?.[1]?.toLowerCase() ?? "";
|
|
2900
|
-
return `data:${MIME_BY_EXT2[ext] ?? "image/png"};base64,${bytesToBase643(bytes)}`;
|
|
2901
|
-
}
|
|
2902
|
-
};
|
|
2903
|
-
function toPercentBox(xfrm, map, ctx) {
|
|
2904
|
-
return {
|
|
2905
|
-
x: pct(map.tx + (xfrm.x - map.chx) * map.sx, ctx.slideCx),
|
|
2906
|
-
y: pct(map.ty + (xfrm.y - map.chy) * map.sy, ctx.slideCy),
|
|
2907
|
-
w: pct(xfrm.cx * map.sx, ctx.slideCx),
|
|
2908
|
-
h: pct(xfrm.cy * map.sy, ctx.slideCy)
|
|
2909
|
-
};
|
|
2910
|
-
}
|
|
2911
|
-
function normalizedRotation(deg) {
|
|
2912
|
-
const n = Math.round((deg % 360 + 360) % 360 * 100) / 100;
|
|
2913
|
-
return n || void 0;
|
|
2914
|
-
}
|
|
2915
|
-
function rotationOf(xfrm) {
|
|
2916
|
-
if (!xfrm.rot) return void 0;
|
|
2917
|
-
const deg = Math.round(xfrm.rot / 6e4 * 100) / 100;
|
|
2918
|
-
return deg || void 0;
|
|
2919
|
-
}
|
|
2920
|
-
function txBodyText(txBody) {
|
|
2921
|
-
const paras = [];
|
|
2922
|
-
for (const p of Array.from(txBody.children)) {
|
|
2923
|
-
if (p.localName !== "p") continue;
|
|
2924
|
-
let line = "";
|
|
2925
|
-
for (const c of Array.from(p.children)) {
|
|
2926
|
-
if (c.localName === "r") line += childOf2(c, "t")?.textContent ?? "";
|
|
2927
|
-
else if (c.localName === "br") line += "\n";
|
|
2928
|
-
}
|
|
2929
|
-
paras.push(line);
|
|
2930
|
-
}
|
|
2931
|
-
return paras.join("\n");
|
|
2932
|
-
}
|
|
2933
|
-
function placeholderOf(sp) {
|
|
2934
|
-
for (const nv of Array.from(sp.children)) {
|
|
2935
|
-
if (!/^nv(Sp|Pic|GrpSp|Cxn|GraphicFrame)Pr$/.test(nv.localName ?? "")) continue;
|
|
2936
|
-
const nvPr = childOf2(nv, "nvPr");
|
|
2937
|
-
const ph = nvPr && childOf2(nvPr, "ph");
|
|
2938
|
-
if (ph) return { type: attrOf2(ph, "type") ?? "body", idx: attrOf2(ph, "idx") };
|
|
2939
|
-
}
|
|
2940
|
-
return null;
|
|
2941
|
-
}
|
|
2942
|
-
function placeholderType(sp) {
|
|
2943
|
-
return placeholderOf(sp)?.type ?? null;
|
|
2944
|
-
}
|
|
2945
|
-
function findPlaceholder(tree, want) {
|
|
2946
|
-
if (!tree) return null;
|
|
2947
|
-
const shapes = Array.from(tree.getElementsByTagNameNS("*", "sp"));
|
|
2948
|
-
for (const sp of shapes) if (placeholderOf(sp)?.type === want.type) return sp;
|
|
2949
|
-
if (want.idx !== null) {
|
|
2950
|
-
for (const sp of shapes) if (placeholderOf(sp)?.idx === want.idx) return sp;
|
|
2951
|
-
}
|
|
2952
|
-
return null;
|
|
2953
|
-
}
|
|
2954
|
-
function inheritedXfrm(sp, ctx) {
|
|
2955
|
-
const ph = placeholderOf(sp);
|
|
2956
|
-
if (!ph) return null;
|
|
2957
|
-
for (const tree of [ctx.chain.layoutTree, ctx.chain.masterTree]) {
|
|
2958
|
-
const match = findPlaceholder(tree, ph);
|
|
2959
|
-
const spPr = match && childOf2(match, "spPr");
|
|
2960
|
-
const xfrm = spPr && xfrmOf(spPr);
|
|
2961
|
-
if (xfrm) return xfrm;
|
|
2962
|
-
}
|
|
2963
|
-
return null;
|
|
2964
|
-
}
|
|
2965
|
-
function styleSources(sp, txBody, ctx) {
|
|
2966
|
-
const out = [];
|
|
2967
|
-
const own = childOf2(txBody, "lstStyle");
|
|
2968
|
-
if (own) out.push(own);
|
|
2969
|
-
const ph = placeholderOf(sp);
|
|
2970
|
-
if (!ph) return out;
|
|
2971
|
-
for (const tree of [ctx.chain.layoutTree, ctx.chain.masterTree]) {
|
|
2972
|
-
const match = findPlaceholder(tree, ph);
|
|
2973
|
-
const body = match && childOf2(match, "txBody");
|
|
2974
|
-
const lstStyle = body && childOf2(body, "lstStyle");
|
|
2975
|
-
if (lstStyle) out.push(lstStyle);
|
|
2976
|
-
}
|
|
2977
|
-
if (ctx.chain.txStyles) {
|
|
2978
|
-
const bucket = /^(title|ctrTitle)$/.test(ph.type) ? "titleStyle" : /^(body|subTitle|obj)$/.test(ph.type) ? "bodyStyle" : "otherStyle";
|
|
2979
|
-
const styles2 = childOf2(ctx.chain.txStyles, bucket);
|
|
2980
|
-
if (styles2) out.push(styles2);
|
|
2981
|
-
}
|
|
2982
|
-
return out;
|
|
2983
|
-
}
|
|
2984
|
-
function lvlPrOf(source, lvl) {
|
|
2985
|
-
return childOf2(source, `lvl${lvl + 1}pPr`) ?? childOf2(source, "lvl1pPr");
|
|
2986
|
-
}
|
|
2987
|
-
function bulletOf(pPr, sources, lvl) {
|
|
2988
|
-
for (const el of [pPr, ...sources.map((s) => lvlPrOf(s, lvl))]) {
|
|
2989
|
-
if (!el) continue;
|
|
2990
|
-
if (childOf2(el, "buNone")) return false;
|
|
2991
|
-
if (childOf2(el, "buChar") || childOf2(el, "buAutoNum")) return true;
|
|
2992
|
-
}
|
|
2993
|
-
return false;
|
|
2994
|
-
}
|
|
2995
|
-
function paragraphsOf(txBody, sp, ctx) {
|
|
2996
|
-
const colors = ctx.chain.colors;
|
|
2997
|
-
const sources = styleSources(sp, txBody, ctx);
|
|
2998
|
-
const shapeTextColor = styleRefColor(sp, "fontRef", colors);
|
|
2999
|
-
const out = [];
|
|
3000
|
-
for (const p of Array.from(txBody.children)) {
|
|
3001
|
-
if (p.localName !== "p") continue;
|
|
3002
|
-
let text = "";
|
|
3003
|
-
for (const c of Array.from(p.children)) {
|
|
3004
|
-
if (c.localName === "r") text += childOf2(c, "t")?.textContent ?? "";
|
|
3005
|
-
else if (c.localName === "br") text += "\n";
|
|
3006
|
-
}
|
|
3007
|
-
const pPr = childOf2(p, "pPr");
|
|
3008
|
-
const lvl = Math.max(0, Number(pPr && attrOf2(pPr, "lvl")) || 0);
|
|
3009
|
-
const firstRun = childOf2(p, "r");
|
|
3010
|
-
const rPr = firstRun && childOf2(firstRun, "rPr");
|
|
3011
|
-
const props = [];
|
|
3012
|
-
if (rPr) props.push(rPr);
|
|
3013
|
-
const pDef = pPr && childOf2(pPr, "defRPr");
|
|
3014
|
-
if (pDef) props.push(pDef);
|
|
3015
|
-
for (const src of sources) {
|
|
3016
|
-
const lvlPr = lvlPrOf(src, lvl);
|
|
3017
|
-
const def = lvlPr && childOf2(lvlPr, "defRPr");
|
|
3018
|
-
if (def) props.push(def);
|
|
3019
|
-
}
|
|
3020
|
-
const para = { text, bullet: bulletOf(pPr, sources, lvl) };
|
|
3021
|
-
for (const pr of props) {
|
|
3022
|
-
if (para.szCenti === void 0) {
|
|
3023
|
-
const sz = Number(attrOf2(pr, "sz"));
|
|
3024
|
-
if (Number.isFinite(sz) && sz > 0) para.szCenti = sz;
|
|
3025
|
-
}
|
|
3026
|
-
if (para.bold === void 0 && attrOf2(pr, "b") !== null) para.bold = isOn(attrOf2(pr, "b"));
|
|
3027
|
-
if (para.color === void 0) {
|
|
3028
|
-
const color = solidFillColor(pr, colors);
|
|
3029
|
-
if (color) para.color = color;
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
|
-
if (para.color === void 0 && shapeTextColor) para.color = shapeTextColor;
|
|
3033
|
-
for (const el of [pPr, ...sources.map((s) => lvlPrOf(s, lvl))]) {
|
|
3034
|
-
const algn = el && attrOf2(el, "algn");
|
|
3035
|
-
if (!algn) continue;
|
|
3036
|
-
const align = algn === "l" ? "left" : algn === "ctr" ? "center" : algn === "r" ? "right" : void 0;
|
|
3037
|
-
if (align) para.align = align;
|
|
3038
|
-
break;
|
|
3039
|
-
}
|
|
3040
|
-
out.push(para);
|
|
3041
|
-
}
|
|
3042
|
-
return out;
|
|
3043
|
-
}
|
|
3044
|
-
var TITLE_BOX = { x: 8, y: 10, w: 84, h: 18 };
|
|
3045
|
-
var BODY_BOX = { x: 8, y: 32, w: 84, h: 55 };
|
|
3046
|
-
var MAX_SPLIT_PARAGRAPHS = 8;
|
|
3047
|
-
var DESIGN_WIDTH_PX = 1280;
|
|
3048
|
-
var DESIGN_HEIGHT_PX = 720;
|
|
3049
|
-
var DEFAULT_PARA_PX = 24;
|
|
3050
|
-
var LINE_HEIGHT = 1.3;
|
|
3051
|
-
var OVERFLOW_TOLERANCE = 1.05;
|
|
3052
|
-
var OVERFLOW_MIN_SCALE = 0.65;
|
|
3053
|
-
function textWidthEm(text) {
|
|
3054
|
-
let em = 0;
|
|
3055
|
-
for (const ch of text) {
|
|
3056
|
-
em += /[\u1100-\u11ff\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/.test(ch) ? 1 : 0.55;
|
|
3057
|
-
}
|
|
3058
|
-
return em;
|
|
3059
|
-
}
|
|
3060
|
-
function estimatedLines(text, sizePx, boxWPct) {
|
|
3061
|
-
const widthPx = Math.max(1, boxWPct / 100 * DESIGN_WIDTH_PX);
|
|
3062
|
-
let lines = 0;
|
|
3063
|
-
for (const seg of text.split("\n")) {
|
|
3064
|
-
lines += Math.max(1, Math.ceil(textWidthEm(seg) * sizePx / widthPx));
|
|
3065
|
-
}
|
|
3066
|
-
return lines;
|
|
3067
|
-
}
|
|
3068
|
-
function fitFontPx(sizePx, text, boxWPct, boxHPct) {
|
|
3069
|
-
const estimated = estimatedLines(text, sizePx, boxWPct) * sizePx * LINE_HEIGHT;
|
|
3070
|
-
const boxPx = boxHPct / 100 * DESIGN_HEIGHT_PX;
|
|
3071
|
-
if (boxPx <= 0 || estimated <= boxPx * OVERFLOW_TOLERANCE) return sizePx;
|
|
3072
|
-
const scale = Math.max(OVERFLOW_MIN_SCALE, boxPx / estimated);
|
|
3073
|
-
return Math.max(6, Math.round(sizePx * scale));
|
|
3074
|
-
}
|
|
3075
|
-
function fontPx(szCenti, fontScale, ctx) {
|
|
3076
|
-
return Math.max(6, Math.round(szCenti / 100 * fontScale * (4 / 3) * (12192e3 / ctx.slideCx)));
|
|
3077
|
-
}
|
|
3078
|
-
function textElements(sp, txBody, map, ctx) {
|
|
3079
|
-
const spPr = childOf2(sp, "spPr");
|
|
3080
|
-
const xfrm = (spPr && xfrmOf(spPr)) ?? inheritedXfrm(sp, ctx);
|
|
3081
|
-
const box = xfrm ? toPercentBox(xfrm, map, ctx) : /^(title|ctrTitle)$/.test(placeholderType(sp) ?? "") ? { ...TITLE_BOX } : { ...BODY_BOX };
|
|
3082
|
-
const rotate = xfrm ? rotationOf(xfrm) : void 0;
|
|
3083
|
-
const bodyPr = childOf2(txBody, "bodyPr");
|
|
3084
|
-
const autofit = bodyPr ? childOf2(bodyPr, "normAutofit") ?? childOf2(bodyPr, "spAutoFit") : null;
|
|
3085
|
-
const rawScale = autofit ? Number(attrOf2(autofit, "fontScale")) : NaN;
|
|
3086
|
-
const fontScale = Number.isFinite(rawScale) && rawScale > 0 ? rawScale / 1e5 : 1;
|
|
3087
|
-
const guard = (sizePx, text, hPct) => (
|
|
3088
|
-
// The guard runs regardless of autofit: PPT's own fontScale is already
|
|
3089
|
-
// applied, but our Korean wrap is wider than PowerPoint's, so even
|
|
3090
|
-
// autofitted boxes can overflow the model.
|
|
3091
|
-
fitFontPx(sizePx, text, box.w, hPct)
|
|
3092
|
-
);
|
|
3093
|
-
const paras = paragraphsOf(txBody, sp, ctx).map((p) => ({
|
|
3094
|
-
...p,
|
|
3095
|
-
px: p.szCenti !== void 0 ? fontPx(p.szCenti, fontScale, ctx) : void 0
|
|
3096
|
-
}));
|
|
3097
|
-
const filled = paras.filter((p) => p.text.trim());
|
|
3098
|
-
const apply = (el, p) => {
|
|
3099
|
-
if (p.px !== void 0) el.fontSize = p.px;
|
|
3100
|
-
if (p.bold) el.bold = true;
|
|
3101
|
-
if (p.color) el.color = p.color;
|
|
3102
|
-
if (p.align) el.align = p.align;
|
|
3103
|
-
};
|
|
3104
|
-
const distinctSizes = new Set(filled.map((p) => p.px ?? DEFAULT_PARA_PX));
|
|
3105
|
-
const mixedBullets = new Set(filled.map((p) => p.bullet)).size > 1;
|
|
3106
|
-
const split = filled.length >= 2 && paras.length <= MAX_SPLIT_PARAGRAPHS && rotate === void 0 && (distinctSizes.size > 1 || mixedBullets);
|
|
3107
|
-
if (!split) {
|
|
3108
|
-
const el = {
|
|
3109
|
-
id: ctx.nextId(),
|
|
3110
|
-
type: "text",
|
|
3111
|
-
...box,
|
|
3112
|
-
text: paras.map((p) => p.bullet && p.text.trim() ? `\u2022 ${p.text}` : p.text).join("\n")
|
|
3113
|
-
};
|
|
3114
|
-
const lead = filled[0] ?? paras[0];
|
|
3115
|
-
if (lead) apply(el, lead);
|
|
3116
|
-
if (el.fontSize !== void 0 && el.text) el.fontSize = guard(el.fontSize, el.text, box.h);
|
|
3117
|
-
if (rotate !== void 0) el.rotate = rotate;
|
|
3118
|
-
return [el];
|
|
3119
|
-
}
|
|
3120
|
-
const weights = paras.map((p) => estimatedLines(p.text, p.px ?? DEFAULT_PARA_PX, box.w) * (p.px ?? DEFAULT_PARA_PX));
|
|
3121
|
-
const total = weights.reduce((a, b) => a + b, 0) || 1;
|
|
3122
|
-
const out = [];
|
|
3123
|
-
let used = 0;
|
|
3124
|
-
paras.forEach((p, i) => {
|
|
3125
|
-
const y = box.y + box.h * used / total;
|
|
3126
|
-
used += weights[i];
|
|
3127
|
-
if (!p.text.trim()) return;
|
|
3128
|
-
const el = {
|
|
3129
|
-
id: ctx.nextId(),
|
|
3130
|
-
type: "text",
|
|
3131
|
-
x: box.x,
|
|
3132
|
-
y: round2(y),
|
|
3133
|
-
w: box.w,
|
|
3134
|
-
h: round2(box.h * weights[i] / total),
|
|
3135
|
-
text: p.bullet ? `\u2022 ${p.text}` : p.text
|
|
3136
|
-
};
|
|
3137
|
-
apply(el, p);
|
|
3138
|
-
if (el.fontSize !== void 0) el.fontSize = guard(el.fontSize, el.text ?? "", el.h);
|
|
3139
|
-
out.push(el);
|
|
3140
|
-
});
|
|
3141
|
-
return out;
|
|
3142
|
-
}
|
|
3143
|
-
function mappableShapeOf(prst, isConnector) {
|
|
3144
|
-
if (isConnector) return "line";
|
|
3145
|
-
if (!prst) return null;
|
|
3146
|
-
if (prst === "line" || /^straightConnector\d*$/.test(prst)) return "line";
|
|
3147
|
-
if (prst === "ellipse" || prst === "oval") return "ellipse";
|
|
3148
|
-
if (prst === "rect" || prst === "roundRect") return "rect";
|
|
3149
|
-
return null;
|
|
3150
|
-
}
|
|
3151
|
-
function shapeElement(sp, map, ctx, isConnector) {
|
|
3152
|
-
const colors = ctx.chain.colors;
|
|
3153
|
-
const spPr = childOf2(sp, "spPr");
|
|
3154
|
-
const xfrm = spPr && (xfrmOf(spPr) ?? inheritedXfrm(sp, ctx));
|
|
3155
|
-
if (!spPr || !xfrm) return null;
|
|
3156
|
-
const prst = attrOf2(childOf2(spPr, "prstGeom") ?? spPr, "prst");
|
|
3157
|
-
const shape = mappableShapeOf(prst, isConnector);
|
|
3158
|
-
if (!shape) return null;
|
|
3159
|
-
const el = { id: ctx.nextId(), type: "shape", shape, ...toPercentBox(xfrm, map, ctx) };
|
|
3160
|
-
const ln = childOf2(spPr, "ln");
|
|
3161
|
-
const areaFill = childOf2(spPr, "noFill") ? void 0 : solidFillColor(spPr, colors) ?? gradientFirstStop(spPr, colors) ?? styleRefColor(sp, "fillRef", colors);
|
|
3162
|
-
const lineFill = (ln && !childOf2(ln, "noFill") ? solidFillColor(ln, colors) : void 0) ?? styleRefColor(sp, "lnRef", colors);
|
|
3163
|
-
const fill = shape === "line" ? lineFill ?? areaFill : areaFill;
|
|
3164
|
-
if (fill) el.fill = fill;
|
|
3165
|
-
if (prst === "roundRect") el.radius = 8;
|
|
3166
|
-
el.rotate = rotationOf(xfrm);
|
|
3167
|
-
if (shape === "line" && !(el.w > 3 && el.h > 5)) {
|
|
3168
|
-
if (el.h >= el.w && el.w > 1.2) {
|
|
3169
|
-
el.x = Math.round((el.x + el.w / 2 - 0.3) * 100) / 100;
|
|
3170
|
-
el.w = 0.6;
|
|
3171
|
-
} else if (el.w > el.h && el.h > 1.2) {
|
|
3172
|
-
el.y = Math.round((el.y + el.h / 2 - 0.3) * 100) / 100;
|
|
3173
|
-
el.h = 0.6;
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
if (shape === "line" && el.w > 3 && el.h > 5) {
|
|
3177
|
-
const wPx = el.w / 100 * 1280;
|
|
3178
|
-
const hPx = el.h / 100 * 720;
|
|
3179
|
-
const lenPct = Math.hypot(wPx, hPx) / 1280 * 100;
|
|
3180
|
-
const flip = attrOf2(childOf2(spPr, "xfrm") ?? spPr, "flipH") === "1" !== (attrOf2(childOf2(spPr, "xfrm") ?? spPr, "flipV") === "1");
|
|
3181
|
-
const angle = Math.atan2(hPx, wPx) * 180 / Math.PI;
|
|
3182
|
-
const cx = el.x + el.w / 2;
|
|
3183
|
-
const cy = el.y + el.h / 2;
|
|
3184
|
-
el.x = Math.round((cx - lenPct / 2) * 100) / 100;
|
|
3185
|
-
el.y = Math.round((cy - 0.3) * 100) / 100;
|
|
3186
|
-
el.w = Math.round(lenPct * 100) / 100;
|
|
3187
|
-
el.h = 0.6;
|
|
3188
|
-
el.rotate = normalizedRotation((el.rotate ?? 0) + (flip ? -angle : angle));
|
|
3189
|
-
}
|
|
3190
|
-
return el;
|
|
3191
|
-
}
|
|
3192
|
-
function pictureElement(pic, map, ctx) {
|
|
3193
|
-
const spPr = childOf2(pic, "spPr");
|
|
3194
|
-
const xfrm = (spPr && xfrmOf(spPr)) ?? inheritedXfrm(pic, ctx);
|
|
3195
|
-
if (!xfrm) return null;
|
|
3196
|
-
const blip = childOf2(pic, "blipFill") && childOf2(childOf2(pic, "blipFill"), "blip");
|
|
3197
|
-
const embed = blip && attrOf2(blip, "embed");
|
|
3198
|
-
const rel = embed ? ctx.rels.get(embed) : void 0;
|
|
3199
|
-
const src = rel ? ctx.media.dataUrl(rel.target) : null;
|
|
3200
|
-
if (!src) return null;
|
|
3201
|
-
const el = { id: ctx.nextId(), type: "image", ...toPercentBox(xfrm, map, ctx), src };
|
|
3202
|
-
el.rotate = rotationOf(xfrm);
|
|
3203
|
-
return el;
|
|
3204
|
-
}
|
|
3205
|
-
var MAX_TABLE_CELL_ELEMENTS = 60;
|
|
3206
|
-
var TABLE_FONT_PX_CAP = 14;
|
|
3207
|
-
var TABLE_FONT_PX_DEFAULT = 12;
|
|
3208
|
-
function cellText(tc) {
|
|
3209
|
-
const txBody = childOf2(tc, "txBody");
|
|
3210
|
-
return txBody ? txBodyText(txBody).trim() : "";
|
|
3211
|
-
}
|
|
3212
|
-
function tableElements(frame, map, ctx) {
|
|
3213
|
-
try {
|
|
3214
|
-
return tableGrid(frame, map, ctx);
|
|
3215
|
-
} catch {
|
|
3216
|
-
return [];
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
|
-
function tableGrid(frame, map, ctx) {
|
|
3220
|
-
const graphic = childOf2(frame, "graphic");
|
|
3221
|
-
const data = graphic && childOf2(graphic, "graphicData");
|
|
3222
|
-
const tbl = data && childOf2(data, "tbl");
|
|
3223
|
-
const xfrm = xfrmOf(frame);
|
|
3224
|
-
if (!tbl || !xfrm) return [];
|
|
3225
|
-
const box = toPercentBox(xfrm, map, ctx);
|
|
3226
|
-
const rows = Array.from(tbl.children).filter((c) => c.localName === "tr");
|
|
3227
|
-
const cellsOf = (tr) => Array.from(tr.children).filter((c) => c.localName === "tc");
|
|
3228
|
-
if (!rows.length) return [];
|
|
3229
|
-
const grid = childOf2(tbl, "tblGrid");
|
|
3230
|
-
const colWidths = Array.from(grid?.children ?? []).filter((c) => c.localName === "gridCol").map((c) => Math.max(0, Number(attrOf2(c, "w")) || 0));
|
|
3231
|
-
const colCount = colWidths.length || Math.max(...rows.map((r) => cellsOf(r).length));
|
|
3232
|
-
if (!colCount || !Number.isFinite(colCount)) return [];
|
|
3233
|
-
if (rows.length * colCount > MAX_TABLE_CELL_ELEMENTS) {
|
|
3234
|
-
const text = rows.map((r) => cellsOf(r).map(cellText).join(" ")).join("\n").trim();
|
|
3235
|
-
if (!text) return [];
|
|
3236
|
-
return [{ id: ctx.nextId(), type: "text", ...box, text, fontSize: TABLE_FONT_PX_DEFAULT }];
|
|
3237
|
-
}
|
|
3238
|
-
const colSum = colWidths.reduce((a, b) => a + b, 0);
|
|
3239
|
-
const colFrac = colSum > 0 ? colWidths.map((w) => w / colSum) : Array(colCount).fill(1 / colCount);
|
|
3240
|
-
const rowHeights = rows.map((r) => Math.max(0, Number(attrOf2(r, "h")) || 0));
|
|
3241
|
-
const rowSum = rowHeights.reduce((a, b) => a + b, 0);
|
|
3242
|
-
const rowFrac = rowSum > 0 ? rowHeights.map((h) => h / rowSum) : rows.map(() => 1 / rows.length);
|
|
3243
|
-
const out = [];
|
|
3244
|
-
let yFrac = 0;
|
|
3245
|
-
rows.forEach((tr, ri) => {
|
|
3246
|
-
let col = 0;
|
|
3247
|
-
for (const tc of cellsOf(tr)) {
|
|
3248
|
-
const span = Math.max(1, Number(attrOf2(tc, "gridSpan")) || 1);
|
|
3249
|
-
const from = col;
|
|
3250
|
-
col += span;
|
|
3251
|
-
if (isOn(attrOf2(tc, "hMerge")) || isOn(attrOf2(tc, "vMerge")) || from >= colCount) continue;
|
|
3252
|
-
const text = cellText(tc);
|
|
3253
|
-
if (!text) continue;
|
|
3254
|
-
const xFrac = colFrac.slice(0, from).reduce((a, b) => a + b, 0);
|
|
3255
|
-
const wFrac = colFrac.slice(from, Math.min(from + span, colCount)).reduce((a, b) => a + b, 0);
|
|
3256
|
-
const el = {
|
|
3257
|
-
id: ctx.nextId(),
|
|
3258
|
-
type: "text",
|
|
3259
|
-
x: round2(box.x + box.w * xFrac),
|
|
3260
|
-
y: round2(box.y + box.h * yFrac),
|
|
3261
|
-
w: round2(box.w * wFrac),
|
|
3262
|
-
h: round2(box.h * rowFrac[ri]),
|
|
3263
|
-
text
|
|
3264
|
-
};
|
|
3265
|
-
const txBody = childOf2(tc, "txBody");
|
|
3266
|
-
const para = txBody ? paragraphsOf(txBody, tc, ctx).find((p) => p.text.trim()) : void 0;
|
|
3267
|
-
const sizePx = para?.szCenti !== void 0 ? fontPx(para.szCenti, 1, ctx) : TABLE_FONT_PX_DEFAULT;
|
|
3268
|
-
el.fontSize = Math.min(sizePx, TABLE_FONT_PX_CAP);
|
|
3269
|
-
if (para?.bold) el.bold = true;
|
|
3270
|
-
if (para?.color) el.color = para.color;
|
|
3271
|
-
if (para?.align) el.align = para.align;
|
|
3272
|
-
out.push(el);
|
|
3273
|
-
}
|
|
3274
|
-
yFrac += rowFrac[ri];
|
|
3275
|
-
});
|
|
3276
|
-
return out;
|
|
3277
|
-
}
|
|
3278
|
-
function collectElements(tree, map, ctx, out) {
|
|
3279
|
-
for (const child of Array.from(tree.children)) {
|
|
3280
|
-
if (child.localName === "sp") {
|
|
3281
|
-
const txBody = childOf2(child, "txBody");
|
|
3282
|
-
if (txBody && txBodyText(txBody).trim()) {
|
|
3283
|
-
out.push(...textElements(child, txBody, map, ctx));
|
|
3284
|
-
} else {
|
|
3285
|
-
const el = shapeElement(child, map, ctx, false);
|
|
3286
|
-
if (el) out.push(el);
|
|
3287
|
-
}
|
|
3288
|
-
} else if (child.localName === "cxnSp") {
|
|
3289
|
-
const el = shapeElement(child, map, ctx, true);
|
|
3290
|
-
if (el) out.push(el);
|
|
3291
|
-
} else if (child.localName === "pic") {
|
|
3292
|
-
const el = pictureElement(child, map, ctx);
|
|
3293
|
-
if (el) out.push(el);
|
|
3294
|
-
} else if (child.localName === "graphicFrame") {
|
|
3295
|
-
out.push(...tableElements(child, map, ctx));
|
|
3296
|
-
} else if (child.localName === "grpSp") {
|
|
3297
|
-
collectElements(child, groupChildMap(child, map), ctx, out);
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
function backgroundOf(doc, colors) {
|
|
3302
|
-
const cSld = doc?.getElementsByTagNameNS("*", "cSld")[0];
|
|
3303
|
-
const bg = cSld ? childOf2(cSld, "bg") : null;
|
|
3304
|
-
if (!bg) return void 0;
|
|
3305
|
-
const bgPr = childOf2(bg, "bgPr");
|
|
3306
|
-
if (bgPr) return solidFillColor(bgPr, colors) ?? gradientFirstStop(bgPr, colors);
|
|
3307
|
-
const bgRef = childOf2(bg, "bgRef");
|
|
3308
|
-
if (!bgRef || attrOf2(bgRef, "idx") === "0") return void 0;
|
|
3309
|
-
for (const c of Array.from(bgRef.children)) {
|
|
3310
|
-
const hex = colors.resolve(c);
|
|
3311
|
-
if (hex) return hex;
|
|
3312
|
-
}
|
|
3313
|
-
return void 0;
|
|
3314
|
-
}
|
|
3315
|
-
var NOTES_CHROME = /^(sldNum|sldImg|hdr|ftr|dt)$/;
|
|
3316
|
-
function notesFor(entries, slideName, rels) {
|
|
3317
|
-
let notesName = relOfType(rels, "/notesSlide")?.target;
|
|
3318
|
-
if (!notesName) {
|
|
3319
|
-
const num = /(\d+)\.xml$/.exec(slideName)?.[1];
|
|
3320
|
-
if (num) notesName = `ppt/notesSlides/notesSlide${num}.xml`;
|
|
3321
|
-
}
|
|
3322
|
-
const part = notesName ? entries.get(notesName) : void 0;
|
|
3323
|
-
const doc = part ? parseXml2(new TextDecoder().decode(part)) : null;
|
|
3324
|
-
if (!doc) return void 0;
|
|
3325
|
-
const texts = [];
|
|
3326
|
-
for (const sp of Array.from(doc.getElementsByTagNameNS("*", "sp"))) {
|
|
3327
|
-
if (NOTES_CHROME.test(placeholderType(sp) ?? "")) continue;
|
|
3328
|
-
const txBody = childOf2(sp, "txBody");
|
|
3329
|
-
const text = txBody ? txBodyText(txBody).trim() : "";
|
|
3330
|
-
if (text) texts.push(text);
|
|
3331
|
-
}
|
|
3332
|
-
return texts.length ? texts.join("\n") : void 0;
|
|
3333
|
-
}
|
|
3334
|
-
function orderedSlideNames(entries) {
|
|
3335
|
-
const presPart = entries.get("ppt/presentation.xml");
|
|
3336
|
-
const doc = presPart ? parseXml2(new TextDecoder().decode(presPart)) : null;
|
|
3337
|
-
if (doc) {
|
|
3338
|
-
const rels = relsOf(entries, "ppt/presentation.xml");
|
|
3339
|
-
const names = [];
|
|
3340
|
-
for (const sldId of Array.from(doc.getElementsByTagNameNS("*", "sldId"))) {
|
|
3341
|
-
const rel = relIdOf(sldId) ? rels.get(relIdOf(sldId)) : void 0;
|
|
3342
|
-
if (rel && entries.has(rel.target)) names.push(rel.target);
|
|
3343
|
-
}
|
|
3344
|
-
if (names.length) return names;
|
|
3345
|
-
}
|
|
3346
|
-
return [...entries.keys()].filter((n) => /^ppt\/slides\/slide\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)\.xml$/.exec(a)?.[1] ?? 0) - Number(/(\d+)\.xml$/.exec(b)?.[1] ?? 0));
|
|
3347
|
-
}
|
|
3348
|
-
function slideSizeOf(entries) {
|
|
3349
|
-
const presPart = entries.get("ppt/presentation.xml");
|
|
3350
|
-
const doc = presPart ? parseXml2(new TextDecoder().decode(presPart)) : null;
|
|
3351
|
-
const sldSz = doc && doc.getElementsByTagNameNS("*", "sldSz")[0];
|
|
3352
|
-
const cx = sldSz ? Number(attrOf2(sldSz, "cx")) : NaN;
|
|
3353
|
-
const cy = sldSz ? Number(attrOf2(sldSz, "cy")) : NaN;
|
|
3354
|
-
return { cx: cx > 0 ? cx : DEFAULT_SLIDE_CX, cy: cy > 0 ? cy : DEFAULT_SLIDE_CY };
|
|
3355
|
-
}
|
|
3356
|
-
async function pptxToSlides(buffer) {
|
|
3357
|
-
const entries = await readZip(buffer);
|
|
3358
|
-
const names = orderedSlideNames(entries);
|
|
3359
|
-
if (!names.length) {
|
|
3360
|
-
throw new Error("PPTX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC2AC\uB77C\uC774\uB4DC(ppt/slides)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a PPTX document \u2014 no slides found).");
|
|
3361
|
-
}
|
|
3362
|
-
const { cx, cy } = slideSizeOf(entries);
|
|
3363
|
-
const media = new MediaResolver(entries);
|
|
3364
|
-
const partCache = /* @__PURE__ */ new Map();
|
|
3365
|
-
const decoder = new TextDecoder();
|
|
3366
|
-
let seq = 0;
|
|
3367
|
-
const slides2 = [];
|
|
3368
|
-
for (const name of names) {
|
|
3369
|
-
const doc = parseXml2(decoder.decode(entries.get(name)));
|
|
3370
|
-
if (!doc) continue;
|
|
3371
|
-
const rels = relsOf(entries, name);
|
|
3372
|
-
const chain = chainFor(entries, rels, partCache);
|
|
3373
|
-
const ctx = { slideCx: cx, slideCy: cy, rels, media, chain, nextId: () => `el_${++seq}` };
|
|
3374
|
-
const elements = [];
|
|
3375
|
-
const spTree = doc.getElementsByTagNameNS("*", "spTree")[0];
|
|
3376
|
-
if (spTree) collectElements(spTree, IDENTITY_MAP, ctx, elements);
|
|
3377
|
-
const slide = { layout: "blank", elements };
|
|
3378
|
-
const background = backgroundOf(doc, chain.colors) ?? backgroundOf(chain.layoutDoc, chain.colors) ?? backgroundOf(chain.masterDoc, chain.colors);
|
|
3379
|
-
if (background) slide.background = background;
|
|
3380
|
-
const notes = notesFor(entries, name, rels);
|
|
3381
|
-
if (notes) slide.notes = notes;
|
|
3382
|
-
slides2.push(slide);
|
|
3383
|
-
}
|
|
3384
|
-
if (!slides2.length) {
|
|
3385
|
-
throw new Error("PPTX \uC2AC\uB77C\uC774\uB4DC XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a readable PPTX \u2014 every slide failed to parse).");
|
|
3386
|
-
}
|
|
3387
|
-
return { slides: slides2 };
|
|
3388
|
-
}
|
|
3389
|
-
|
|
3390
1523
|
// src/io/importers.ts
|
|
3391
|
-
var IMPORTABLE_EXTENSIONS = [
|
|
3392
|
-
".csv",
|
|
3393
|
-
".md",
|
|
3394
|
-
".markdown",
|
|
3395
|
-
".txt",
|
|
3396
|
-
".html",
|
|
3397
|
-
".htm",
|
|
3398
|
-
".json",
|
|
3399
|
-
".xlsx",
|
|
3400
|
-
".pdf",
|
|
3401
|
-
".hwpx",
|
|
3402
|
-
".hwp",
|
|
3403
|
-
".docx",
|
|
3404
|
-
".pptx"
|
|
3405
|
-
];
|
|
1524
|
+
var IMPORTABLE_EXTENSIONS = [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
|
|
3406
1525
|
var extensionOf = (name) => {
|
|
3407
1526
|
const dot = name.lastIndexOf(".");
|
|
3408
1527
|
return dot === -1 ? "" : name.slice(dot).toLowerCase();
|
|
@@ -3443,26 +1562,6 @@ async function importFile(file) {
|
|
|
3443
1562
|
const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
|
|
3444
1563
|
return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
|
|
3445
1564
|
}
|
|
3446
|
-
case ".pdf": {
|
|
3447
|
-
const src = await fileToDataUrl(file);
|
|
3448
|
-
return toEvents(artifact(id, "pdf", title, { src, filename: file.name }));
|
|
3449
|
-
}
|
|
3450
|
-
case ".hwpx": {
|
|
3451
|
-
const html = await hwpxToHtml(await file.arrayBuffer());
|
|
3452
|
-
return toEvents({ ...artifact(id, "html", title, { html }), meta: { kind: "doc" } });
|
|
3453
|
-
}
|
|
3454
|
-
case ".docx": {
|
|
3455
|
-
const content = await docxToMarkdown(await file.arrayBuffer());
|
|
3456
|
-
return toEvents(artifact(id, "document", title, { format: "markdown", content }));
|
|
3457
|
-
}
|
|
3458
|
-
case ".pptx": {
|
|
3459
|
-
const data = await pptxToSlides(await file.arrayBuffer());
|
|
3460
|
-
return toEvents(artifact(id, "slides", title, data));
|
|
3461
|
-
}
|
|
3462
|
-
case ".hwp": {
|
|
3463
|
-
const html = await hwpToHtml(await file.arrayBuffer());
|
|
3464
|
-
return toEvents({ ...artifact(id, "html", title, { html }), meta: { kind: "doc" } });
|
|
3465
|
-
}
|
|
3466
1565
|
default:
|
|
3467
1566
|
throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
|
|
3468
1567
|
}
|
|
@@ -3470,14 +1569,6 @@ async function importFile(file) {
|
|
|
3470
1569
|
function artifact(id, type, title, data) {
|
|
3471
1570
|
return { id, type, title, version: 1, status: "complete", data };
|
|
3472
1571
|
}
|
|
3473
|
-
function fileToDataUrl(file) {
|
|
3474
|
-
return new Promise((resolve, reject) => {
|
|
3475
|
-
const reader = new FileReader();
|
|
3476
|
-
reader.onload = () => resolve(reader.result);
|
|
3477
|
-
reader.onerror = () => reject(reader.error ?? new Error("Failed to read file."));
|
|
3478
|
-
reader.readAsDataURL(file);
|
|
3479
|
-
});
|
|
3480
|
-
}
|
|
3481
1572
|
function importJson(text, id, title) {
|
|
3482
1573
|
let parsed;
|
|
3483
1574
|
try {
|
|
@@ -3550,8 +1641,6 @@ var DEVICES = [
|
|
|
3550
1641
|
{ id: "tablet", label: "Tablet", width: "768px" },
|
|
3551
1642
|
{ id: "mobile", label: "Mobile", width: "390px" }
|
|
3552
1643
|
];
|
|
3553
|
-
var BLOCK_KEYS = { h2: "heading", p: "text", button: "button", img: "image", hr: "divider" };
|
|
3554
|
-
var DEVICE_KEYS = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
|
|
3555
1644
|
var BLOCKS = [
|
|
3556
1645
|
{ tag: "h2", label: "Heading" },
|
|
3557
1646
|
{ tag: "p", label: "Text" },
|
|
@@ -3797,8 +1886,8 @@ function checkA11y(html) {
|
|
|
3797
1886
|
if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
|
|
3798
1887
|
return issues;
|
|
3799
1888
|
}
|
|
3800
|
-
var
|
|
3801
|
-
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>";
|
|
3802
1891
|
function withScrollableBody(html) {
|
|
3803
1892
|
const i = html.lastIndexOf("</body>");
|
|
3804
1893
|
return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
|
|
@@ -3806,7 +1895,7 @@ function withScrollableBody(html) {
|
|
|
3806
1895
|
function useSlideFit(ratio, boxRef) {
|
|
3807
1896
|
const [scale, setScale] = useState(1);
|
|
3808
1897
|
const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
|
|
3809
|
-
const
|
|
1898
|
+
const height = rw && rh ? Math.round(SLIDE_W * rh / rw) : 720;
|
|
3810
1899
|
useEffect(() => {
|
|
3811
1900
|
if (!ratio) return;
|
|
3812
1901
|
const el = boxRef.current;
|
|
@@ -3814,17 +1903,16 @@ function useSlideFit(ratio, boxRef) {
|
|
|
3814
1903
|
const fit = () => {
|
|
3815
1904
|
const w = el.clientWidth;
|
|
3816
1905
|
if (w <= 40) return;
|
|
3817
|
-
setScale(Math.min(1, (w - 40) /
|
|
1906
|
+
setScale(Math.min(1, (w - 40) / SLIDE_W));
|
|
3818
1907
|
};
|
|
3819
1908
|
fit();
|
|
3820
1909
|
const ro = new ResizeObserver(fit);
|
|
3821
1910
|
ro.observe(el);
|
|
3822
1911
|
return () => ro.disconnect();
|
|
3823
|
-
}, [ratio,
|
|
3824
|
-
return { scale, width, height
|
|
1912
|
+
}, [ratio, height]);
|
|
1913
|
+
return { scale, width: SLIDE_W, height };
|
|
3825
1914
|
}
|
|
3826
1915
|
function HtmlRenderer({ artifact: artifact2 }) {
|
|
3827
|
-
const t = useT();
|
|
3828
1916
|
const iframeRef = useRef(null);
|
|
3829
1917
|
const imgFileRef = useRef(null);
|
|
3830
1918
|
const bgFileRef = useRef(null);
|
|
@@ -3835,6 +1923,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3835
1923
|
const sendIframeCommand = useCanvasStore((s) => s.sendIframeCommand);
|
|
3836
1924
|
const selections = useCanvasStore((s) => s.selections);
|
|
3837
1925
|
const iframeCommand = useCanvasStore((s) => s.iframeCommand);
|
|
1926
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
3838
1927
|
const [device, setDevice] = useState("desktop");
|
|
3839
1928
|
const [mode, setMode] = useState("design");
|
|
3840
1929
|
const [a11y, setA11y] = useState(null);
|
|
@@ -3843,11 +1932,11 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3843
1932
|
const isFixedSlide = Boolean(artifact2.meta?.ratio);
|
|
3844
1933
|
const srcDoc = useMemo(() => {
|
|
3845
1934
|
if (mode === "design" && artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
|
|
3846
|
-
const base = withInspector(artifact2.data.html);
|
|
1935
|
+
const base = withInspector(artifact2.data.html, assetBaseUrl ?? void 0);
|
|
3847
1936
|
srcDocRef.current = isFixedSlide ? base : withScrollableBody(base);
|
|
3848
1937
|
lastSelfHtml.current = null;
|
|
3849
1938
|
return srcDocRef.current;
|
|
3850
|
-
}, [artifact2.data.html, mode, isFixedSlide]);
|
|
1939
|
+
}, [artifact2.data.html, mode, isFixedSlide, assetBaseUrl]);
|
|
3851
1940
|
const selected = selections.filter((s) => s.artifactId === artifact2.id);
|
|
3852
1941
|
const single = selected.length === 1 ? selected[0] : null;
|
|
3853
1942
|
const outline = useMemo(() => {
|
|
@@ -3924,7 +2013,6 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3924
2013
|
};
|
|
3925
2014
|
const setSlideStyle = (style) => sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style });
|
|
3926
2015
|
const ratio = artifact2.meta?.ratio;
|
|
3927
|
-
const isDoc = !ratio && artifact2.meta?.kind === "doc";
|
|
3928
2016
|
const slide = useSlideFit(ratio, stageRef);
|
|
3929
2017
|
return /* @__PURE__ */ jsxs("div", { className: "cv-html-wrap", children: [
|
|
3930
2018
|
/* @__PURE__ */ jsx("input", { ref: imgFileRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
|
|
@@ -3937,30 +2025,26 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3937
2025
|
} }),
|
|
3938
2026
|
/* @__PURE__ */ jsxs("div", { className: "cv-html-bar cv-chrome", children: [
|
|
3939
2027
|
mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3940
|
-
!ratio &&
|
|
3941
|
-
/* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children:
|
|
2028
|
+
!ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2029
|
+
/* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children: d.label }, d.id)) }),
|
|
3942
2030
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" })
|
|
3943
2031
|
] }),
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
] }),
|
|
3948
|
-
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: t("add") }),
|
|
3949
|
-
(ratio || isDoc ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: BLOCK_KEYS[b.tag] ? t(BLOCK_KEYS[b.tag]) : b.label }, b.tag)),
|
|
3950
|
-
!ratio && !isDoc && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2032
|
+
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Add" }),
|
|
2033
|
+
(ratio ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: b.label }, b.tag)),
|
|
2034
|
+
!ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3951
2035
|
/* @__PURE__ */ jsxs(
|
|
3952
2036
|
"select",
|
|
3953
2037
|
{
|
|
3954
2038
|
className: "cv-html-tpl",
|
|
3955
2039
|
value: "",
|
|
3956
|
-
title:
|
|
2040
|
+
title: "Start from a full page template (replaces the page)",
|
|
3957
2041
|
onChange: (e) => {
|
|
3958
2042
|
const s = STARTERS[e.target.value];
|
|
3959
2043
|
if (s) applyEvent({ type: "canvas.patch", id: artifact2.id, patch: { html: s.build() } });
|
|
3960
2044
|
e.currentTarget.value = "";
|
|
3961
2045
|
},
|
|
3962
2046
|
children: [
|
|
3963
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2047
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Page\u2026" }),
|
|
3964
2048
|
Object.entries(STARTERS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
3965
2049
|
]
|
|
3966
2050
|
}
|
|
@@ -3970,14 +2054,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3970
2054
|
{
|
|
3971
2055
|
className: "cv-html-tpl",
|
|
3972
2056
|
value: "",
|
|
3973
|
-
title:
|
|
2057
|
+
title: "Insert a section template",
|
|
3974
2058
|
onChange: (e) => {
|
|
3975
|
-
const
|
|
3976
|
-
if (
|
|
2059
|
+
const t = TEMPLATES[e.target.value];
|
|
2060
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
3977
2061
|
e.currentTarget.value = "";
|
|
3978
2062
|
},
|
|
3979
2063
|
children: [
|
|
3980
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2064
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Section\u2026" }),
|
|
3981
2065
|
Object.entries(TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
3982
2066
|
]
|
|
3983
2067
|
}
|
|
@@ -3987,51 +2071,34 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
3987
2071
|
{
|
|
3988
2072
|
className: "cv-html-tpl",
|
|
3989
2073
|
value: "",
|
|
3990
|
-
title:
|
|
2074
|
+
title: "Jump to a heading",
|
|
3991
2075
|
onChange: (e) => {
|
|
3992
2076
|
const idx = Number(e.target.value);
|
|
3993
2077
|
if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
|
|
3994
2078
|
e.currentTarget.value = "";
|
|
3995
2079
|
},
|
|
3996
2080
|
children: [
|
|
3997
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2081
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Outline\u2026" }),
|
|
3998
2082
|
outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: "\xA0".repeat((h.level - 1) * 2) + h.text }, i))
|
|
3999
2083
|
]
|
|
4000
2084
|
}
|
|
4001
2085
|
),
|
|
4002
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title:
|
|
2086
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Accessibility check", onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
|
|
4003
2087
|
] }),
|
|
4004
|
-
isDoc && outline.length > 0 && /* @__PURE__ */ jsxs(
|
|
4005
|
-
"select",
|
|
4006
|
-
{
|
|
4007
|
-
className: "cv-html-tpl",
|
|
4008
|
-
value: "",
|
|
4009
|
-
title: t("jumpToHeading"),
|
|
4010
|
-
onChange: (e) => {
|
|
4011
|
-
const idx = Number(e.target.value);
|
|
4012
|
-
if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
|
|
4013
|
-
e.currentTarget.value = "";
|
|
4014
|
-
},
|
|
4015
|
-
children: [
|
|
4016
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
|
|
4017
|
-
outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: h.text }, `d${i}`))
|
|
4018
|
-
]
|
|
4019
|
-
}
|
|
4020
|
-
),
|
|
4021
2088
|
ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4022
2089
|
/* @__PURE__ */ jsxs(
|
|
4023
2090
|
"select",
|
|
4024
2091
|
{
|
|
4025
2092
|
className: "cv-html-tpl",
|
|
4026
2093
|
value: "",
|
|
4027
|
-
title:
|
|
2094
|
+
title: "Insert a slide layout",
|
|
4028
2095
|
onChange: (e) => {
|
|
4029
|
-
const
|
|
4030
|
-
if (
|
|
2096
|
+
const t = SLIDE_TEMPLATES[e.target.value];
|
|
2097
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
4031
2098
|
e.currentTarget.value = "";
|
|
4032
2099
|
},
|
|
4033
2100
|
children: [
|
|
4034
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2101
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Layout\u2026" }),
|
|
4035
2102
|
Object.entries(SLIDE_TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
4036
2103
|
]
|
|
4037
2104
|
}
|
|
@@ -4041,14 +2108,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
4041
2108
|
{
|
|
4042
2109
|
className: "cv-html-tpl",
|
|
4043
2110
|
value: "",
|
|
4044
|
-
title:
|
|
2111
|
+
title: "Apply a slide theme",
|
|
4045
2112
|
onChange: (e) => {
|
|
4046
|
-
const
|
|
4047
|
-
if (
|
|
2113
|
+
const t = SLIDE_THEMES[e.target.value];
|
|
2114
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t.style });
|
|
4048
2115
|
e.currentTarget.value = "";
|
|
4049
2116
|
},
|
|
4050
2117
|
children: [
|
|
4051
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2118
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Theme\u2026" }),
|
|
4052
2119
|
Object.entries(SLIDE_THEMES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
4053
2120
|
]
|
|
4054
2121
|
}
|
|
@@ -4058,14 +2125,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
4058
2125
|
{
|
|
4059
2126
|
className: "cv-html-tpl",
|
|
4060
2127
|
value: "",
|
|
4061
|
-
title:
|
|
2128
|
+
title: "Insert a shape",
|
|
4062
2129
|
onChange: (e) => {
|
|
4063
|
-
const
|
|
4064
|
-
if (
|
|
2130
|
+
const t = SLIDE_SHAPES[e.target.value];
|
|
2131
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
4065
2132
|
e.currentTarget.value = "";
|
|
4066
2133
|
},
|
|
4067
2134
|
children: [
|
|
4068
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2135
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Shape\u2026" }),
|
|
4069
2136
|
Object.entries(SLIDE_SHAPES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
4070
2137
|
]
|
|
4071
2138
|
}
|
|
@@ -4075,19 +2142,19 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
4075
2142
|
{
|
|
4076
2143
|
className: "cv-html-tpl",
|
|
4077
2144
|
value: "",
|
|
4078
|
-
title:
|
|
2145
|
+
title: "Slide font",
|
|
4079
2146
|
onChange: (e) => {
|
|
4080
2147
|
const f = SLIDE_FONTS[e.target.value];
|
|
4081
2148
|
if (f) setSlideStyle({ fontFamily: f.stack });
|
|
4082
2149
|
e.currentTarget.value = "";
|
|
4083
2150
|
},
|
|
4084
2151
|
children: [
|
|
4085
|
-
/* @__PURE__ */ jsx("option", { value: "", children:
|
|
2152
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Font\u2026" }),
|
|
4086
2153
|
Object.entries(SLIDE_FONTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
4087
2154
|
]
|
|
4088
2155
|
}
|
|
4089
2156
|
),
|
|
4090
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title:
|
|
2157
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Slide background image", onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
|
|
4091
2158
|
] }),
|
|
4092
2159
|
selected.length >= 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4093
2160
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" }),
|
|
@@ -4117,33 +2184,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
4117
2184
|
)
|
|
4118
2185
|
] }),
|
|
4119
2186
|
single && single.tag !== "img" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4120
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4121
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4122
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4123
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
2187
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align left", onClick: () => command("style_persist", { prop: "textAlign", value: "left" }), children: "\u2B05" }),
|
|
2188
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align center", onClick: () => command("style_persist", { prop: "textAlign", value: "center" }), children: "\u2B0C" }),
|
|
2189
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align right", onClick: () => command("style_persist", { prop: "textAlign", value: "right" }), children: "\u27A1" }),
|
|
2190
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Bold", onClick: () => command("style_persist", { prop: "fontWeight", value: "800" }), children: "B" })
|
|
4124
2191
|
] }),
|
|
4125
2192
|
single && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4126
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4127
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4128
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title:
|
|
4129
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title:
|
|
2193
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Duplicate", onClick: () => command("duplicate"), children: "\u29C9" }),
|
|
2194
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move up", onClick: () => command("move_up"), children: "\u2191" }),
|
|
2195
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move down", onClick: () => command("move_down"), children: "\u2193" }),
|
|
2196
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title: "Delete", onClick: () => command("delete"), children: "\u{1F5D1}" })
|
|
4130
2197
|
] })
|
|
4131
2198
|
] })
|
|
4132
2199
|
] }),
|
|
4133
2200
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__spacer" }),
|
|
4134
2201
|
/* @__PURE__ */ jsxs("div", { className: "cv-html-seg", role: "group", "aria-label": "View mode", children: [
|
|
4135
|
-
/* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children:
|
|
4136
|
-
/* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children:
|
|
2202
|
+
/* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children: "Design" }),
|
|
2203
|
+
/* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children: "Code" })
|
|
4137
2204
|
] })
|
|
4138
2205
|
] }),
|
|
4139
2206
|
a11y !== null && /* @__PURE__ */ jsxs("div", { className: "cv-a11y", role: "status", children: [
|
|
4140
2207
|
/* @__PURE__ */ jsx("button", { className: "cv-a11y__close", onClick: () => setA11y(null), "aria-label": "Dismiss", children: "\xD7" }),
|
|
4141
|
-
a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children:
|
|
2208
|
+
a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: "\u267F No accessibility issues found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4142
2209
|
/* @__PURE__ */ jsxs("b", { children: [
|
|
4143
2210
|
"\u267F ",
|
|
4144
2211
|
a11y.length,
|
|
4145
|
-
" ",
|
|
4146
|
-
|
|
2212
|
+
" accessibility issue",
|
|
2213
|
+
a11y.length > 1 ? "s" : ""
|
|
4147
2214
|
] }),
|
|
4148
2215
|
/* @__PURE__ */ jsx("ul", { children: a11y.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) })
|
|
4149
2216
|
] })
|
|
@@ -4173,48 +2240,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
4173
2240
|
sandbox: "allow-scripts allow-popups allow-modals",
|
|
4174
2241
|
style: { width: DEVICES.find((d) => d.id === device).width }
|
|
4175
2242
|
}
|
|
4176
|
-
) }) : /* @__PURE__ */ jsx(
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
}, [html]);
|
|
4185
|
-
return /* @__PURE__ */ jsx(
|
|
4186
|
-
"textarea",
|
|
4187
|
-
{
|
|
4188
|
-
className: "cv-html-code",
|
|
4189
|
-
value: draft,
|
|
4190
|
-
spellCheck: false,
|
|
4191
|
-
onChange: (e) => {
|
|
4192
|
-
dirty.current = true;
|
|
4193
|
-
setDraft(e.target.value);
|
|
4194
|
-
},
|
|
4195
|
-
onBlur: () => {
|
|
4196
|
-
if (!dirty.current) return;
|
|
4197
|
-
dirty.current = false;
|
|
4198
|
-
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"
|
|
4199
2251
|
},
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
);
|
|
2252
|
+
artifact2.data.html
|
|
2253
|
+
)
|
|
2254
|
+
] });
|
|
4203
2255
|
}
|
|
4204
2256
|
|
|
4205
2257
|
// src/components/renderers/index.ts
|
|
4206
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
4207
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
4208
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
4209
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
4210
|
-
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-4TZNTSEL.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2262
|
+
var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
|
|
4211
2263
|
var builtinRenderers = {
|
|
4212
2264
|
html: HtmlRenderer,
|
|
4213
2265
|
document: DocumentRenderer,
|
|
4214
2266
|
chart: ChartRenderer,
|
|
4215
2267
|
table: TableRenderer,
|
|
4216
2268
|
slides: SlidesRenderer,
|
|
4217
|
-
|
|
2269
|
+
file: FileRenderer
|
|
4218
2270
|
};
|
|
4219
2271
|
|
|
4220
2272
|
// src/export/download.ts
|
|
@@ -4233,232 +2285,6 @@ function slugify(text) {
|
|
|
4233
2285
|
return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
|
|
4234
2286
|
}
|
|
4235
2287
|
|
|
4236
|
-
// src/io/hwpxWrite.ts
|
|
4237
|
-
var CRC_TABLE = (() => {
|
|
4238
|
-
const table2 = new Uint32Array(256);
|
|
4239
|
-
for (let n = 0; n < 256; n++) {
|
|
4240
|
-
let c = n;
|
|
4241
|
-
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
4242
|
-
table2[n] = c >>> 0;
|
|
4243
|
-
}
|
|
4244
|
-
return table2;
|
|
4245
|
-
})();
|
|
4246
|
-
function crc32(bytes) {
|
|
4247
|
-
let c = 4294967295;
|
|
4248
|
-
for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
4249
|
-
return (c ^ 4294967295) >>> 0;
|
|
4250
|
-
}
|
|
4251
|
-
function storeZip(parts) {
|
|
4252
|
-
const dosTime = 0;
|
|
4253
|
-
const dosDate = 2026 - 1980 << 9 | 1 << 5 | 1;
|
|
4254
|
-
const enc = new TextEncoder();
|
|
4255
|
-
const chunks = [];
|
|
4256
|
-
const central = [];
|
|
4257
|
-
let offset = 0;
|
|
4258
|
-
for (const [name, data] of parts) {
|
|
4259
|
-
const nameBytes = enc.encode(name);
|
|
4260
|
-
const crc = crc32(data);
|
|
4261
|
-
const local = new Uint8Array(30 + nameBytes.length);
|
|
4262
|
-
const lv = new DataView(local.buffer);
|
|
4263
|
-
lv.setUint32(0, 67324752, true);
|
|
4264
|
-
lv.setUint16(4, 20, true);
|
|
4265
|
-
lv.setUint16(8, 0, true);
|
|
4266
|
-
lv.setUint16(10, dosTime, true);
|
|
4267
|
-
lv.setUint16(12, dosDate, true);
|
|
4268
|
-
lv.setUint32(14, crc, true);
|
|
4269
|
-
lv.setUint32(18, data.length, true);
|
|
4270
|
-
lv.setUint32(22, data.length, true);
|
|
4271
|
-
lv.setUint16(26, nameBytes.length, true);
|
|
4272
|
-
local.set(nameBytes, 30);
|
|
4273
|
-
chunks.push(local, data);
|
|
4274
|
-
const cdir = new Uint8Array(46 + nameBytes.length);
|
|
4275
|
-
const cv = new DataView(cdir.buffer);
|
|
4276
|
-
cv.setUint32(0, 33639248, true);
|
|
4277
|
-
cv.setUint16(4, 20, true);
|
|
4278
|
-
cv.setUint16(6, 20, true);
|
|
4279
|
-
cv.setUint16(10, 0, true);
|
|
4280
|
-
cv.setUint16(12, dosTime, true);
|
|
4281
|
-
cv.setUint16(14, dosDate, true);
|
|
4282
|
-
cv.setUint32(16, crc, true);
|
|
4283
|
-
cv.setUint32(20, data.length, true);
|
|
4284
|
-
cv.setUint32(24, data.length, true);
|
|
4285
|
-
cv.setUint16(28, nameBytes.length, true);
|
|
4286
|
-
cv.setUint32(42, offset, true);
|
|
4287
|
-
cdir.set(nameBytes, 46);
|
|
4288
|
-
central.push(cdir);
|
|
4289
|
-
offset += local.length + data.length;
|
|
4290
|
-
}
|
|
4291
|
-
const cdirSize = central.reduce((n, c) => n + c.length, 0);
|
|
4292
|
-
const eocd = new Uint8Array(22);
|
|
4293
|
-
const ev = new DataView(eocd.buffer);
|
|
4294
|
-
ev.setUint32(0, 101010256, true);
|
|
4295
|
-
ev.setUint16(8, parts.length, true);
|
|
4296
|
-
ev.setUint16(10, parts.length, true);
|
|
4297
|
-
ev.setUint32(12, cdirSize, true);
|
|
4298
|
-
ev.setUint32(16, offset, true);
|
|
4299
|
-
const all = [...chunks, ...central, eocd];
|
|
4300
|
-
const out = new Uint8Array(all.reduce((n, c) => n + c.length, 0));
|
|
4301
|
-
let pos = 0;
|
|
4302
|
-
for (const c of all) {
|
|
4303
|
-
out.set(c, pos);
|
|
4304
|
-
pos += c.length;
|
|
4305
|
-
}
|
|
4306
|
-
return out;
|
|
4307
|
-
}
|
|
4308
|
-
var XML_DECL = `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>`;
|
|
4309
|
-
var HWPML_NS = [
|
|
4310
|
-
`xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app"`,
|
|
4311
|
-
`xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"`,
|
|
4312
|
-
`xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph"`,
|
|
4313
|
-
`xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section"`,
|
|
4314
|
-
`xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core"`,
|
|
4315
|
-
`xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"`,
|
|
4316
|
-
`xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history"`,
|
|
4317
|
-
`xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page"`,
|
|
4318
|
-
`xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"`,
|
|
4319
|
-
`xmlns:dc="http://purl.org/dc/elements/1.1/"`,
|
|
4320
|
-
`xmlns:opf="http://www.idpf.org/2007/opf/"`,
|
|
4321
|
-
`xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart"`,
|
|
4322
|
-
`xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar"`,
|
|
4323
|
-
`xmlns:epub="http://www.idpf.org/2007/ops"`,
|
|
4324
|
-
`xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"`
|
|
4325
|
-
].join(" ");
|
|
4326
|
-
var VERSION_XML = XML_DECL + `<hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" tagetApplication="WORDPROCESSOR" major="5" minor="0" micro="5" buildNumber="0" xmlVersion="1.4" application="langchain-canvas" appVersion="1.0"/>`;
|
|
4327
|
-
var CONTAINER_XML = XML_DECL + `<ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"><ocf:rootfiles><ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/></ocf:rootfiles></ocf:container>`;
|
|
4328
|
-
var MANIFEST_XML = XML_DECL + `<odf:manifest xmlns:odf="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"/>`;
|
|
4329
|
-
var CONTENT_HPF = XML_DECL + `<opf:package ${HWPML_NS} version="" unique-identifier="" id=""><opf:metadata><opf:title/><opf:language>ko</opf:language></opf:metadata><opf:manifest><opf:item id="header" href="Contents/header.xml" media-type="application/xml"/><opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/><opf:item id="settings" href="settings.xml" media-type="application/xml"/></opf:manifest><opf:spine><opf:itemref idref="header"/><opf:itemref idref="section0"/></opf:spine></opf:package>`;
|
|
4330
|
-
var SETTINGS_XML = XML_DECL + `<ha:HWPApplicationSetting xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"><ha:CaretPosition listIDRef="0" paraIDRef="0" pos="0"/></ha:HWPApplicationSetting>`;
|
|
4331
|
-
function fontfaces() {
|
|
4332
|
-
const langs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
|
|
4333
|
-
const face = (lang) => `<hh:fontface lang="${lang}" fontCnt="1"><hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="8" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font></hh:fontface>`;
|
|
4334
|
-
return `<hh:fontfaces itemCnt="7">${langs.map(face).join("")}</hh:fontfaces>`;
|
|
4335
|
-
}
|
|
4336
|
-
function borderFills() {
|
|
4337
|
-
const fill = (id) => `<hh:borderFill id="${id}" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0"><hh:slash type="NONE" Crooked="0" isCounter="0"/><hh:backSlash type="NONE" Crooked="0" isCounter="0"/><hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/><hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/><hh:topBorder type="NONE" width="0.1 mm" color="#000000"/><hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/><hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/></hh:borderFill>`;
|
|
4338
|
-
return `<hh:borderFills itemCnt="2">${fill(1)}${fill(2)}</hh:borderFills>`;
|
|
4339
|
-
}
|
|
4340
|
-
var CHAR_PR = [
|
|
4341
|
-
{ id: 0, height: 1e3, bold: false, italic: false },
|
|
4342
|
-
// body
|
|
4343
|
-
{ id: 1, height: 1800, bold: true, italic: false },
|
|
4344
|
-
// heading 1
|
|
4345
|
-
{ id: 2, height: 1500, bold: true, italic: false },
|
|
4346
|
-
// heading 2
|
|
4347
|
-
{ id: 3, height: 1200, bold: true, italic: false },
|
|
4348
|
-
// heading 3
|
|
4349
|
-
{ id: 4, height: 1e3, bold: true, italic: false },
|
|
4350
|
-
// inline bold
|
|
4351
|
-
{ id: 5, height: 1e3, bold: false, italic: true }
|
|
4352
|
-
// inline italic
|
|
4353
|
-
];
|
|
4354
|
-
function charProperties() {
|
|
4355
|
-
const one = (c) => `<hh:charPr id="${c.id}" height="${c.height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2"><hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>` + (c.bold ? `<hh:bold/>` : "") + (c.italic ? `<hh:italic/>` : "") + `<hh:underline type="NONE" shape="SOLID" color="#000000"/><hh:strikeout shape="NONE" color="#000000"/><hh:outline type="NONE"/><hh:shadow type="NONE" color="#B2B2B2" offsetX="10" offsetY="10"/></hh:charPr>`;
|
|
4356
|
-
return `<hh:charProperties itemCnt="${CHAR_PR.length}">${CHAR_PR.map(one).join("")}</hh:charProperties>`;
|
|
4357
|
-
}
|
|
4358
|
-
function numberings() {
|
|
4359
|
-
const heads = Array.from(
|
|
4360
|
-
{ length: 7 },
|
|
4361
|
-
(_, i) => `<hh:paraHead start="1" level="${i + 1}" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="1">^${i + 1}.</hh:paraHead>`
|
|
4362
|
-
).join("");
|
|
4363
|
-
return `<hh:numberings itemCnt="1"><hh:numbering id="1" start="0">${heads}</hh:numbering></hh:numberings>`;
|
|
4364
|
-
}
|
|
4365
|
-
function paraProperties() {
|
|
4366
|
-
return `<hh:paraProperties itemCnt="1"><hh:paraPr id="0" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0"><hh:align horizontal="JUSTIFY" vertical="BASELINE"/><hh:heading type="NONE" idRef="0" level="0"/><hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="KEEP_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/><hh:autoSpacing eAsianEng="0" eAsianNum="0"/><hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="0" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="0" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin><hh:lineSpacing type="PERCENT" value="160" unit="HWPUNIT"/><hh:border borderFillIDRef="2" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/></hh:paraPr></hh:paraProperties>`;
|
|
4367
|
-
}
|
|
4368
|
-
var STYLES = [
|
|
4369
|
-
{ id: 0, name: "\uBC14\uD0D5\uAE00", engName: "Normal", charPr: 0, next: 0 },
|
|
4370
|
-
{ id: 1, name: "\uAC1C\uC694 1", engName: "Outline 1", charPr: 1, next: 1 },
|
|
4371
|
-
{ id: 2, name: "\uAC1C\uC694 2", engName: "Outline 2", charPr: 2, next: 2 },
|
|
4372
|
-
{ id: 3, name: "\uAC1C\uC694 3", engName: "Outline 3", charPr: 3, next: 3 }
|
|
4373
|
-
];
|
|
4374
|
-
function styles() {
|
|
4375
|
-
const one = (s) => `<hh:style id="${s.id}" type="PARA" name="${s.name}" engName="${s.engName}" paraPrIDRef="0" charPrIDRef="${s.charPr}" nextStyleIDRef="${s.next}" langID="1042" lockForm="0"/>`;
|
|
4376
|
-
return `<hh:styles itemCnt="${STYLES.length}">${STYLES.map(one).join("")}</hh:styles>`;
|
|
4377
|
-
}
|
|
4378
|
-
function headerXml() {
|
|
4379
|
-
return XML_DECL + `<hh:head ${HWPML_NS} version="1.4" secCnt="1"><hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/><hh:refList>` + fontfaces() + borderFills() + charProperties() + `<hh:tabProperties itemCnt="2"><hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/><hh:tabPr id="1" autoTabLeft="1" autoTabRight="0"/></hh:tabProperties>` + numberings() + paraProperties() + styles() + `</hh:refList><hh:compatibleDocument targetProgram="HWP201X"><hh:layoutCompatibility/></hh:compatibleDocument><hh:docOption><hh:linkinfo path="" pageInherit="0" footnoteInherit="0"/></hh:docOption><hh:trackchageConfig flags="56"/></hh:head>`;
|
|
4380
|
-
}
|
|
4381
|
-
var SEC_PR = `<hp:secPr id="" textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" tabStopVal="4000" tabStopUnit="HWPUNIT" outlineShapeIDRef="1" memoShapeIDRef="0" textVerticalWidthHead="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:lineNumberShape restartType="0" countBy="0" distance="0" startNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="4252" footer="4252" gutter="0" left="8504" right="8504" top="5668" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr><hp:pageBorderFill type="BOTH" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="EVEN" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="ODD" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill></hp:secPr><hp:ctrl><hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/></hp:ctrl>`;
|
|
4382
|
-
function escapeXml(value) {
|
|
4383
|
-
return String(value ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4384
|
-
}
|
|
4385
|
-
function inlineRuns(text, baseCharPr) {
|
|
4386
|
-
const runs = [];
|
|
4387
|
-
const pattern = /(\*\*[^*\n]+\*\*|\*[^*\n]+\*|`[^`\n]+`)/g;
|
|
4388
|
-
let last = 0;
|
|
4389
|
-
for (const m of text.matchAll(pattern)) {
|
|
4390
|
-
if (m.index > last) runs.push({ text: text.slice(last, m.index), charPr: baseCharPr });
|
|
4391
|
-
const token = m[0];
|
|
4392
|
-
if (token.startsWith("**")) runs.push({ text: token.slice(2, -2), charPr: 4 });
|
|
4393
|
-
else if (token.startsWith("*")) runs.push({ text: token.slice(1, -1), charPr: 5 });
|
|
4394
|
-
else runs.push({ text: token.slice(1, -1), charPr: baseCharPr });
|
|
4395
|
-
last = m.index + token.length;
|
|
4396
|
-
}
|
|
4397
|
-
if (last < text.length) runs.push({ text: text.slice(last), charPr: baseCharPr });
|
|
4398
|
-
return runs.length ? runs : [{ text: "", charPr: baseCharPr }];
|
|
4399
|
-
}
|
|
4400
|
-
function isTableSeparator(line) {
|
|
4401
|
-
return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/.test(line);
|
|
4402
|
-
}
|
|
4403
|
-
function tableCells(line) {
|
|
4404
|
-
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
4405
|
-
}
|
|
4406
|
-
function markdownToBlocks(content) {
|
|
4407
|
-
const blocks = [];
|
|
4408
|
-
for (const raw of content.split("\n")) {
|
|
4409
|
-
const line = raw.replace(/\s+$/, "");
|
|
4410
|
-
if (!line.trim()) continue;
|
|
4411
|
-
const heading = line.match(/^(#{1,3})\s+(.*)$/);
|
|
4412
|
-
if (heading) {
|
|
4413
|
-
const text = heading[2].replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1");
|
|
4414
|
-
blocks.push({ runs: [{ text, charPr: heading[1].length }], style: heading[1].length });
|
|
4415
|
-
continue;
|
|
4416
|
-
}
|
|
4417
|
-
const bullet = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
4418
|
-
if (bullet) {
|
|
4419
|
-
blocks.push({ runs: [{ text: "\u2022 ", charPr: 0 }, ...inlineRuns(bullet[1], 0)], style: 0 });
|
|
4420
|
-
continue;
|
|
4421
|
-
}
|
|
4422
|
-
const numbered = line.match(/^\s*(\d+)[.)]\s+(.*)$/);
|
|
4423
|
-
if (numbered) {
|
|
4424
|
-
blocks.push({ runs: [{ text: `${numbered[1]}. `, charPr: 0 }, ...inlineRuns(numbered[2], 0)], style: 0 });
|
|
4425
|
-
continue;
|
|
4426
|
-
}
|
|
4427
|
-
if (line.trim().startsWith("|")) {
|
|
4428
|
-
if (isTableSeparator(line)) continue;
|
|
4429
|
-
blocks.push({ runs: [{ text: tableCells(line).join(" | "), charPr: 0 }], style: 0 });
|
|
4430
|
-
continue;
|
|
4431
|
-
}
|
|
4432
|
-
blocks.push({ runs: inlineRuns(line, 0), style: 0 });
|
|
4433
|
-
}
|
|
4434
|
-
return blocks.length ? blocks : [{ runs: [{ text: "", charPr: 0 }], style: 0 }];
|
|
4435
|
-
}
|
|
4436
|
-
function paragraphXml(block, index) {
|
|
4437
|
-
const runs = block.runs.map(
|
|
4438
|
-
(r, i) => `<hp:run charPrIDRef="${r.charPr}">${index === 0 && i === 0 ? SEC_PR : ""}<hp:t>${escapeXml(r.text)}</hp:t></hp:run>`
|
|
4439
|
-
).join("");
|
|
4440
|
-
return `<hp:p id="${index}" paraPrIDRef="0" styleIDRef="${block.style}" pageBreak="0" columnBreak="0" merged="0">` + runs + `<hp:linesegarray><hp:lineseg textpos="0" vertpos="0" vertsize="1000" textheight="1000" baseline="850" spacing="600" horzpos="0" horzsize="42520" flags="393216"/></hp:linesegarray></hp:p>`;
|
|
4441
|
-
}
|
|
4442
|
-
function sectionXml(blocks) {
|
|
4443
|
-
return XML_DECL + `<hs:sec ${HWPML_NS}>` + blocks.map(paragraphXml).join("") + `</hs:sec>`;
|
|
4444
|
-
}
|
|
4445
|
-
async function documentToHwpx(data) {
|
|
4446
|
-
const enc = new TextEncoder();
|
|
4447
|
-
const blocks = markdownToBlocks(data.content ?? "");
|
|
4448
|
-
const parts = [
|
|
4449
|
-
// `mimetype` must be the container's first entry, stored.
|
|
4450
|
-
["mimetype", enc.encode("application/hwp+zip")],
|
|
4451
|
-
["version.xml", enc.encode(VERSION_XML)],
|
|
4452
|
-
["META-INF/manifest.xml", enc.encode(MANIFEST_XML)],
|
|
4453
|
-
["META-INF/container.xml", enc.encode(CONTAINER_XML)],
|
|
4454
|
-
["Contents/content.hpf", enc.encode(CONTENT_HPF)],
|
|
4455
|
-
["Contents/header.xml", enc.encode(headerXml())],
|
|
4456
|
-
["Contents/section0.xml", enc.encode(sectionXml(blocks))],
|
|
4457
|
-
["settings.xml", enc.encode(SETTINGS_XML)]
|
|
4458
|
-
];
|
|
4459
|
-
return storeZip(parts);
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
2288
|
// src/export/exporters.ts
|
|
4463
2289
|
var MIME = {
|
|
4464
2290
|
md: "text/markdown",
|
|
@@ -4471,8 +2297,7 @@ var MIME = {
|
|
|
4471
2297
|
var dataExporters = {
|
|
4472
2298
|
document: [
|
|
4473
2299
|
{ label: "Markdown", extension: "md", mime: MIME.md, build: (a) => a.data.content },
|
|
4474
|
-
{ label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) }
|
|
4475
|
-
{ label: "\uD55C\uAE00 (HWPX)", extension: "hwpx", mime: "application/vnd.hancom.hwpx", build: (a) => documentToHwpx(a.data) }
|
|
2300
|
+
{ label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) }
|
|
4476
2301
|
],
|
|
4477
2302
|
table: [
|
|
4478
2303
|
{ label: "CSV", extension: "csv", mime: MIME.csv, build: (a) => tableToCsv(a.data) },
|
|
@@ -4491,7 +2316,7 @@ function toStandaloneHtml(title, renderedHtml) {
|
|
|
4491
2316
|
<head>
|
|
4492
2317
|
<meta charset="utf-8" />
|
|
4493
2318
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
4494
|
-
<title>${
|
|
2319
|
+
<title>${escapeHtml(title)}</title>
|
|
4495
2320
|
<style>${EXPORT_CSS}</style>
|
|
4496
2321
|
</head>
|
|
4497
2322
|
<body>
|
|
@@ -4502,43 +2327,38 @@ ${renderedHtml}
|
|
|
4502
2327
|
</html>`;
|
|
4503
2328
|
}
|
|
4504
2329
|
function tableToCsv(data) {
|
|
4505
|
-
|
|
2330
|
+
const rows = data.sheet?.length ? projectSheetIntoRows(data.columns, data.rows, data.sheet) : data.rows;
|
|
4506
2331
|
const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
|
|
4507
|
-
const body =
|
|
2332
|
+
const body = rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
|
|
4508
2333
|
return `${header}
|
|
4509
2334
|
${body}`;
|
|
4510
2335
|
}
|
|
4511
|
-
function fortuneToCsv(sheet) {
|
|
4512
|
-
let maxR = -1;
|
|
4513
|
-
let maxC = -1;
|
|
4514
|
-
const grid = /* @__PURE__ */ new Map();
|
|
4515
|
-
for (const cell of sheet.celldata ?? []) {
|
|
4516
|
-
const v = cell?.v;
|
|
4517
|
-
if (v == null) continue;
|
|
4518
|
-
if (typeof v === "object" && v.mc && v.v == null && v.m == null) continue;
|
|
4519
|
-
const value = typeof v === "object" ? v.v ?? v.m ?? "" : v;
|
|
4520
|
-
if (value === "" || value == null) continue;
|
|
4521
|
-
grid.set(`${cell.r},${cell.c}`, String(value));
|
|
4522
|
-
if (cell.r > maxR) maxR = cell.r;
|
|
4523
|
-
if (cell.c > maxC) maxC = cell.c;
|
|
4524
|
-
}
|
|
4525
|
-
const lines = [];
|
|
4526
|
-
for (let r = 0; r <= maxR; r++) {
|
|
4527
|
-
const row = [];
|
|
4528
|
-
for (let c = 0; c <= maxC; c++) row.push(csvCell(grid.get(`${r},${c}`) ?? ""));
|
|
4529
|
-
lines.push(row.join(","));
|
|
4530
|
-
}
|
|
4531
|
-
return lines.join("\n");
|
|
4532
|
-
}
|
|
4533
2336
|
async function tableToXlsx(data) {
|
|
4534
2337
|
const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
|
|
4535
2338
|
const workbook = new Workbook();
|
|
4536
2339
|
if (data.sheet?.length) {
|
|
4537
|
-
|
|
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);
|
|
4538
2348
|
} else {
|
|
4539
2349
|
const sheet = workbook.addWorksheet("Sheet1");
|
|
4540
2350
|
sheet.addRow(data.columns.map((c) => c.label ?? c.key));
|
|
4541
|
-
|
|
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
|
+
});
|
|
4542
2362
|
sheet.getRow(1).font = { bold: true };
|
|
4543
2363
|
}
|
|
4544
2364
|
return workbook.xlsx.writeBuffer();
|
|
@@ -4551,7 +2371,8 @@ function fortuneToWorkbook(workbook, sheets) {
|
|
|
4551
2371
|
const v = cell.v;
|
|
4552
2372
|
const value = v && typeof v === "object" ? v.v ?? v.m ?? null : v;
|
|
4553
2373
|
const xc = ws.getCell(cell.r + 1, cell.c + 1);
|
|
4554
|
-
|
|
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;
|
|
4555
2376
|
if (v && typeof v === "object") {
|
|
4556
2377
|
if (v.bl) xc.font = { ...xc.font, bold: true };
|
|
4557
2378
|
if (v.it) xc.font = { ...xc.font, italic: true };
|
|
@@ -4593,10 +2414,9 @@ async function documentToDocx(data) {
|
|
|
4593
2414
|
async function slidesToPptx(data, _title) {
|
|
4594
2415
|
const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
|
|
4595
2416
|
const pptx = new PptxGenJS();
|
|
4596
|
-
const W =
|
|
4597
|
-
|
|
4598
|
-
pptx.
|
|
4599
|
-
pptx.layout = "CV_16x9";
|
|
2417
|
+
const { widthIn: W, heightIn: H } = deckPage(data);
|
|
2418
|
+
pptx.defineLayout({ name: "CV_PAGE", width: W, height: H });
|
|
2419
|
+
pptx.layout = "CV_PAGE";
|
|
4600
2420
|
for (const slide of data.slides) {
|
|
4601
2421
|
const s = pptx.addSlide();
|
|
4602
2422
|
if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
|
|
@@ -4605,28 +2425,18 @@ async function slidesToPptx(data, _title) {
|
|
|
4605
2425
|
const inset = (v) => pad + v / 100 * (1 - 2 * pad);
|
|
4606
2426
|
for (const el of resolveElements(slide)) {
|
|
4607
2427
|
const box = { x: inset(el.x) * W, y: inset(el.y) * H, w: el.w / 100 * (1 - 2 * pad) * W, h: el.h / 100 * (1 - 2 * pad) * H };
|
|
4608
|
-
const spin = el.rotate ? { rotate: (Math.round(el.rotate) % 360 + 360) % 360 } : {};
|
|
4609
2428
|
if (el.type === "text") {
|
|
4610
2429
|
const color = el.color ? el.color.replace("#", "") : tc;
|
|
4611
|
-
s.addText(el.text ?? "", { ...box,
|
|
2430
|
+
s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
|
|
4612
2431
|
} else if (el.type === "shape") {
|
|
4613
2432
|
const fill = (el.fill ?? "#5b5bd6").replace("#", "");
|
|
4614
|
-
const paint = el.fill ? { fill: { color: fill } } : { fill: { color: "FFFFFF", transparency: 100 }, line: { color: (tc || "1f2328").replace("#", ""), width: 1 } };
|
|
4615
2433
|
if (el.shape === "line") {
|
|
4616
|
-
s.addShape(pptx.ShapeType.line, { ...box,
|
|
4617
|
-
} else if (el.shape !== "ellipse" && el.radius) {
|
|
4618
|
-
s.addShape(pptx.ShapeType.roundRect, { ...box, ...spin, ...paint, rectRadius: el.radius * W / 1280 });
|
|
2434
|
+
s.addShape(pptx.ShapeType.line, { ...box, line: { color: fill, width: 2 } });
|
|
4619
2435
|
} else {
|
|
4620
|
-
s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box,
|
|
2436
|
+
s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, fill: { color: fill } });
|
|
4621
2437
|
}
|
|
4622
2438
|
} else if (el.src) {
|
|
4623
|
-
s.addImage({
|
|
4624
|
-
data: el.src,
|
|
4625
|
-
...box,
|
|
4626
|
-
...spin,
|
|
4627
|
-
sizing: { type: el.fit === "cover" ? "cover" : "contain", w: box.w, h: box.h },
|
|
4628
|
-
...el.radius ? { rounding: true } : {}
|
|
4629
|
-
});
|
|
2439
|
+
s.addImage({ data: el.src, ...box, sizing: { type: "contain", w: box.w, h: box.h } });
|
|
4630
2440
|
}
|
|
4631
2441
|
}
|
|
4632
2442
|
if (slide.notes) s.addNotes(slide.notes);
|
|
@@ -4635,38 +2445,36 @@ async function slidesToPptx(data, _title) {
|
|
|
4635
2445
|
}
|
|
4636
2446
|
function slidesToPrintHtml(data, title) {
|
|
4637
2447
|
const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
|
|
2448
|
+
const page = deckPage(data);
|
|
2449
|
+
const pw = Math.round(page.widthIn * 128);
|
|
2450
|
+
const ph = Math.round(page.heightIn * 128);
|
|
4638
2451
|
const pages = slides2.map((slide) => {
|
|
4639
2452
|
const bg = slide.background ?? "#ffffff";
|
|
4640
2453
|
const fg = slide.textColor ?? "#1f2328";
|
|
4641
2454
|
const els = resolveElements(slide).map((el) => {
|
|
4642
|
-
const
|
|
4643
|
-
const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%${spin}`;
|
|
2455
|
+
const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%`;
|
|
4644
2456
|
if (el.type === "text") {
|
|
4645
|
-
const style = `${box};font-size:${el.fontSize ?? 24}
|
|
4646
|
-
return `<div class="el" style="${style}">${
|
|
2457
|
+
const style = `${box};font-size:${(el.fontSize ?? 24) / 7.2}vw;font-weight:${el.bold ? 700 : 400};color:${escapeAttr(el.color ?? fg)};text-align:${escapeAttr(el.align ?? "left")};white-space:pre-wrap`;
|
|
2458
|
+
return `<div class="el" style="${style}">${escapeXml(el.text ?? "")}</div>`;
|
|
4647
2459
|
}
|
|
4648
2460
|
if (el.type === "shape") {
|
|
4649
|
-
const
|
|
4650
|
-
const
|
|
4651
|
-
return `<div class="el" style="${box}
|
|
2461
|
+
const fill = escapeAttr(el.fill ?? fg);
|
|
2462
|
+
const radius = el.shape === "ellipse" ? "50%" : el.shape === "line" ? "2px" : "8px";
|
|
2463
|
+
return `<div class="el" style="${box};background:${fill};border-radius:${radius}"></div>`;
|
|
4652
2464
|
}
|
|
4653
2465
|
const src = safeSrc(el.src);
|
|
4654
|
-
|
|
4655
|
-
return src ? `<img class="el" style="${imgStyle}" src="${escapeAttr(src)}"/>` : "";
|
|
2466
|
+
return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
|
|
4656
2467
|
}).join("");
|
|
4657
2468
|
const pad = slide.padding ?? 0;
|
|
4658
2469
|
const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
|
|
4659
2470
|
return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
|
|
4660
2471
|
}).join("");
|
|
4661
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>${
|
|
4662
|
-
@page { size:
|
|
2472
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
|
|
2473
|
+
@page { size: ${pw}px ${ph}px; margin: 0; }
|
|
4663
2474
|
* { margin: 0; box-sizing: border-box; }
|
|
4664
2475
|
body { font-family: Inter, Arial, sans-serif; }
|
|
4665
|
-
.slide { position: relative; width:
|
|
4666
|
-
.el { position: absolute; overflow: hidden; line-height: 1.
|
|
4667
|
-
/* PowerPoint lets text paint past its box \u2014 only shapes/images clip. */
|
|
4668
|
-
div.el { overflow: visible; }
|
|
4669
|
-
img.el { overflow: hidden; }
|
|
2476
|
+
.slide { position: relative; width: ${pw}px; height: ${ph}px; overflow: hidden; page-break-after: always; }
|
|
2477
|
+
.el { position: absolute; overflow: hidden; line-height: 1.25; }
|
|
4670
2478
|
img.el { object-fit: contain; }
|
|
4671
2479
|
</style></head><body>${pages}</body></html>`;
|
|
4672
2480
|
}
|
|
@@ -4677,7 +2485,7 @@ function htmlSlideToPrintHtml(html, ratio) {
|
|
|
4677
2485
|
const i = html.toLowerCase().lastIndexOf("</head>");
|
|
4678
2486
|
return i === -1 ? style + html : html.slice(0, i) + style + html.slice(i);
|
|
4679
2487
|
}
|
|
4680
|
-
function
|
|
2488
|
+
function escapeXml(value) {
|
|
4681
2489
|
return String(value ?? "").replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
4682
2490
|
}
|
|
4683
2491
|
function escapeAttr(value) {
|
|
@@ -4690,7 +2498,7 @@ function safeSrc(src) {
|
|
|
4690
2498
|
function csvCell(value) {
|
|
4691
2499
|
return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
4692
2500
|
}
|
|
4693
|
-
function
|
|
2501
|
+
function escapeHtml(text) {
|
|
4694
2502
|
return text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4695
2503
|
}
|
|
4696
2504
|
var EXPORT_CSS = `
|
|
@@ -4734,63 +2542,62 @@ function printToPdf(html) {
|
|
|
4734
2542
|
document.body.appendChild(iframe);
|
|
4735
2543
|
}
|
|
4736
2544
|
var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
|
|
4737
|
-
var escapeHtml3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4738
|
-
var escapeAttr2 = (s) => escapeHtml3(s).replace(/"/g, """);
|
|
4739
2545
|
function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
4740
|
-
const t = useT();
|
|
4741
2546
|
const [open, setOpen] = useState(false);
|
|
4742
2547
|
const stem = slugify(artifact2.title);
|
|
4743
2548
|
const dataOptions = dataExporters[artifact2.type] ?? [];
|
|
4744
|
-
const
|
|
2549
|
+
const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
|
|
2550
|
+
const prepare = () => inlineArtifactAssets(artifact2, assetBaseUrl);
|
|
2551
|
+
const prepareHtml = (html) => assetBaseUrl ? inlineHtmlAssets(html, assetBaseUrl) : Promise.resolve(html);
|
|
2552
|
+
const exportHtml = async () => {
|
|
4745
2553
|
if (artifact2.type === "html") {
|
|
4746
2554
|
const ratio = artifact2.meta?.ratio;
|
|
4747
|
-
const html =
|
|
2555
|
+
const html = (await prepare()).data.html;
|
|
4748
2556
|
downloadBlob(`${stem}.html`, "text/html", ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
4749
2557
|
} else {
|
|
4750
2558
|
const html = getRenderedHtml();
|
|
4751
2559
|
if (html == null) return;
|
|
4752
|
-
downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, html));
|
|
2560
|
+
downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, await prepareHtml(html)));
|
|
4753
2561
|
}
|
|
4754
2562
|
setOpen(false);
|
|
4755
2563
|
};
|
|
4756
2564
|
const exportData = async (option) => {
|
|
4757
|
-
const content = await option.build(
|
|
2565
|
+
const content = await option.build(await prepare());
|
|
4758
2566
|
downloadBlob(`${stem}.${option.extension}`, option.mime, content);
|
|
4759
2567
|
setOpen(false);
|
|
4760
2568
|
};
|
|
4761
|
-
const exportPdf = () => {
|
|
2569
|
+
const exportPdf = async () => {
|
|
4762
2570
|
if (artifact2.type === "slides") {
|
|
4763
|
-
printToPdf(slidesToPrintHtml(
|
|
2571
|
+
printToPdf(slidesToPrintHtml((await prepare()).data, artifact2.title));
|
|
4764
2572
|
} else if (artifact2.type === "html") {
|
|
4765
2573
|
const ratio = artifact2.meta?.ratio;
|
|
4766
|
-
const html =
|
|
2574
|
+
const html = (await prepare()).data.html;
|
|
4767
2575
|
printToPdf(ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
4768
2576
|
} else {
|
|
4769
2577
|
const html = getRenderedHtml();
|
|
4770
2578
|
if (html == null) return;
|
|
4771
|
-
printToPdf(toStandaloneHtml(artifact2.title, html));
|
|
2579
|
+
printToPdf(toStandaloneHtml(artifact2.title, await prepareHtml(html)));
|
|
4772
2580
|
}
|
|
4773
2581
|
setOpen(false);
|
|
4774
2582
|
};
|
|
4775
|
-
const openInTab = () => {
|
|
4776
|
-
const html = artifact2.type === "html" ?
|
|
2583
|
+
const openInTab = async () => {
|
|
2584
|
+
const html = artifact2.type === "html" ? (await prepare()).data.html : artifact2.type === "slides" ? slidesToPrintHtml((await prepare()).data, artifact2.title) : await (async () => {
|
|
4777
2585
|
const h = getRenderedHtml();
|
|
4778
|
-
return h == null ? null : toStandaloneHtml(artifact2.title, h);
|
|
2586
|
+
return h == null ? null : toStandaloneHtml(artifact2.title, await prepareHtml(h));
|
|
4779
2587
|
})();
|
|
4780
2588
|
if (html == null) return;
|
|
4781
|
-
const
|
|
4782
|
-
const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
|
|
2589
|
+
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
|
4783
2590
|
window.open(url, "_blank", "noopener");
|
|
4784
2591
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
4785
2592
|
setOpen(false);
|
|
4786
2593
|
};
|
|
4787
2594
|
const [copied, setCopied] = useState(false);
|
|
4788
2595
|
const copyHtml = async () => {
|
|
4789
|
-
const html = artifact2.type === "html" ?
|
|
2596
|
+
const html = artifact2.type === "html" ? (await prepare()).data.html : getRenderedHtml();
|
|
4790
2597
|
if (html == null) return;
|
|
4791
2598
|
try {
|
|
4792
2599
|
await navigator.clipboard.writeText(
|
|
4793
|
-
artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, html)
|
|
2600
|
+
artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, await prepareHtml(html))
|
|
4794
2601
|
);
|
|
4795
2602
|
setCopied(true);
|
|
4796
2603
|
setTimeout(() => setCopied(false), 1400);
|
|
@@ -4805,14 +2612,14 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
4805
2612
|
onClick: () => setOpen((o) => !o),
|
|
4806
2613
|
"aria-haspopup": "menu",
|
|
4807
2614
|
"aria-expanded": open,
|
|
4808
|
-
children:
|
|
2615
|
+
children: "Export \u25BE"
|
|
4809
2616
|
}
|
|
4810
2617
|
),
|
|
4811
2618
|
open && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4812
2619
|
/* @__PURE__ */ jsx("div", { className: "cv-export__scrim", onClick: () => setOpen(false) }),
|
|
4813
2620
|
/* @__PURE__ */ jsxs("div", { className: "cv-export__menu", role: "menu", children: [
|
|
4814
|
-
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children:
|
|
4815
|
-
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ?
|
|
2621
|
+
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: "Open in new tab \u2197" }),
|
|
2622
|
+
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? "Copied \u2713" : "Copy HTML" }),
|
|
4816
2623
|
/* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: exportHtml, children: [
|
|
4817
2624
|
"HTML ",
|
|
4818
2625
|
/* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".html" })
|
|
@@ -4901,17 +2708,17 @@ var GRADIENTS = [
|
|
|
4901
2708
|
function StylePanel({ selection }) {
|
|
4902
2709
|
const send = useCanvasStore((s) => s.sendIframeCommand);
|
|
4903
2710
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
4904
|
-
const
|
|
4905
|
-
const [color, setColor] = useState(toHex2(
|
|
4906
|
-
const [background, setBackground] = useState(toHex2(
|
|
4907
|
-
const [fontSize, setFontSize] = useState(px(
|
|
4908
|
-
const [fontWeight, setFontWeight] = useState(String(
|
|
4909
|
-
const [textAlign, setTextAlign] = useState(
|
|
4910
|
-
const [lineHeight, setLineHeight] = useState(px(
|
|
4911
|
-
const [letterSpacing, setLetterSpacing] = useState(px(
|
|
4912
|
-
const [padding, setPadding] = useState(px(
|
|
4913
|
-
const [radius, setRadius] = useState(px(
|
|
4914
|
-
const [width, setWidth] = useState(px(
|
|
2711
|
+
const styles = selection.styles ?? {};
|
|
2712
|
+
const [color, setColor] = useState(toHex2(styles.color));
|
|
2713
|
+
const [background, setBackground] = useState(toHex2(styles.backgroundColor));
|
|
2714
|
+
const [fontSize, setFontSize] = useState(px(styles.fontSize, 16));
|
|
2715
|
+
const [fontWeight, setFontWeight] = useState(String(styles.fontWeight ?? "400"));
|
|
2716
|
+
const [textAlign, setTextAlign] = useState(styles.textAlign ?? "left");
|
|
2717
|
+
const [lineHeight, setLineHeight] = useState(px(styles.lineHeight, 0));
|
|
2718
|
+
const [letterSpacing, setLetterSpacing] = useState(px(styles.letterSpacing, 0));
|
|
2719
|
+
const [padding, setPadding] = useState(px(styles.padding, 0));
|
|
2720
|
+
const [radius, setRadius] = useState(px(styles.borderRadius, 0));
|
|
2721
|
+
const [width, setWidth] = useState(px(styles.width, 0));
|
|
4915
2722
|
const bgFileRef = useRef(null);
|
|
4916
2723
|
const dirty = useRef(false);
|
|
4917
2724
|
const setStyle = (prop, value) => {
|
|
@@ -5127,8 +2934,10 @@ function toHex2(value) {
|
|
|
5127
2934
|
if (!parts || parts.length < 3) return "#000000";
|
|
5128
2935
|
return "#" + parts.slice(0, 3).map((n) => Number(n).toString(16).padStart(2, "0")).join("");
|
|
5129
2936
|
}
|
|
5130
|
-
function useCanvasImport() {
|
|
2937
|
+
function useCanvasImport({ onImported } = {}) {
|
|
5131
2938
|
const api = useCanvasStoreApi();
|
|
2939
|
+
const imported = useRef(onImported);
|
|
2940
|
+
imported.current = onImported;
|
|
5132
2941
|
const importFiles = useCallback(
|
|
5133
2942
|
async (files) => {
|
|
5134
2943
|
let lastId = null;
|
|
@@ -5141,6 +2950,7 @@ function useCanvasImport() {
|
|
|
5141
2950
|
if (created && created.type === "canvas.create") {
|
|
5142
2951
|
lastId = created.artifact.id;
|
|
5143
2952
|
api.getState().setActiveArtifact(lastId);
|
|
2953
|
+
imported.current?.(created.artifact, file);
|
|
5144
2954
|
}
|
|
5145
2955
|
} catch (err) {
|
|
5146
2956
|
console.error("[langchain-canvas] import failed:", file.name, err);
|
|
@@ -5153,16 +2963,38 @@ function useCanvasImport() {
|
|
|
5153
2963
|
return { importFiles, canImport };
|
|
5154
2964
|
}
|
|
5155
2965
|
var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
|
|
5156
|
-
function Canvas({
|
|
5157
|
-
|
|
2966
|
+
function Canvas({
|
|
2967
|
+
registry = builtinRenderers,
|
|
2968
|
+
emptyState,
|
|
2969
|
+
onEditElement,
|
|
2970
|
+
onUserEdit,
|
|
2971
|
+
onSave,
|
|
2972
|
+
onFilesOpened,
|
|
2973
|
+
onImported,
|
|
2974
|
+
assetBaseUrl
|
|
2975
|
+
}) {
|
|
2976
|
+
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(
|
|
2977
|
+
CanvasPanel,
|
|
2978
|
+
{
|
|
2979
|
+
emptyState,
|
|
2980
|
+
onEditElement,
|
|
2981
|
+
onUserEdit,
|
|
2982
|
+
onSave,
|
|
2983
|
+
onFilesOpened,
|
|
2984
|
+
onImported,
|
|
2985
|
+
assetBaseUrl
|
|
2986
|
+
}
|
|
2987
|
+
) });
|
|
5158
2988
|
}
|
|
5159
2989
|
function CanvasPanel({
|
|
5160
2990
|
emptyState,
|
|
5161
2991
|
onEditElement,
|
|
5162
2992
|
onUserEdit,
|
|
5163
|
-
onSave
|
|
2993
|
+
onSave,
|
|
2994
|
+
onFilesOpened,
|
|
2995
|
+
onImported,
|
|
2996
|
+
assetBaseUrl
|
|
5164
2997
|
}) {
|
|
5165
|
-
const t = useT();
|
|
5166
2998
|
const debouncedSave = useCanvasSave(onSave);
|
|
5167
2999
|
const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
|
|
5168
3000
|
const history = useCanvasStore((s) => s.canvas.history);
|
|
@@ -5170,8 +3002,16 @@ function CanvasPanel({
|
|
|
5170
3002
|
const selections = useCanvasStore((s) => s.selections);
|
|
5171
3003
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
5172
3004
|
const setOnUserEdit = useCanvasStore((s) => s.setOnUserEdit);
|
|
5173
|
-
const
|
|
3005
|
+
const setAssetBaseUrl = useCanvasStore((s) => s.setAssetBaseUrl);
|
|
3006
|
+
const { importFiles } = useCanvasImport({ onImported });
|
|
5174
3007
|
const [dropping, setDropping] = useState(false);
|
|
3008
|
+
useEffect(() => {
|
|
3009
|
+
setAssetBaseUrl(assetBaseUrl ?? null);
|
|
3010
|
+
}, [assetBaseUrl, setAssetBaseUrl]);
|
|
3011
|
+
const openFiles = (files) => {
|
|
3012
|
+
onFilesOpened?.(Array.from(files));
|
|
3013
|
+
void importFiles(files);
|
|
3014
|
+
};
|
|
5175
3015
|
useEffect(() => {
|
|
5176
3016
|
if (!onUserEdit && !debouncedSave) {
|
|
5177
3017
|
setOnUserEdit(null);
|
|
@@ -5205,13 +3045,13 @@ function CanvasPanel({
|
|
|
5205
3045
|
onDrop: (e) => {
|
|
5206
3046
|
e.preventDefault();
|
|
5207
3047
|
setDropping(false);
|
|
5208
|
-
if (e.dataTransfer.files.length)
|
|
3048
|
+
if (e.dataTransfer.files.length) openFiles(e.dataTransfer.files);
|
|
5209
3049
|
}
|
|
5210
3050
|
};
|
|
5211
|
-
const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children:
|
|
3051
|
+
const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "Drop to open on the canvas" }) : null;
|
|
5212
3052
|
if (!active) {
|
|
5213
3053
|
return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
|
|
5214
|
-
emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles:
|
|
3054
|
+
emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: openFiles, acceptAll: Boolean(onFilesOpened) }),
|
|
5215
3055
|
dropOverlay
|
|
5216
3056
|
] });
|
|
5217
3057
|
}
|
|
@@ -5238,11 +3078,10 @@ function CanvasPanel({
|
|
|
5238
3078
|
] });
|
|
5239
3079
|
}
|
|
5240
3080
|
function ArtifactView({ artifact: artifact2, versions: versions2 }) {
|
|
5241
|
-
const t = useT();
|
|
5242
3081
|
const [viewIndex, setViewIndex] = useState(null);
|
|
5243
3082
|
const bodyRef = useRef(null);
|
|
5244
|
-
const shown = viewIndex === null ? artifact2 : versions2[
|
|
5245
|
-
const viewingHistory = viewIndex !== null &&
|
|
3083
|
+
const shown = viewIndex === null ? artifact2 : versions2[viewIndex];
|
|
3084
|
+
const viewingHistory = viewIndex !== null && viewIndex !== versions2.length - 1;
|
|
5246
3085
|
const Renderer = useRenderer(shown.type);
|
|
5247
3086
|
const getRenderedHtml = () => {
|
|
5248
3087
|
const node = bodyRef.current;
|
|
@@ -5272,24 +3111,21 @@ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
|
|
|
5272
3111
|
] })
|
|
5273
3112
|
] }),
|
|
5274
3113
|
viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
|
|
5275
|
-
"v",
|
|
5276
|
-
|
|
5277
|
-
"
|
|
3114
|
+
"Viewing v",
|
|
3115
|
+
(viewIndex ?? 0) + 1,
|
|
3116
|
+
" of ",
|
|
5278
3117
|
versions2.length,
|
|
5279
|
-
" \
|
|
5280
|
-
t("historyNote"),
|
|
3118
|
+
" \u2014 read-only.",
|
|
5281
3119
|
" ",
|
|
5282
|
-
/* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children:
|
|
3120
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: "Back to latest" })
|
|
5283
3121
|
] }),
|
|
5284
3122
|
/* @__PURE__ */ jsx(
|
|
5285
3123
|
"div",
|
|
5286
3124
|
{
|
|
5287
|
-
className: `cv-body${shown.type === "table"
|
|
3125
|
+
className: `cv-body${shown.type === "table" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
|
|
5288
3126
|
ref: bodyRef,
|
|
5289
|
-
"
|
|
5290
|
-
|
|
5291
|
-
t("noRenderer"),
|
|
5292
|
-
" \u201C",
|
|
3127
|
+
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: [
|
|
3128
|
+
"No renderer registered for type \u201C",
|
|
5293
3129
|
shown.type,
|
|
5294
3130
|
"\u201D."
|
|
5295
3131
|
] })
|
|
@@ -5298,7 +3134,6 @@ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
|
|
|
5298
3134
|
] });
|
|
5299
3135
|
}
|
|
5300
3136
|
function UndoRedo() {
|
|
5301
|
-
const t = useT();
|
|
5302
3137
|
const undo = useCanvasStore((s) => s.undo);
|
|
5303
3138
|
const redo = useCanvasStore((s) => s.redo);
|
|
5304
3139
|
const canUndo = useCanvasStore((s) => s.undoStack.length > 0);
|
|
@@ -5318,8 +3153,8 @@ function UndoRedo() {
|
|
|
5318
3153
|
return () => window.removeEventListener("keydown", onKey);
|
|
5319
3154
|
}, [undo, redo]);
|
|
5320
3155
|
return /* @__PURE__ */ jsxs("div", { className: "cv-undo", role: "group", "aria-label": "Undo and redo", children: [
|
|
5321
|
-
/* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title:
|
|
5322
|
-
/* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title:
|
|
3156
|
+
/* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title: "Undo (\u2318Z)", "aria-label": "Undo", children: "\u21B6" }),
|
|
3157
|
+
/* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "Redo (\u2318\u21E7Z)", "aria-label": "Redo", children: "\u21B7" })
|
|
5323
3158
|
] });
|
|
5324
3159
|
}
|
|
5325
3160
|
function VersionHistory({
|
|
@@ -5327,15 +3162,14 @@ function VersionHistory({
|
|
|
5327
3162
|
index,
|
|
5328
3163
|
onSelect
|
|
5329
3164
|
}) {
|
|
5330
|
-
const t = useT();
|
|
5331
3165
|
const [open, setOpen] = useState(false);
|
|
5332
3166
|
const total = versions2.length;
|
|
5333
3167
|
const pick = (i) => {
|
|
5334
3168
|
setOpen(false);
|
|
5335
3169
|
onSelect(i);
|
|
5336
3170
|
};
|
|
5337
|
-
return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label":
|
|
5338
|
-
/* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label":
|
|
3171
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": "Version history", children: [
|
|
3172
|
+
/* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": "Previous version", children: "\u2039" }),
|
|
5339
3173
|
/* @__PURE__ */ jsxs(
|
|
5340
3174
|
"button",
|
|
5341
3175
|
{
|
|
@@ -5357,7 +3191,7 @@ function VersionHistory({
|
|
|
5357
3191
|
className: "cv-versions__nav",
|
|
5358
3192
|
disabled: index === total - 1,
|
|
5359
3193
|
onClick: () => pick(index + 1),
|
|
5360
|
-
"aria-label":
|
|
3194
|
+
"aria-label": "Next version",
|
|
5361
3195
|
children: "\u203A"
|
|
5362
3196
|
}
|
|
5363
3197
|
),
|
|
@@ -5383,7 +3217,10 @@ function StatusBadge({ status }) {
|
|
|
5383
3217
|
const label = status === "streaming" ? "Writing\u2026" : status === "error" ? "Error" : "Ready";
|
|
5384
3218
|
return /* @__PURE__ */ jsx("span", { className: `cv-badge cv-badge--${status}`, children: label });
|
|
5385
3219
|
}
|
|
5386
|
-
function EmptyState({
|
|
3220
|
+
function EmptyState({
|
|
3221
|
+
onOpenFiles,
|
|
3222
|
+
acceptAll = false
|
|
3223
|
+
}) {
|
|
5387
3224
|
const inputRef = useRef(null);
|
|
5388
3225
|
return /* @__PURE__ */ jsxs("div", { className: "cv-empty", children: [
|
|
5389
3226
|
/* @__PURE__ */ jsx("p", { className: "cv-empty__title", children: "Nothing on the canvas yet" }),
|
|
@@ -5395,7 +3232,7 @@ function EmptyState({ onOpenFiles }) {
|
|
|
5395
3232
|
{
|
|
5396
3233
|
ref: inputRef,
|
|
5397
3234
|
type: "file",
|
|
5398
|
-
accept: ACCEPT,
|
|
3235
|
+
accept: acceptAll ? void 0 : ACCEPT,
|
|
5399
3236
|
multiple: true,
|
|
5400
3237
|
hidden: true,
|
|
5401
3238
|
onChange: (e) => {
|
|
@@ -5404,17 +3241,17 @@ function EmptyState({ onOpenFiles }) {
|
|
|
5404
3241
|
}
|
|
5405
3242
|
}
|
|
5406
3243
|
),
|
|
5407
|
-
/* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
|
|
3244
|
+
/* @__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" })
|
|
5408
3245
|
] })
|
|
5409
3246
|
] });
|
|
5410
3247
|
}
|
|
5411
3248
|
var TYPE_META = {
|
|
5412
|
-
html: { icon: "\u{1F310}", label: "
|
|
5413
|
-
document: { icon: "\u{1F4C4}", label: "
|
|
5414
|
-
chart: { icon: "\u{1F4CA}", label: "
|
|
5415
|
-
table: { icon: "\u{1F522}", label: "
|
|
5416
|
-
slides: { icon: "\u{1F4FD}\uFE0F", label: "
|
|
5417
|
-
|
|
3249
|
+
html: { icon: "\u{1F310}", label: "Web page" },
|
|
3250
|
+
document: { icon: "\u{1F4C4}", label: "Word document" },
|
|
3251
|
+
chart: { icon: "\u{1F4CA}", label: "Chart" },
|
|
3252
|
+
table: { icon: "\u{1F522}", label: "Excel sheet" },
|
|
3253
|
+
slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
|
|
3254
|
+
file: { icon: "\u{1F4CE}", label: "File" }
|
|
5418
3255
|
};
|
|
5419
3256
|
var KIND_META = {
|
|
5420
3257
|
web: TYPE_META.html,
|
|
@@ -5425,16 +3262,14 @@ var KIND_META = {
|
|
|
5425
3262
|
table: TYPE_META.table,
|
|
5426
3263
|
sheet: TYPE_META.table,
|
|
5427
3264
|
slide: TYPE_META.slides,
|
|
5428
|
-
slides: TYPE_META.slides
|
|
5429
|
-
pdf: TYPE_META.pdf
|
|
3265
|
+
slides: TYPE_META.slides
|
|
5430
3266
|
};
|
|
5431
3267
|
function resolveCardMeta(artifact2) {
|
|
5432
3268
|
const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
|
|
5433
3269
|
const byKind = kind ? KIND_META[kind] : void 0;
|
|
5434
|
-
return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}" };
|
|
3270
|
+
return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}", label: artifact2.type };
|
|
5435
3271
|
}
|
|
5436
3272
|
function ArtifactCard({ artifactId }) {
|
|
5437
|
-
const t = useT();
|
|
5438
3273
|
const artifact2 = useCanvasStore((s) => s.canvas.artifacts[artifactId]);
|
|
5439
3274
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
5440
3275
|
if (!artifact2) return null;
|
|
@@ -5444,12 +3279,12 @@ function ArtifactCard({ artifactId }) {
|
|
|
5444
3279
|
/* @__PURE__ */ jsxs("span", { className: "cv-card__meta", children: [
|
|
5445
3280
|
/* @__PURE__ */ jsx("b", { children: artifact2.title }),
|
|
5446
3281
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
5447
|
-
meta.label
|
|
3282
|
+
meta.label,
|
|
5448
3283
|
" \xB7 ",
|
|
5449
|
-
artifact2.status === "streaming" ?
|
|
3284
|
+
artifact2.status === "streaming" ? "writing\u2026" : "open \u2192"
|
|
5450
3285
|
] })
|
|
5451
3286
|
] })
|
|
5452
3287
|
] });
|
|
5453
3288
|
}
|
|
5454
3289
|
|
|
5455
|
-
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK,
|
|
3290
|
+
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 };
|