@meistrari/minuta-editor 1.0.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.
@@ -0,0 +1,711 @@
1
+ import { PluginKey, Plugin } from '@tiptap/pm/state';
2
+ import { Decoration, DecorationSet } from '@tiptap/pm/view';
3
+
4
+ const BLOCKED_TAGS = /* @__PURE__ */ new Set([
5
+ "SCRIPT",
6
+ "STYLE",
7
+ "IFRAME",
8
+ "OBJECT",
9
+ "EMBED",
10
+ "LINK",
11
+ "META",
12
+ "BASE",
13
+ "FORM",
14
+ "INPUT",
15
+ "BUTTON",
16
+ "TEXTAREA",
17
+ "SELECT",
18
+ "TEMPLATE",
19
+ "DIALOG",
20
+ // SMIL: anima/troca atributo de outro elemento DEPOIS da sanitização —
21
+ // <a><animate attributeName="href" to="javascript:…"> devolve o href
22
+ // proibido no clique, com a URL nunca passando pelo allowlist abaixo
23
+ "ANIMATE",
24
+ "ANIMATEMOTION",
25
+ "ANIMATETRANSFORM",
26
+ "SET",
27
+ "DISCARD",
28
+ // hospedam markup de outro namespace (HTML dentro de SVG, MathML) — o
29
+ // parser reinterpreta na 2ª passada e é o vetor clássico de mXSS
30
+ "FOREIGNOBJECT",
31
+ "ANNOTATION-XML",
32
+ "NOSCRIPT",
33
+ "NOEMBED",
34
+ "NOFRAMES",
35
+ "XMP",
36
+ "PLAINTEXT",
37
+ // <use> puxa e clona um nó por referência (inclusive de fora do documento)
38
+ "USE"
39
+ ]);
40
+ const BLOCKED_ATTRS = /* @__PURE__ */ new Set(["attributename", "attributetype", "to", "values", "from", "by"]);
41
+ const SAFE_URL = /^(?:https?:|data:image\/|blob:|vault:)/i;
42
+ function scrubElement(el) {
43
+ for (const child of [...el.children]) {
44
+ if (BLOCKED_TAGS.has(child.tagName.toUpperCase())) {
45
+ child.remove();
46
+ continue;
47
+ }
48
+ for (const attr of [...child.attributes]) {
49
+ const name = attr.name.toLowerCase();
50
+ if (name.startsWith("on")) {
51
+ child.removeAttribute(attr.name);
52
+ continue;
53
+ }
54
+ if (BLOCKED_ATTRS.has(name)) {
55
+ child.removeAttribute(attr.name);
56
+ continue;
57
+ }
58
+ if ((name === "src" || name === "href" || name === "xlink:href" || name === "formaction" || name === "srcdoc") && (name === "srcdoc" || !SAFE_URL.test(attr.value.trim()))) {
59
+ child.removeAttribute(attr.name);
60
+ continue;
61
+ }
62
+ if (name === "style" && /url\s*\(/i.test(attr.value) && !/url\s*\(\s*['"]?(?:https?:|data:image\/|blob:)/i.test(attr.value)) {
63
+ child.removeAttribute(attr.name);
64
+ }
65
+ }
66
+ scrubElement(child);
67
+ }
68
+ }
69
+ function sanitizeRichHtml(html) {
70
+ if (!html)
71
+ return "";
72
+ const tpl = document.createElement("template");
73
+ tpl.innerHTML = html;
74
+ scrubElement(tpl.content);
75
+ return tpl.innerHTML;
76
+ }
77
+
78
+ const INLINE_TAGS = /* @__PURE__ */ new Set(["B", "STRONG", "I", "EM", "U", "SUP", "SUB", "BR"]);
79
+ function safeHref(value) {
80
+ const href = (value ?? "").trim();
81
+ return /^https?:\/\//i.test(href) ? href : null;
82
+ }
83
+ function rgbToHex(value) {
84
+ const match = value.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
85
+ if (!match)
86
+ return null;
87
+ return `#${match.slice(1, 4).map((n) => Number(n).toString(16).padStart(2, "0")).join("")}`;
88
+ }
89
+ function safeSpanStyle(style) {
90
+ if (!style)
91
+ return null;
92
+ const out = [];
93
+ const family = style.match(/font-family\s*:\s*([^;]+)/i)?.[1]?.trim();
94
+ if (family && /^[\w\s,'"-]+$/.test(family))
95
+ out.push(`font-family: ${family.replace(/"/g, "'")}`);
96
+ const size = style.match(/font-size\s*:\s*([\d.]+)\s*pt/i)?.[1];
97
+ if (size)
98
+ out.push(`font-size: ${size}pt`);
99
+ const rawColor = style.match(/(?:^|;)\s*color\s*:\s*([^;]+)/i)?.[1]?.trim();
100
+ if (rawColor) {
101
+ const hex = rawColor.startsWith("#") ? /^#[0-9a-f]{6}$/i.test(rawColor) ? rawColor : null : rgbToHex(rawColor);
102
+ if (hex)
103
+ out.push(`color: ${hex}`);
104
+ }
105
+ return out.length ? out.join("; ") : null;
106
+ }
107
+ function sanitizeFootnoteHtml(html) {
108
+ if (!html)
109
+ return "";
110
+ const doc = new DOMParser().parseFromString(`<div>${html}</div>`, "text/html");
111
+ const root = doc.body.firstElementChild;
112
+ if (!root)
113
+ return "";
114
+ const walk = (node) => {
115
+ if (node.nodeType === Node.TEXT_NODE)
116
+ return (node.textContent ?? "").replace(/[<>&]/g, (c) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;" })[c]);
117
+ if (!(node instanceof Element))
118
+ return "";
119
+ if (node.tagName === "SCRIPT" || node.tagName === "STYLE")
120
+ return "";
121
+ const inner = Array.from(node.childNodes).map(walk).join("");
122
+ if (node.tagName === "BR")
123
+ return "<br>";
124
+ if (node.tagName === "A") {
125
+ const href = safeHref(node.getAttribute("href"));
126
+ return href ? `<a href="${href.replace(/"/g, "&quot;")}">${inner}</a>` : inner;
127
+ }
128
+ if (node.tagName === "SPAN" || node.tagName === "FONT") {
129
+ const style = safeSpanStyle(node.getAttribute("style"));
130
+ return style ? `<span style="${style}">${inner}</span>` : inner;
131
+ }
132
+ if (node.tagName === "DIV" || node.tagName === "P")
133
+ return inner ? `${inner}<br>` : "";
134
+ if (INLINE_TAGS.has(node.tagName))
135
+ return `<${node.tagName.toLowerCase()}>${inner}</${node.tagName.toLowerCase()}>`;
136
+ return inner;
137
+ };
138
+ return Array.from(root.childNodes).map(walk).join("").replace(/\s+/g, " ").replace(/(<br>\s*)+$/i, "").trim();
139
+ }
140
+ function footnoteHtmlToRuns(html) {
141
+ const runs = [];
142
+ if (!html)
143
+ return runs;
144
+ const doc = new DOMParser().parseFromString(`<div>${html}</div>`, "text/html");
145
+ const root = doc.body.firstElementChild;
146
+ if (!root)
147
+ return runs;
148
+ const walk = (node, style) => {
149
+ if (node.nodeType === Node.TEXT_NODE) {
150
+ const text = node.textContent ?? "";
151
+ if (text)
152
+ runs.push({ ...style, text });
153
+ return;
154
+ }
155
+ if (!(node instanceof Element))
156
+ return;
157
+ if (node.tagName === "BR") {
158
+ runs.push({ text: "", break: true });
159
+ return;
160
+ }
161
+ const next = { ...style };
162
+ if (node.tagName === "B" || node.tagName === "STRONG")
163
+ next.bold = true;
164
+ if (node.tagName === "I" || node.tagName === "EM")
165
+ next.italics = true;
166
+ if (node.tagName === "U")
167
+ next.underline = true;
168
+ if (node.tagName === "A") {
169
+ const href = safeHref(node.getAttribute("href"));
170
+ if (href)
171
+ next.href = href;
172
+ }
173
+ if (node.tagName === "SPAN") {
174
+ const spanStyle = node.getAttribute("style") ?? "";
175
+ const family = spanStyle.match(/font-family\s*:\s*([^;]+)/i)?.[1]?.split(",")[0]?.trim().replace(/['"]/g, "");
176
+ if (family)
177
+ next.font = family;
178
+ const size = spanStyle.match(/font-size\s*:\s*([\d.]+)\s*pt/i)?.[1];
179
+ if (size)
180
+ next.sizePt = Number.parseFloat(size);
181
+ const color = spanStyle.match(/color\s*:\s*#([0-9a-f]{6})/i)?.[1];
182
+ if (color)
183
+ next.color = color.toUpperCase();
184
+ }
185
+ node.childNodes.forEach((child) => walk(child, next));
186
+ };
187
+ root.childNodes.forEach((child) => walk(child, {}));
188
+ return runs;
189
+ }
190
+ function footnoteSizePt(style) {
191
+ const match = style?.match(/font-size\s*:\s*([\d.]+)\s*pt/i);
192
+ const value = match ? Number.parseFloat(match[1]) : Number.NaN;
193
+ return Number.isFinite(value) ? value : null;
194
+ }
195
+
196
+ function shouldMoveBreakToHeading(params) {
197
+ const { heading, item, pageStart, capacity } = params;
198
+ if (!heading || !heading.heading || heading.marker)
199
+ return false;
200
+ const headingStart = heading.top - heading.marginTop;
201
+ if (headingStart <= pageStart)
202
+ return false;
203
+ return item.top + item.height - headingStart <= capacity;
204
+ }
205
+
206
+ const pocPageKey = new PluginKey("pocPagination");
207
+ const PAGINATE_META = "poc-paginate";
208
+ function pocChromeFlowHeight(cfg) {
209
+ return cfg.margins.bottom + cfg.gap + cfg.margins.top;
210
+ }
211
+ function editHint(part) {
212
+ return (event) => {
213
+ event.preventDefault();
214
+ event.stopPropagation();
215
+ window.dispatchEvent(new CustomEvent("poc:edit-chrome", { detail: { part } }));
216
+ };
217
+ }
218
+ function isPocChromeCanvas(html) {
219
+ return !!html && html.includes("data-el=");
220
+ }
221
+ function chromeContentEl(html, cfg) {
222
+ const clean = sanitizeRichHtml(html);
223
+ if (isPocChromeCanvas(clean)) {
224
+ const canvas = document.createElement("div");
225
+ canvas.className = "poc-chrome-canvas";
226
+ canvas.style.cssText = `position:absolute;top:0;left:${-cfg.margins.left}px;width:794px;height:100%;pointer-events:none;`;
227
+ canvas.innerHTML = clean;
228
+ return canvas;
229
+ }
230
+ const text = document.createElement("div");
231
+ text.className = "poc-chrome-text";
232
+ text.innerHTML = clean;
233
+ return text;
234
+ }
235
+ function buildPocHeaderEl(cfg) {
236
+ const header = document.createElement("div");
237
+ header.className = "poc-page-headerarea";
238
+ header.style.height = `${cfg.margins.top}px`;
239
+ header.style.position = "relative";
240
+ header.style.justifyContent = cfg.headerPosition === "center" ? "center" : cfg.headerPosition === "right" ? "flex-end" : "flex-start";
241
+ header.title = "Clique para editar o cabe\xE7alho";
242
+ header.addEventListener("click", editHint("header"));
243
+ if (cfg.headerScope === "first")
244
+ return header;
245
+ if (cfg.headerLogoUrl) {
246
+ const img = document.createElement("img");
247
+ img.src = cfg.headerLogoUrl;
248
+ img.style.maxHeight = `${Math.max(24, cfg.margins.top - 36)}px`;
249
+ img.style.maxWidth = "50%";
250
+ img.style.display = "inline-block";
251
+ header.appendChild(img);
252
+ }
253
+ const content = chromeContentEl(cfg.headerText, cfg);
254
+ if (content.className === "poc-chrome-text" && !content.innerHTML && !cfg.headerLogoUrl)
255
+ content.innerHTML = "&nbsp;";
256
+ header.appendChild(content);
257
+ return header;
258
+ }
259
+ function buildPocFooterEl(cfg, pageNo, total) {
260
+ const footer = document.createElement("div");
261
+ footer.className = "poc-page-footerarea";
262
+ footer.style.height = `${cfg.margins.bottom}px`;
263
+ footer.style.position = "relative";
264
+ footer.title = "Clique para editar o rodap\xE9";
265
+ footer.addEventListener("click", editHint("footer"));
266
+ const showCustom = !(cfg.footerScope === "first" && pageNo !== 1);
267
+ const custom = showCustom ? chromeContentEl(cfg.footerText, cfg) : document.createElement("div");
268
+ if (!showCustom)
269
+ custom.className = "poc-chrome-text";
270
+ footer.appendChild(custom);
271
+ if (cfg.showPageNumber !== false) {
272
+ const pageLabel = document.createElement("span");
273
+ pageLabel.textContent = `P\xE1gina ${pageNo} de ${total}`;
274
+ footer.appendChild(pageLabel);
275
+ }
276
+ if (custom.className === "poc-chrome-canvas")
277
+ footer.style.justifyContent = "flex-end";
278
+ return footer;
279
+ }
280
+ function buildPocNotesEl(notes) {
281
+ const box = document.createElement("div");
282
+ box.className = "poc-page-notes";
283
+ for (const note of notes) {
284
+ const line = document.createElement("p");
285
+ line.className = "poc-page-note";
286
+ if (note.size)
287
+ line.style.fontSize = `${note.size}pt`;
288
+ line.innerHTML = `<sup>${note.n}</sup> ${sanitizeFootnoteHtml(note.text)}`;
289
+ box.appendChild(line);
290
+ }
291
+ return box;
292
+ }
293
+ function buildChrome(cfg, brk, total) {
294
+ const chrome = document.createElement("div");
295
+ chrome.className = "poc-page-chrome";
296
+ chrome.contentEditable = "false";
297
+ chrome.style.height = `${brk.filler + (brk.notesH ?? 0) + pocChromeFlowHeight(cfg)}px`;
298
+ const filler = document.createElement("div");
299
+ filler.style.height = `${brk.filler}px`;
300
+ chrome.appendChild(filler);
301
+ if (brk.notes?.length)
302
+ chrome.appendChild(buildPocNotesEl(brk.notes));
303
+ chrome.appendChild(buildPocFooterEl(cfg, brk.pageNo, total));
304
+ const band = document.createElement("div");
305
+ band.className = "poc-page-gapband";
306
+ band.style.height = `${cfg.gap}px`;
307
+ band.style.marginLeft = `-${cfg.margins.left}px`;
308
+ band.style.marginRight = `-${cfg.margins.right}px`;
309
+ chrome.appendChild(band);
310
+ chrome.appendChild(buildPocHeaderEl(cfg));
311
+ return chrome;
312
+ }
313
+ function absTop(el, sheet) {
314
+ let top = 0;
315
+ let node = el;
316
+ while (node && node !== sheet) {
317
+ top += node.offsetTop;
318
+ node = node.offsetParent;
319
+ }
320
+ return top;
321
+ }
322
+ const HEADING_TAGS = /* @__PURE__ */ new Set(["H1", "H2", "H3", "H4"]);
323
+ const SPLITTABLE_TAGS = /* @__PURE__ */ new Set(["P"]);
324
+ const MIN_LINES_PER_SIDE = 2;
325
+ function findInlineSplit(view, dom, availablePx, fromLine = 0, minHead = MIN_LINES_PER_SIDE, minTail = MIN_LINES_PER_SIDE) {
326
+ if (!SPLITTABLE_TAGS.has(dom.tagName))
327
+ return null;
328
+ let rects;
329
+ try {
330
+ const range = document.createRange();
331
+ range.selectNodeContents(dom);
332
+ rects = Array.from(range.getClientRects()).filter((r) => r.height > 0 && r.width > 0);
333
+ } catch {
334
+ return null;
335
+ }
336
+ if (rects.length < minHead + minTail)
337
+ return null;
338
+ const lines = [];
339
+ for (const r of rects) {
340
+ const last2 = lines[lines.length - 1];
341
+ if (last2 && r.top < last2.bottom - 1) {
342
+ last2.bottom = Math.max(last2.bottom, r.bottom);
343
+ last2.left = Math.min(last2.left, r.left);
344
+ } else {
345
+ lines.push({ top: r.top, bottom: r.bottom, left: r.left });
346
+ }
347
+ }
348
+ if (lines.length < minHead + minTail)
349
+ return null;
350
+ const scale = dom.offsetHeight > 0 ? dom.getBoundingClientRect().height / dom.offsetHeight : 1;
351
+ if (!Number.isFinite(scale) || scale <= 0)
352
+ return null;
353
+ const originTop = lines[0].top;
354
+ const first = fromLine + minHead;
355
+ const last = lines.length - minTail;
356
+ let cut = -1;
357
+ for (let i = first; i <= last; i++) {
358
+ if ((lines[i].bottom - originTop) / scale > availablePx) {
359
+ cut = i;
360
+ break;
361
+ }
362
+ }
363
+ if (cut < first || cut > last)
364
+ return null;
365
+ const line = lines[cut];
366
+ const found = view.posAtCoords({ left: line.left + 1, top: (line.top + line.bottom) / 2 });
367
+ if (!found)
368
+ return null;
369
+ return { pos: found.pos, usedPx: (lines[cut - 1].bottom - originTop) / scale, line: cut };
370
+ }
371
+ function pageSafetyPx(contentHeight) {
372
+ return Math.max(32, Math.round(contentHeight * 0.08));
373
+ }
374
+ const NOTES_RULE_PX = 4;
375
+ function createNotesMeasurer(sheet, contentWidth) {
376
+ const host = document.createElement("div");
377
+ host.className = "poc-page-notes poc-notes-measure";
378
+ host.style.cssText = "position:absolute;left:0;top:0;visibility:hidden;pointer-events:none;";
379
+ host.style.width = `${contentWidth}px`;
380
+ sheet.appendChild(host);
381
+ const cache = /* @__PURE__ */ new Map();
382
+ return {
383
+ /** altura total (filete + notas) de um conjunto de notas */
384
+ height(notes) {
385
+ if (!notes.length)
386
+ return 0;
387
+ const key = notes.map((n) => `${n.n}:${n.size ?? ""}:${n.text.length}`).join("|");
388
+ const hit = cache.get(key);
389
+ if (hit != null)
390
+ return hit;
391
+ host.replaceChildren(...Array.from(buildPocNotesEl(notes).childNodes));
392
+ const value = host.offsetHeight + NOTES_RULE_PX;
393
+ cache.set(key, value);
394
+ return value;
395
+ },
396
+ dispose() {
397
+ host.remove();
398
+ }
399
+ };
400
+ }
401
+ function computePocBreaks(view, cfg, sheet) {
402
+ const bandH = cfg.pageHeight - cfg.margins.top - cfg.margins.bottom;
403
+ const safety = pageSafetyPx(bandH);
404
+ const contentH = bandH - safety;
405
+ if (contentH <= 40)
406
+ return { breaks: [], total: 1, markers: [], lastNotes: [] };
407
+ const chromes = Array.from(sheet.querySelectorAll(".poc-page-chrome")).map((el) => ({ top: absTop(el, sheet), height: el.offsetHeight })).sort((a, b) => a.top - b.top);
408
+ const chromeAbove = (top) => {
409
+ let sum = 0;
410
+ for (const c of chromes) {
411
+ if (c.top < top)
412
+ sum += c.height;
413
+ else
414
+ break;
415
+ }
416
+ return sum;
417
+ };
418
+ const items = [];
419
+ const baseY = cfg.margins.top;
420
+ let noteCounter = 0;
421
+ view.state.doc.forEach((block, blockOffset) => {
422
+ if (block.type.name !== "minutaBlock") {
423
+ return;
424
+ }
425
+ block.forEach((child, childOffset) => {
426
+ const pos = blockOffset + 1 + childOffset;
427
+ const dom = view.nodeDOM(pos);
428
+ if (!(dom instanceof HTMLElement))
429
+ return;
430
+ const top = absTop(dom, sheet) - baseY - chromeAbove(absTop(dom, sheet));
431
+ const notes = [];
432
+ child.descendants((inner, innerPos) => {
433
+ if (inner.type.name === "footnoteRef" && inner.attrs.text) {
434
+ noteCounter += 1;
435
+ notes.push({ n: noteCounter, text: String(inner.attrs.text), size: inner.attrs.size ?? null, at: pos + 1 + innerPos });
436
+ }
437
+ return true;
438
+ });
439
+ items.push({
440
+ pos,
441
+ size: child.nodeSize,
442
+ top,
443
+ height: dom.offsetHeight,
444
+ notes,
445
+ dom,
446
+ // margem superior conta como parte da página que o item abre —
447
+ // senão a "página" do preview fica mais alta que um A4 real e
448
+ // o DOCX (página física) divergiria
449
+ marginTop: Number.parseFloat(getComputedStyle(dom).marginTop) || 0,
450
+ heading: HEADING_TAGS.has(dom.tagName),
451
+ marker: (dom.textContent || "").trim() === "PAGEBREAK",
452
+ // quebra que cai no 1º filho de um bloco vai ANTES do bloco
453
+ // (o chrome fora do div do bloco — senão o outline/label de
454
+ // proveniência "vaza" para a página anterior)
455
+ blockStartPos: childOffset === 0 ? blockOffset : null
456
+ });
457
+ });
458
+ });
459
+ if (!items.length)
460
+ return { breaks: [], total: 1, markers: [], lastNotes: [] };
461
+ const markers = items.filter((i) => i.marker).map((i) => ({ from: i.pos, to: i.pos + i.size }));
462
+ const hasNotes = items.some((i) => i.notes.length);
463
+ const measurer = hasNotes ? createNotesMeasurer(sheet, sheet.clientWidth - cfg.margins.left - cfg.margins.right) : null;
464
+ const breaks = [];
465
+ let pageStart = 0;
466
+ let pageNo = 1;
467
+ let forcedBreakPending = false;
468
+ let pageNotes = [];
469
+ const notesH = (notes) => measurer && notes.length ? measurer.height(notes) : 0;
470
+ const closePage = (pos, filler, soft = false) => {
471
+ const height = notesH(pageNotes);
472
+ breaks.push({ pos, filler: Math.max(0, filler - height), pageNo, notes: pageNotes, notesH: height, soft });
473
+ pageNo++;
474
+ pageNotes = [];
475
+ };
476
+ const maxSteps = items.length * 4 + 64;
477
+ let steps = 0;
478
+ for (let i = 0; i < items.length; i++) {
479
+ if (++steps > maxSteps) {
480
+ console.warn("[poc] pagina\xE7\xE3o interrompida: la\xE7o sem progresso", { items: items.length, breaks: breaks.length });
481
+ break;
482
+ }
483
+ const item = items[i];
484
+ if (item.marker) {
485
+ forcedBreakPending = true;
486
+ continue;
487
+ }
488
+ const fullH = contentH + safety;
489
+ const itemEnd = item.top + item.height;
490
+ const capacity = contentH - notesH([...pageNotes, ...item.notes]);
491
+ if (forcedBreakPending && item.top > pageStart) {
492
+ const startWithMargin = item.top - item.marginTop;
493
+ closePage(item.blockStartPos ?? item.pos, fullH - (startWithMargin - pageStart));
494
+ pageStart = startWithMargin;
495
+ forcedBreakPending = false;
496
+ } else if (item.top > pageStart && itemEnd - pageStart > capacity) {
497
+ const disponivel = capacity - (item.top - pageStart);
498
+ let split = findInlineSplit(view, item.dom, disponivel);
499
+ if (!split)
500
+ split = findInlineSplit(view, item.dom, disponivel, 0, MIN_LINES_PER_SIDE, 1);
501
+ if (split) {
502
+ let pending = [...item.notes];
503
+ let lastLine = split.line;
504
+ while (split) {
505
+ lastLine = split.line;
506
+ const usedEnd = item.top + split.usedPx;
507
+ const cutAt = split.pos;
508
+ const stay = pending.filter((n) => n.at === void 0 || n.at < cutAt);
509
+ pending = pending.filter((n) => !stay.includes(n));
510
+ pageNotes.push(...stay);
511
+ closePage(cutAt, fullH - (usedEnd - pageStart), true);
512
+ pageStart = usedEnd;
513
+ if (itemEnd - pageStart <= contentH - notesH(pending))
514
+ break;
515
+ const fromTop = pageStart - item.top;
516
+ split = findInlineSplit(
517
+ view,
518
+ item.dom,
519
+ fromTop + contentH - notesH(pending),
520
+ split.line,
521
+ 1,
522
+ 1
523
+ );
524
+ if (!split)
525
+ split = findInlineSplit(view, item.dom, fromTop + contentH, lastLine, 1, 1);
526
+ }
527
+ pageNotes.push(...pending);
528
+ forcedBreakPending = false;
529
+ while (item.top + item.height - pageStart > contentH) {
530
+ pageStart += contentH;
531
+ pageNo++;
532
+ }
533
+ continue;
534
+ }
535
+ let breakIndex = i;
536
+ if (shouldMoveBreakToHeading({ heading: items[i - 1], item, pageStart, capacity }))
537
+ breakIndex = i - 1;
538
+ const target = items[breakIndex];
539
+ const startWithMargin = target.top - target.marginTop;
540
+ const moved = [];
541
+ for (let k = breakIndex; k < i; k++) {
542
+ for (const note of items[k].notes) {
543
+ const at = pageNotes.indexOf(note);
544
+ if (at >= 0)
545
+ pageNotes.splice(at, 1);
546
+ moved.push(note);
547
+ }
548
+ }
549
+ closePage(target.blockStartPos ?? target.pos, fullH - (startWithMargin - pageStart));
550
+ pageNotes = moved;
551
+ pageStart = startWithMargin;
552
+ if (breakIndex < i) {
553
+ i = breakIndex;
554
+ continue;
555
+ }
556
+ }
557
+ forcedBreakPending = false;
558
+ pageNotes.push(...item.notes);
559
+ while (item.top + item.height - pageStart > contentH) {
560
+ pageStart += contentH;
561
+ pageNo++;
562
+ }
563
+ }
564
+ measurer?.dispose();
565
+ return { breaks, total: pageNo, markers, lastNotes: pageNotes };
566
+ }
567
+ function pocStyleProp(style, prop) {
568
+ if (!style)
569
+ return void 0;
570
+ const match = style.match(new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i"));
571
+ return match?.[1]?.trim();
572
+ }
573
+ function isPocFlexRow(node) {
574
+ if (node.type.name !== "styledDiv")
575
+ return false;
576
+ const display = pocStyleProp(node.attrs.style, "display");
577
+ if (display !== "flex" && display !== "table")
578
+ return false;
579
+ if (pocStyleProp(node.attrs.style, "flex-direction") === "column")
580
+ return false;
581
+ return node.childCount > 1;
582
+ }
583
+ function pocNodeHasVisibleContent(node) {
584
+ if (node.textContent.trim().length > 0)
585
+ return true;
586
+ let found = false;
587
+ const checkIsland = (island) => {
588
+ const html = island.attrs.html || "";
589
+ if (/<img[^>]*src\s*=\s*"[^"]+"/i.test(html) || html.includes("<svg"))
590
+ found = true;
591
+ };
592
+ if (node.type.name === "htmlIsland")
593
+ checkIsland(node);
594
+ node.descendants((child) => {
595
+ if (found)
596
+ return false;
597
+ if (child.type.name === "htmlIsland")
598
+ checkIsland(child);
599
+ return !found;
600
+ });
601
+ return found;
602
+ }
603
+ function paintsSomething(value) {
604
+ if (!value)
605
+ return false;
606
+ const v = value.trim().toLowerCase();
607
+ if (!v || /\b(?:none|hidden|transparent)\b/.test(v))
608
+ return false;
609
+ const width = v.match(/(?:^|\s)(\d*\.?\d+)(?:px|pt|em|rem|cm|mm|in)?(?=\s|$)/);
610
+ return !(width && Number.parseFloat(width[1]) === 0);
611
+ }
612
+ function pocNodePaintsBox(node) {
613
+ const style = node.attrs?.style ?? null;
614
+ if (!style)
615
+ return false;
616
+ const props = ["border", "border-top", "border-right", "border-bottom", "border-left", "background", "background-color"];
617
+ return props.some((prop) => paintsSomething(pocStyleProp(style, prop)));
618
+ }
619
+ function pocLoneCellIndex(row) {
620
+ if (pocNodePaintsBox(row))
621
+ return null;
622
+ let found = -1;
623
+ for (let i = 0; i < row.childCount; i++) {
624
+ const child = row.child(i);
625
+ if (pocNodePaintsBox(child))
626
+ return null;
627
+ if (pocNodeHasVisibleContent(child)) {
628
+ if (found !== -1)
629
+ return null;
630
+ found = i;
631
+ }
632
+ }
633
+ return found === -1 ? null : found;
634
+ }
635
+ function computePocLoneRows(doc) {
636
+ const out = [];
637
+ doc.descendants((node, pos) => {
638
+ if (!isPocFlexRow(node))
639
+ return true;
640
+ const index = pocLoneCellIndex(node);
641
+ if (index == null)
642
+ return true;
643
+ let offset = pos + 1;
644
+ for (let i = 0; i < index; i++)
645
+ offset += node.child(i).nodeSize;
646
+ out.push({
647
+ rowFrom: pos,
648
+ rowTo: pos + node.nodeSize,
649
+ cellFrom: offset,
650
+ cellTo: offset + node.child(index).nodeSize
651
+ });
652
+ return true;
653
+ });
654
+ return out;
655
+ }
656
+ function breaksEqual(a, b) {
657
+ if (a.length !== b.length)
658
+ return false;
659
+ for (let i = 0; i < a.length; i++) {
660
+ if (a[i].pos !== b[i].pos || Math.abs(a[i].filler - b[i].filler) > 1)
661
+ return false;
662
+ if ((a[i].notes?.length ?? 0) !== (b[i].notes?.length ?? 0))
663
+ return false;
664
+ }
665
+ return true;
666
+ }
667
+ function PocPaginationPlugin(cfg) {
668
+ return new Plugin({
669
+ key: pocPageKey,
670
+ state: {
671
+ init: () => ({ breaks: [], total: 1, markers: [] }),
672
+ apply(tr, value) {
673
+ const next = tr.getMeta(PAGINATE_META);
674
+ if (next)
675
+ return { markers: value.markers, ...next };
676
+ if (tr.docChanged) {
677
+ return {
678
+ ...value,
679
+ breaks: value.breaks.map((b) => ({ ...b, pos: tr.mapping.map(b.pos) })),
680
+ markers: value.markers.map((m) => ({ from: tr.mapping.map(m.from), to: tr.mapping.map(m.to) }))
681
+ };
682
+ }
683
+ return value;
684
+ }
685
+ },
686
+ props: {
687
+ decorations(state) {
688
+ const pag = pocPageKey.getState(state);
689
+ const decorations = (pag?.breaks ?? []).map(
690
+ (brk) => Decoration.widget(brk.pos, () => buildChrome(cfg, brk, pag.total), {
691
+ side: -1,
692
+ key: `chrome-${brk.pageNo}-${Math.round(brk.filler)}-${brk.notes?.length ?? 0}-${pag.total}-v${cfg.version ?? 0}`
693
+ })
694
+ );
695
+ for (const marker of pag?.markers ?? []) {
696
+ if (marker.to > marker.from && marker.to <= state.doc.content.size)
697
+ decorations.push(Decoration.node(marker.from, marker.to, { class: "poc-pagebreak-marker" }));
698
+ }
699
+ for (const lone of computePocLoneRows(state.doc)) {
700
+ decorations.push(Decoration.node(lone.rowFrom, lone.rowTo, { class: "poc-lone-row" }));
701
+ decorations.push(Decoration.node(lone.cellFrom, lone.cellTo, { class: "poc-lone-cell" }));
702
+ }
703
+ if (!decorations.length)
704
+ return null;
705
+ return DecorationSet.create(state.doc, decorations);
706
+ }
707
+ }
708
+ });
709
+ }
710
+
711
+ export { PocPaginationPlugin as P, sanitizeFootnoteHtml as a, pocPageKey as b, computePocBreaks as c, breaksEqual as d, PAGINATE_META as e, footnoteHtmlToRuns as f, pocChromeFlowHeight as g, buildPocHeaderEl as h, isPocChromeCanvas as i, buildPocFooterEl as j, buildPocNotesEl as k, isPocFlexRow as l, pocNodeHasVisibleContent as m, pocNodePaintsBox as n, computePocLoneRows as o, pocLoneCellIndex as p, footnoteSizePt as q, shouldMoveBreakToHeading as r, sanitizeRichHtml as s };