react-next-editor-js 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/LICENSE +21 -0
- package/README.md +877 -0
- package/dist/chunk-3QWXTDLY.cjs +486 -0
- package/dist/chunk-3QWXTDLY.cjs.map +1 -0
- package/dist/chunk-5F6SPYCN.cjs +180 -0
- package/dist/chunk-5F6SPYCN.cjs.map +1 -0
- package/dist/chunk-6NTSXJX4.js +174 -0
- package/dist/chunk-6NTSXJX4.js.map +1 -0
- package/dist/chunk-7VYJDBH7.js +261 -0
- package/dist/chunk-7VYJDBH7.js.map +1 -0
- package/dist/chunk-DBSFCCBG.cjs +1712 -0
- package/dist/chunk-DBSFCCBG.cjs.map +1 -0
- package/dist/chunk-EFE6RHDL.cjs +4 -0
- package/dist/chunk-EFE6RHDL.cjs.map +1 -0
- package/dist/chunk-G6YRIEK4.js +3 -0
- package/dist/chunk-G6YRIEK4.js.map +1 -0
- package/dist/chunk-GFNFJ3FL.cjs +119 -0
- package/dist/chunk-GFNFJ3FL.cjs.map +1 -0
- package/dist/chunk-IG2YLUFW.js +114 -0
- package/dist/chunk-IG2YLUFW.js.map +1 -0
- package/dist/chunk-JQXTWLHL.js +176 -0
- package/dist/chunk-JQXTWLHL.js.map +1 -0
- package/dist/chunk-NJCEHQV3.cjs +454 -0
- package/dist/chunk-NJCEHQV3.cjs.map +1 -0
- package/dist/chunk-O4GTLC3T.js +478 -0
- package/dist/chunk-O4GTLC3T.js.map +1 -0
- package/dist/chunk-ODHABIIC.cjs +82 -0
- package/dist/chunk-ODHABIIC.cjs.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/chunk-QIUIYBCZ.js +80 -0
- package/dist/chunk-QIUIYBCZ.js.map +1 -0
- package/dist/chunk-QROUNVQK.js +450 -0
- package/dist/chunk-QROUNVQK.js.map +1 -0
- package/dist/chunk-T6FR37IC.js +41 -0
- package/dist/chunk-T6FR37IC.js.map +1 -0
- package/dist/chunk-TI44I654.cjs +265 -0
- package/dist/chunk-TI44I654.cjs.map +1 -0
- package/dist/chunk-TXPLBAH5.cjs +47 -0
- package/dist/chunk-TXPLBAH5.cjs.map +1 -0
- package/dist/chunk-U3O54IYI.cjs +187 -0
- package/dist/chunk-U3O54IYI.cjs.map +1 -0
- package/dist/chunk-VLC7SZMT.js +1669 -0
- package/dist/chunk-VLC7SZMT.js.map +1 -0
- package/dist/core/index.cjs +232 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +122 -0
- package/dist/core/index.d.ts +122 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/defaults-EQD5QKCU.js +4 -0
- package/dist/defaults-EQD5QKCU.js.map +1 -0
- package/dist/defaults-MLYXD2BG.cjs +49 -0
- package/dist/defaults-MLYXD2BG.cjs.map +1 -0
- package/dist/docx-BUrf4PFj.d.ts +49 -0
- package/dist/docx-DLfSdvXm.d.cts +49 -0
- package/dist/docx-LDETXV3L.js +5 -0
- package/dist/docx-LDETXV3L.js.map +1 -0
- package/dist/docx-N2LKIOK3.cjs +14 -0
- package/dist/docx-N2LKIOK3.cjs.map +1 -0
- package/dist/export/index.cjs +54 -0
- package/dist/export/index.cjs.map +1 -0
- package/dist/export/index.d.cts +60 -0
- package/dist/export/index.d.ts +60 -0
- package/dist/export/index.js +9 -0
- package/dist/export/index.js.map +1 -0
- package/dist/html-5BXJPQU3.js +7 -0
- package/dist/html-5BXJPQU3.js.map +1 -0
- package/dist/html-KU2KHLRF.cjs +24 -0
- package/dist/html-KU2KHLRF.cjs.map +1 -0
- package/dist/import/index.cjs +15 -0
- package/dist/import/index.cjs.map +1 -0
- package/dist/import/index.d.cts +37 -0
- package/dist/import/index.d.ts +37 -0
- package/dist/import/index.js +6 -0
- package/dist/import/index.js.map +1 -0
- package/dist/index.cjs +1035 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +248 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.js +885 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/index.cjs +37 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +279 -0
- package/dist/persistence/index.d.ts +279 -0
- package/dist/persistence/index.js +4 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/sanitize-7IZ-SW1f.d.ts +361 -0
- package/dist/sanitize-CvmgqbsA.d.cts +361 -0
- package/dist/server/index.cjs +400 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +229 -0
- package/dist/server/index.d.ts +229 -0
- package/dist/server/index.js +390 -0
- package/dist/server/index.js.map +1 -0
- package/dist/styles.css +680 -0
- package/dist/types-B4z0Quvv.d.cts +193 -0
- package/dist/types-B4z0Quvv.d.ts +193 -0
- package/package.json +183 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { cssInteger, normalizeCssColor, cssAlign, cssNumber, cssFontFamily } from './chunk-T6FR37IC.js';
|
|
2
|
+
import { sanitizeUrl, sanitizeImageSrc } from './chunk-6NTSXJX4.js';
|
|
3
|
+
import { resolvePageDimensions } from './chunk-JQXTWLHL.js';
|
|
4
|
+
|
|
5
|
+
// src/export/html.ts
|
|
6
|
+
function escapeHtml(value) {
|
|
7
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8
|
+
}
|
|
9
|
+
function escapeAttr(value) {
|
|
10
|
+
return escapeHtml(value).replace(/"/g, """);
|
|
11
|
+
}
|
|
12
|
+
var MARK_ORDER = [
|
|
13
|
+
"link",
|
|
14
|
+
"textColor",
|
|
15
|
+
"highlight",
|
|
16
|
+
"fontFamily",
|
|
17
|
+
"fontSize",
|
|
18
|
+
"strong",
|
|
19
|
+
"em",
|
|
20
|
+
"underline",
|
|
21
|
+
"strikethrough",
|
|
22
|
+
"superscript",
|
|
23
|
+
"subscript",
|
|
24
|
+
"code"
|
|
25
|
+
];
|
|
26
|
+
function openMark(mark) {
|
|
27
|
+
const attrs = mark.attrs ?? {};
|
|
28
|
+
switch (mark.type) {
|
|
29
|
+
case "strong":
|
|
30
|
+
return "<strong>";
|
|
31
|
+
case "em":
|
|
32
|
+
return "<em>";
|
|
33
|
+
case "underline":
|
|
34
|
+
return "<u>";
|
|
35
|
+
case "strikethrough":
|
|
36
|
+
return "<s>";
|
|
37
|
+
case "superscript":
|
|
38
|
+
return "<sup>";
|
|
39
|
+
case "subscript":
|
|
40
|
+
return "<sub>";
|
|
41
|
+
case "code":
|
|
42
|
+
return "<code>";
|
|
43
|
+
case "link": {
|
|
44
|
+
const href = sanitizeUrl(String(attrs.href ?? "")) ?? "";
|
|
45
|
+
const title = attrs.title ? ` title="${escapeAttr(String(attrs.title))}"` : "";
|
|
46
|
+
return `<a href="${escapeAttr(href)}"${title} rel="noopener noreferrer nofollow">`;
|
|
47
|
+
}
|
|
48
|
+
case "fontFamily": {
|
|
49
|
+
const family = cssFontFamily(attrs.family);
|
|
50
|
+
return family ? `<span style="font-family: ${family}">` : "<span>";
|
|
51
|
+
}
|
|
52
|
+
case "fontSize": {
|
|
53
|
+
const size = cssNumber(attrs.size, 1, 1638);
|
|
54
|
+
return size ? `<span style="font-size: ${size}pt">` : "<span>";
|
|
55
|
+
}
|
|
56
|
+
case "textColor": {
|
|
57
|
+
const color = normalizeCssColor(attrs.color);
|
|
58
|
+
return color ? `<span style="color: ${color}">` : "<span>";
|
|
59
|
+
}
|
|
60
|
+
case "highlight": {
|
|
61
|
+
const color = normalizeCssColor(attrs.color) ?? "#fff2a8";
|
|
62
|
+
return `<mark style="background-color: ${color}">`;
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function closeMark(mark) {
|
|
69
|
+
switch (mark.type) {
|
|
70
|
+
case "strong":
|
|
71
|
+
return "</strong>";
|
|
72
|
+
case "em":
|
|
73
|
+
return "</em>";
|
|
74
|
+
case "underline":
|
|
75
|
+
return "</u>";
|
|
76
|
+
case "strikethrough":
|
|
77
|
+
return "</s>";
|
|
78
|
+
case "superscript":
|
|
79
|
+
return "</sup>";
|
|
80
|
+
case "subscript":
|
|
81
|
+
return "</sub>";
|
|
82
|
+
case "code":
|
|
83
|
+
return "</code>";
|
|
84
|
+
case "link":
|
|
85
|
+
return "</a>";
|
|
86
|
+
case "fontFamily":
|
|
87
|
+
case "fontSize":
|
|
88
|
+
case "textColor":
|
|
89
|
+
return "</span>";
|
|
90
|
+
case "highlight":
|
|
91
|
+
return "</mark>";
|
|
92
|
+
default:
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function sortMarks(marks) {
|
|
97
|
+
return [...marks].sort((a, b) => {
|
|
98
|
+
const ia = MARK_ORDER.indexOf(a.type);
|
|
99
|
+
const ib = MARK_ORDER.indexOf(b.type);
|
|
100
|
+
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function serializeInline(content) {
|
|
104
|
+
if (!content) return "";
|
|
105
|
+
let out = "";
|
|
106
|
+
for (const node of content) {
|
|
107
|
+
if (node.type === "text") {
|
|
108
|
+
const marks = sortMarks(node.marks ?? []);
|
|
109
|
+
let text = escapeHtml(node.text ?? "");
|
|
110
|
+
for (let i = marks.length - 1; i >= 0; i--) text = openMark(marks[i]) + text;
|
|
111
|
+
for (const mark of marks) text += closeMark(mark);
|
|
112
|
+
out += text;
|
|
113
|
+
} else if (node.type === "hard_break") {
|
|
114
|
+
out += "<br>";
|
|
115
|
+
} else if (node.type === "image") {
|
|
116
|
+
out += serializeImage(node);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function serializeImage(node) {
|
|
122
|
+
const src = sanitizeImageSrc(String(node.attrs?.src ?? ""));
|
|
123
|
+
if (!src) return "";
|
|
124
|
+
const alt = node.attrs?.alt ? ` alt="${escapeAttr(String(node.attrs.alt))}"` : ' alt=""';
|
|
125
|
+
const title = node.attrs?.title ? ` title="${escapeAttr(String(node.attrs.title))}"` : "";
|
|
126
|
+
const safeWidth = cssInteger(node.attrs?.width, 1, 4e3);
|
|
127
|
+
const width = safeWidth ? ` style="width: ${safeWidth}px"` : "";
|
|
128
|
+
return `<img class="rne-image" src="${escapeAttr(src)}"${alt}${title}${width}>`;
|
|
129
|
+
}
|
|
130
|
+
function blockStyle(attrs) {
|
|
131
|
+
if (!attrs) return "";
|
|
132
|
+
const styles = [];
|
|
133
|
+
const align = cssAlign(attrs.align);
|
|
134
|
+
if (align) styles.push(`text-align: ${align}`);
|
|
135
|
+
const indent = cssInteger(attrs.indent, 0, 12);
|
|
136
|
+
if (indent && indent > 0) styles.push(`margin-left: ${indent * 3}em`);
|
|
137
|
+
const lineHeight = cssNumber(attrs.lineHeight, 0.1, 10);
|
|
138
|
+
if (lineHeight) styles.push(`line-height: ${lineHeight}`);
|
|
139
|
+
return styles.length ? ` style="${styles.join("; ")}"` : "";
|
|
140
|
+
}
|
|
141
|
+
function serializeBlock(node) {
|
|
142
|
+
switch (node.type) {
|
|
143
|
+
case "paragraph":
|
|
144
|
+
return `<p${blockStyle(node.attrs)}>${serializeInline(node.content) || "<br>"}</p>`;
|
|
145
|
+
case "heading": {
|
|
146
|
+
const level = Math.min(6, Math.max(1, Number(node.attrs?.level ?? 1)));
|
|
147
|
+
return `<h${level}${blockStyle(node.attrs)}>${serializeInline(node.content)}</h${level}>`;
|
|
148
|
+
}
|
|
149
|
+
case "blockquote":
|
|
150
|
+
return `<blockquote class="rne-blockquote">${(node.content ?? []).map(serializeBlock).join("")}</blockquote>`;
|
|
151
|
+
case "horizontal_rule":
|
|
152
|
+
return '<hr class="rne-hr">';
|
|
153
|
+
case "page_break":
|
|
154
|
+
return '<div class="rne-page-break"></div>';
|
|
155
|
+
case "bullet_list":
|
|
156
|
+
case "ordered_list":
|
|
157
|
+
return serializeList(node);
|
|
158
|
+
case "table":
|
|
159
|
+
return serializeTable(node);
|
|
160
|
+
default:
|
|
161
|
+
if (node.content) return node.content.map(serializeBlock).join("");
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function serializeList(node) {
|
|
166
|
+
const ordered = node.type === "ordered_list";
|
|
167
|
+
const kind = node.attrs?.kind;
|
|
168
|
+
const tag = ordered ? "ol" : "ul";
|
|
169
|
+
const order = ordered ? Number(node.attrs?.order ?? 1) : 1;
|
|
170
|
+
const startAttr = ordered && order !== 1 ? ` start="${order}"` : "";
|
|
171
|
+
const classAttr = ordered ? ' class="rne-ordered-list"' : kind === "task" ? ' class="rne-task-list" data-type="task"' : ' class="rne-bullet-list"';
|
|
172
|
+
const items = (node.content ?? []).map((item) => {
|
|
173
|
+
if (item.type !== "list_item") return "";
|
|
174
|
+
const checked = item.attrs?.checked;
|
|
175
|
+
const inner = (item.content ?? []).map(serializeBlock).join("");
|
|
176
|
+
if (checked === true || checked === false) {
|
|
177
|
+
const box = checked ? "\u2611" : "\u2610";
|
|
178
|
+
return `<li class="rne-task-item" data-checked="${checked}"><span class="rne-task-checkbox">${box}</span><div class="rne-task-content">${inner}</div></li>`;
|
|
179
|
+
}
|
|
180
|
+
return `<li>${inner}</li>`;
|
|
181
|
+
}).join("");
|
|
182
|
+
return `<${tag}${classAttr}${startAttr}>${items}</${tag}>`;
|
|
183
|
+
}
|
|
184
|
+
function serializeTable(node) {
|
|
185
|
+
const rows = (node.content ?? []).map((row) => {
|
|
186
|
+
if (row.type !== "table_row") return "";
|
|
187
|
+
const cells = (row.content ?? []).map((cell) => {
|
|
188
|
+
const tag = cell.type === "table_header" ? "th" : "td";
|
|
189
|
+
const attrs = cell.attrs ?? {};
|
|
190
|
+
const parts = [];
|
|
191
|
+
const colspan = cssInteger(attrs.colspan, 1, 100);
|
|
192
|
+
if (colspan && colspan > 1) parts.push(`colspan="${colspan}"`);
|
|
193
|
+
const rowspan = cssInteger(attrs.rowspan, 1, 100);
|
|
194
|
+
if (rowspan && rowspan > 1) parts.push(`rowspan="${rowspan}"`);
|
|
195
|
+
const styles = [];
|
|
196
|
+
const bg = normalizeCssColor(attrs.background);
|
|
197
|
+
if (bg) styles.push(`background-color: ${bg}`);
|
|
198
|
+
const cellAlign = cssAlign(attrs.align);
|
|
199
|
+
if (cellAlign) styles.push(`text-align: ${cellAlign}`);
|
|
200
|
+
if (styles.length) parts.push(`style="${styles.join("; ")}"`);
|
|
201
|
+
const attrStr = parts.length ? ` ${parts.join(" ")}` : "";
|
|
202
|
+
return `<${tag}${attrStr}>${(cell.content ?? []).map(serializeBlock).join("")}</${tag}>`;
|
|
203
|
+
}).join("");
|
|
204
|
+
return `<tr>${cells}</tr>`;
|
|
205
|
+
}).join("");
|
|
206
|
+
return `<table class="rne-table"><tbody>${rows}</tbody></table>`;
|
|
207
|
+
}
|
|
208
|
+
function documentToHtml(doc) {
|
|
209
|
+
return (doc.content ?? []).map(serializeBlock).join("\n");
|
|
210
|
+
}
|
|
211
|
+
function printStylesheet(page) {
|
|
212
|
+
const { width, height } = resolvePageDimensions(page);
|
|
213
|
+
const { top, right, bottom, left } = page.margins;
|
|
214
|
+
return `
|
|
215
|
+
@page { size: ${width}mm ${height}mm; margin: ${top}mm ${right}mm ${bottom}mm ${left}mm; }
|
|
216
|
+
* { box-sizing: border-box; }
|
|
217
|
+
html, body { margin: 0; padding: 0; }
|
|
218
|
+
body {
|
|
219
|
+
font-family: 'Times New Roman', Georgia, serif;
|
|
220
|
+
font-size: 12pt;
|
|
221
|
+
line-height: 1.5;
|
|
222
|
+
color: #111;
|
|
223
|
+
}
|
|
224
|
+
.rne-print-page { width: ${width - left - right}mm; margin: 0 auto; }
|
|
225
|
+
h1 { font-size: 2em; } h2 { font-size: 1.5em; } h3 { font-size: 1.25em; }
|
|
226
|
+
h4 { font-size: 1.1em; } h5 { font-size: 1em; } h6 { font-size: 0.9em; }
|
|
227
|
+
p { margin: 0 0 0.6em; }
|
|
228
|
+
blockquote.rne-blockquote { border-left: 3px solid #ccc; margin: 0 0 0.6em; padding-left: 1em; color: #444; }
|
|
229
|
+
hr.rne-hr { border: none; border-top: 1px solid #ccc; margin: 1em 0; }
|
|
230
|
+
img.rne-image { max-width: 100%; height: auto; }
|
|
231
|
+
table.rne-table { border-collapse: collapse; width: 100%; margin: 0 0 0.8em; }
|
|
232
|
+
table.rne-table td, table.rne-table th { border: 1px solid #999; padding: 4px 8px; vertical-align: top; }
|
|
233
|
+
table.rne-table th { background: #f0f0f0; font-weight: bold; }
|
|
234
|
+
ul, ol { margin: 0 0 0.6em; padding-left: 1.6em; }
|
|
235
|
+
ul.rne-task-list { list-style: none; padding-left: 0.4em; }
|
|
236
|
+
.rne-task-item { display: flex; gap: 0.4em; align-items: flex-start; }
|
|
237
|
+
.rne-page-break { break-after: page; page-break-after: always; height: 0; }
|
|
238
|
+
code { font-family: 'Courier New', monospace; background: #f3f3f3; padding: 0 2px; }
|
|
239
|
+
`.trim();
|
|
240
|
+
}
|
|
241
|
+
function buildPrintDocument(doc, page, title = "Document") {
|
|
242
|
+
const body = documentToHtml(doc);
|
|
243
|
+
return `<!DOCTYPE html>
|
|
244
|
+
<html lang="en">
|
|
245
|
+
<head>
|
|
246
|
+
<meta charset="utf-8">
|
|
247
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
248
|
+
<title>${escapeHtml(title)}</title>
|
|
249
|
+
<style>${printStylesheet(page)}</style>
|
|
250
|
+
</head>
|
|
251
|
+
<body>
|
|
252
|
+
<div class="rne-print-page">
|
|
253
|
+
${body}
|
|
254
|
+
</div>
|
|
255
|
+
</body>
|
|
256
|
+
</html>`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export { buildPrintDocument, documentToHtml, printStylesheet };
|
|
260
|
+
//# sourceMappingURL=chunk-7VYJDBH7.js.map
|
|
261
|
+
//# sourceMappingURL=chunk-7VYJDBH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/export/html.ts"],"names":[],"mappings":"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","file":"chunk-7VYJDBH7.js","sourcesContent":["import type { DocumentJSON, PageConfig } from '../config/types';\nimport { sanitizeImageSrc, sanitizeUrl } from '../security/sanitize';\nimport { cssAlign, cssFontFamily, cssInteger, cssNumber, normalizeCssColor } from '../security/css';\nimport { resolvePageDimensions } from '../config/defaults';\n\n/** Escape text for safe inclusion in HTML element content. */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n\n/** Escape a value for inclusion in a double-quoted HTML attribute. */\nfunction escapeAttr(value: string): string {\n return escapeHtml(value).replace(/\"/g, '"');\n}\n\n/** Order in which inline marks are nested (outermost first). */\nconst MARK_ORDER = [\n 'link',\n 'textColor',\n 'highlight',\n 'fontFamily',\n 'fontSize',\n 'strong',\n 'em',\n 'underline',\n 'strikethrough',\n 'superscript',\n 'subscript',\n 'code',\n];\n\ninterface MarkJSON {\n type: string;\n attrs?: Record<string, unknown>;\n}\n\nfunction openMark(mark: MarkJSON): string {\n const attrs = mark.attrs ?? {};\n switch (mark.type) {\n case 'strong':\n return '<strong>';\n case 'em':\n return '<em>';\n case 'underline':\n return '<u>';\n case 'strikethrough':\n return '<s>';\n case 'superscript':\n return '<sup>';\n case 'subscript':\n return '<sub>';\n case 'code':\n return '<code>';\n case 'link': {\n const href = sanitizeUrl(String(attrs.href ?? '')) ?? '';\n const title = attrs.title ? ` title=\"${escapeAttr(String(attrs.title))}\"` : '';\n return `<a href=\"${escapeAttr(href)}\"${title} rel=\"noopener noreferrer nofollow\">`;\n }\n case 'fontFamily': {\n const family = cssFontFamily(attrs.family);\n return family ? `<span style=\"font-family: ${family}\">` : '<span>';\n }\n case 'fontSize': {\n const size = cssNumber(attrs.size, 1, 1638);\n return size ? `<span style=\"font-size: ${size}pt\">` : '<span>';\n }\n case 'textColor': {\n const color = normalizeCssColor(attrs.color);\n return color ? `<span style=\"color: ${color}\">` : '<span>';\n }\n case 'highlight': {\n const color = normalizeCssColor(attrs.color) ?? '#fff2a8';\n return `<mark style=\"background-color: ${color}\">`;\n }\n default:\n return '';\n }\n}\n\nfunction closeMark(mark: MarkJSON): string {\n switch (mark.type) {\n case 'strong':\n return '</strong>';\n case 'em':\n return '</em>';\n case 'underline':\n return '</u>';\n case 'strikethrough':\n return '</s>';\n case 'superscript':\n return '</sup>';\n case 'subscript':\n return '</sub>';\n case 'code':\n return '</code>';\n case 'link':\n return '</a>';\n case 'fontFamily':\n case 'fontSize':\n case 'textColor':\n return '</span>';\n case 'highlight':\n return '</mark>';\n default:\n return '';\n }\n}\n\nfunction sortMarks(marks: MarkJSON[]): MarkJSON[] {\n return [...marks].sort((a, b) => {\n const ia = MARK_ORDER.indexOf(a.type);\n const ib = MARK_ORDER.indexOf(b.type);\n return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);\n });\n}\n\nfunction serializeInline(content: DocumentJSON[] | undefined): string {\n if (!content) return '';\n let out = '';\n for (const node of content) {\n if (node.type === 'text') {\n const marks = sortMarks((node.marks ?? []) as MarkJSON[]);\n let text = escapeHtml(node.text ?? '');\n for (let i = marks.length - 1; i >= 0; i--) text = openMark(marks[i]!) + text;\n for (const mark of marks) text += closeMark(mark);\n out += text;\n } else if (node.type === 'hard_break') {\n out += '<br>';\n } else if (node.type === 'image') {\n out += serializeImage(node);\n }\n }\n return out;\n}\n\nfunction serializeImage(node: DocumentJSON): string {\n const src = sanitizeImageSrc(String(node.attrs?.src ?? ''));\n if (!src) return '';\n const alt = node.attrs?.alt ? ` alt=\"${escapeAttr(String(node.attrs.alt))}\"` : ' alt=\"\"';\n const title = node.attrs?.title ? ` title=\"${escapeAttr(String(node.attrs.title))}\"` : '';\n const safeWidth = cssInteger(node.attrs?.width, 1, 4000);\n const width = safeWidth ? ` style=\"width: ${safeWidth}px\"` : '';\n return `<img class=\"rne-image\" src=\"${escapeAttr(src)}\"${alt}${title}${width}>`;\n}\n\nfunction blockStyle(attrs: Record<string, unknown> | undefined): string {\n if (!attrs) return '';\n const styles: string[] = [];\n const align = cssAlign(attrs.align);\n if (align) styles.push(`text-align: ${align}`);\n const indent = cssInteger(attrs.indent, 0, 12);\n if (indent && indent > 0) styles.push(`margin-left: ${indent * 3}em`);\n const lineHeight = cssNumber(attrs.lineHeight, 0.1, 10);\n if (lineHeight) styles.push(`line-height: ${lineHeight}`);\n return styles.length ? ` style=\"${styles.join('; ')}\"` : '';\n}\n\nfunction serializeBlock(node: DocumentJSON): string {\n switch (node.type) {\n case 'paragraph':\n return `<p${blockStyle(node.attrs)}>${serializeInline(node.content) || '<br>'}</p>`;\n case 'heading': {\n const level = Math.min(6, Math.max(1, Number(node.attrs?.level ?? 1)));\n return `<h${level}${blockStyle(node.attrs)}>${serializeInline(node.content)}</h${level}>`;\n }\n case 'blockquote':\n return `<blockquote class=\"rne-blockquote\">${(node.content ?? []).map(serializeBlock).join('')}</blockquote>`;\n case 'horizontal_rule':\n return '<hr class=\"rne-hr\">';\n case 'page_break':\n return '<div class=\"rne-page-break\"></div>';\n case 'bullet_list':\n case 'ordered_list':\n return serializeList(node);\n case 'table':\n return serializeTable(node);\n default:\n if (node.content) return node.content.map(serializeBlock).join('');\n return '';\n }\n}\n\nfunction serializeList(node: DocumentJSON): string {\n const ordered = node.type === 'ordered_list';\n const kind = node.attrs?.kind;\n const tag = ordered ? 'ol' : 'ul';\n const order = ordered ? Number(node.attrs?.order ?? 1) : 1;\n const startAttr = ordered && order !== 1 ? ` start=\"${order}\"` : '';\n const classAttr = ordered\n ? ' class=\"rne-ordered-list\"'\n : kind === 'task'\n ? ' class=\"rne-task-list\" data-type=\"task\"'\n : ' class=\"rne-bullet-list\"';\n\n const items = (node.content ?? [])\n .map((item) => {\n if (item.type !== 'list_item') return '';\n const checked = item.attrs?.checked;\n const inner = (item.content ?? []).map(serializeBlock).join('');\n if (checked === true || checked === false) {\n const box = checked ? '☑' : '☐';\n return `<li class=\"rne-task-item\" data-checked=\"${checked}\"><span class=\"rne-task-checkbox\">${box}</span><div class=\"rne-task-content\">${inner}</div></li>`;\n }\n return `<li>${inner}</li>`;\n })\n .join('');\n return `<${tag}${classAttr}${startAttr}>${items}</${tag}>`;\n}\n\nfunction serializeTable(node: DocumentJSON): string {\n const rows = (node.content ?? [])\n .map((row) => {\n if (row.type !== 'table_row') return '';\n const cells = (row.content ?? [])\n .map((cell) => {\n const tag = cell.type === 'table_header' ? 'th' : 'td';\n const attrs = cell.attrs ?? {};\n const parts: string[] = [];\n const colspan = cssInteger(attrs.colspan, 1, 100);\n if (colspan && colspan > 1) parts.push(`colspan=\"${colspan}\"`);\n const rowspan = cssInteger(attrs.rowspan, 1, 100);\n if (rowspan && rowspan > 1) parts.push(`rowspan=\"${rowspan}\"`);\n const styles: string[] = [];\n const bg = normalizeCssColor(attrs.background);\n if (bg) styles.push(`background-color: ${bg}`);\n const cellAlign = cssAlign(attrs.align);\n if (cellAlign) styles.push(`text-align: ${cellAlign}`);\n if (styles.length) parts.push(`style=\"${styles.join('; ')}\"`);\n const attrStr = parts.length ? ` ${parts.join(' ')}` : '';\n return `<${tag}${attrStr}>${(cell.content ?? []).map(serializeBlock).join('')}</${tag}>`;\n })\n .join('');\n return `<tr>${cells}</tr>`;\n })\n .join('');\n return `<table class=\"rne-table\"><tbody>${rows}</tbody></table>`;\n}\n\n/** Serialize a document (JSON) to an HTML fragment (the document body content). */\nexport function documentToHtml(doc: DocumentJSON): string {\n return (doc.content ?? []).map(serializeBlock).join('\\n');\n}\n\n/** Print-oriented stylesheet matching the on-screen document surface. */\nexport function printStylesheet(page: PageConfig): string {\n const { width, height } = resolvePageDimensions(page);\n const { top, right, bottom, left } = page.margins;\n return `\n @page { size: ${width}mm ${height}mm; margin: ${top}mm ${right}mm ${bottom}mm ${left}mm; }\n * { box-sizing: border-box; }\n html, body { margin: 0; padding: 0; }\n body {\n font-family: 'Times New Roman', Georgia, serif;\n font-size: 12pt;\n line-height: 1.5;\n color: #111;\n }\n .rne-print-page { width: ${width - left - right}mm; margin: 0 auto; }\n h1 { font-size: 2em; } h2 { font-size: 1.5em; } h3 { font-size: 1.25em; }\n h4 { font-size: 1.1em; } h5 { font-size: 1em; } h6 { font-size: 0.9em; }\n p { margin: 0 0 0.6em; }\n blockquote.rne-blockquote { border-left: 3px solid #ccc; margin: 0 0 0.6em; padding-left: 1em; color: #444; }\n hr.rne-hr { border: none; border-top: 1px solid #ccc; margin: 1em 0; }\n img.rne-image { max-width: 100%; height: auto; }\n table.rne-table { border-collapse: collapse; width: 100%; margin: 0 0 0.8em; }\n table.rne-table td, table.rne-table th { border: 1px solid #999; padding: 4px 8px; vertical-align: top; }\n table.rne-table th { background: #f0f0f0; font-weight: bold; }\n ul, ol { margin: 0 0 0.6em; padding-left: 1.6em; }\n ul.rne-task-list { list-style: none; padding-left: 0.4em; }\n .rne-task-item { display: flex; gap: 0.4em; align-items: flex-start; }\n .rne-page-break { break-after: page; page-break-after: always; height: 0; }\n code { font-family: 'Courier New', monospace; background: #f3f3f3; padding: 0 2px; }\n `.trim();\n}\n\n/**\n * Build a complete, standalone HTML document for PDF rendering — used by both\n * the client print path and the server headless-browser renderer so output is\n * consistent (F-6.4, F-6.11).\n */\nexport function buildPrintDocument(\n doc: DocumentJSON,\n page: PageConfig,\n title = 'Document',\n): string {\n const body = documentToHtml(doc);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>${printStylesheet(page)}</style>\n</head>\n<body>\n<div class=\"rne-print-page\">\n${body}\n</div>\n</body>\n</html>`;\n}\n"]}
|