@particle-academy/fancy-docs 0.0.0-stage → 0.1.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +78 -2
- package/dist/index.cjs +385 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@particle-academy/fancy-docs` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
> **Pre-1.0:** breaking changes land in MINOR releases. Until 1.0 the minor
|
|
8
|
+
> number is not a compatibility promise — read the entry, not the version.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.1.0] - 2026-10-19
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`DocViewer`**: a last-word document rendered the way Word renders it —
|
|
17
|
+
page size, orientation and margins, a sheet per page break, the running
|
|
18
|
+
header and footer with `{page}` / `{pages}`, run and paragraph styling,
|
|
19
|
+
headings, nested lists, tables (widths, borders including removed ones,
|
|
20
|
+
header rows and their default fill, cell fills, padding, vertical alignment,
|
|
21
|
+
spans), code, quotes, rules and embedded images. In react-fancy's
|
|
22
|
+
`FauxClient` frame, scaled to fit the page width, with zoom and full screen
|
|
23
|
+
revealed on hover.
|
|
24
|
+
- **`DocFileView`**: the same, for fancy-code's `FileViewer` —
|
|
25
|
+
`registerOfficeViewer("docx", DocFileView, { read })`.
|
|
26
|
+
- Asked for in last-word-js#2, where a consumer's preview went through a
|
|
27
|
+
third-party converter that flattened every style the last-word guide makes
|
|
28
|
+
required.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Particle Academy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @particle-academy/fancy-docs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://particle.academy)
|
|
4
|
+
|
|
5
|
+
Render a **last-word document** (`.docx`) the way Word renders it, in React.
|
|
6
|
+
|
|
7
|
+
`last-word` writes and reads `.docx` in PHP and Node. This is the view: the
|
|
8
|
+
document its reader returns, drawn page by page with the styling a reviewer
|
|
9
|
+
expects to see — header-row fills, right-aligned numbers, callout shading and
|
|
10
|
+
borders, heading colours, borderless tables, and the running header and footer
|
|
11
|
+
with page numbers. It is the docx member of the Fancy file viewers, next to
|
|
12
|
+
`fancy-slides` (pptx) and `fancy-sheets` (xlsx), and sits in react-fancy's
|
|
13
|
+
`FauxClient` frame like them: zoom and full screen, revealed on hover.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @particle-academy/fancy-docs @particle-academy/react-fancy
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Peers: `react` and `react-dom` 19, `@particle-academy/react-fancy` `>=5.49.1 <6`.
|
|
22
|
+
|
|
23
|
+
## Show a document
|
|
24
|
+
|
|
25
|
+
Read the file on your server with PHP `particle-academy/last-word` (the
|
|
26
|
+
preferred path) and hand the result to the page:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { DocViewer } from "@particle-academy/fancy-docs";
|
|
30
|
+
|
|
31
|
+
<div className="h-[40rem]">
|
|
32
|
+
<DocViewer doc={doc} />
|
|
33
|
+
</div>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`frame={false}` gives the bare pages; `frameProps` reaches the frame.
|
|
37
|
+
|
|
38
|
+
## In fancy-code's FileViewer
|
|
39
|
+
|
|
40
|
+
Register it once at app start and `FileViewer` opens `.docx` files with it —
|
|
41
|
+
from a server-read `document` when you have one, or by reading the file in the
|
|
42
|
+
browser with last-word's JS reader when you only have a URL:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { registerOfficeViewer } from "@particle-academy/fancy-code";
|
|
46
|
+
import { DocFileView } from "@particle-academy/fancy-docs";
|
|
47
|
+
import { DocxReader } from "@particle-academy/last-word";
|
|
48
|
+
|
|
49
|
+
registerOfficeViewer("docx", DocFileView, { read: (bytes) => new DocxReader().read(bytes) });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What it renders
|
|
53
|
+
|
|
54
|
+
| | |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Page | size (letter / legal / A4), orientation, margins; a sheet per page break |
|
|
57
|
+
| Header / footer | left / center / right on every page, `{page}` and `{pages}` filled in |
|
|
58
|
+
| Runs | bold, italic, underline, strike, colour, highlight, size, font, small caps, letter spacing, inline code, links |
|
|
59
|
+
| Paragraphs | alignment, spacing, line height, indents, shading, borders |
|
|
60
|
+
| Headings | levels 1–6 at the writer's sizes |
|
|
61
|
+
| Lists | ordered and bulleted, nested |
|
|
62
|
+
| Tables | column widths, table width and alignment, borders (including removed ones), header rows and their default fill, cell fills, padding, vertical alignment, row and column spans |
|
|
63
|
+
| Also | code blocks, quotes, rules, embedded images |
|
|
64
|
+
|
|
65
|
+
An unstyled document gets the writer's own defaults (Calibri 11pt, 8pt after
|
|
66
|
+
each paragraph, Word's single-line height), so it looks as it does in Word.
|
|
67
|
+
|
|
68
|
+
## Safety
|
|
69
|
+
|
|
70
|
+
Everything shown came out of a file:
|
|
71
|
+
|
|
72
|
+
- links go through react-fancy's `sanitizeHref` (`javascript:` and `data:` are dropped)
|
|
73
|
+
- images are `data:` rasters only — a remote URL in a document would fetch the
|
|
74
|
+
moment it opened, a tracking pixel in a file someone sent you
|
|
75
|
+
- colours must be `#RRGGBB`; anything else is ignored rather than handed to a style
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactFancy = require('@particle-academy/react-fancy');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/DocViewer.tsx
|
|
8
|
+
|
|
9
|
+
// src/style.ts
|
|
10
|
+
var DEFAULTS = {
|
|
11
|
+
font: "Calibri",
|
|
12
|
+
/** Points. */
|
|
13
|
+
size: 11,
|
|
14
|
+
/** w:spacing after="160": 8pt after every paragraph. */
|
|
15
|
+
spaceAfter: 8,
|
|
16
|
+
/** w:line="259" of 240: 1.08. */
|
|
17
|
+
lineHeight: 1.08,
|
|
18
|
+
/** Heading 1-6, points. */
|
|
19
|
+
headingSizes: [20, 16, 14, 13, 12, 11],
|
|
20
|
+
headingSpaceBefore: 12,
|
|
21
|
+
headingSpaceAfter: 6,
|
|
22
|
+
quoteIndent: 36,
|
|
23
|
+
quoteColor: "#595959",
|
|
24
|
+
codeFont: "Consolas",
|
|
25
|
+
codeSize: 10,
|
|
26
|
+
codeFill: "#F2F2F2",
|
|
27
|
+
linkColor: "#0563C1",
|
|
28
|
+
/** Cell margins 60 / 108 twips. */
|
|
29
|
+
cellPadding: { top: 3, right: 5.4, bottom: 3, left: 5.4 },
|
|
30
|
+
/** The header-cell grey the writer and all three engines share. */
|
|
31
|
+
headerFill: "#E7E7E7",
|
|
32
|
+
/** A border edge that names no width. */
|
|
33
|
+
borderWidth: 0.5,
|
|
34
|
+
/** Page margins when none are given: 1440 twips. */
|
|
35
|
+
margin: 72,
|
|
36
|
+
/** Header / footer distance from the page edge: 720 twips. */
|
|
37
|
+
bandDistance: 36,
|
|
38
|
+
bandSize: 9
|
|
39
|
+
};
|
|
40
|
+
var PAGE_SIZES = {
|
|
41
|
+
letter: [612, 792],
|
|
42
|
+
legal: [612, 1008],
|
|
43
|
+
a4: [595, 842]
|
|
44
|
+
};
|
|
45
|
+
function hex(value) {
|
|
46
|
+
if (typeof value !== "string") return void 0;
|
|
47
|
+
const v = value.trim();
|
|
48
|
+
if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toUpperCase();
|
|
49
|
+
if (/^[0-9a-fA-F]{6}$/.test(v)) return `#${v.toUpperCase()}`;
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
function num(value) {
|
|
53
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
54
|
+
}
|
|
55
|
+
function fontStack(font, generic = "sans-serif") {
|
|
56
|
+
if (typeof font !== "string" || font.trim() === "") return void 0;
|
|
57
|
+
const name = font.replace(/["\\;{}<>]/g, "").trim();
|
|
58
|
+
return `"${name}", ${generic}`;
|
|
59
|
+
}
|
|
60
|
+
var CSS_STYLE = { single: "solid", double: "double", dashed: "dashed", dotted: "dotted" };
|
|
61
|
+
function borderCss(border) {
|
|
62
|
+
if (!border) return void 0;
|
|
63
|
+
if (border.style === "none") return "none";
|
|
64
|
+
const width = num(border.width) ?? DEFAULTS.borderWidth;
|
|
65
|
+
const style = CSS_STYLE[border.style ?? "single"] ?? "solid";
|
|
66
|
+
return `${width}pt ${style} ${hex(border.color) ?? "#000000"}`;
|
|
67
|
+
}
|
|
68
|
+
function boxBorders(borders) {
|
|
69
|
+
if (!borders) return {};
|
|
70
|
+
const out = {};
|
|
71
|
+
const top = borderCss(borders.top);
|
|
72
|
+
const right = borderCss(borders.right);
|
|
73
|
+
const bottom = borderCss(borders.bottom);
|
|
74
|
+
const left = borderCss(borders.left);
|
|
75
|
+
if (top) out.borderTop = top;
|
|
76
|
+
if (right) out.borderRight = right;
|
|
77
|
+
if (bottom) out.borderBottom = bottom;
|
|
78
|
+
if (left) out.borderLeft = left;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function padding(sides, fallback) {
|
|
82
|
+
const pick = (k) => num(sides?.[k]) ?? num(fallback?.[k]);
|
|
83
|
+
const out = {};
|
|
84
|
+
const t = pick("top");
|
|
85
|
+
const r = pick("right");
|
|
86
|
+
const b = pick("bottom");
|
|
87
|
+
const l = pick("left");
|
|
88
|
+
if (t !== void 0) out.paddingTop = `${t}pt`;
|
|
89
|
+
if (r !== void 0) out.paddingRight = `${r}pt`;
|
|
90
|
+
if (b !== void 0) out.paddingBottom = `${b}pt`;
|
|
91
|
+
if (l !== void 0) out.paddingLeft = `${l}pt`;
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
var SINGLE_LINE = 1.2;
|
|
95
|
+
function lineHeightCss(lines) {
|
|
96
|
+
return Math.round(lines * SINGLE_LINE * 1e3) / 1e3;
|
|
97
|
+
}
|
|
98
|
+
function paragraphCss(p, defaults = {}) {
|
|
99
|
+
const out = {
|
|
100
|
+
marginTop: `${num(p.spaceBefore) ?? defaults.before ?? 0}pt`,
|
|
101
|
+
marginBottom: `${num(p.spaceAfter) ?? defaults.after ?? DEFAULTS.spaceAfter}pt`,
|
|
102
|
+
lineHeight: String(lineHeightCss(num(p.lineHeight) ?? DEFAULTS.lineHeight)),
|
|
103
|
+
...boxBorders(p.borders)
|
|
104
|
+
};
|
|
105
|
+
if (p.align) out.textAlign = p.align;
|
|
106
|
+
const left = num(p.indentLeft);
|
|
107
|
+
const right = num(p.indentRight);
|
|
108
|
+
if (left !== void 0) out.marginLeft = `${left}pt`;
|
|
109
|
+
if (right !== void 0) out.marginRight = `${right}pt`;
|
|
110
|
+
const fill = hex(p.shading);
|
|
111
|
+
if (fill) {
|
|
112
|
+
out.backgroundColor = fill;
|
|
113
|
+
out.padding = "2pt 4pt";
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
function runCss(run) {
|
|
118
|
+
const out = {};
|
|
119
|
+
if (run.bold) out.fontWeight = 700;
|
|
120
|
+
if (run.italic) out.fontStyle = "italic";
|
|
121
|
+
const lines = [run.underline ? "underline" : "", run.strike ? "line-through" : ""].filter(Boolean);
|
|
122
|
+
if (lines.length) out.textDecorationLine = lines.join(" ");
|
|
123
|
+
const color = hex(run.color);
|
|
124
|
+
if (color) out.color = color;
|
|
125
|
+
const highlight = hex(run.highlight);
|
|
126
|
+
if (highlight) out.backgroundColor = highlight;
|
|
127
|
+
const size = num(run.size);
|
|
128
|
+
if (size !== void 0 && size > 0) out.fontSize = `${size}pt`;
|
|
129
|
+
const font = fontStack(run.font);
|
|
130
|
+
if (font) out.fontFamily = font;
|
|
131
|
+
if (run.smallCaps) out.fontVariant = "small-caps";
|
|
132
|
+
const spacing = num(run.letterSpacing);
|
|
133
|
+
if (spacing) out.letterSpacing = `${spacing}pt`;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
function DocViewer({ doc, frame = true, frameProps, className }) {
|
|
137
|
+
const pages = splitPages(Array.isArray(doc?.blocks) ? doc.blocks : []);
|
|
138
|
+
const total = pages.length;
|
|
139
|
+
const sheets = /* @__PURE__ */ jsxRuntime.jsx(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
"data-fancy-docs-viewer": "",
|
|
143
|
+
className,
|
|
144
|
+
style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 16, padding: 16, background: "#E5E7EB" },
|
|
145
|
+
children: pages.map((blocks, i) => /* @__PURE__ */ jsxRuntime.jsx(PageSheet, { doc, blocks, number: i + 1, total }, i))
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
if (!frame) return sheets;
|
|
149
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
150
|
+
reactFancy.FauxClient,
|
|
151
|
+
{
|
|
152
|
+
variant: "device",
|
|
153
|
+
controls: true,
|
|
154
|
+
fill: true,
|
|
155
|
+
width: logicalWidth(doc),
|
|
156
|
+
scale: "fit",
|
|
157
|
+
bodyClassName: "overflow-auto",
|
|
158
|
+
...frameProps,
|
|
159
|
+
children: sheets
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function logicalWidth(doc) {
|
|
164
|
+
const page = doc?.page ?? {};
|
|
165
|
+
const [w, h] = PAGE_SIZES[page.size ?? "letter"] ?? PAGE_SIZES.letter;
|
|
166
|
+
const width = page.orientation === "landscape" ? h : w;
|
|
167
|
+
return Math.ceil(width * 4 / 3) + 32;
|
|
168
|
+
}
|
|
169
|
+
function splitPages(blocks) {
|
|
170
|
+
const pages = [[]];
|
|
171
|
+
for (const block of blocks) {
|
|
172
|
+
if (block?.type === "pageBreak") pages.push([]);
|
|
173
|
+
else pages[pages.length - 1].push(block);
|
|
174
|
+
}
|
|
175
|
+
return pages;
|
|
176
|
+
}
|
|
177
|
+
function PageSheet({ doc, blocks, number, total }) {
|
|
178
|
+
const page = doc.page ?? {};
|
|
179
|
+
const [w, h] = PAGE_SIZES[page.size ?? "letter"] ?? PAGE_SIZES.letter;
|
|
180
|
+
const [width, height] = page.orientation === "landscape" ? [h, w] : [w, h];
|
|
181
|
+
const m = page.margins ?? {};
|
|
182
|
+
const margin = (side) => num(m[side]) ?? DEFAULTS.margin;
|
|
183
|
+
const style = {
|
|
184
|
+
position: "relative",
|
|
185
|
+
width: `${width}pt`,
|
|
186
|
+
minHeight: `${height}pt`,
|
|
187
|
+
background: "#FFFFFF",
|
|
188
|
+
color: "#000000",
|
|
189
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.15)",
|
|
190
|
+
fontFamily: fontStack(doc.defaultFont ?? DEFAULTS.font),
|
|
191
|
+
fontSize: `${num(doc.defaultSize) ?? DEFAULTS.size}pt`,
|
|
192
|
+
flexShrink: 0
|
|
193
|
+
};
|
|
194
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-fancy-docs-page": number, style, children: [
|
|
195
|
+
page.header && /* @__PURE__ */ jsxRuntime.jsx(BandLine, { band: page.header, kind: "header", number, total, left: margin("left"), right: margin("right") }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
197
|
+
"div",
|
|
198
|
+
{
|
|
199
|
+
"data-fancy-docs-body": "",
|
|
200
|
+
style: { paddingTop: `${margin("top")}pt`, paddingRight: `${margin("right")}pt`, paddingBottom: `${margin("bottom")}pt`, paddingLeft: `${margin("left")}pt` },
|
|
201
|
+
children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block }, i))
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
page.footer && /* @__PURE__ */ jsxRuntime.jsx(BandLine, { band: page.footer, kind: "footer", number, total, left: margin("left"), right: margin("right") })
|
|
205
|
+
] });
|
|
206
|
+
}
|
|
207
|
+
function BandLine({ band, kind, number, total, left, right }) {
|
|
208
|
+
const fill = (s) => typeof s === "string" ? s.split("{page}").join(String(number)).split("{pages}").join(String(total)) : "";
|
|
209
|
+
const style = {
|
|
210
|
+
position: "absolute",
|
|
211
|
+
left: `${left}pt`,
|
|
212
|
+
right: `${right}pt`,
|
|
213
|
+
[kind === "header" ? "top" : "bottom"]: `${DEFAULTS.bandDistance}pt`,
|
|
214
|
+
display: "grid",
|
|
215
|
+
gridTemplateColumns: "1fr auto 1fr",
|
|
216
|
+
fontSize: `${num(band.size) ?? DEFAULTS.bandSize}pt`,
|
|
217
|
+
color: hex(band.color) ?? "#595959"
|
|
218
|
+
};
|
|
219
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-fancy-docs-band": kind, style, children: [
|
|
220
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textAlign: "left" }, children: fill(band.left) }),
|
|
221
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textAlign: "center" }, children: fill(band.center) }),
|
|
222
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { textAlign: "right" }, children: fill(band.right) })
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
function Runs({ runs }) {
|
|
226
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: (Array.isArray(runs) ? runs : []).map((run, i) => /* @__PURE__ */ jsxRuntime.jsx(RunView, { run }, i)) });
|
|
227
|
+
}
|
|
228
|
+
function RunView({ run }) {
|
|
229
|
+
const text = typeof run?.text === "string" ? run.text : "";
|
|
230
|
+
const style = runCss(run);
|
|
231
|
+
const href = typeof run.link === "string" ? reactFancy.sanitizeHref(run.link) : void 0;
|
|
232
|
+
if (run.code) {
|
|
233
|
+
style.fontFamily = style.fontFamily ?? fontStack(DEFAULTS.codeFont, "monospace");
|
|
234
|
+
style.backgroundColor = style.backgroundColor ?? DEFAULTS.codeFill;
|
|
235
|
+
}
|
|
236
|
+
if (href) {
|
|
237
|
+
style.color = style.color ?? DEFAULTS.linkColor;
|
|
238
|
+
style.textDecorationLine = style.textDecorationLine ?? "underline";
|
|
239
|
+
}
|
|
240
|
+
const parts = text.split("\n");
|
|
241
|
+
const content = parts.map((part, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
242
|
+
i > 0 && /* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
243
|
+
part
|
|
244
|
+
] }, i));
|
|
245
|
+
const span = run.code ? /* @__PURE__ */ jsxRuntime.jsx("code", { "data-fancy-docs-run": "", style, children: content }) : /* @__PURE__ */ jsxRuntime.jsx("span", { "data-fancy-docs-run": "", style, children: content });
|
|
246
|
+
return href ? /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: span }) : span;
|
|
247
|
+
}
|
|
248
|
+
function BlockView({ block }) {
|
|
249
|
+
switch (block?.type) {
|
|
250
|
+
case "heading": {
|
|
251
|
+
const level = Math.min(6, Math.max(1, Math.round(num(block.level) ?? 1)));
|
|
252
|
+
const Tag = `h${level}`;
|
|
253
|
+
const style = {
|
|
254
|
+
...paragraphCss(block, { before: DEFAULTS.headingSpaceBefore, after: DEFAULTS.headingSpaceAfter }),
|
|
255
|
+
fontSize: `${DEFAULTS.headingSizes[level - 1]}pt`,
|
|
256
|
+
fontWeight: 700
|
|
257
|
+
};
|
|
258
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tag, { "data-fancy-docs-block": "heading", style, children: /* @__PURE__ */ jsxRuntime.jsx(Runs, { runs: block.runs }) });
|
|
259
|
+
}
|
|
260
|
+
case "paragraph":
|
|
261
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { "data-fancy-docs-block": "paragraph", style: paragraphCss(block), children: /* @__PURE__ */ jsxRuntime.jsx(Runs, { runs: block.runs }) });
|
|
262
|
+
case "list":
|
|
263
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ListView, { items: block.items, ordered: !!block.ordered });
|
|
264
|
+
case "table":
|
|
265
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TableView, { block });
|
|
266
|
+
case "code":
|
|
267
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
268
|
+
"pre",
|
|
269
|
+
{
|
|
270
|
+
"data-fancy-docs-block": "code",
|
|
271
|
+
style: {
|
|
272
|
+
margin: `0 0 ${DEFAULTS.spaceAfter}pt`,
|
|
273
|
+
padding: "4pt 6pt",
|
|
274
|
+
background: DEFAULTS.codeFill,
|
|
275
|
+
fontFamily: fontStack(DEFAULTS.codeFont, "monospace"),
|
|
276
|
+
fontSize: `${DEFAULTS.codeSize}pt`,
|
|
277
|
+
lineHeight: 1,
|
|
278
|
+
whiteSpace: "pre-wrap"
|
|
279
|
+
},
|
|
280
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: typeof block.text === "string" ? block.text : "" })
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
case "quote":
|
|
284
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
285
|
+
"blockquote",
|
|
286
|
+
{
|
|
287
|
+
"data-fancy-docs-block": "quote",
|
|
288
|
+
style: { margin: `0 0 0 ${DEFAULTS.quoteIndent}pt`, fontStyle: "italic", color: DEFAULTS.quoteColor },
|
|
289
|
+
children: (Array.isArray(block.blocks) ? block.blocks : []).map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b }, i))
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
case "image": {
|
|
293
|
+
const src = typeof block.src === "string" && /^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/=]+$/.test(block.src) ? block.src : null;
|
|
294
|
+
if (!src) return null;
|
|
295
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
296
|
+
"img",
|
|
297
|
+
{
|
|
298
|
+
"data-fancy-docs-block": "image",
|
|
299
|
+
src,
|
|
300
|
+
alt: typeof block.alt === "string" ? block.alt : "",
|
|
301
|
+
style: { display: "block", maxWidth: "100%", width: num(block.widthPx), height: num(block.heightPx) ? void 0 : "auto", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
case "hr":
|
|
306
|
+
return /* @__PURE__ */ jsxRuntime.jsx("hr", { "data-fancy-docs-block": "hr", style: { border: 0, borderTop: "0.5pt solid #000000", margin: `0 0 ${DEFAULTS.spaceAfter}pt` } });
|
|
307
|
+
default:
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function ListView({ items, ordered }) {
|
|
312
|
+
const Tag = ordered ? "ol" : "ul";
|
|
313
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tag, { "data-fancy-docs-block": "list", style: { margin: `0 0 ${DEFAULTS.spaceAfter}pt`, paddingLeft: "18pt", listStyleType: ordered ? "decimal" : "disc" }, children: (Array.isArray(items) ? items : []).map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { ...paragraphCss(item, { after: 0 }) }, children: [
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsx(Runs, { runs: item.runs }),
|
|
315
|
+
Array.isArray(item.children) && item.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ListView, { items: item.children, ordered })
|
|
316
|
+
] }, i)) });
|
|
317
|
+
}
|
|
318
|
+
function TableView({ block }) {
|
|
319
|
+
const rows = Array.isArray(block.rows) ? block.rows : [];
|
|
320
|
+
const widths = Array.isArray(block.widths) ? block.widths.filter((w) => num(w) !== void 0 && w > 0) : [];
|
|
321
|
+
const sum = widths.reduce((a, b) => a + b, 0);
|
|
322
|
+
const width = num(block.width);
|
|
323
|
+
const borders = block.borders ?? {};
|
|
324
|
+
const style = {
|
|
325
|
+
borderCollapse: "collapse",
|
|
326
|
+
width: width !== void 0 ? `${width}%` : "100%",
|
|
327
|
+
tableLayout: widths.length > 0 ? "fixed" : "auto",
|
|
328
|
+
marginBottom: `${DEFAULTS.spaceAfter}pt`,
|
|
329
|
+
...block.align === "center" ? { marginLeft: "auto", marginRight: "auto" } : block.align === "right" ? { marginLeft: "auto" } : {}
|
|
330
|
+
};
|
|
331
|
+
const edge = (b) => borderCss(b) ?? `${DEFAULTS.borderWidth}pt solid #000000`;
|
|
332
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("table", { "data-fancy-docs-block": "table", style, children: [
|
|
333
|
+
widths.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("colgroup", { children: widths.map((w, i) => /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: `${Math.round(w / sum * 1e3) / 10}%` } }, i)) }),
|
|
334
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: (Array.isArray(row.cells) ? row.cells : []).map((cell, c, cells) => {
|
|
335
|
+
const header = !!row.header;
|
|
336
|
+
const Tag = header ? "th" : "td";
|
|
337
|
+
const first = c === 0;
|
|
338
|
+
const last = c === cells.length - 1;
|
|
339
|
+
const cellStyle = {
|
|
340
|
+
// Outer edges from the table's box; inner ones from insideH / insideV.
|
|
341
|
+
borderTop: r === 0 ? edge(borders.top) : edge(borders.insideH),
|
|
342
|
+
borderBottom: r === rows.length - 1 ? edge(borders.bottom) : edge(borders.insideH),
|
|
343
|
+
borderLeft: first ? edge(borders.left) : edge(borders.insideV),
|
|
344
|
+
borderRight: last ? edge(borders.right) : edge(borders.insideV),
|
|
345
|
+
...padding(cell.padding, { ...DEFAULTS.cellPadding, ...block.cellPadding ?? {} }),
|
|
346
|
+
...boxBorders(cell.borders),
|
|
347
|
+
verticalAlign: cell.valign === "center" ? "middle" : cell.valign ?? "top",
|
|
348
|
+
textAlign: "left",
|
|
349
|
+
fontWeight: header ? 700 : void 0,
|
|
350
|
+
backgroundColor: hex(cell.shading) ?? (header ? DEFAULTS.headerFill : void 0)
|
|
351
|
+
};
|
|
352
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
353
|
+
Tag,
|
|
354
|
+
{
|
|
355
|
+
style: cellStyle,
|
|
356
|
+
colSpan: num(cell.colSpan) && cell.colSpan > 1 ? cell.colSpan : void 0,
|
|
357
|
+
rowSpan: num(cell.rowSpan) && cell.rowSpan > 1 ? cell.rowSpan : void 0,
|
|
358
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CellBlocks, { cell })
|
|
359
|
+
},
|
|
360
|
+
c
|
|
361
|
+
);
|
|
362
|
+
}) }, r)) })
|
|
363
|
+
] });
|
|
364
|
+
}
|
|
365
|
+
function CellBlocks({ cell }) {
|
|
366
|
+
const blocks = Array.isArray(cell.blocks) ? cell.blocks : [];
|
|
367
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: blocks.map(
|
|
368
|
+
(b, i) => b?.type === "paragraph" ? /* @__PURE__ */ jsxRuntime.jsx("p", { "data-fancy-docs-block": "paragraph", style: { ...paragraphCss(b), ...i === blocks.length - 1 && num(b.spaceAfter) === void 0 ? { marginBottom: 0 } : {} }, children: /* @__PURE__ */ jsxRuntime.jsx(Runs, { runs: b.runs }) }, i) : /* @__PURE__ */ jsxRuntime.jsx(BlockView, { block: b }, i)
|
|
369
|
+
) });
|
|
370
|
+
}
|
|
371
|
+
function DocFileView({ document, filename, frameProps }) {
|
|
372
|
+
const doc = document;
|
|
373
|
+
if (!doc || typeof doc !== "object" || !Array.isArray(doc.blocks)) {
|
|
374
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactFancy.Callout, { color: "red", children: [
|
|
375
|
+
filename ?? "This file",
|
|
376
|
+
" did not read as a document."
|
|
377
|
+
] });
|
|
378
|
+
}
|
|
379
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DocViewer, { doc, frameProps });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
exports.DocFileView = DocFileView;
|
|
383
|
+
exports.DocViewer = DocViewer;
|
|
384
|
+
//# sourceMappingURL=index.cjs.map
|
|
385
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/style.ts","../src/DocViewer.tsx","../src/DocFileView.tsx"],"names":["jsx","FauxClient","jsxs","Fragment","sanitizeHref","Callout"],"mappings":";;;;;;;;;AASO,IAAM,QAAA,GAAW;AAAA,EACtB,IAAA,EAAM,SAAA;AAAA;AAAA,EAEN,IAAA,EAAM,EAAA;AAAA;AAAA,EAEN,UAAA,EAAY,CAAA;AAAA;AAAA,EAEZ,UAAA,EAAY,IAAA;AAAA;AAAA,EAEZ,cAAc,CAAC,EAAA,EAAI,IAAI,EAAA,EAAI,EAAA,EAAI,IAAI,EAAE,CAAA;AAAA,EACrC,kBAAA,EAAoB,EAAA;AAAA,EACpB,iBAAA,EAAmB,CAAA;AAAA,EACnB,WAAA,EAAa,EAAA;AAAA,EACb,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,EAAA;AAAA,EACV,QAAA,EAAU,SAAA;AAAA,EACV,SAAA,EAAW,SAAA;AAAA;AAAA,EAEX,WAAA,EAAa,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,GAAA,EAAK,MAAA,EAAQ,CAAA,EAAG,IAAA,EAAM,GAAA,EAAI;AAAA;AAAA,EAExD,UAAA,EAAY,SAAA;AAAA;AAAA,EAEZ,WAAA,EAAa,GAAA;AAAA;AAAA,EAEb,MAAA,EAAQ,EAAA;AAAA;AAAA,EAER,YAAA,EAAc,EAAA;AAAA,EACd,QAAA,EAAU;AACZ,CAAA;AAGO,IAAM,UAAA,GAA+C;AAAA,EAC1D,MAAA,EAAQ,CAAC,GAAA,EAAK,GAAG,CAAA;AAAA,EACjB,KAAA,EAAO,CAAC,GAAA,EAAK,IAAI,CAAA;AAAA,EACjB,EAAA,EAAI,CAAC,GAAA,EAAK,GAAG;AACf,CAAA;AAOO,SAAS,IAAI,KAAA,EAAoC;AACtD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,MAAA;AACtC,EAAA,MAAM,CAAA,GAAI,MAAM,IAAA,EAAK;AACrB,EAAA,IAAI,oBAAoB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,EAAE,WAAA,EAAY;AACtD,EAAA,IAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,SAAU,CAAA,CAAA,EAAI,CAAA,CAAE,aAAa,CAAA,CAAA;AAC1D,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,IAAI,KAAA,EAAoC;AACtD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,MAAA;AACvE;AAGO,SAAS,SAAA,CAAU,IAAA,EAAe,OAAA,GAAU,YAAA,EAAkC;AACnF,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,KAAK,IAAA,EAAK,KAAM,IAAI,OAAO,MAAA;AAC3D,EAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,aAAA,EAAe,EAAE,EAAE,IAAA,EAAK;AAClD,EAAA,OAAO,CAAA,CAAA,EAAI,IAAI,CAAA,GAAA,EAAM,OAAO,CAAA,CAAA;AAC9B;AAEA,IAAM,SAAA,GAAoC,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAQ,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAS;AAG3G,SAAS,UAAU,MAAA,EAAgD;AACxE,EAAA,IAAI,CAAC,QAAQ,OAAO,MAAA;AACpB,EAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,EAAQ,OAAO,MAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,MAAA,CAAO,KAAK,KAAK,QAAA,CAAS,WAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,MAAA,CAAO,KAAA,IAAS,QAAQ,CAAA,IAAK,OAAA;AACrD,EAAA,OAAO,CAAA,EAAG,KAAK,CAAA,GAAA,EAAM,KAAK,IAAI,GAAA,CAAI,MAAA,CAAO,KAAK,CAAA,IAAK,SAAS,CAAA,CAAA;AAC9D;AAEO,SAAS,WAAW,OAAA,EAAgD;AACzE,EAAA,IAAI,CAAC,OAAA,EAAS,OAAO,EAAC;AACtB,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,MAAM,GAAA,GAAM,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA;AACrC,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,MAAM,CAAA;AACvC,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAA;AACnC,EAAA,IAAI,GAAA,MAAS,SAAA,GAAY,GAAA;AACzB,EAAA,IAAI,KAAA,MAAW,WAAA,GAAc,KAAA;AAC7B,EAAA,IAAI,MAAA,MAAY,YAAA,GAAe,MAAA;AAC/B,EAAA,IAAI,IAAA,MAAU,UAAA,GAAa,IAAA;AAC3B,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,OAAA,CAAQ,OAA6B,QAAA,EAAoC;AACvF,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAsB,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAC,CAAA,IAAK,GAAA,CAAI,QAAA,GAAW,CAAC,CAAC,CAAA;AACxE,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,MAAM,CAAA,GAAI,KAAK,KAAK,CAAA;AACpB,EAAA,MAAM,CAAA,GAAI,KAAK,OAAO,CAAA;AACtB,EAAA,MAAM,CAAA,GAAI,KAAK,QAAQ,CAAA;AACvB,EAAA,MAAM,CAAA,GAAI,KAAK,MAAM,CAAA;AACrB,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,UAAA,GAAa,GAAG,CAAC,CAAA,EAAA,CAAA;AAC1C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,YAAA,GAAe,GAAG,CAAC,CAAA,EAAA,CAAA;AAC5C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,aAAA,GAAgB,GAAG,CAAC,CAAA,EAAA,CAAA;AAC7C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,WAAA,GAAc,GAAG,CAAC,CAAA,EAAA,CAAA;AAC3C,EAAA,OAAO,GAAA;AACT;AAQO,IAAM,WAAA,GAAc,GAAA;AAEpB,SAAS,cAAc,KAAA,EAAuB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,WAAA,GAAc,GAAI,CAAA,GAAI,GAAA;AAClD;AAGO,SAAS,YAAA,CAAa,CAAA,EAAmB,QAAA,GAAgD,EAAC,EAAkB;AACjH,EAAA,MAAM,GAAA,GAAqB;AAAA,IACzB,SAAA,EAAW,GAAG,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA,IAAK,QAAA,CAAS,UAAU,CAAC,CAAA,EAAA,CAAA;AAAA,IACxD,YAAA,EAAc,GAAG,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA,IAAK,QAAA,CAAS,KAAA,IAAS,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,IAC3E,UAAA,EAAY,OAAO,aAAA,CAAc,GAAA,CAAI,EAAE,UAAU,CAAA,IAAK,QAAA,CAAS,UAAU,CAAC,CAAA;AAAA,IAC1E,GAAG,UAAA,CAAW,CAAA,CAAE,OAAO;AAAA,GACzB;AACA,EAAA,IAAI,CAAA,CAAE,KAAA,EAAO,GAAA,CAAI,SAAA,GAAY,CAAA,CAAE,KAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA;AAC/B,EAAA,IAAI,IAAA,KAAS,MAAA,EAAW,GAAA,CAAI,UAAA,GAAa,GAAG,IAAI,CAAA,EAAA,CAAA;AAChD,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,GAAA,CAAI,WAAA,GAAc,GAAG,KAAK,CAAA,EAAA,CAAA;AACnD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,OAAO,CAAA;AAC1B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,GAAA,CAAI,eAAA,GAAkB,IAAA;AAGtB,IAAA,GAAA,CAAI,OAAA,GAAU,SAAA;AAAA,EAChB;AACA,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,OAAO,GAAA,EAAyB;AAC9C,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,IAAI,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,UAAA,GAAa,GAAA;AAC/B,EAAA,IAAI,GAAA,CAAI,MAAA,EAAQ,GAAA,CAAI,SAAA,GAAY,QAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,CAAI,SAAA,GAAY,WAAA,GAAc,EAAA,EAAI,GAAA,CAAI,MAAA,GAAS,cAAA,GAAiB,EAAE,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AACjG,EAAA,IAAI,MAAM,MAAA,EAAQ,GAAA,CAAI,kBAAA,GAAqB,KAAA,CAAM,KAAK,GAAG,CAAA;AACzD,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,GAAA,CAAI,KAAK,CAAA;AAC3B,EAAA,IAAI,KAAA,MAAW,KAAA,GAAQ,KAAA;AACvB,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA;AACnC,EAAA,IAAI,SAAA,MAAe,eAAA,GAAkB,SAAA;AACrC,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AACzB,EAAA,IAAI,SAAS,MAAA,IAAa,IAAA,GAAO,GAAG,GAAA,CAAI,QAAA,GAAW,GAAG,IAAI,CAAA,EAAA,CAAA;AAC1D,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAC/B,EAAA,IAAI,IAAA,MAAU,UAAA,GAAa,IAAA;AAC3B,EAAA,IAAI,GAAA,CAAI,SAAA,EAAW,GAAA,CAAI,WAAA,GAAc,YAAA;AACrC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,GAAA,CAAI,aAAa,CAAA;AACrC,EAAA,IAAI,OAAA,EAAS,GAAA,CAAI,aAAA,GAAgB,CAAA,EAAG,OAAO,CAAA,EAAA,CAAA;AAC3C,EAAA,OAAO,GAAA;AACT;ACtIO,SAAS,UAAU,EAAE,GAAA,EAAK,QAAQ,IAAA,EAAM,UAAA,EAAY,WAAU,EAAmB;AACtF,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,MAAM,CAAA,GAAI,GAAA,CAAI,MAAA,GAAS,EAAE,CAAA;AACrE,EAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AAEpB,EAAA,MAAM,MAAA,mBACJA,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,wBAAA,EAAuB,EAAA;AAAA,MACvB,SAAA;AAAA,MACA,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,aAAA,EAAe,QAAA,EAAU,UAAA,EAAY,QAAA,EAAU,GAAA,EAAK,EAAA,EAAI,OAAA,EAAS,EAAA,EAAI,YAAY,SAAA,EAAU;AAAA,MAEpH,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,MAAA,EAAQ,sBAClBA,cAAA,CAAC,SAAA,EAAA,EAAkB,GAAA,EAAU,MAAA,EAAgB,MAAA,EAAQ,CAAA,GAAI,CAAA,EAAG,KAAA,EAAA,EAA5C,CAA0D,CAC3E;AAAA;AAAA,GACH;AAGF,EAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAInB,EAAA,uBACEA,cAAA;AAAA,IAACC,qBAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,QAAA;AAAA,MACR,QAAA,EAAQ,IAAA;AAAA,MACR,IAAA,EAAI,IAAA;AAAA,MACJ,KAAA,EAAO,aAAa,GAAG,CAAA;AAAA,MACvB,KAAA,EAAM,KAAA;AAAA,MACN,aAAA,EAAc,eAAA;AAAA,MACb,GAAG,UAAA;AAAA,MAEH,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ;AAGA,SAAS,aAAa,GAAA,EAAkB;AACtC,EAAA,MAAM,IAAA,GAAO,GAAA,EAAK,IAAA,IAAQ,EAAC;AAC3B,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,WAAW,IAAA,CAAK,IAAA,IAAQ,QAAQ,CAAA,IAAK,UAAA,CAAW,MAAA;AAC/D,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,KAAgB,WAAA,GAAc,CAAA,GAAI,CAAA;AACrD,EAAA,OAAO,IAAA,CAAK,IAAA,CAAM,KAAA,GAAQ,CAAA,GAAK,CAAC,CAAA,GAAI,EAAA;AACtC;AAGA,SAAS,WAAW,MAAA,EAA4B;AAC9C,EAAA,MAAM,KAAA,GAAmB,CAAC,EAAE,CAAA;AAC5B,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,OAAO,IAAA,KAAS,WAAA,EAAa,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAAA,eACnC,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,CAAE,KAAK,KAAK,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,UAAU,EAAE,GAAA,EAAK,MAAA,EAAQ,MAAA,EAAQ,OAAM,EAAiE;AAC/G,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,IAAA,IAAQ,EAAC;AAC1B,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,WAAW,IAAA,CAAK,IAAA,IAAQ,QAAQ,CAAA,IAAK,UAAA,CAAW,MAAA;AAC/D,EAAA,MAAM,CAAC,KAAA,EAAO,MAAM,CAAA,GAAI,IAAA,CAAK,WAAA,KAAgB,WAAA,GAAc,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,CAAC,GAAG,CAAC,CAAA;AACzE,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,IAAW,EAAC;AAC3B,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAA8C,GAAA,CAAI,EAAE,IAAI,CAAC,KAAK,QAAA,CAAS,MAAA;AAEvF,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,QAAA,EAAU,UAAA;AAAA,IACV,KAAA,EAAO,GAAG,KAAK,CAAA,EAAA,CAAA;AAAA,IACf,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,CAAA;AAAA,IACpB,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA,EAAO,SAAA;AAAA,IACP,SAAA,EAAW,4BAAA;AAAA,IACX,UAAA,EAAY,SAAA,CAAU,GAAA,CAAI,WAAA,IAAe,SAAS,IAAI,CAAA;AAAA,IACtD,UAAU,CAAA,EAAG,GAAA,CAAI,IAAI,WAAW,CAAA,IAAK,SAAS,IAAI,CAAA,EAAA,CAAA;AAAA,IAClD,UAAA,EAAY;AAAA,GACd;AAEA,EAAA,uBACEC,eAAA,CAAC,SAAA,EAAA,EAAQ,sBAAA,EAAsB,MAAA,EAAQ,KAAA,EACpC,QAAA,EAAA;AAAA,IAAA,IAAA,CAAK,0BAAUF,cAAA,CAAC,QAAA,EAAA,EAAS,IAAA,EAAM,IAAA,CAAK,QAAQ,IAAA,EAAK,QAAA,EAAS,MAAA,EAAgB,KAAA,EAAc,MAAM,MAAA,CAAO,MAAM,GAAG,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA,EAAG,CAAA;AAAA,oBACvIA,cAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,sBAAA,EAAqB,EAAA;AAAA,QACrB,KAAA,EAAO,EAAE,UAAA,EAAY,CAAA,EAAG,MAAA,CAAO,KAAK,CAAC,CAAA,EAAA,CAAA,EAAM,YAAA,EAAc,CAAA,EAAG,MAAA,CAAO,OAAO,CAAC,CAAA,EAAA,CAAA,EAAM,aAAA,EAAe,CAAA,EAAG,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAA,CAAA,EAAM,WAAA,EAAa,CAAA,EAAG,MAAA,CAAO,MAAM,CAAC,CAAA,EAAA,CAAA,EAAK;AAAA,QAE3J,QAAA,EAAA,MAAA,CAAO,IAAI,CAAC,KAAA,EAAO,sBAClBA,cAAA,CAAC,SAAA,EAAA,EAAkB,KAAA,EAAA,EAAH,CAAiB,CAClC;AAAA;AAAA,KACH;AAAA,IACC,KAAK,MAAA,oBAAUA,cAAA,CAAC,YAAS,IAAA,EAAM,IAAA,CAAK,QAAQ,IAAA,EAAK,QAAA,EAAS,MAAA,EAAgB,KAAA,EAAc,MAAM,MAAA,CAAO,MAAM,GAAG,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA,EAAG;AAAA,GAAA,EACzI,CAAA;AAEJ;AAGA,SAAS,QAAA,CAAS,EAAE,IAAA,EAAM,IAAA,EAAM,QAAQ,KAAA,EAAO,IAAA,EAAM,OAAM,EAA0G;AACnK,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAgB,OAAO,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,CAAM,QAAQ,CAAA,CAAE,IAAA,CAAK,OAAO,MAAM,CAAC,EAAE,KAAA,CAAM,SAAS,EAAE,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA,GAAI,EAAA;AACpI,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,QAAA,EAAU,UAAA;AAAA,IACV,IAAA,EAAM,GAAG,IAAI,CAAA,EAAA,CAAA;AAAA,IACb,KAAA,EAAO,GAAG,KAAK,CAAA,EAAA,CAAA;AAAA,IACf,CAAC,SAAS,QAAA,GAAW,KAAA,GAAQ,QAAQ,GAAG,CAAA,EAAG,SAAS,YAAY,CAAA,EAAA,CAAA;AAAA,IAChE,OAAA,EAAS,MAAA;AAAA,IACT,mBAAA,EAAqB,cAAA;AAAA,IACrB,UAAU,CAAA,EAAG,GAAA,CAAI,KAAK,IAAI,CAAA,IAAK,SAAS,QAAQ,CAAA,EAAA,CAAA;AAAA,IAChD,KAAA,EAAO,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA,IAAK;AAAA,GAC5B;AACA,EAAA,uBACEE,eAAA,CAAC,KAAA,EAAA,EAAI,sBAAA,EAAsB,IAAA,EAAM,KAAA,EAC/B,QAAA,EAAA;AAAA,oBAAAF,cAAA,CAAC,MAAA,EAAA,EAAK,OAAO,EAAE,SAAA,EAAW,QAAO,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAE,CAAA;AAAA,oBACrDA,cAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,SAAA,EAAW,UAAS,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA,EAAE,CAAA;AAAA,oBACzDA,cAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,SAAA,EAAW,SAAQ,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAE;AAAA,GAAA,EACzD,CAAA;AAEJ;AAEA,SAAS,IAAA,CAAK,EAAE,IAAA,EAAK,EAAgC;AACnD,EAAA,sCACEG,mBAAAA,EAAA,EACI,iBAAM,OAAA,CAAQ,IAAI,IAAI,IAAA,GAAO,IAAI,GAAA,CAAI,CAAC,KAAK,CAAA,qBAC3CH,cAAA,CAAC,WAAgB,GAAA,EAAA,EAAH,CAAa,CAC5B,CAAA,EACH,CAAA;AAEJ;AAEA,SAAS,OAAA,CAAQ,EAAE,GAAA,EAAI,EAAiB;AACtC,EAAA,MAAM,OAAO,OAAO,GAAA,EAAK,IAAA,KAAS,QAAA,GAAW,IAAI,IAAA,GAAO,EAAA;AACxD,EAAA,MAAM,KAAA,GAAQ,OAAO,GAAG,CAAA;AACxB,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,CAAI,IAAA,KAAS,WAAWI,uBAAA,CAAa,GAAA,CAAI,IAAI,CAAA,GAAI,MAAA;AAErE,EAAA,IAAI,IAAI,IAAA,EAAM;AACZ,IAAA,KAAA,CAAM,aAAa,KAAA,CAAM,UAAA,IAAc,SAAA,CAAU,QAAA,CAAS,UAAU,WAAW,CAAA;AAC/E,IAAA,KAAA,CAAM,eAAA,GAAkB,KAAA,CAAM,eAAA,IAAmB,QAAA,CAAS,QAAA;AAAA,EAC5D;AACA,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,IAAS,QAAA,CAAS,SAAA;AACtC,IAAA,KAAA,CAAM,kBAAA,GAAqB,MAAM,kBAAA,IAAsB,WAAA;AAAA,EACzD;AAGA,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,UAAU,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,qCAC9BD,cAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,CAAA,GAAI,CAAA,mCAAM,IAAA,EAAA,EAAG,CAAA;AAAA,IACb;AAAA,GAAA,EAAA,EAFY,CAGf,CACD,CAAA;AACD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,IAAA,mBACfH,cAAA,CAAC,UAAK,qBAAA,EAAoB,EAAA,EAAG,KAAA,EAAe,QAAA,EAAA,OAAA,EAAQ,oBAEpDA,cAAA,CAAC,MAAA,EAAA,EAAK,qBAAA,EAAoB,EAAA,EAAG,OAAe,QAAA,EAAA,OAAA,EAAQ,CAAA;AAGtD,EAAA,OAAO,IAAA,kCACJ,GAAA,EAAA,EAAE,IAAA,EAAY,QAAO,QAAA,EAAS,GAAA,EAAI,8BAAA,EAChC,QAAA,EAAA,IAAA,EACH,CAAA,GAEA,IAAA;AAEJ;AAEA,SAAS,SAAA,CAAU,EAAE,KAAA,EAAM,EAAgC;AACzD,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,SAAA,EAAW;AACd,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAI,KAAA,CAAM,KAAK,CAAA,IAAK,CAAC,CAAC,CAAC,CAAA;AACxE,MAAA,MAAM,GAAA,GAAM,IAAI,KAAK,CAAA,CAAA;AACrB,MAAA,MAAM,KAAA,GAAuB;AAAA,QAC3B,GAAG,YAAA,CAAa,KAAA,EAAO,EAAE,MAAA,EAAQ,SAAS,kBAAA,EAAoB,KAAA,EAAO,QAAA,CAAS,iBAAA,EAAmB,CAAA;AAAA,QACjG,UAAU,CAAA,EAAG,QAAA,CAAS,YAAA,CAAa,KAAA,GAAQ,CAAC,CAAC,CAAA,EAAA,CAAA;AAAA,QAC7C,UAAA,EAAY;AAAA,OACd;AACA,MAAA,uBACEA,cAAA,CAAC,GAAA,EAAA,EAAI,uBAAA,EAAsB,SAAA,EAAU,KAAA,EACnC,yCAAC,IAAA,EAAA,EAAK,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,CAAA,EAC1B,CAAA;AAAA,IAEJ;AAAA,IACA,KAAK,WAAA;AACH,MAAA,uBACEA,cAAA,CAAC,GAAA,EAAA,EAAE,uBAAA,EAAsB,WAAA,EAAY,KAAA,EAAO,YAAA,CAAa,KAAK,CAAA,EAC5D,QAAA,kBAAAA,cAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA,EAC1B,CAAA;AAAA,IAEJ,KAAK,MAAA;AACH,MAAA,uBAAOA,cAAA,CAAC,YAAS,KAAA,EAAO,KAAA,CAAM,OAAO,OAAA,EAAS,CAAC,CAAC,KAAA,CAAM,OAAA,EAAS,CAAA;AAAA,IACjE,KAAK,OAAA;AACH,MAAA,uBAAOA,cAAA,CAAC,aAAU,KAAA,EAAc,CAAA;AAAA,IAClC,KAAK,MAAA;AACH,MAAA,uBACEA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,MAAA;AAAA,UACtB,KAAA,EAAO;AAAA,YACL,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,YAClC,OAAA,EAAS,SAAA;AAAA,YACT,YAAY,QAAA,CAAS,QAAA;AAAA,YACrB,UAAA,EAAY,SAAA,CAAU,QAAA,CAAS,QAAA,EAAU,WAAW,CAAA;AAAA,YACpD,QAAA,EAAU,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAA,CAAA;AAAA,YAC9B,UAAA,EAAY,CAAA;AAAA,YACZ,UAAA,EAAY;AAAA,WACd;AAAA,UAEA,QAAA,kBAAAA,cAAA,CAAC,UAAM,QAAA,EAAA,OAAO,KAAA,CAAM,SAAS,QAAA,GAAW,KAAA,CAAM,OAAO,EAAA,EAAG;AAAA;AAAA,OAC1D;AAAA,IAEJ,KAAK,OAAA;AACH,MAAA,uBACEA,cAAA;AAAA,QAAC,YAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,OAAA;AAAA,UACtB,KAAA,EAAO,EAAE,MAAA,EAAQ,CAAA,MAAA,EAAS,QAAA,CAAS,WAAW,CAAA,EAAA,CAAA,EAAM,SAAA,EAAW,QAAA,EAAU,KAAA,EAAO,QAAA,CAAS,UAAA,EAAW;AAAA,UAElG,iBAAM,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA,GAAI,KAAA,CAAM,SAAS,EAAC,EAAG,GAAA,CAAI,CAAC,GAAG,CAAA,qBACzDA,cAAA,CAAC,aAAkB,KAAA,EAAO,CAAA,EAAA,EAAV,CAAa,CAC9B;AAAA;AAAA,OACH;AAAA,IAEJ,KAAK,OAAA,EAAS;AAGZ,MAAA,MAAM,GAAA,GAAM,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,IAAY,0DAAA,CAA2D,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,KAAA,CAAM,GAAA,GAAM,IAAA;AACtI,MAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,MAAA,uBACEA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,OAAA;AAAA,UACtB,GAAA;AAAA,UACA,KAAK,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,GAAW,MAAM,GAAA,GAAM,EAAA;AAAA,UACjD,KAAA,EAAO,EAAE,OAAA,EAAS,OAAA,EAAS,UAAU,MAAA,EAAQ,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG,QAAQ,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA,GAAI,MAAA,GAAY,QAAQ,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAK;AAAA,OACnK;AAAA,IAEJ;AAAA,IACA,KAAK,IAAA;AACH,MAAA,uBAAOA,cAAA,CAAC,IAAA,EAAA,EAAG,uBAAA,EAAsB,IAAA,EAAK,OAAO,EAAE,MAAA,EAAQ,CAAA,EAAG,SAAA,EAAW,uBAAuB,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,MAAK,EAAG,CAAA;AAAA,IACxI;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAEA,SAAS,QAAA,CAAS,EAAE,KAAA,EAAO,OAAA,EAAQ,EAAwD;AACzF,EAAA,MAAM,GAAA,GAAM,UAAU,IAAA,GAAO,IAAA;AAC7B,EAAA,sCACG,GAAA,EAAA,EAAI,uBAAA,EAAsB,QAAO,KAAA,EAAO,EAAE,QAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA,EAAM,aAAa,MAAA,EAAQ,aAAA,EAAe,UAAU,SAAA,GAAY,MAAA,IACzI,QAAA,EAAA,CAAA,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,QAAQ,EAAC,EAAG,IAAI,CAAC,IAAA,EAAM,sBAC9CE,eAAA,CAAC,IAAA,EAAA,EAAW,OAAO,EAAE,GAAG,aAAa,IAAA,EAAM,EAAE,OAAO,CAAA,EAAG,GAAE,EACvD,QAAA,EAAA;AAAA,oBAAAF,cAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IACtB,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,QAAQ,KAAK,IAAA,CAAK,QAAA,CAAS,MAAA,GAAS,CAAA,oBAAKA,cAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAO,IAAA,CAAK,UAAU,OAAA,EAAkB;AAAA,GAAA,EAAA,EAFxG,CAGT,CACD,CAAA,EACH,CAAA;AAEJ;AAIA,SAAS,SAAA,CAAU,EAAE,KAAA,EAAM,EAA0B;AACnD,EAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAM,OAAO,EAAC;AACvD,EAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,MAAM,MAAM,CAAA,GAAI,MAAM,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,IAAI,CAAC,CAAA,KAAM,UAAa,CAAA,GAAI,CAAC,IAAI,EAAC;AAC1G,EAAA,MAAM,GAAA,GAAM,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAC7B,EAAA,MAAM,OAAA,GAAwB,KAAA,CAAM,OAAA,IAAW,EAAC;AAEhD,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,cAAA,EAAgB,UAAA;AAAA,IAChB,KAAA,EAAO,KAAA,KAAU,MAAA,GAAY,CAAA,EAAG,KAAK,CAAA,CAAA,CAAA,GAAM,MAAA;AAAA,IAC3C,WAAA,EAAa,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,OAAA,GAAU,MAAA;AAAA,IAC3C,YAAA,EAAc,CAAA,EAAG,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,IACpC,GAAI,KAAA,CAAM,KAAA,KAAU,QAAA,GAAW,EAAE,YAAY,MAAA,EAAQ,WAAA,EAAa,MAAA,EAAO,GAAI,MAAM,KAAA,KAAU,OAAA,GAAU,EAAE,UAAA,EAAY,MAAA,KAAW;AAAC,GACnI;AAGA,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAwC,SAAA,CAAU,CAAC,CAAA,IAAK,CAAA,EAAG,SAAS,WAAW,CAAA,gBAAA,CAAA;AAE7F,EAAA,uBACEE,eAAA,CAAC,OAAA,EAAA,EAAM,uBAAA,EAAsB,OAAA,EAAQ,KAAA,EAClC,QAAA,EAAA;AAAA,IAAA,MAAA,CAAO,MAAA,GAAS,CAAA,oBACfF,cAAA,CAAC,UAAA,EAAA,EACE,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,qBACdA,cAAA,CAAC,KAAA,EAAA,EAAY,KAAA,EAAO,EAAE,OAAO,CAAA,EAAG,IAAA,CAAK,KAAA,CAAO,CAAA,GAAI,GAAA,GAAO,GAAI,CAAA,GAAI,EAAE,CAAA,CAAA,CAAA,EAAI,EAAA,EAA3D,CAA8D,CACzE,CAAA,EACH,CAAA;AAAA,oBAEFA,cAAA,CAAC,WACE,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,EAAK,CAAA,qBACdA,cAAA,CAAC,IAAA,EAAA,EACG,QAAA,EAAA,CAAA,KAAA,CAAM,OAAA,CAAQ,IAAI,KAAK,CAAA,GAAI,IAAI,KAAA,GAAQ,IAAI,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,EAAG,KAAA,KAAU;AACnE,MAAA,MAAM,MAAA,GAAS,CAAC,CAAC,GAAA,CAAI,MAAA;AACrB,MAAA,MAAM,GAAA,GAAM,SAAS,IAAA,GAAO,IAAA;AAC5B,MAAA,MAAM,QAAQ,CAAA,KAAM,CAAA;AACpB,MAAA,MAAM,IAAA,GAAO,CAAA,KAAM,KAAA,CAAM,MAAA,GAAS,CAAA;AAClC,MAAA,MAAM,SAAA,GAA2B;AAAA;AAAA,QAE/B,SAAA,EAAW,MAAM,CAAA,GAAI,IAAA,CAAK,QAAQ,GAAG,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,QAC7D,YAAA,EAAc,CAAA,KAAM,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,QACjF,UAAA,EAAY,QAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,QAC7D,WAAA,EAAa,OAAO,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,QAC9D,GAAG,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAS,EAAE,GAAG,QAAA,CAAS,WAAA,EAAa,GAAI,KAAA,CAAM,WAAA,IAAe,IAAK,CAAA;AAAA,QAClF,GAAG,UAAA,CAAW,IAAA,CAAK,OAAO,CAAA;AAAA,QAC1B,eAAe,IAAA,CAAK,MAAA,KAAW,QAAA,GAAW,QAAA,GAAW,KAAK,MAAA,IAAU,KAAA;AAAA,QACpE,SAAA,EAAW,MAAA;AAAA,QACX,UAAA,EAAY,SAAS,GAAA,GAAM,MAAA;AAAA,QAC3B,iBAAiB,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,KAAM,MAAA,GAAS,SAAS,UAAA,GAAa,MAAA;AAAA,OACxE;AACA,MAAA,uBACEA,cAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UAEC,KAAA,EAAO,SAAA;AAAA,UACP,OAAA,EAAS,IAAI,IAAA,CAAK,OAAO,KAAK,IAAA,CAAK,OAAA,GAAW,CAAA,GAAI,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,UACjE,OAAA,EAAS,IAAI,IAAA,CAAK,OAAO,KAAK,IAAA,CAAK,OAAA,GAAW,CAAA,GAAI,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,UAEjE,QAAA,kBAAAA,cAAA,CAAC,cAAW,IAAA,EAAY;AAAA,SAAA;AAAA,QALnB;AAAA,OAMP;AAAA,IAEJ,CAAC,CAAA,EAAA,EA7BM,CA8BT,CACD,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAGA,SAAS,UAAA,CAAW,EAAE,IAAA,EAAK,EAAwB;AACjD,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA,CAAK,SAAS,EAAC;AAC3D,EAAA,uBACEA,cAAA,CAAAG,mBAAAA,EAAA,EACG,QAAA,EAAA,MAAA,CAAO,GAAA;AAAA,IAAI,CAAC,GAAG,CAAA,KACd,CAAA,EAAG,SAAS,WAAA,mBACVH,cAAA,CAAC,OAAU,uBAAA,EAAsB,WAAA,EAAY,OAAO,EAAE,GAAG,aAAa,CAAC,CAAA,EAAG,GAAI,CAAA,KAAM,MAAA,CAAO,SAAS,CAAA,IAAK,GAAA,CAAI,EAAE,UAAU,CAAA,KAAM,SAAY,EAAE,YAAA,EAAc,GAAE,GAAI,IAAI,EACnK,QAAA,kBAAAA,cAAA,CAAC,QAAK,IAAA,EAAM,CAAA,CAAE,MAAM,CAAA,EAAA,EADd,CAER,oBAEAA,cAAA,CAAC,SAAA,EAAA,EAAkB,KAAA,EAAO,CAAA,EAAA,EAAV,CAAa;AAAA,GAEjC,EACF,CAAA;AAEJ;ACnVO,SAAS,WAAA,CAAY,EAAE,QAAA,EAAU,QAAA,EAAU,YAAW,EAAqB;AAChF,EAAA,MAAM,GAAA,GAAM,QAAA;AAGZ,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,EAAG;AACjE,IAAA,uBAAOE,eAAAA,CAACG,kBAAA,EAAA,EAAQ,KAAA,EAAM,KAAA,EAAO,QAAA,EAAA;AAAA,MAAA,QAAA,IAAY,WAAA;AAAA,MAAY;AAAA,KAAA,EAA4B,CAAA;AAAA,EACnF;AAEA,EAAA,uBAAOL,cAAAA,CAAC,SAAA,EAAA,EAAU,GAAA,EAAiB,UAAA,EAAwB,CAAA;AAC7D","file":"index.cjs","sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { Border, BoxBorders, BoxSides, ParagraphProps, Run } from \"./types\";\n\n/**\n * What last-word's writer gives a document that asks for nothing, so an\n * unstyled document looks here as it does in Word. Read off the writer's\n * styles part (docDefaults, Heading1-6, Quote, CodeBlock, Hyperlink) rather\n * than guessed.\n */\nexport const DEFAULTS = {\n font: \"Calibri\",\n /** Points. */\n size: 11,\n /** w:spacing after=\"160\": 8pt after every paragraph. */\n spaceAfter: 8,\n /** w:line=\"259\" of 240: 1.08. */\n lineHeight: 1.08,\n /** Heading 1-6, points. */\n headingSizes: [20, 16, 14, 13, 12, 11],\n headingSpaceBefore: 12,\n headingSpaceAfter: 6,\n quoteIndent: 36,\n quoteColor: \"#595959\",\n codeFont: \"Consolas\",\n codeSize: 10,\n codeFill: \"#F2F2F2\",\n linkColor: \"#0563C1\",\n /** Cell margins 60 / 108 twips. */\n cellPadding: { top: 3, right: 5.4, bottom: 3, left: 5.4 },\n /** The header-cell grey the writer and all three engines share. */\n headerFill: \"#E7E7E7\",\n /** A border edge that names no width. */\n borderWidth: 0.5,\n /** Page margins when none are given: 1440 twips. */\n margin: 72,\n /** Header / footer distance from the page edge: 720 twips. */\n bandDistance: 36,\n bandSize: 9,\n};\n\n/** Page sizes in points, portrait. */\nexport const PAGE_SIZES: Record<string, [number, number]> = {\n letter: [612, 792],\n legal: [612, 1008],\n a4: [595, 842],\n};\n\n/**\n * `#RRGGBB` (or `RRGGBB`), or nothing. A colour comes out of a file, so anything\n * else -- a CSS expression smuggled into a colour field -- is dropped rather\n * than handed to a style.\n */\nexport function hex(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim();\n if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toUpperCase();\n if (/^[0-9a-fA-F]{6}$/.test(v)) return `#${v.toUpperCase()}`;\n return undefined;\n}\n\n/** A finite number, or nothing. */\nexport function num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/** A font name as a CSS family list: quoted, with a generic fallback. */\nexport function fontStack(font: unknown, generic = \"sans-serif\"): string | undefined {\n if (typeof font !== \"string\" || font.trim() === \"\") return undefined;\n const name = font.replace(/[\"\\\\;{}<>]/g, \"\").trim();\n return `\"${name}\", ${generic}`;\n}\n\nconst CSS_STYLE: Record<string, string> = { single: \"solid\", double: \"double\", dashed: \"dashed\", dotted: \"dotted\" };\n\n/** One border edge as CSS. `style: \"none\"` removes the edge. */\nexport function borderCss(border: Border | undefined): string | undefined {\n if (!border) return undefined;\n if (border.style === \"none\") return \"none\";\n const width = num(border.width) ?? DEFAULTS.borderWidth;\n const style = CSS_STYLE[border.style ?? \"single\"] ?? \"solid\";\n return `${width}pt ${style} ${hex(border.color) ?? \"#000000\"}`;\n}\n\nexport function boxBorders(borders: BoxBorders | undefined): CSSProperties {\n if (!borders) return {};\n const out: CSSProperties = {};\n const top = borderCss(borders.top);\n const right = borderCss(borders.right);\n const bottom = borderCss(borders.bottom);\n const left = borderCss(borders.left);\n if (top) out.borderTop = top;\n if (right) out.borderRight = right;\n if (bottom) out.borderBottom = bottom;\n if (left) out.borderLeft = left;\n return out;\n}\n\nexport function padding(sides: BoxSides | undefined, fallback?: BoxSides): CSSProperties {\n const pick = (k: keyof BoxSides) => num(sides?.[k]) ?? num(fallback?.[k]);\n const out: CSSProperties = {};\n const t = pick(\"top\");\n const r = pick(\"right\");\n const b = pick(\"bottom\");\n const l = pick(\"left\");\n if (t !== undefined) out.paddingTop = `${t}pt`;\n if (r !== undefined) out.paddingRight = `${r}pt`;\n if (b !== undefined) out.paddingBottom = `${b}pt`;\n if (l !== undefined) out.paddingLeft = `${l}pt`;\n return out;\n}\n\n/**\n * Word's \"single\" line is the font's own line height -- ascent + descent + gap,\n * about 1.2 em for Calibri and most text faces -- where CSS's 1.0 is the bare\n * em. So a document's 1.08 lines is 1.08 x 1.2 here; set as 1.08 it drew every\n * paragraph visibly tighter than LibreOffice's render of the same file.\n */\nexport const SINGLE_LINE = 1.2;\n\nexport function lineHeightCss(lines: number): number {\n return Math.round(lines * SINGLE_LINE * 1000) / 1000;\n}\n\n/** A paragraph-shaped block's box: alignment, spacing, indents, shading, borders. */\nexport function paragraphCss(p: ParagraphProps, defaults: { before?: number; after?: number } = {}): CSSProperties {\n const out: CSSProperties = {\n marginTop: `${num(p.spaceBefore) ?? defaults.before ?? 0}pt`,\n marginBottom: `${num(p.spaceAfter) ?? defaults.after ?? DEFAULTS.spaceAfter}pt`,\n lineHeight: String(lineHeightCss(num(p.lineHeight) ?? DEFAULTS.lineHeight)),\n ...boxBorders(p.borders),\n };\n if (p.align) out.textAlign = p.align;\n const left = num(p.indentLeft);\n const right = num(p.indentRight);\n if (left !== undefined) out.marginLeft = `${left}pt`;\n if (right !== undefined) out.marginRight = `${right}pt`;\n const fill = hex(p.shading);\n if (fill) {\n out.backgroundColor = fill;\n // Word draws the shading behind the text with a little room; without it\n // the fill hugs the glyphs and a callout reads as a highlight.\n out.padding = \"2pt 4pt\";\n }\n return out;\n}\n\n/** A run's character style. */\nexport function runCss(run: Run): CSSProperties {\n const out: CSSProperties = {};\n if (run.bold) out.fontWeight = 700;\n if (run.italic) out.fontStyle = \"italic\";\n const lines = [run.underline ? \"underline\" : \"\", run.strike ? \"line-through\" : \"\"].filter(Boolean);\n if (lines.length) out.textDecorationLine = lines.join(\" \");\n const color = hex(run.color);\n if (color) out.color = color;\n const highlight = hex(run.highlight);\n if (highlight) out.backgroundColor = highlight;\n const size = num(run.size);\n if (size !== undefined && size > 0) out.fontSize = `${size}pt`;\n const font = fontStack(run.font);\n if (font) out.fontFamily = font;\n if (run.smallCaps) out.fontVariant = \"small-caps\";\n const spacing = num(run.letterSpacing);\n if (spacing) out.letterSpacing = `${spacing}pt`;\n return out;\n}\n","import { Fragment, type CSSProperties, type ReactNode } from \"react\";\nimport { FauxClient, sanitizeHref, type FauxClientProps } from \"@particle-academy/react-fancy\";\nimport type { Band, Block, Doc, ListItem, Run, TableBorders, TableCell } from \"./types\";\nimport { borderCss, boxBorders, DEFAULTS, fontStack, hex, num, padding, PAGE_SIZES, paragraphCss, runCss } from \"./style\";\n\nexport interface DocViewerProps {\n /** A last-word document, as its readers return it. */\n doc: Doc;\n /**\n * Show the document in react-fancy's `FauxClient` frame: zoom and full screen,\n * revealed on hover, like every file viewer. Default `true`.\n */\n frame?: boolean;\n /** Passed to the frame: `variant`, a controlled `zoom`, `onFullscreenChange`, … */\n frameProps?: Partial<FauxClientProps>;\n className?: string;\n}\n\n/**\n * A last-word document rendered the way Word renders it: page size, margins and\n * running header / footer per page; runs, paragraphs, lists, tables (fills,\n * borders, spans, alignment), code, quotes and images.\n *\n * Measurements stay in points, the model's own unit, so a 0.5pt rule is a 0.5pt\n * rule and the page is the page's size; the frame scales the whole sheet to fit\n * and zooms it.\n *\n * Everything shown came out of a file, so links go through react-fancy's\n * `sanitizeHref`, images are `data:` rasters only (a remote URL in a document\n * is a tracking pixel waiting to fire), and colours must be `#RRGGBB`.\n */\nexport function DocViewer({ doc, frame = true, frameProps, className }: DocViewerProps) {\n const pages = splitPages(Array.isArray(doc?.blocks) ? doc.blocks : []);\n const total = pages.length;\n\n const sheets = (\n <div\n data-fancy-docs-viewer=\"\"\n className={className}\n style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 16, padding: 16, background: \"#E5E7EB\" }}\n >\n {pages.map((blocks, i) => (\n <PageSheet key={i} doc={doc} blocks={blocks} number={i + 1} total={total} />\n ))}\n </div>\n );\n\n if (!frame) return sheets;\n\n // The frame lays the sheets out at their real width and scales them to fit,\n // as a word processor opens at \"fit page width\"; zoom works on top of that.\n return (\n <FauxClient\n variant=\"device\"\n controls\n fill\n width={logicalWidth(doc)}\n scale=\"fit\"\n bodyClassName=\"overflow-auto\"\n {...frameProps}\n >\n {sheets}\n </FauxClient>\n );\n}\n\n/** The sheets' width in CSS pixels, gutter included: the frame's logical width. */\nfunction logicalWidth(doc: Doc): number {\n const page = doc?.page ?? {};\n const [w, h] = PAGE_SIZES[page.size ?? \"letter\"] ?? PAGE_SIZES.letter;\n const width = page.orientation === \"landscape\" ? h : w;\n return Math.ceil((width * 4) / 3) + 32;\n}\n\n/** Page breaks split the flow into sheets; an empty document is one blank sheet. */\nfunction splitPages(blocks: Block[]): Block[][] {\n const pages: Block[][] = [[]];\n for (const block of blocks) {\n if (block?.type === \"pageBreak\") pages.push([]);\n else pages[pages.length - 1].push(block);\n }\n return pages;\n}\n\nfunction PageSheet({ doc, blocks, number, total }: { doc: Doc; blocks: Block[]; number: number; total: number }) {\n const page = doc.page ?? {};\n const [w, h] = PAGE_SIZES[page.size ?? \"letter\"] ?? PAGE_SIZES.letter;\n const [width, height] = page.orientation === \"landscape\" ? [h, w] : [w, h];\n const m = page.margins ?? {};\n const margin = (side: \"top\" | \"right\" | \"bottom\" | \"left\") => num(m[side]) ?? DEFAULTS.margin;\n\n const style: CSSProperties = {\n position: \"relative\",\n width: `${width}pt`,\n minHeight: `${height}pt`,\n background: \"#FFFFFF\",\n color: \"#000000\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.15)\",\n fontFamily: fontStack(doc.defaultFont ?? DEFAULTS.font),\n fontSize: `${num(doc.defaultSize) ?? DEFAULTS.size}pt`,\n flexShrink: 0,\n };\n\n return (\n <section data-fancy-docs-page={number} style={style}>\n {page.header && <BandLine band={page.header} kind=\"header\" number={number} total={total} left={margin(\"left\")} right={margin(\"right\")} />}\n <div\n data-fancy-docs-body=\"\"\n style={{ paddingTop: `${margin(\"top\")}pt`, paddingRight: `${margin(\"right\")}pt`, paddingBottom: `${margin(\"bottom\")}pt`, paddingLeft: `${margin(\"left\")}pt` }}\n >\n {blocks.map((block, i) => (\n <BlockView key={i} block={block} />\n ))}\n </div>\n {page.footer && <BandLine band={page.footer} kind=\"footer\" number={number} total={total} left={margin(\"left\")} right={margin(\"right\")} />}\n </section>\n );\n}\n\n/** A running header or footer: left / center / right, with `{page}` and `{pages}` filled in. */\nfunction BandLine({ band, kind, number, total, left, right }: { band: Band; kind: \"header\" | \"footer\"; number: number; total: number; left: number; right: number }) {\n const fill = (s: unknown) => (typeof s === \"string\" ? s.split(\"{page}\").join(String(number)).split(\"{pages}\").join(String(total)) : \"\");\n const style: CSSProperties = {\n position: \"absolute\",\n left: `${left}pt`,\n right: `${right}pt`,\n [kind === \"header\" ? \"top\" : \"bottom\"]: `${DEFAULTS.bandDistance}pt`,\n display: \"grid\",\n gridTemplateColumns: \"1fr auto 1fr\",\n fontSize: `${num(band.size) ?? DEFAULTS.bandSize}pt`,\n color: hex(band.color) ?? \"#595959\",\n };\n return (\n <div data-fancy-docs-band={kind} style={style}>\n <span style={{ textAlign: \"left\" }}>{fill(band.left)}</span>\n <span style={{ textAlign: \"center\" }}>{fill(band.center)}</span>\n <span style={{ textAlign: \"right\" }}>{fill(band.right)}</span>\n </div>\n );\n}\n\nfunction Runs({ runs }: { runs: Run[] | undefined }) {\n return (\n <>\n {(Array.isArray(runs) ? runs : []).map((run, i) => (\n <RunView key={i} run={run} />\n ))}\n </>\n );\n}\n\nfunction RunView({ run }: { run: Run }) {\n const text = typeof run?.text === \"string\" ? run.text : \"\";\n const style = runCss(run);\n const href = typeof run.link === \"string\" ? sanitizeHref(run.link) : undefined;\n\n if (run.code) {\n style.fontFamily = style.fontFamily ?? fontStack(DEFAULTS.codeFont, \"monospace\");\n style.backgroundColor = style.backgroundColor ?? DEFAULTS.codeFill;\n }\n if (href) {\n style.color = style.color ?? DEFAULTS.linkColor;\n style.textDecorationLine = style.textDecorationLine ?? \"underline\";\n }\n\n // Line breaks inside a run are real breaks, as Word shows them.\n const parts = text.split(\"\\n\");\n const content = parts.map((part, i) => (\n <Fragment key={i}>\n {i > 0 && <br />}\n {part}\n </Fragment>\n ));\n const span = run.code ? (\n <code data-fancy-docs-run=\"\" style={style}>{content}</code>\n ) : (\n <span data-fancy-docs-run=\"\" style={style}>{content}</span>\n );\n\n return href ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer nofollow\">\n {span}\n </a>\n ) : (\n span\n );\n}\n\nfunction BlockView({ block }: { block: Block }): ReactNode {\n switch (block?.type) {\n case \"heading\": {\n const level = Math.min(6, Math.max(1, Math.round(num(block.level) ?? 1)));\n const Tag = `h${level}` as \"h1\";\n const style: CSSProperties = {\n ...paragraphCss(block, { before: DEFAULTS.headingSpaceBefore, after: DEFAULTS.headingSpaceAfter }),\n fontSize: `${DEFAULTS.headingSizes[level - 1]}pt`,\n fontWeight: 700,\n };\n return (\n <Tag data-fancy-docs-block=\"heading\" style={style}>\n <Runs runs={block.runs} />\n </Tag>\n );\n }\n case \"paragraph\":\n return (\n <p data-fancy-docs-block=\"paragraph\" style={paragraphCss(block)}>\n <Runs runs={block.runs} />\n </p>\n );\n case \"list\":\n return <ListView items={block.items} ordered={!!block.ordered} />;\n case \"table\":\n return <TableView block={block} />;\n case \"code\":\n return (\n <pre\n data-fancy-docs-block=\"code\"\n style={{\n margin: `0 0 ${DEFAULTS.spaceAfter}pt`,\n padding: \"4pt 6pt\",\n background: DEFAULTS.codeFill,\n fontFamily: fontStack(DEFAULTS.codeFont, \"monospace\"),\n fontSize: `${DEFAULTS.codeSize}pt`,\n lineHeight: 1,\n whiteSpace: \"pre-wrap\",\n }}\n >\n <code>{typeof block.text === \"string\" ? block.text : \"\"}</code>\n </pre>\n );\n case \"quote\":\n return (\n <blockquote\n data-fancy-docs-block=\"quote\"\n style={{ margin: `0 0 0 ${DEFAULTS.quoteIndent}pt`, fontStyle: \"italic\", color: DEFAULTS.quoteColor }}\n >\n {(Array.isArray(block.blocks) ? block.blocks : []).map((b, i) => (\n <BlockView key={i} block={b} />\n ))}\n </blockquote>\n );\n case \"image\": {\n // Rasters embedded in the file only: a remote URL would fetch as soon as\n // the document opened -- a tracking pixel in a file someone sent you.\n const src = typeof block.src === \"string\" && /^data:image\\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/=]+$/.test(block.src) ? block.src : null;\n if (!src) return null;\n return (\n <img\n data-fancy-docs-block=\"image\"\n src={src}\n alt={typeof block.alt === \"string\" ? block.alt : \"\"}\n style={{ display: \"block\", maxWidth: \"100%\", width: num(block.widthPx), height: num(block.heightPx) ? undefined : \"auto\", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }}\n />\n );\n }\n case \"hr\":\n return <hr data-fancy-docs-block=\"hr\" style={{ border: 0, borderTop: \"0.5pt solid #000000\", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }} />;\n default:\n return null;\n }\n}\n\nfunction ListView({ items, ordered }: { items: ListItem[] | undefined; ordered: boolean }) {\n const Tag = ordered ? \"ol\" : \"ul\";\n return (\n <Tag data-fancy-docs-block=\"list\" style={{ margin: `0 0 ${DEFAULTS.spaceAfter}pt`, paddingLeft: \"18pt\", listStyleType: ordered ? \"decimal\" : \"disc\" }}>\n {(Array.isArray(items) ? items : []).map((item, i) => (\n <li key={i} style={{ ...paragraphCss(item, { after: 0 }) }}>\n <Runs runs={item.runs} />\n {Array.isArray(item.children) && item.children.length > 0 && <ListView items={item.children} ordered={ordered} />}\n </li>\n ))}\n </Tag>\n );\n}\n\ntype TableBlock = Extract<Block, { type: \"table\" }>;\n\nfunction TableView({ block }: { block: TableBlock }) {\n const rows = Array.isArray(block.rows) ? block.rows : [];\n const widths = Array.isArray(block.widths) ? block.widths.filter((w) => num(w) !== undefined && w > 0) : [];\n const sum = widths.reduce((a, b) => a + b, 0);\n const width = num(block.width);\n const borders: TableBorders = block.borders ?? {};\n\n const style: CSSProperties = {\n borderCollapse: \"collapse\",\n width: width !== undefined ? `${width}%` : \"100%\",\n tableLayout: widths.length > 0 ? \"fixed\" : \"auto\",\n marginBottom: `${DEFAULTS.spaceAfter}pt`,\n ...(block.align === \"center\" ? { marginLeft: \"auto\", marginRight: \"auto\" } : block.align === \"right\" ? { marginLeft: \"auto\" } : {}),\n };\n\n // Every edge defaults to the writer's 0.5pt single line; `{ style: \"none\" }` removes one.\n const edge = (b: TableBorders[keyof TableBorders]) => borderCss(b) ?? `${DEFAULTS.borderWidth}pt solid #000000`;\n\n return (\n <table data-fancy-docs-block=\"table\" style={style}>\n {widths.length > 0 && (\n <colgroup>\n {widths.map((w, i) => (\n <col key={i} style={{ width: `${Math.round((w / sum) * 1000) / 10}%` }} />\n ))}\n </colgroup>\n )}\n <tbody>\n {rows.map((row, r) => (\n <tr key={r}>\n {(Array.isArray(row.cells) ? row.cells : []).map((cell, c, cells) => {\n const header = !!row.header;\n const Tag = header ? \"th\" : \"td\";\n const first = c === 0;\n const last = c === cells.length - 1;\n const cellStyle: CSSProperties = {\n // Outer edges from the table's box; inner ones from insideH / insideV.\n borderTop: r === 0 ? edge(borders.top) : edge(borders.insideH),\n borderBottom: r === rows.length - 1 ? edge(borders.bottom) : edge(borders.insideH),\n borderLeft: first ? edge(borders.left) : edge(borders.insideV),\n borderRight: last ? edge(borders.right) : edge(borders.insideV),\n ...padding(cell.padding, { ...DEFAULTS.cellPadding, ...(block.cellPadding ?? {}) }),\n ...boxBorders(cell.borders),\n verticalAlign: cell.valign === \"center\" ? \"middle\" : cell.valign ?? \"top\",\n textAlign: \"left\",\n fontWeight: header ? 700 : undefined,\n backgroundColor: hex(cell.shading) ?? (header ? DEFAULTS.headerFill : undefined),\n };\n return (\n <Tag\n key={c}\n style={cellStyle}\n colSpan={num(cell.colSpan) && cell.colSpan! > 1 ? cell.colSpan : undefined}\n rowSpan={num(cell.rowSpan) && cell.rowSpan! > 1 ? cell.rowSpan : undefined}\n >\n <CellBlocks cell={cell} />\n </Tag>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\n/** A cell's blocks, with the last paragraph's trailing space dropped as Word does inside a cell. */\nfunction CellBlocks({ cell }: { cell: TableCell }) {\n const blocks = Array.isArray(cell.blocks) ? cell.blocks : [];\n return (\n <>\n {blocks.map((b, i) =>\n b?.type === \"paragraph\" ? (\n <p key={i} data-fancy-docs-block=\"paragraph\" style={{ ...paragraphCss(b), ...(i === blocks.length - 1 && num(b.spaceAfter) === undefined ? { marginBottom: 0 } : {}) }}>\n <Runs runs={b.runs} />\n </p>\n ) : (\n <BlockView key={i} block={b} />\n ),\n )}\n </>\n );\n}\n","import { Callout, type FauxClientProps } from \"@particle-academy/react-fancy\";\nimport { DocViewer } from \"./DocViewer\";\nimport type { Doc } from \"./types\";\n\nexport interface DocFileViewProps {\n /** A document as last-word's reader returns it. */\n document: unknown;\n filename?: string;\n /** Passed to the FauxClient frame. */\n frameProps?: Partial<FauxClientProps>;\n}\n\n/**\n * A .docx in fancy-code's `FileViewer`: register it once at app start.\n *\n * ```ts\n * registerOfficeViewer(\"docx\", DocFileView, { read: (b) => new DocxReader().read(b) });\n * ```\n *\n * The props match fancy-code's `OfficeViewerProps` without importing them, so\n * this package does not depend on fancy-code.\n */\nexport function DocFileView({ document, filename, frameProps }: DocFileViewProps) {\n const doc = document as Partial<Doc> | null;\n\n // A reader that returned something else would otherwise draw a blank page.\n if (!doc || typeof doc !== \"object\" || !Array.isArray(doc.blocks)) {\n return <Callout color=\"red\">{filename ?? \"This file\"} did not read as a document.</Callout>;\n }\n\n return <DocViewer doc={doc as Doc} frameProps={frameProps} />;\n}\n"]}
|