@meistrari/minuta-nuxt 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,139 +0,0 @@
1
- import type { Node as PMNode } from '@tiptap/pm/model';
2
- import type { EditorView } from '@tiptap/pm/view';
3
- import { Plugin, PluginKey } from '@tiptap/pm/state';
4
- /**
5
- * Engine de paginação da PoC.
6
- *
7
- * O documento continua linear no ProseMirror; as "páginas" nascem de widget
8
- * decorations ("chrome") inseridas ENTRE os nós internos dos blocos:
9
- * cada chrome preenche o resto da área útil da página (filler), desenha o
10
- * rodapé (nº da página) dentro da margem inferior, a faixa de separação
11
- * entre páginas e o cabeçalho (logo do template) dentro da margem superior
12
- * da página seguinte.
13
- *
14
- * A medição roda fora do plugin (componente agenda via rAF) em coordenadas
15
- * de layout (offsetTop/offsetHeight — imunes ao CSS zoom da folha) e
16
- * NORMALIZADAS: a altura de todo chrome existente é subtraída, então o
17
- * algoritmo sempre enxerga o fluxo "sem paginação" e converge (recomputar
18
- * com chrome na tela produz os mesmos breaks → sem loop de layout).
19
- *
20
- * Regras de quebra:
21
- * - quebra somente entre nós (parágrafo/título/ilha) — nó mais alto que a
22
- * página flui e as páginas internas são contadas, sem chrome no meio;
23
- * - keep-with-next: título não fica órfão no fim da página (a quebra sobe
24
- * para antes do título).
25
- */
26
- export interface PocPageConfig {
27
- pageHeight: number;
28
- margins: {
29
- top: number;
30
- right: number;
31
- bottom: number;
32
- left: number;
33
- };
34
- gap: number;
35
- headerLogoUrl?: string | null;
36
- headerPosition?: 'left' | 'center' | 'right';
37
- /** texto editável do cabeçalho (ao lado do logo) */
38
- headerText?: string;
39
- /** texto editável do rodapé (lado esquerdo; o nº de página fica à direita) */
40
- footerText?: string;
41
- /** onde o conteúdo do cabeçalho aparece: todas as páginas ou só a primeira */
42
- headerScope?: 'all' | 'first';
43
- /** onde o conteúdo custom do rodapé aparece (o nº de página é sempre em todas) */
44
- footerScope?: 'all' | 'first';
45
- /** numeração "Página N de M" no rodapé — opcional por conjunto de cabeçalho */
46
- showPageNumber?: boolean;
47
- /** bump força a reconstrução do chrome (muda a key das decorations) */
48
- version?: number;
49
- }
50
- /** nota de rodapé que cai numa página (o marcador está no corpo dela) */
51
- export interface PocPageNote {
52
- n: number;
53
- /** conteúdo em HTML de ênfase (b/i/u/br) — já sanitizado no render */
54
- text: string;
55
- /** corpo declarado no template, em pontos (default: 8pt, como o Word) */
56
- size?: number | null;
57
- /**
58
- * posição do MARCADOR no documento. Só é usada para repartir as notas
59
- * quando a quebra cai no meio do parágrafo: a nota mora na página da
60
- * chamada, então as que ficam depois do corte viajam para a página nova.
61
- */
62
- at?: number;
63
- }
64
- export interface PocPageBreak {
65
- pos: number;
66
- filler: number;
67
- pageNo: number;
68
- /**
69
- * quebra no MEIO de um parágrafo (entre linhas). O DOCX não recebe
70
- * pageBreakBefore nessas: partir o parágrafo em dois no Word entregaria
71
- * ao advogado um arquivo que não reflui quando ele edita o texto. Aqui o
72
- * Word quebra naturalmente, e o preview só antecipa onde.
73
- */
74
- soft?: boolean;
75
- /** notas cujos marcadores estão nesta página — desenhadas acima do rodapé */
76
- notes?: PocPageNote[];
77
- /** altura reservada pelas notas (já descontada do filler) */
78
- notesH?: number;
79
- }
80
- interface PagState {
81
- breaks: PocPageBreak[];
82
- total: number;
83
- /** parágrafos-marcador "PAGEBREAK" (quebra manual do template) */
84
- markers: {
85
- from: number;
86
- to: number;
87
- }[];
88
- }
89
- export declare const pocPageKey: PluginKey<PagState>;
90
- export declare const PAGINATE_META = "poc-paginate";
91
- export declare function pocChromeFlowHeight(cfg: PocPageConfig): number;
92
- /**
93
- * o chrome tem dois formatos: canvas (elementos posicionados, coordenadas no
94
- * frame da PÁGINA — vem do editor estilo Word) e flow legado (texto corrido)
95
- */
96
- export declare function isPocChromeCanvas(html: string | undefined | null): boolean;
97
- export declare function buildPocHeaderEl(cfg: PocPageConfig): HTMLElement;
98
- export declare function buildPocFooterEl(cfg: PocPageConfig, pageNo: number, total: number): HTMLElement;
99
- /** área de notas de rodapé: filete + notas, no pé da área útil (como no Word) */
100
- export declare function buildPocNotesEl(notes: PocPageNote[]): HTMLElement;
101
- export declare function computePocBreaks(view: EditorView, cfg: PocPageConfig, sheet: HTMLElement): {
102
- breaks: PocPageBreak[];
103
- total: number;
104
- markers: {
105
- from: number;
106
- to: number;
107
- }[];
108
- lastNotes: PocPageNote[];
109
- };
110
- export declare function isPocFlexRow(node: PMNode): boolean;
111
- /** conteúdo visível = texto, imagem com src preenchido ou svg (imgs vazias do pipeline não contam) */
112
- export declare function pocNodeHasVisibleContent(node: PMNode): boolean;
113
- /**
114
- * A célula/linha PINTA alguma coisa (borda ou fundo)?
115
- *
116
- * Uma coluna sem texto mas com borda não é espaço sobrando: é estrutura. A
117
- * folha de rosto da Telefônica desenha uma timeline com `border-right` numa
118
- * coluna VAZIA (o eixo central) e `border-bottom` alternando entre as duas —
119
- * colapsar a coluna "vazia" apaga o eixo e centraliza o que devia ficar
120
- * alternando esquerda/direita.
121
- */
122
- export declare function pocNodePaintsBox(node: PMNode): boolean;
123
- /**
124
- * Índice da única célula com conteúdo da linha, ou null se a linha não pode
125
- * ser colapsada. Fonte única da regra: o preview (decorations .poc-lone-row)
126
- * e o export DOCX chamam esta função — se divergirem, o Word deixa de bater
127
- * com a tela.
128
- */
129
- export declare function pocLoneCellIndex(row: PMNode): number | null;
130
- export interface PocLoneRow {
131
- rowFrom: number;
132
- rowTo: number;
133
- cellFrom: number;
134
- cellTo: number;
135
- }
136
- export declare function computePocLoneRows(doc: PMNode): PocLoneRow[];
137
- export declare function breaksEqual(a: PocPageBreak[], b: PocPageBreak[]): boolean;
138
- export declare function PocPaginationPlugin(cfg: PocPageConfig): Plugin<PagState>;
139
- export {};
@@ -1,505 +0,0 @@
1
- import { Plugin, PluginKey } from "@tiptap/pm/state";
2
- import { Decoration, DecorationSet } from "@tiptap/pm/view";
3
- import { sanitizeRichHtml } from "../utils/sanitizeRichHtml.js";
4
- import { sanitizeFootnoteHtml } from "../utils/footnoteHtml.js";
5
- import { shouldMoveBreakToHeading } from "../utils/pocKeepWithNext.js";
6
- export const pocPageKey = new PluginKey("pocPagination");
7
- export const PAGINATE_META = "poc-paginate";
8
- export function pocChromeFlowHeight(cfg) {
9
- return cfg.margins.bottom + cfg.gap + cfg.margins.top;
10
- }
11
- function editHint(part) {
12
- return (event) => {
13
- event.preventDefault();
14
- event.stopPropagation();
15
- window.dispatchEvent(new CustomEvent("poc:edit-chrome", { detail: { part } }));
16
- };
17
- }
18
- export function isPocChromeCanvas(html) {
19
- return !!html && html.includes("data-el=");
20
- }
21
- function chromeContentEl(html, cfg) {
22
- const clean = sanitizeRichHtml(html);
23
- if (isPocChromeCanvas(clean)) {
24
- const canvas = document.createElement("div");
25
- canvas.className = "poc-chrome-canvas";
26
- canvas.style.cssText = `position:absolute;top:0;left:${-cfg.margins.left}px;width:794px;height:100%;pointer-events:none;`;
27
- canvas.innerHTML = clean;
28
- return canvas;
29
- }
30
- const text = document.createElement("div");
31
- text.className = "poc-chrome-text";
32
- text.innerHTML = clean;
33
- return text;
34
- }
35
- export function buildPocHeaderEl(cfg) {
36
- const header = document.createElement("div");
37
- header.className = "poc-page-headerarea";
38
- header.style.height = `${cfg.margins.top}px`;
39
- header.style.position = "relative";
40
- header.style.justifyContent = cfg.headerPosition === "center" ? "center" : cfg.headerPosition === "right" ? "flex-end" : "flex-start";
41
- header.title = "Clique para editar o cabe\xE7alho";
42
- header.addEventListener("click", editHint("header"));
43
- if (cfg.headerScope === "first")
44
- return header;
45
- if (cfg.headerLogoUrl) {
46
- const img = document.createElement("img");
47
- img.src = cfg.headerLogoUrl;
48
- img.style.maxHeight = `${Math.max(24, cfg.margins.top - 36)}px`;
49
- img.style.maxWidth = "50%";
50
- img.style.display = "inline-block";
51
- header.appendChild(img);
52
- }
53
- const content = chromeContentEl(cfg.headerText, cfg);
54
- if (content.className === "poc-chrome-text" && !content.innerHTML && !cfg.headerLogoUrl)
55
- content.innerHTML = "&nbsp;";
56
- header.appendChild(content);
57
- return header;
58
- }
59
- export function buildPocFooterEl(cfg, pageNo, total) {
60
- const footer = document.createElement("div");
61
- footer.className = "poc-page-footerarea";
62
- footer.style.height = `${cfg.margins.bottom}px`;
63
- footer.style.position = "relative";
64
- footer.title = "Clique para editar o rodap\xE9";
65
- footer.addEventListener("click", editHint("footer"));
66
- const showCustom = !(cfg.footerScope === "first" && pageNo !== 1);
67
- const custom = showCustom ? chromeContentEl(cfg.footerText, cfg) : document.createElement("div");
68
- if (!showCustom)
69
- custom.className = "poc-chrome-text";
70
- footer.appendChild(custom);
71
- if (cfg.showPageNumber !== false) {
72
- const pageLabel = document.createElement("span");
73
- pageLabel.textContent = `P\xE1gina ${pageNo} de ${total}`;
74
- footer.appendChild(pageLabel);
75
- }
76
- if (custom.className === "poc-chrome-canvas")
77
- footer.style.justifyContent = "flex-end";
78
- return footer;
79
- }
80
- export function buildPocNotesEl(notes) {
81
- const box = document.createElement("div");
82
- box.className = "poc-page-notes";
83
- for (const note of notes) {
84
- const line = document.createElement("p");
85
- line.className = "poc-page-note";
86
- if (note.size)
87
- line.style.fontSize = `${note.size}pt`;
88
- line.innerHTML = `<sup>${note.n}</sup> ${sanitizeFootnoteHtml(note.text)}`;
89
- box.appendChild(line);
90
- }
91
- return box;
92
- }
93
- function buildChrome(cfg, brk, total) {
94
- const chrome = document.createElement("div");
95
- chrome.className = "poc-page-chrome";
96
- chrome.contentEditable = "false";
97
- chrome.style.height = `${brk.filler + (brk.notesH ?? 0) + pocChromeFlowHeight(cfg)}px`;
98
- const filler = document.createElement("div");
99
- filler.style.height = `${brk.filler}px`;
100
- chrome.appendChild(filler);
101
- if (brk.notes?.length)
102
- chrome.appendChild(buildPocNotesEl(brk.notes));
103
- chrome.appendChild(buildPocFooterEl(cfg, brk.pageNo, total));
104
- const band = document.createElement("div");
105
- band.className = "poc-page-gapband";
106
- band.style.height = `${cfg.gap}px`;
107
- band.style.marginLeft = `-${cfg.margins.left}px`;
108
- band.style.marginRight = `-${cfg.margins.right}px`;
109
- chrome.appendChild(band);
110
- chrome.appendChild(buildPocHeaderEl(cfg));
111
- return chrome;
112
- }
113
- function absTop(el, sheet) {
114
- let top = 0;
115
- let node = el;
116
- while (node && node !== sheet) {
117
- top += node.offsetTop;
118
- node = node.offsetParent;
119
- }
120
- return top;
121
- }
122
- const HEADING_TAGS = /* @__PURE__ */ new Set(["H1", "H2", "H3", "H4"]);
123
- const SPLITTABLE_TAGS = /* @__PURE__ */ new Set(["P"]);
124
- const MIN_LINES_PER_SIDE = 2;
125
- function findInlineSplit(view, dom, availablePx, fromLine = 0, minHead = MIN_LINES_PER_SIDE, minTail = MIN_LINES_PER_SIDE) {
126
- if (!SPLITTABLE_TAGS.has(dom.tagName))
127
- return null;
128
- let rects;
129
- try {
130
- const range = document.createRange();
131
- range.selectNodeContents(dom);
132
- rects = Array.from(range.getClientRects()).filter((r) => r.height > 0 && r.width > 0);
133
- } catch {
134
- return null;
135
- }
136
- if (rects.length < minHead + minTail)
137
- return null;
138
- const lines = [];
139
- for (const r of rects) {
140
- const last2 = lines[lines.length - 1];
141
- if (last2 && r.top < last2.bottom - 1) {
142
- last2.bottom = Math.max(last2.bottom, r.bottom);
143
- last2.left = Math.min(last2.left, r.left);
144
- } else {
145
- lines.push({ top: r.top, bottom: r.bottom, left: r.left });
146
- }
147
- }
148
- if (lines.length < minHead + minTail)
149
- return null;
150
- const scale = dom.offsetHeight > 0 ? dom.getBoundingClientRect().height / dom.offsetHeight : 1;
151
- if (!Number.isFinite(scale) || scale <= 0)
152
- return null;
153
- const originTop = lines[0].top;
154
- const first = fromLine + minHead;
155
- const last = lines.length - minTail;
156
- let cut = -1;
157
- for (let i = first; i <= last; i++) {
158
- if ((lines[i].bottom - originTop) / scale > availablePx) {
159
- cut = i;
160
- break;
161
- }
162
- }
163
- if (cut < first || cut > last)
164
- return null;
165
- const line = lines[cut];
166
- const found = view.posAtCoords({ left: line.left + 1, top: (line.top + line.bottom) / 2 });
167
- if (!found)
168
- return null;
169
- return { pos: found.pos, usedPx: (lines[cut - 1].bottom - originTop) / scale, line: cut };
170
- }
171
- function pageSafetyPx(contentHeight) {
172
- return Math.max(32, Math.round(contentHeight * 0.08));
173
- }
174
- const NOTES_RULE_PX = 4;
175
- function createNotesMeasurer(sheet, contentWidth) {
176
- const host = document.createElement("div");
177
- host.className = "poc-page-notes poc-notes-measure";
178
- host.style.cssText = "position:absolute;left:0;top:0;visibility:hidden;pointer-events:none;";
179
- host.style.width = `${contentWidth}px`;
180
- sheet.appendChild(host);
181
- const cache = /* @__PURE__ */ new Map();
182
- return {
183
- /** altura total (filete + notas) de um conjunto de notas */
184
- height(notes) {
185
- if (!notes.length)
186
- return 0;
187
- const key = notes.map((n) => `${n.n}:${n.size ?? ""}:${n.text.length}`).join("|");
188
- const hit = cache.get(key);
189
- if (hit != null)
190
- return hit;
191
- host.replaceChildren(...Array.from(buildPocNotesEl(notes).childNodes));
192
- const value = host.offsetHeight + NOTES_RULE_PX;
193
- cache.set(key, value);
194
- return value;
195
- },
196
- dispose() {
197
- host.remove();
198
- }
199
- };
200
- }
201
- export function computePocBreaks(view, cfg, sheet) {
202
- const bandH = cfg.pageHeight - cfg.margins.top - cfg.margins.bottom;
203
- const safety = pageSafetyPx(bandH);
204
- const contentH = bandH - safety;
205
- if (contentH <= 40)
206
- return { breaks: [], total: 1, markers: [], lastNotes: [] };
207
- 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);
208
- const chromeAbove = (top) => {
209
- let sum = 0;
210
- for (const c of chromes) {
211
- if (c.top < top)
212
- sum += c.height;
213
- else break;
214
- }
215
- return sum;
216
- };
217
- const items = [];
218
- const baseY = cfg.margins.top;
219
- let noteCounter = 0;
220
- view.state.doc.forEach((block, blockOffset) => {
221
- if (block.type.name !== "minutaBlock") {
222
- return;
223
- }
224
- block.forEach((child, childOffset) => {
225
- const pos = blockOffset + 1 + childOffset;
226
- const dom = view.nodeDOM(pos);
227
- if (!(dom instanceof HTMLElement))
228
- return;
229
- const top = absTop(dom, sheet) - baseY - chromeAbove(absTop(dom, sheet));
230
- const notes = [];
231
- child.descendants((inner, innerPos) => {
232
- if (inner.type.name === "footnoteRef" && inner.attrs.text) {
233
- noteCounter += 1;
234
- notes.push({ n: noteCounter, text: String(inner.attrs.text), size: inner.attrs.size ?? null, at: pos + 1 + innerPos });
235
- }
236
- return true;
237
- });
238
- items.push({
239
- pos,
240
- size: child.nodeSize,
241
- top,
242
- height: dom.offsetHeight,
243
- notes,
244
- dom,
245
- // margem superior conta como parte da página que o item abre —
246
- // senão a "página" do preview fica mais alta que um A4 real e
247
- // o DOCX (página física) divergiria
248
- marginTop: Number.parseFloat(getComputedStyle(dom).marginTop) || 0,
249
- heading: HEADING_TAGS.has(dom.tagName),
250
- marker: (dom.textContent || "").trim() === "PAGEBREAK",
251
- // quebra que cai no 1º filho de um bloco vai ANTES do bloco
252
- // (o chrome fora do div do bloco — senão o outline/label de
253
- // proveniência "vaza" para a página anterior)
254
- blockStartPos: childOffset === 0 ? blockOffset : null
255
- });
256
- });
257
- });
258
- if (!items.length)
259
- return { breaks: [], total: 1, markers: [], lastNotes: [] };
260
- const markers = items.filter((i) => i.marker).map((i) => ({ from: i.pos, to: i.pos + i.size }));
261
- const hasNotes = items.some((i) => i.notes.length);
262
- const measurer = hasNotes ? createNotesMeasurer(sheet, sheet.clientWidth - cfg.margins.left - cfg.margins.right) : null;
263
- const breaks = [];
264
- let pageStart = 0;
265
- let pageNo = 1;
266
- let forcedBreakPending = false;
267
- let pageNotes = [];
268
- const notesH = (notes) => measurer && notes.length ? measurer.height(notes) : 0;
269
- const closePage = (pos, filler, soft = false) => {
270
- const height = notesH(pageNotes);
271
- breaks.push({ pos, filler: Math.max(0, filler - height), pageNo, notes: pageNotes, notesH: height, soft });
272
- pageNo++;
273
- pageNotes = [];
274
- };
275
- const maxSteps = items.length * 4 + 64;
276
- let steps = 0;
277
- for (let i = 0; i < items.length; i++) {
278
- if (++steps > maxSteps) {
279
- console.warn("[poc] pagina\xE7\xE3o interrompida: la\xE7o sem progresso", { items: items.length, breaks: breaks.length });
280
- break;
281
- }
282
- const item = items[i];
283
- if (item.marker) {
284
- forcedBreakPending = true;
285
- continue;
286
- }
287
- const fullH = contentH + safety;
288
- const itemEnd = item.top + item.height;
289
- const capacity = contentH - notesH([...pageNotes, ...item.notes]);
290
- if (forcedBreakPending && item.top > pageStart) {
291
- const startWithMargin = item.top - item.marginTop;
292
- closePage(item.blockStartPos ?? item.pos, fullH - (startWithMargin - pageStart));
293
- pageStart = startWithMargin;
294
- forcedBreakPending = false;
295
- } else if (item.top > pageStart && itemEnd - pageStart > capacity) {
296
- let split = findInlineSplit(view, item.dom, capacity - (item.top - pageStart));
297
- if (split) {
298
- let pending = [...item.notes];
299
- let lastLine = split.line;
300
- while (split) {
301
- lastLine = split.line;
302
- const usedEnd = item.top + split.usedPx;
303
- const cutAt = split.pos;
304
- const stay = pending.filter((n) => n.at === void 0 || n.at < cutAt);
305
- pending = pending.filter((n) => !stay.includes(n));
306
- pageNotes.push(...stay);
307
- closePage(cutAt, fullH - (usedEnd - pageStart), true);
308
- pageStart = usedEnd;
309
- if (itemEnd - pageStart <= contentH - notesH(pending))
310
- break;
311
- const fromTop = pageStart - item.top;
312
- split = findInlineSplit(
313
- view,
314
- item.dom,
315
- fromTop + contentH - notesH(pending),
316
- split.line,
317
- 1,
318
- 1
319
- );
320
- if (!split)
321
- split = findInlineSplit(view, item.dom, fromTop + contentH, lastLine, 1, 1);
322
- }
323
- pageNotes.push(...pending);
324
- forcedBreakPending = false;
325
- while (item.top + item.height - pageStart > contentH) {
326
- pageStart += contentH;
327
- pageNo++;
328
- }
329
- continue;
330
- }
331
- let breakIndex = i;
332
- if (shouldMoveBreakToHeading({ heading: items[i - 1], item, pageStart, capacity }))
333
- breakIndex = i - 1;
334
- const target = items[breakIndex];
335
- const startWithMargin = target.top - target.marginTop;
336
- const moved = [];
337
- for (let k = breakIndex; k < i; k++) {
338
- for (const note of items[k].notes) {
339
- const at = pageNotes.indexOf(note);
340
- if (at >= 0)
341
- pageNotes.splice(at, 1);
342
- moved.push(note);
343
- }
344
- }
345
- closePage(target.blockStartPos ?? target.pos, fullH - (startWithMargin - pageStart));
346
- pageNotes = moved;
347
- pageStart = startWithMargin;
348
- if (breakIndex < i) {
349
- i = breakIndex;
350
- continue;
351
- }
352
- }
353
- forcedBreakPending = false;
354
- pageNotes.push(...item.notes);
355
- while (item.top + item.height - pageStart > contentH) {
356
- pageStart += contentH;
357
- pageNo++;
358
- }
359
- }
360
- measurer?.dispose();
361
- return { breaks, total: pageNo, markers, lastNotes: pageNotes };
362
- }
363
- function pocStyleProp(style, prop) {
364
- if (!style)
365
- return void 0;
366
- const match = style.match(new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i"));
367
- return match?.[1]?.trim();
368
- }
369
- export function isPocFlexRow(node) {
370
- if (node.type.name !== "styledDiv")
371
- return false;
372
- const display = pocStyleProp(node.attrs.style, "display");
373
- if (display !== "flex" && display !== "table")
374
- return false;
375
- if (pocStyleProp(node.attrs.style, "flex-direction") === "column")
376
- return false;
377
- return node.childCount > 1;
378
- }
379
- export function pocNodeHasVisibleContent(node) {
380
- if (node.textContent.trim().length > 0)
381
- return true;
382
- let found = false;
383
- const checkIsland = (island) => {
384
- const html = island.attrs.html || "";
385
- if (/<img[^>]*src\s*=\s*"[^"]+"/i.test(html) || html.includes("<svg"))
386
- found = true;
387
- };
388
- if (node.type.name === "htmlIsland")
389
- checkIsland(node);
390
- node.descendants((child) => {
391
- if (found)
392
- return false;
393
- if (child.type.name === "htmlIsland")
394
- checkIsland(child);
395
- return !found;
396
- });
397
- return found;
398
- }
399
- function paintsSomething(value) {
400
- if (!value)
401
- return false;
402
- const v = value.trim().toLowerCase();
403
- if (!v || /\b(?:none|hidden|transparent)\b/.test(v))
404
- return false;
405
- const width = v.match(/(?:^|\s)(\d*\.?\d+)(?:px|pt|em|rem|cm|mm|in)?(?=\s|$)/);
406
- return !(width && Number.parseFloat(width[1]) === 0);
407
- }
408
- export function pocNodePaintsBox(node) {
409
- const style = node.attrs?.style ?? null;
410
- if (!style)
411
- return false;
412
- const props = ["border", "border-top", "border-right", "border-bottom", "border-left", "background", "background-color"];
413
- return props.some((prop) => paintsSomething(pocStyleProp(style, prop)));
414
- }
415
- export function pocLoneCellIndex(row) {
416
- if (pocNodePaintsBox(row))
417
- return null;
418
- let found = -1;
419
- for (let i = 0; i < row.childCount; i++) {
420
- const child = row.child(i);
421
- if (pocNodePaintsBox(child))
422
- return null;
423
- if (pocNodeHasVisibleContent(child)) {
424
- if (found !== -1)
425
- return null;
426
- found = i;
427
- }
428
- }
429
- return found === -1 ? null : found;
430
- }
431
- export function computePocLoneRows(doc) {
432
- const out = [];
433
- doc.descendants((node, pos) => {
434
- if (!isPocFlexRow(node))
435
- return true;
436
- const index = pocLoneCellIndex(node);
437
- if (index == null)
438
- return true;
439
- let offset = pos + 1;
440
- for (let i = 0; i < index; i++)
441
- offset += node.child(i).nodeSize;
442
- out.push({
443
- rowFrom: pos,
444
- rowTo: pos + node.nodeSize,
445
- cellFrom: offset,
446
- cellTo: offset + node.child(index).nodeSize
447
- });
448
- return true;
449
- });
450
- return out;
451
- }
452
- export function breaksEqual(a, b) {
453
- if (a.length !== b.length)
454
- return false;
455
- for (let i = 0; i < a.length; i++) {
456
- if (a[i].pos !== b[i].pos || Math.abs(a[i].filler - b[i].filler) > 1)
457
- return false;
458
- if ((a[i].notes?.length ?? 0) !== (b[i].notes?.length ?? 0))
459
- return false;
460
- }
461
- return true;
462
- }
463
- export function PocPaginationPlugin(cfg) {
464
- return new Plugin({
465
- key: pocPageKey,
466
- state: {
467
- init: () => ({ breaks: [], total: 1, markers: [] }),
468
- apply(tr, value) {
469
- const next = tr.getMeta(PAGINATE_META);
470
- if (next)
471
- return { markers: value.markers, ...next };
472
- if (tr.docChanged) {
473
- return {
474
- ...value,
475
- breaks: value.breaks.map((b) => ({ ...b, pos: tr.mapping.map(b.pos) })),
476
- markers: value.markers.map((m) => ({ from: tr.mapping.map(m.from), to: tr.mapping.map(m.to) }))
477
- };
478
- }
479
- return value;
480
- }
481
- },
482
- props: {
483
- decorations(state) {
484
- const pag = pocPageKey.getState(state);
485
- const decorations = (pag?.breaks ?? []).map(
486
- (brk) => Decoration.widget(brk.pos, () => buildChrome(cfg, brk, pag.total), {
487
- side: -1,
488
- key: `chrome-${brk.pageNo}-${Math.round(brk.filler)}-${brk.notes?.length ?? 0}-${pag.total}-v${cfg.version ?? 0}`
489
- })
490
- );
491
- for (const marker of pag?.markers ?? []) {
492
- if (marker.to > marker.from && marker.to <= state.doc.content.size)
493
- decorations.push(Decoration.node(marker.from, marker.to, { class: "poc-pagebreak-marker" }));
494
- }
495
- for (const lone of computePocLoneRows(state.doc)) {
496
- decorations.push(Decoration.node(lone.rowFrom, lone.rowTo, { class: "poc-lone-row" }));
497
- decorations.push(Decoration.node(lone.cellFrom, lone.cellTo, { class: "poc-lone-cell" }));
498
- }
499
- if (!decorations.length)
500
- return null;
501
- return DecorationSet.create(state.doc, decorations);
502
- }
503
- }
504
- });
505
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * Conteúdo de nota de rodapé: o template escreve HTML dentro de `<footnote>`
3
- * (negrito, itálico, links) e declara o corpo da nota em `style="font-size: 9pt"`.
4
- * O marcador do preview carrega esse conteúdo num atributo, então ele precisa
5
- * de um subconjunto seguro — atributo nenhum sobrevive, só as tags de ênfase.
6
- */
7
- /** HTML da nota reduzido a ênfase inline + links (o resto vira texto) */
8
- export declare function sanitizeFootnoteHtml(html: string): string;
9
- export interface FootnoteRun {
10
- text: string;
11
- bold?: boolean;
12
- italics?: boolean;
13
- underline?: boolean;
14
- break?: boolean;
15
- /** quando presente, o run vira ExternalHyperlink no .docx */
16
- href?: string;
17
- /** personalização por trecho (spans do popover) */
18
- font?: string;
19
- sizePt?: number;
20
- /** hex SEM '#' (formato do docx) */
21
- color?: string;
22
- }
23
- /** o mesmo HTML fatiado em runs — é assim que o .docx representa ênfase */
24
- export declare function footnoteHtmlToRuns(html: string): FootnoteRun[];
25
- /** tamanho declarado na nota (`font-size: 9pt`) em pontos */
26
- export declare function footnoteSizePt(style: string | null | undefined): number | null;