@meistrari/minuta-editor 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/chunks/docxExport.mjs +136 -25
- package/dist/docx.mjs +1 -1
- package/dist/index.d.mts +110 -3
- package/dist/index.d.ts +110 -3
- package/dist/index.mjs +436 -9
- package/dist/shared/{minuta-editor.b3351035.mjs → minuta-editor.f1239e0b.mjs} +81 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,13 @@ editor.setImageFallback('dataUri' | 'none')
|
|
|
61
61
|
editor.variablesInDocument() // → string[]
|
|
62
62
|
editor.refreshVaultImages(vaultUrl => Promise<string>) // renova o src das imagens do vault (data-vault-src ou src="vault://"); sem undo/onChange
|
|
63
63
|
|
|
64
|
+
// checklist junto da edição (o documento gerado vem do GET /data/case/:id/html?source=blocks)
|
|
65
|
+
editor.stampGenerated() // ao CRIAR a edição a partir do HTML do checklist: marca os blocos como "gerados"
|
|
66
|
+
editor.applyChecklist(htmlGerado, { toggled? }) // → { inserted, removed, regenerated, variablesUpdated, conflicts, … }
|
|
67
|
+
editor.checklistConflicts() // → [{ blockId, blockTitle, kind: 'replace' | 'remove' }]
|
|
68
|
+
editor.resolveChecklistConflict(blockId, 'keep' | 'apply')
|
|
69
|
+
editor.documentBlockIds() // → blocos no texto, na ordem
|
|
70
|
+
|
|
64
71
|
editor.schedulePaginate() // repagina (após mudar algo que afeta altura por fora)
|
|
65
72
|
editor.totalPages()
|
|
66
73
|
editor.destroy()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as footnoteHtmlToRuns, p as pocLoneCellIndex, a as formatTopicNumber, b as pocLooksLikeTitle, T as TOPIC_DEFAULT_GROUP } from '../shared/minuta-editor.
|
|
1
|
+
import { f as footnoteHtmlToRuns, p as pocLoneCellIndex, a as formatTopicNumber, b as pocLooksLikeTitle, c as pocNodeHasVisibleContent, d as pocNodePaintsBox, T as TOPIC_DEFAULT_GROUP } from '../shared/minuta-editor.f1239e0b.mjs';
|
|
2
2
|
import { BorderStyle, TextRun, ExternalHyperlink, Paragraph, Document, LevelFormat, AlignmentType, LevelSuffix, Header, Packer, LineRuleType, Table, TableRow, TableCell, WidthType, TableLayoutType, Footer, FootnoteReferenceRun, VerticalAlign, ImageRun, HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom, TextWrappingType, FrameAnchorType, PageNumber, HeadingLevel } from 'docx';
|
|
3
3
|
import '@tiptap/pm/state';
|
|
4
4
|
import '@tiptap/pm/view';
|
|
@@ -18,6 +18,48 @@ function topicSequenceFor(ctx, group, roman, number) {
|
|
|
18
18
|
seq.next = number + 1;
|
|
19
19
|
return seq.reference;
|
|
20
20
|
}
|
|
21
|
+
function listFormatFor(type) {
|
|
22
|
+
switch (type) {
|
|
23
|
+
case "a":
|
|
24
|
+
return LevelFormat.LOWER_LETTER;
|
|
25
|
+
case "A":
|
|
26
|
+
return LevelFormat.UPPER_LETTER;
|
|
27
|
+
case "i":
|
|
28
|
+
return LevelFormat.LOWER_ROMAN;
|
|
29
|
+
case "I":
|
|
30
|
+
return LevelFormat.UPPER_ROMAN;
|
|
31
|
+
default:
|
|
32
|
+
return LevelFormat.DECIMAL;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function listFormatFromCss(listStyleType) {
|
|
36
|
+
switch (listStyleType) {
|
|
37
|
+
case "lower-alpha":
|
|
38
|
+
case "lower-latin":
|
|
39
|
+
return LevelFormat.LOWER_LETTER;
|
|
40
|
+
case "upper-alpha":
|
|
41
|
+
case "upper-latin":
|
|
42
|
+
return LevelFormat.UPPER_LETTER;
|
|
43
|
+
case "lower-roman":
|
|
44
|
+
return LevelFormat.LOWER_ROMAN;
|
|
45
|
+
case "upper-roman":
|
|
46
|
+
return LevelFormat.UPPER_ROMAN;
|
|
47
|
+
default:
|
|
48
|
+
return LevelFormat.DECIMAL;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function listSequenceFor(ctx, list, run) {
|
|
52
|
+
const reference = `cdb-list-${ctx.listNumbering.size + 1}`;
|
|
53
|
+
const start = Number.parseInt(String(list.start ?? 1), 10);
|
|
54
|
+
ctx.listNumbering.set(reference, {
|
|
55
|
+
format: list.format,
|
|
56
|
+
// `start="0"` é válido (o Word aceita w:start=0); só o ausente/inválido vira 1
|
|
57
|
+
start: Number.isFinite(start) && start >= 0 ? start : 1,
|
|
58
|
+
suffix: list.suffix || ".",
|
|
59
|
+
run
|
|
60
|
+
});
|
|
61
|
+
return reference;
|
|
62
|
+
}
|
|
21
63
|
function leadingTopicNumber(node) {
|
|
22
64
|
for (let i = 0; i < node.childCount; i++) {
|
|
23
65
|
const child = node.child(i);
|
|
@@ -466,20 +508,28 @@ async function islandToDocx(node, pos, contentWidthPx, ctx) {
|
|
|
466
508
|
const paragraphs = [];
|
|
467
509
|
const liveLis = islandEl ? [...islandEl.querySelectorAll(":scope > ul > li, :scope > ol > li, :scope > li")] : [];
|
|
468
510
|
const parsedLis = [...el.querySelectorAll(":scope > li")];
|
|
469
|
-
let
|
|
511
|
+
let reference = null;
|
|
470
512
|
for (let i = 0; i < parsedLis.length; i++) {
|
|
471
513
|
const live = liveLis[i] ?? null;
|
|
472
514
|
const source = live ?? parsedLis[i];
|
|
473
515
|
const m = live ? measureEl(live) : null;
|
|
474
|
-
const
|
|
475
|
-
|
|
516
|
+
const markerRun = { color: m?.color, size: m?.sizeHalf, font: m ? primaryFont(m.cs) : void 0 };
|
|
517
|
+
if (tag === "ol" && !reference) {
|
|
518
|
+
const liveStyleType = liveLis[0] ? getComputedStyle(liveLis[0]).listStyleType : "";
|
|
519
|
+
reference = listSequenceFor(ctx, {
|
|
520
|
+
format: el.hasAttribute("type") ? listFormatFor(el.getAttribute("type")) : listFormatFromCss(liveStyleType),
|
|
521
|
+
start: el.getAttribute("start"),
|
|
522
|
+
suffix: el.getAttribute("data-list-suffix")
|
|
523
|
+
}, markerRun);
|
|
524
|
+
}
|
|
476
525
|
const runs = live ? domInlineToRuns(live) : [new TextRun({ text: source.textContent?.trim() || "", ...runOptions(parseStyle(source.getAttribute("style"))) })];
|
|
477
526
|
const indentPx = live && islandEl ? live.offsetLeft : 24;
|
|
478
527
|
paragraphs.push(new Paragraph({
|
|
479
528
|
children: [
|
|
480
|
-
new TextRun({ text:
|
|
529
|
+
...tag === "ul" ? [new TextRun({ text: "\u2022 ", ...markerRun })] : [],
|
|
481
530
|
...runs
|
|
482
531
|
],
|
|
532
|
+
numbering: reference ? { reference, level: 0 } : void 0,
|
|
483
533
|
spacing: m ? { after: m.afterTw, before: m.beforeTw, ...m.lineExactTw ? { line: m.lineExactTw, lineRule: LineRuleType.EXACT } : {} } : { after: 80 },
|
|
484
534
|
indent: { left: Math.round(indentPx * PX_TO_TWIP) },
|
|
485
535
|
alignment: m?.align,
|
|
@@ -694,6 +744,27 @@ async function flexRowToDocx(node, pos, contentWidthPx, ctx) {
|
|
|
694
744
|
}
|
|
695
745
|
return tables;
|
|
696
746
|
}
|
|
747
|
+
const INVISIBLE_NODE_TYPES = /* @__PURE__ */ new Set(["paragraph", "styledDiv", "missingImage", "hardBreak", "text"]);
|
|
748
|
+
function rowPaintsOrShowsAnything(row, rowPos, ctx) {
|
|
749
|
+
const paintsAt = (node, pos) => {
|
|
750
|
+
if (pocNodePaintsBox(node))
|
|
751
|
+
return true;
|
|
752
|
+
const dom = domAt(ctx, pos);
|
|
753
|
+
return Boolean(bordersFrom(measureAt(ctx, pos), node.attrs?.style)) || Boolean(cellShading(dom ? getComputedStyle(dom) : null));
|
|
754
|
+
};
|
|
755
|
+
if (paintsAt(row, rowPos))
|
|
756
|
+
return true;
|
|
757
|
+
let found = false;
|
|
758
|
+
row.descendants((node, offset) => {
|
|
759
|
+
if (found)
|
|
760
|
+
return false;
|
|
761
|
+
const pos = rowPos + 1 + offset;
|
|
762
|
+
if (!INVISIBLE_NODE_TYPES.has(node.type.name) || pocNodeHasVisibleContent(node) || paintsAt(node, pos))
|
|
763
|
+
found = true;
|
|
764
|
+
return !found;
|
|
765
|
+
});
|
|
766
|
+
return found;
|
|
767
|
+
}
|
|
697
768
|
function isPlainContainer(node) {
|
|
698
769
|
return node.type.name === "styledDiv" && !isFlexRow(node);
|
|
699
770
|
}
|
|
@@ -930,6 +1001,18 @@ async function nodeToDocx(node, pos, contentWidthPx, ctx) {
|
|
|
930
1001
|
}
|
|
931
1002
|
case "styledDiv": {
|
|
932
1003
|
if (isFlexRow(node)) {
|
|
1004
|
+
if (!rowPaintsOrShowsAnything(node, pos, ctx)) {
|
|
1005
|
+
const rowM2 = measureAt(ctx, pos);
|
|
1006
|
+
const breakBefore = consumeBreak(ctx);
|
|
1007
|
+
const before = breakBefore ? rowM2?.beforeTw ?? 0 : Math.max(0, (rowM2?.beforeTw ?? 0) - ctx.lastAfterTw);
|
|
1008
|
+
const lugar = new Paragraph({
|
|
1009
|
+
children: [],
|
|
1010
|
+
spacing: { after: rowM2?.afterTw ?? 0, before },
|
|
1011
|
+
pageBreakBefore: breakBefore
|
|
1012
|
+
});
|
|
1013
|
+
ctx.lastAfterTw = rowM2?.afterTw ?? 0;
|
|
1014
|
+
return [lugar];
|
|
1015
|
+
}
|
|
933
1016
|
const loneIndex = pocLoneCellIndex(node);
|
|
934
1017
|
if (loneIndex != null) {
|
|
935
1018
|
let childPos = pos + 1;
|
|
@@ -1012,31 +1095,41 @@ async function nodeToDocx(node, pos, contentWidthPx, ctx) {
|
|
|
1012
1095
|
case "bulletList":
|
|
1013
1096
|
case "orderedList": {
|
|
1014
1097
|
const paragraphs = [];
|
|
1015
|
-
|
|
1098
|
+
const ordered = node.type.name === "orderedList";
|
|
1099
|
+
let reference = null;
|
|
1016
1100
|
let liPos = pos + 1;
|
|
1017
1101
|
for (let i = 0; i < node.childCount; i++) {
|
|
1018
1102
|
const li = node.child(i);
|
|
1019
1103
|
let childPos = liPos + 1;
|
|
1104
|
+
let marked = false;
|
|
1020
1105
|
li.forEach((child) => {
|
|
1021
1106
|
if (child.isTextblock) {
|
|
1022
1107
|
const m = measureAt(ctx, childPos);
|
|
1023
1108
|
const base = m ? { bold: m.bold, italics: m.italics, color: m.color, size: m.sizeHalf } : parseStyle(child.attrs.style);
|
|
1024
|
-
const marker = node.type.name === "orderedList" ? `${n}. ` : "\u2022 ";
|
|
1025
1109
|
const liDom = domAt(ctx, liPos);
|
|
1026
1110
|
const indentPx = liDom?.offsetLeft ?? 48;
|
|
1027
1111
|
const liBreak = consumeBreak(ctx);
|
|
1112
|
+
if (ordered && !reference) {
|
|
1113
|
+
reference = listSequenceFor(
|
|
1114
|
+
ctx,
|
|
1115
|
+
{ format: listFormatFor(node.attrs.type), start: node.attrs.start },
|
|
1116
|
+
{ bold: base.bold, italics: base.italics, color: base.color, size: base.size, font: base.font }
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const bullet = !ordered && !marked ? [new TextRun({ text: "\u2022 ", ...runOptions(base) })] : [];
|
|
1028
1120
|
const liParagraph = new Paragraph({
|
|
1029
|
-
children: [
|
|
1121
|
+
children: [...bullet, ...inlineToRuns(child, base, ctx)],
|
|
1122
|
+
numbering: ordered && !marked && reference ? { reference, level: 0 } : void 0,
|
|
1030
1123
|
spacing: spacingFrom(m, child.attrs.style),
|
|
1031
1124
|
indent: { left: Math.round(indentPx * PX_TO_TWIP) },
|
|
1032
1125
|
pageBreakBefore: liBreak
|
|
1033
1126
|
});
|
|
1127
|
+
marked = true;
|
|
1034
1128
|
ctx.meta.set(liParagraph, { breakBefore: liBreak });
|
|
1035
1129
|
paragraphs.push(liParagraph);
|
|
1036
1130
|
}
|
|
1037
1131
|
childPos += child.nodeSize;
|
|
1038
1132
|
});
|
|
1039
|
-
n++;
|
|
1040
1133
|
liPos += li.nodeSize;
|
|
1041
1134
|
}
|
|
1042
1135
|
return paragraphs;
|
|
@@ -1355,7 +1448,8 @@ async function pocExportDocx(editor, margins, chrome, options = {}) {
|
|
|
1355
1448
|
meta: /* @__PURE__ */ new WeakMap(),
|
|
1356
1449
|
footnotes: [],
|
|
1357
1450
|
topicNumbering: /* @__PURE__ */ new Map(),
|
|
1358
|
-
topicSequences: /* @__PURE__ */ new Map()
|
|
1451
|
+
topicSequences: /* @__PURE__ */ new Map(),
|
|
1452
|
+
listNumbering: /* @__PURE__ */ new Map()
|
|
1359
1453
|
};
|
|
1360
1454
|
const children = [];
|
|
1361
1455
|
let pos = 0;
|
|
@@ -1404,21 +1498,38 @@ async function pocExportDocx(editor, margins, chrome, options = {}) {
|
|
|
1404
1498
|
}
|
|
1405
1499
|
},
|
|
1406
1500
|
footnotes: Object.keys(footnotes).length ? footnotes : void 0,
|
|
1407
|
-
numbering: ctx.topicNumbering.size ? {
|
|
1408
|
-
config: [
|
|
1409
|
-
reference,
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1501
|
+
numbering: ctx.topicNumbering.size || ctx.listNumbering.size ? {
|
|
1502
|
+
config: [
|
|
1503
|
+
...[...ctx.topicNumbering].map(([reference, { roman, start }]) => ({
|
|
1504
|
+
reference,
|
|
1505
|
+
levels: [{
|
|
1506
|
+
level: 0,
|
|
1507
|
+
format: roman ? LevelFormat.UPPER_ROMAN : LevelFormat.DECIMAL,
|
|
1508
|
+
text: "%1",
|
|
1509
|
+
start,
|
|
1510
|
+
alignment: AlignmentType.START,
|
|
1511
|
+
// o ". " depois do número é texto do template; e sem
|
|
1512
|
+
// recuo/tab o parágrafo fica igual ao preview
|
|
1513
|
+
suffix: LevelSuffix.NOTHING,
|
|
1514
|
+
style: { paragraph: { indent: { left: 0, hanging: 0 } } }
|
|
1515
|
+
}]
|
|
1516
|
+
})),
|
|
1517
|
+
...[...ctx.listNumbering].map(([reference, { format, start, suffix, run }]) => ({
|
|
1518
|
+
reference,
|
|
1519
|
+
levels: [{
|
|
1520
|
+
level: 0,
|
|
1521
|
+
format,
|
|
1522
|
+
text: `%1${suffix}`,
|
|
1523
|
+
start,
|
|
1524
|
+
alignment: AlignmentType.LEFT,
|
|
1525
|
+
// "a. texto": marcador colado ao item com um espaço,
|
|
1526
|
+
// como o preview (list-style-position: inside); o
|
|
1527
|
+
// recuo é o do parágrafo, não da sequência
|
|
1528
|
+
suffix: LevelSuffix.SPACE,
|
|
1529
|
+
style: { paragraph: { indent: { left: 0, hanging: 0 } }, run }
|
|
1530
|
+
}]
|
|
1531
|
+
}))
|
|
1532
|
+
]
|
|
1422
1533
|
} : void 0,
|
|
1423
1534
|
sections: [{
|
|
1424
1535
|
properties: {
|
package/dist/docx.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -22,7 +22,7 @@ import { Transaction } from '@tiptap/pm/state';
|
|
|
22
22
|
import { EditorView } from '@tiptap/pm/view';
|
|
23
23
|
import { P as PocPageConfig } from './shared/minuta-editor.9e65d163.mjs';
|
|
24
24
|
export { e as PAGINATE_META, a as PocDocxChrome, b as PocDocxExportOptions, s as PocLoneRow, d as PocPageBreak, c as PocPageNote, v as PocPaginationPlugin, u as breaksEqual, h as buildPocFooterEl, g as buildPocHeaderEl, j as buildPocNotesEl, l as computePocBreaks, t as computePocLoneRows, i as isPocChromeCanvas, n as isPocFlexRow, f as pocChromeFlowHeight, m as pocDescendsInto, r as pocLoneCellIndex, k as pocLooksLikeTitle, o as pocNodeHasVisibleContent, q as pocNodePaintsBox, p as pocPageKey } from './shared/minuta-editor.9e65d163.mjs';
|
|
25
|
-
import '@tiptap/pm/model';
|
|
25
|
+
import { Node as Node$1 } from '@tiptap/pm/model';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Variáveis de template da minuta — o formato do placeholder e a resolução dele.
|
|
@@ -206,6 +206,84 @@ declare function buildPocExtensions(pageConfig?: PocPageConfig, options?: {
|
|
|
206
206
|
handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
207
207
|
}): (Node<any, any> | Extension<any, any> | Mark<any, any>)[];
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Impressão digital de um bloco da minuta (nó `minutaBlock` do editor) — base
|
|
211
|
+
* de "o advogado mexeu neste trecho?" quando o checklist muda com uma edição
|
|
212
|
+
* viva. O bloco nasce com `genHash` = impressão do texto gerado; enquanto a
|
|
213
|
+
* impressão atual bater com ela, o trecho é do checklist e pode ser
|
|
214
|
+
* re-gerado sem perder nada.
|
|
215
|
+
*
|
|
216
|
+
* Fica de fora o que muda SEM o advogado editar:
|
|
217
|
+
* - URL descartável de imagem (blob:/placeholder do load progressivo/vault:// do
|
|
218
|
+
* save): a imagem entra pela identidade DURÁVEL (`imageIdentity`), então
|
|
219
|
+
* trocar a imagem de verdade muda a impressão;
|
|
220
|
+
* - número de tópico e de nota (re-sequenciam quando outro trecho muda);
|
|
221
|
+
* - valor das variáveis (o chip é atualizado no lugar, em qualquer bloco —
|
|
222
|
+
* inclusive dentro de lista/tabela guardada como HTML cru).
|
|
223
|
+
*
|
|
224
|
+
* Mesma regra em `client/utils/minutaBlockFingerprint.ts` (o app ainda mantém a
|
|
225
|
+
* cópia dele) — a impressão gravada por um tem que bater com a do outro.
|
|
226
|
+
*
|
|
227
|
+
* Recebe o JSON do nó (`node.toJSON()`), então é pura e não depende de DOM.
|
|
228
|
+
*/
|
|
229
|
+
interface NodeJson {
|
|
230
|
+
type: string;
|
|
231
|
+
attrs?: Record<string, any>;
|
|
232
|
+
content?: NodeJson[];
|
|
233
|
+
marks?: {
|
|
234
|
+
type: string;
|
|
235
|
+
attrs?: Record<string, any>;
|
|
236
|
+
}[];
|
|
237
|
+
text?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Identidade durável de uma imagem a partir do `src` (e do `data-vault-src` do
|
|
241
|
+
* placeholder do load progressivo) — no app, blob: do cache → vault://.
|
|
242
|
+
*/
|
|
243
|
+
type ImageIdentity = (src: string) => string;
|
|
244
|
+
declare function minutaBlockFingerprint(blockJson: NodeJson, identity?: ImageIdentity): string;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Checklist × edição viva, para quem usa o editor fora do defesa.ai.
|
|
248
|
+
*
|
|
249
|
+
* O documento editado continua dividido em `minutaBlock` (um por bloco do
|
|
250
|
+
* template, `isolating`), e cada bloco guarda em `genHash` a impressão digital
|
|
251
|
+
* do texto que o checklist gerou. Com isso, quando o checklist muda (tese que
|
|
252
|
+
* passou a valer, variável alterada), dá para aplicar a mudança SÓ onde ela
|
|
253
|
+
* cai, sem passar por cima do que o advogado escreveu:
|
|
254
|
+
*
|
|
255
|
+
* - tese que passou a gerar texto → o bloco entra na posição do template;
|
|
256
|
+
* - tese que deixou de gerar → o bloco sai (se foi editado, vira conflito);
|
|
257
|
+
* - variável alterada → o chip atualiza no lugar, em qualquer bloco;
|
|
258
|
+
* - bloco cuja ESTRUTURA mudou → re-gerado se ninguém o editou, conflito se sim.
|
|
259
|
+
*
|
|
260
|
+
* Mesma lógica de `client/composables/poc/pocEditionSync.ts` + o
|
|
261
|
+
* `syncEditionWithChecklist` do app; aqui o documento gerado chega pronto (o
|
|
262
|
+
* `GET /data/case/:id/html?source=blocks`), em vez de renderizado no browser.
|
|
263
|
+
*/
|
|
264
|
+
interface MinutaChecklistConflict {
|
|
265
|
+
blockId: string;
|
|
266
|
+
blockTitle: string | null;
|
|
267
|
+
/** `replace`: o checklist mudou o texto de um trecho editado; `remove`: desligou uma tese editada */
|
|
268
|
+
kind: 'replace' | 'remove';
|
|
269
|
+
}
|
|
270
|
+
interface MinutaChecklistResult {
|
|
271
|
+
inserted: string[];
|
|
272
|
+
removed: string[];
|
|
273
|
+
regenerated: string[];
|
|
274
|
+
/** quantos chips de variável mudaram de valor */
|
|
275
|
+
variablesUpdated: number;
|
|
276
|
+
/** conflitos abertos depois desta aplicação (inclui os que já estavam) */
|
|
277
|
+
conflicts: MinutaChecklistConflict[];
|
|
278
|
+
/** a tese pedida em `toggled.show` não gera texto (condições internas) */
|
|
279
|
+
toggledWithoutContent: boolean;
|
|
280
|
+
/** a remoção não aconteceu porque o documento ficaria sem nenhum bloco */
|
|
281
|
+
lastBlockKept: boolean;
|
|
282
|
+
}
|
|
283
|
+
declare function blockHash(node: Node$1, identity?: ImageIdentity): string;
|
|
284
|
+
/** o conteúdo ainda é o que o checklist gerou (sem `genHash` = editado, por segurança) */
|
|
285
|
+
declare function isBlockUntouched(node: Node$1, identity?: ImageIdentity): boolean;
|
|
286
|
+
|
|
209
287
|
/**
|
|
210
288
|
* O editor de minuta como coisa montável em qualquer lugar: `createMinutaEditor(el, opts)`.
|
|
211
289
|
*
|
|
@@ -374,6 +452,35 @@ interface MinutaEditor {
|
|
|
374
452
|
* guarda uma cópia do documento.
|
|
375
453
|
*/
|
|
376
454
|
refreshVaultImages: (resolveUrl: (vaultUrl: string) => Promise<string>) => Promise<number>;
|
|
455
|
+
/**
|
|
456
|
+
* Checklist junto da edição: aplica o documento GERADO pelo checklist agora
|
|
457
|
+
* (o `GET /data/case/:id/html?source=blocks`) sobre o documento editado,
|
|
458
|
+
* bloco a bloco — tese que passou a valer entra na posição do template, a
|
|
459
|
+
* que deixou de valer sai, variáveis atualizam no lugar e trecho cuja
|
|
460
|
+
* estrutura mudou é re-gerado só se ninguém o editou (senão vira conflito).
|
|
461
|
+
* `toggled` força a decisão de UMA tese (o host ligou/desligou na mão).
|
|
462
|
+
* Uma transação só: o desfazer volta tudo. Blocos novos (e os aceitos em
|
|
463
|
+
* `resolveChecklistConflict`) podem trazer imagem `vault://` — chame
|
|
464
|
+
* `refreshVaultImages` depois de aplicar E de resolver os conflitos.
|
|
465
|
+
*/
|
|
466
|
+
applyChecklist: (generatedHtml: string, options?: {
|
|
467
|
+
toggled?: {
|
|
468
|
+
blockId: string;
|
|
469
|
+
show: boolean;
|
|
470
|
+
};
|
|
471
|
+
}) => MinutaChecklistResult;
|
|
472
|
+
/** trechos editados que o checklist quer mudar e aguardam decisão do advogado */
|
|
473
|
+
checklistConflicts: () => MinutaChecklistConflict[];
|
|
474
|
+
/** `apply` usa o texto novo (ou remove); `keep` mantém o editado e não pergunta de novo pela mesma mudança */
|
|
475
|
+
resolveChecklistConflict: (blockId: string, resolution: 'keep' | 'apply') => boolean;
|
|
476
|
+
/**
|
|
477
|
+
* Marca todos os blocos como "texto gerado". Chame UMA vez ao criar a edição
|
|
478
|
+
* a partir do HTML do checklist — sem a marca, todo bloco conta como editado
|
|
479
|
+
* e o `applyChecklist` pergunta em vez de re-gerar. Devolve quantos marcou.
|
|
480
|
+
*/
|
|
481
|
+
stampGenerated: () => number;
|
|
482
|
+
/** blocos (`data-block-id`) no documento, na ordem — para o checklist mostrar o que está no texto */
|
|
483
|
+
documentBlockIds: () => string[];
|
|
377
484
|
/** repagina (o host chama após mudar algo que afeta altura fora do editor) */
|
|
378
485
|
schedulePaginate: () => void;
|
|
379
486
|
/** reconstrói cabeçalhos/rodapés com o chrome atual, sem repaginar */
|
|
@@ -561,5 +668,5 @@ declare function collectVaultRefs(html: string): string[];
|
|
|
561
668
|
/** aplica as URLs resolvidas às `<img>` do trecho; tags sem resolução ficam como estão */
|
|
562
669
|
declare function renewVaultImageTags(html: string, resolved: Map<string, string>): string;
|
|
563
670
|
|
|
564
|
-
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPageConfig, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, buildPocExtensions, collectVaultRefs, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, footnoteHtmlToRuns, footnoteSizePt, formatTopicNumber, fromRoman, iconSvg, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, shouldMoveBreakToHeading, swapVaultSrc, toRoman, topicNumberHtml, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
565
|
-
export type { FootnoteRun, MinutaChromeText, MinutaEditor, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, PocKeepWithNextItem, RefreshVaultImagesOptions, TopicNumberInfo, VariableClickKind };
|
|
671
|
+
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPageConfig, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, blockHash, buildPocExtensions, collectVaultRefs, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, footnoteHtmlToRuns, footnoteSizePt, formatTopicNumber, fromRoman, iconSvg, isBlockUntouched, minutaBlockFingerprint, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, shouldMoveBreakToHeading, swapVaultSrc, toRoman, topicNumberHtml, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
672
|
+
export type { FootnoteRun, ImageIdentity, MinutaChecklistConflict, MinutaChecklistResult, MinutaChromeText, MinutaEditor, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, PocKeepWithNextItem, RefreshVaultImagesOptions, TopicNumberInfo, VariableClickKind };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { Transaction } from '@tiptap/pm/state';
|
|
|
22
22
|
import { EditorView } from '@tiptap/pm/view';
|
|
23
23
|
import { P as PocPageConfig } from './shared/minuta-editor.9e65d163.js';
|
|
24
24
|
export { e as PAGINATE_META, a as PocDocxChrome, b as PocDocxExportOptions, s as PocLoneRow, d as PocPageBreak, c as PocPageNote, v as PocPaginationPlugin, u as breaksEqual, h as buildPocFooterEl, g as buildPocHeaderEl, j as buildPocNotesEl, l as computePocBreaks, t as computePocLoneRows, i as isPocChromeCanvas, n as isPocFlexRow, f as pocChromeFlowHeight, m as pocDescendsInto, r as pocLoneCellIndex, k as pocLooksLikeTitle, o as pocNodeHasVisibleContent, q as pocNodePaintsBox, p as pocPageKey } from './shared/minuta-editor.9e65d163.js';
|
|
25
|
-
import '@tiptap/pm/model';
|
|
25
|
+
import { Node as Node$1 } from '@tiptap/pm/model';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Variáveis de template da minuta — o formato do placeholder e a resolução dele.
|
|
@@ -206,6 +206,84 @@ declare function buildPocExtensions(pageConfig?: PocPageConfig, options?: {
|
|
|
206
206
|
handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
207
207
|
}): (Node<any, any> | Extension<any, any> | Mark<any, any>)[];
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Impressão digital de um bloco da minuta (nó `minutaBlock` do editor) — base
|
|
211
|
+
* de "o advogado mexeu neste trecho?" quando o checklist muda com uma edição
|
|
212
|
+
* viva. O bloco nasce com `genHash` = impressão do texto gerado; enquanto a
|
|
213
|
+
* impressão atual bater com ela, o trecho é do checklist e pode ser
|
|
214
|
+
* re-gerado sem perder nada.
|
|
215
|
+
*
|
|
216
|
+
* Fica de fora o que muda SEM o advogado editar:
|
|
217
|
+
* - URL descartável de imagem (blob:/placeholder do load progressivo/vault:// do
|
|
218
|
+
* save): a imagem entra pela identidade DURÁVEL (`imageIdentity`), então
|
|
219
|
+
* trocar a imagem de verdade muda a impressão;
|
|
220
|
+
* - número de tópico e de nota (re-sequenciam quando outro trecho muda);
|
|
221
|
+
* - valor das variáveis (o chip é atualizado no lugar, em qualquer bloco —
|
|
222
|
+
* inclusive dentro de lista/tabela guardada como HTML cru).
|
|
223
|
+
*
|
|
224
|
+
* Mesma regra em `client/utils/minutaBlockFingerprint.ts` (o app ainda mantém a
|
|
225
|
+
* cópia dele) — a impressão gravada por um tem que bater com a do outro.
|
|
226
|
+
*
|
|
227
|
+
* Recebe o JSON do nó (`node.toJSON()`), então é pura e não depende de DOM.
|
|
228
|
+
*/
|
|
229
|
+
interface NodeJson {
|
|
230
|
+
type: string;
|
|
231
|
+
attrs?: Record<string, any>;
|
|
232
|
+
content?: NodeJson[];
|
|
233
|
+
marks?: {
|
|
234
|
+
type: string;
|
|
235
|
+
attrs?: Record<string, any>;
|
|
236
|
+
}[];
|
|
237
|
+
text?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Identidade durável de uma imagem a partir do `src` (e do `data-vault-src` do
|
|
241
|
+
* placeholder do load progressivo) — no app, blob: do cache → vault://.
|
|
242
|
+
*/
|
|
243
|
+
type ImageIdentity = (src: string) => string;
|
|
244
|
+
declare function minutaBlockFingerprint(blockJson: NodeJson, identity?: ImageIdentity): string;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Checklist × edição viva, para quem usa o editor fora do defesa.ai.
|
|
248
|
+
*
|
|
249
|
+
* O documento editado continua dividido em `minutaBlock` (um por bloco do
|
|
250
|
+
* template, `isolating`), e cada bloco guarda em `genHash` a impressão digital
|
|
251
|
+
* do texto que o checklist gerou. Com isso, quando o checklist muda (tese que
|
|
252
|
+
* passou a valer, variável alterada), dá para aplicar a mudança SÓ onde ela
|
|
253
|
+
* cai, sem passar por cima do que o advogado escreveu:
|
|
254
|
+
*
|
|
255
|
+
* - tese que passou a gerar texto → o bloco entra na posição do template;
|
|
256
|
+
* - tese que deixou de gerar → o bloco sai (se foi editado, vira conflito);
|
|
257
|
+
* - variável alterada → o chip atualiza no lugar, em qualquer bloco;
|
|
258
|
+
* - bloco cuja ESTRUTURA mudou → re-gerado se ninguém o editou, conflito se sim.
|
|
259
|
+
*
|
|
260
|
+
* Mesma lógica de `client/composables/poc/pocEditionSync.ts` + o
|
|
261
|
+
* `syncEditionWithChecklist` do app; aqui o documento gerado chega pronto (o
|
|
262
|
+
* `GET /data/case/:id/html?source=blocks`), em vez de renderizado no browser.
|
|
263
|
+
*/
|
|
264
|
+
interface MinutaChecklistConflict {
|
|
265
|
+
blockId: string;
|
|
266
|
+
blockTitle: string | null;
|
|
267
|
+
/** `replace`: o checklist mudou o texto de um trecho editado; `remove`: desligou uma tese editada */
|
|
268
|
+
kind: 'replace' | 'remove';
|
|
269
|
+
}
|
|
270
|
+
interface MinutaChecklistResult {
|
|
271
|
+
inserted: string[];
|
|
272
|
+
removed: string[];
|
|
273
|
+
regenerated: string[];
|
|
274
|
+
/** quantos chips de variável mudaram de valor */
|
|
275
|
+
variablesUpdated: number;
|
|
276
|
+
/** conflitos abertos depois desta aplicação (inclui os que já estavam) */
|
|
277
|
+
conflicts: MinutaChecklistConflict[];
|
|
278
|
+
/** a tese pedida em `toggled.show` não gera texto (condições internas) */
|
|
279
|
+
toggledWithoutContent: boolean;
|
|
280
|
+
/** a remoção não aconteceu porque o documento ficaria sem nenhum bloco */
|
|
281
|
+
lastBlockKept: boolean;
|
|
282
|
+
}
|
|
283
|
+
declare function blockHash(node: Node$1, identity?: ImageIdentity): string;
|
|
284
|
+
/** o conteúdo ainda é o que o checklist gerou (sem `genHash` = editado, por segurança) */
|
|
285
|
+
declare function isBlockUntouched(node: Node$1, identity?: ImageIdentity): boolean;
|
|
286
|
+
|
|
209
287
|
/**
|
|
210
288
|
* O editor de minuta como coisa montável em qualquer lugar: `createMinutaEditor(el, opts)`.
|
|
211
289
|
*
|
|
@@ -374,6 +452,35 @@ interface MinutaEditor {
|
|
|
374
452
|
* guarda uma cópia do documento.
|
|
375
453
|
*/
|
|
376
454
|
refreshVaultImages: (resolveUrl: (vaultUrl: string) => Promise<string>) => Promise<number>;
|
|
455
|
+
/**
|
|
456
|
+
* Checklist junto da edição: aplica o documento GERADO pelo checklist agora
|
|
457
|
+
* (o `GET /data/case/:id/html?source=blocks`) sobre o documento editado,
|
|
458
|
+
* bloco a bloco — tese que passou a valer entra na posição do template, a
|
|
459
|
+
* que deixou de valer sai, variáveis atualizam no lugar e trecho cuja
|
|
460
|
+
* estrutura mudou é re-gerado só se ninguém o editou (senão vira conflito).
|
|
461
|
+
* `toggled` força a decisão de UMA tese (o host ligou/desligou na mão).
|
|
462
|
+
* Uma transação só: o desfazer volta tudo. Blocos novos (e os aceitos em
|
|
463
|
+
* `resolveChecklistConflict`) podem trazer imagem `vault://` — chame
|
|
464
|
+
* `refreshVaultImages` depois de aplicar E de resolver os conflitos.
|
|
465
|
+
*/
|
|
466
|
+
applyChecklist: (generatedHtml: string, options?: {
|
|
467
|
+
toggled?: {
|
|
468
|
+
blockId: string;
|
|
469
|
+
show: boolean;
|
|
470
|
+
};
|
|
471
|
+
}) => MinutaChecklistResult;
|
|
472
|
+
/** trechos editados que o checklist quer mudar e aguardam decisão do advogado */
|
|
473
|
+
checklistConflicts: () => MinutaChecklistConflict[];
|
|
474
|
+
/** `apply` usa o texto novo (ou remove); `keep` mantém o editado e não pergunta de novo pela mesma mudança */
|
|
475
|
+
resolveChecklistConflict: (blockId: string, resolution: 'keep' | 'apply') => boolean;
|
|
476
|
+
/**
|
|
477
|
+
* Marca todos os blocos como "texto gerado". Chame UMA vez ao criar a edição
|
|
478
|
+
* a partir do HTML do checklist — sem a marca, todo bloco conta como editado
|
|
479
|
+
* e o `applyChecklist` pergunta em vez de re-gerar. Devolve quantos marcou.
|
|
480
|
+
*/
|
|
481
|
+
stampGenerated: () => number;
|
|
482
|
+
/** blocos (`data-block-id`) no documento, na ordem — para o checklist mostrar o que está no texto */
|
|
483
|
+
documentBlockIds: () => string[];
|
|
377
484
|
/** repagina (o host chama após mudar algo que afeta altura fora do editor) */
|
|
378
485
|
schedulePaginate: () => void;
|
|
379
486
|
/** reconstrói cabeçalhos/rodapés com o chrome atual, sem repaginar */
|
|
@@ -561,5 +668,5 @@ declare function collectVaultRefs(html: string): string[];
|
|
|
561
668
|
/** aplica as URLs resolvidas às `<img>` do trecho; tags sem resolução ficam como estão */
|
|
562
669
|
declare function renewVaultImageTags(html: string, resolved: Map<string, string>): string;
|
|
563
670
|
|
|
564
|
-
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPageConfig, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, buildPocExtensions, collectVaultRefs, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, footnoteHtmlToRuns, footnoteSizePt, formatTopicNumber, fromRoman, iconSvg, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, shouldMoveBreakToHeading, swapVaultSrc, toRoman, topicNumberHtml, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
565
|
-
export type { FootnoteRun, MinutaChromeText, MinutaEditor, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, PocKeepWithNextItem, RefreshVaultImagesOptions, TopicNumberInfo, VariableClickKind };
|
|
671
|
+
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPageConfig, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, blockHash, buildPocExtensions, collectVaultRefs, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, footnoteHtmlToRuns, footnoteSizePt, formatTopicNumber, fromRoman, iconSvg, isBlockUntouched, minutaBlockFingerprint, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, shouldMoveBreakToHeading, swapVaultSrc, toRoman, topicNumberHtml, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
672
|
+
export type { FootnoteRun, ImageIdentity, MinutaChecklistConflict, MinutaChecklistResult, MinutaChromeText, MinutaEditor, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, PocKeepWithNextItem, RefreshVaultImagesOptions, TopicNumberInfo, VariableClickKind };
|
package/dist/index.mjs
CHANGED
|
@@ -22,8 +22,10 @@ import TableCell from '@tiptap/extension-table-cell';
|
|
|
22
22
|
import TableHeader from '@tiptap/extension-table-header';
|
|
23
23
|
import TableRow from '@tiptap/extension-table-row';
|
|
24
24
|
import { Plugin } from '@tiptap/pm/state';
|
|
25
|
-
import { s as sanitizeRichHtml, T as TOPIC_DEFAULT_GROUP,
|
|
26
|
-
export {
|
|
25
|
+
import { s as sanitizeRichHtml, e as sanitizeFootnoteHtml, g as footnoteSizePt, T as TOPIC_DEFAULT_GROUP, h as TOPIC_NUMBER_ATTR, r as readTopicNumber, i as TOPIC_GROUP_ATTR, j as TOPIC_ROMAN_ATTR, k as TOPIC_REF_ATTR, a as formatTopicNumber, P as PocPaginationPlugin, l as computePocBreaks, m as pocPageKey, n as breaksEqual, o as PAGINATE_META } from './shared/minuta-editor.f1239e0b.mjs';
|
|
26
|
+
export { v as buildPocFooterEl, u as buildPocHeaderEl, w as buildPocNotesEl, z as computePocLoneRows, f as footnoteHtmlToRuns, C as fromRoman, t as isPocChromeCanvas, y as isPocFlexRow, q as pocChromeFlowHeight, x as pocDescendsInto, p as pocLoneCellIndex, b as pocLooksLikeTitle, c as pocNodeHasVisibleContent, d as pocNodePaintsBox, A as shouldMoveBreakToHeading, B as toRoman, D as topicNumberHtml } from './shared/minuta-editor.f1239e0b.mjs';
|
|
27
|
+
import { closeHistory } from '@tiptap/pm/history';
|
|
28
|
+
import { DOMParser } from '@tiptap/pm/model';
|
|
27
29
|
import '@tiptap/pm/view';
|
|
28
30
|
|
|
29
31
|
const POC_ASSET_SWAP = "pocAssetSwap";
|
|
@@ -35,7 +37,18 @@ function htmlToDom(html) {
|
|
|
35
37
|
const PocDocument = Node$1.create({
|
|
36
38
|
name: "doc",
|
|
37
39
|
topNode: true,
|
|
38
|
-
content: "minutaBlock+"
|
|
40
|
+
content: "minutaBlock+",
|
|
41
|
+
addAttributes() {
|
|
42
|
+
return {
|
|
43
|
+
/**
|
|
44
|
+
* Estado do checklist × edição (engine/checklistSync): teses que o
|
|
45
|
+
* checklist queria no texto na última aplicação + conflitos abertos.
|
|
46
|
+
* Vive NO documento para o desfazer do editor voltar junto com o
|
|
47
|
+
* texto; não vai para o HTML (sem documento salvo, nasce do texto).
|
|
48
|
+
*/
|
|
49
|
+
checklist: { default: null, rendered: false, parseHTML: () => null }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
39
52
|
});
|
|
40
53
|
const PocMinutaBlock = Node$1.create({
|
|
41
54
|
name: "minutaBlock",
|
|
@@ -46,7 +59,17 @@ const PocMinutaBlock = Node$1.create({
|
|
|
46
59
|
addAttributes() {
|
|
47
60
|
return {
|
|
48
61
|
blockId: { default: null },
|
|
49
|
-
blockTitle: { default: null }
|
|
62
|
+
blockTitle: { default: null },
|
|
63
|
+
/**
|
|
64
|
+
* impressão digital do texto GERADO pelo checklist (ver
|
|
65
|
+
* utils/minutaBlockFingerprint no app): enquanto o conteúdo bater
|
|
66
|
+
* com ela o trecho não foi editado e o checklist pode re-gerá-lo
|
|
67
|
+
*/
|
|
68
|
+
genHash: {
|
|
69
|
+
default: null,
|
|
70
|
+
parseHTML: (el) => el.getAttribute("data-gen-hash"),
|
|
71
|
+
renderHTML: (attrs) => attrs.genHash ? { "data-gen-hash": attrs.genHash } : {}
|
|
72
|
+
}
|
|
50
73
|
};
|
|
51
74
|
},
|
|
52
75
|
parseHTML() {
|
|
@@ -281,6 +304,14 @@ const PocVariableChip = Node$1.create({
|
|
|
281
304
|
return {
|
|
282
305
|
variableId: { default: null },
|
|
283
306
|
parentBlockId: { default: null },
|
|
307
|
+
/**
|
|
308
|
+
* linha do `<for>` — distingue a mesma variável em linhas diferentes
|
|
309
|
+
* (`data-loop-index` do app; `data-variable-index` da Data API)
|
|
310
|
+
*/
|
|
311
|
+
loopIndex: {
|
|
312
|
+
default: null,
|
|
313
|
+
parseHTML: (el) => el.getAttribute("data-loop-index") ?? el.getAttribute("data-variable-index")
|
|
314
|
+
},
|
|
284
315
|
html: { default: "" },
|
|
285
316
|
text: { default: "" }
|
|
286
317
|
};
|
|
@@ -292,6 +323,7 @@ const PocVariableChip = Node$1.create({
|
|
|
292
323
|
getAttrs: (el) => ({
|
|
293
324
|
variableId: el.getAttribute("data-variable-id"),
|
|
294
325
|
parentBlockId: el.getAttribute("data-parent-block-id"),
|
|
326
|
+
loopIndex: el.getAttribute("data-loop-index") ?? el.getAttribute("data-variable-index"),
|
|
295
327
|
html: el.outerHTML,
|
|
296
328
|
text: el.textContent ?? ""
|
|
297
329
|
})
|
|
@@ -408,7 +440,9 @@ const PocFootnoteRef = Node$1.create({
|
|
|
408
440
|
selectable: false,
|
|
409
441
|
addAttributes() {
|
|
410
442
|
return {
|
|
411
|
-
|
|
443
|
+
// o Tiptap relê `number="1"` do `<footnote>` e o converte em número —
|
|
444
|
+
// e o render do ProseMirror só aceita texto como conteúdo
|
|
445
|
+
number: { default: "", parseHTML: (el) => el.getAttribute("number") ?? el.textContent ?? "" },
|
|
412
446
|
/** conteúdo da nota em HTML de ênfase (b/i/u/br) */
|
|
413
447
|
text: { default: "" },
|
|
414
448
|
/** corpo declarado no template (`font-size: 9pt`), em pontos */
|
|
@@ -424,6 +458,17 @@ const PocFootnoteRef = Node$1.create({
|
|
|
424
458
|
text: el.getAttribute("data-footnote") ?? "",
|
|
425
459
|
size: Number.parseFloat(el.getAttribute("data-footnote-size") ?? "") || null
|
|
426
460
|
})
|
|
461
|
+
}, {
|
|
462
|
+
// `<footnote>` cru do template (HTML de servidor antigo ou de
|
|
463
|
+
// outra origem): sem esta regra a tag some e o texto da nota
|
|
464
|
+
// cai no meio do parágrafo
|
|
465
|
+
tag: "footnote",
|
|
466
|
+
priority: 110,
|
|
467
|
+
getAttrs: (el) => ({
|
|
468
|
+
number: el.getAttribute("number") ?? "",
|
|
469
|
+
text: sanitizeFootnoteHtml(el.innerHTML.replace(/ /g, " ")),
|
|
470
|
+
size: footnoteSizePt(el.getAttribute("style"))
|
|
471
|
+
})
|
|
427
472
|
}];
|
|
428
473
|
},
|
|
429
474
|
renderHTML({ node }) {
|
|
@@ -705,7 +750,7 @@ const VARIABLE_TAG_RE = /<span\b[^>]*\bdata-minuta-var="([^"]*)"[^>]*>[^<]*<\/sp
|
|
|
705
750
|
function escapeHtml(value) {
|
|
706
751
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
707
752
|
}
|
|
708
|
-
function decodeAttr(value) {
|
|
753
|
+
function decodeAttr$1(value) {
|
|
709
754
|
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
710
755
|
}
|
|
711
756
|
function extractTemplateVariables(html) {
|
|
@@ -713,7 +758,7 @@ function extractTemplateVariables(html) {
|
|
|
713
758
|
return [];
|
|
714
759
|
const keys = [];
|
|
715
760
|
for (const match of html.matchAll(VARIABLE_TAG_RE)) {
|
|
716
|
-
const key = decodeAttr(match[1] ?? "").trim();
|
|
761
|
+
const key = decodeAttr$1(match[1] ?? "").trim();
|
|
717
762
|
if (key && !keys.includes(key))
|
|
718
763
|
keys.push(key);
|
|
719
764
|
}
|
|
@@ -724,7 +769,7 @@ function resolveTemplateVariables(html, values) {
|
|
|
724
769
|
return { html: "", missing: [] };
|
|
725
770
|
const missing = [];
|
|
726
771
|
const resolved = html.replace(VARIABLE_TAG_RE, (tag, rawKey) => {
|
|
727
|
-
const key = decodeAttr(rawKey ?? "").trim();
|
|
772
|
+
const key = decodeAttr$1(rawKey ?? "").trim();
|
|
728
773
|
if (!key)
|
|
729
774
|
return tag;
|
|
730
775
|
const value = values?.[key];
|
|
@@ -841,6 +886,362 @@ async function refreshVaultImages(editor, resolveUrl, options = {}) {
|
|
|
841
886
|
return touched;
|
|
842
887
|
}
|
|
843
888
|
|
|
889
|
+
const IMG_TAG = /<img\b[^>]*>/g;
|
|
890
|
+
const ATTR = (name) => new RegExp(`\\s${name}="([^"]*)"`);
|
|
891
|
+
function decodeAttr(value) {
|
|
892
|
+
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
893
|
+
}
|
|
894
|
+
function normalizeImgTags(html, identity) {
|
|
895
|
+
if (!html.includes("<img"))
|
|
896
|
+
return html;
|
|
897
|
+
return html.replace(IMG_TAG, (tag) => {
|
|
898
|
+
const vault = tag.match(ATTR("data-vault-src"))?.[1];
|
|
899
|
+
const src = tag.match(ATTR("src"))?.[1] ?? "";
|
|
900
|
+
const id = vault ? decodeAttr(vault) : identity(decodeAttr(src));
|
|
901
|
+
return tag.replace(/\s(?:src|data-vault-src|data-download-url)="[^"]*"/g, "").replace(/^<img\b/, `<img data-img="${id}"`);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const CHIP_SPAN = /<span\b([^>]*\sdata-variable-id="[^"]*"[^>]*)>[\s\S]*?<\/span>/g;
|
|
905
|
+
function normalizeChipSpans(html) {
|
|
906
|
+
if (!html.includes("data-variable-id"))
|
|
907
|
+
return html;
|
|
908
|
+
return html.replace(CHIP_SPAN, (_all, attrs) => {
|
|
909
|
+
const id = attrs.match(/\sdata-variable-id="([^"]*)"/)?.[1] ?? "";
|
|
910
|
+
const index = attrs.match(/\sdata-(?:loop|variable)-index="([^"]*)"/)?.[1] ?? "";
|
|
911
|
+
return `<var:${id}:${index}>`;
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function normalizeAttrs(attrs, identity, chipSpans = true) {
|
|
915
|
+
if (!attrs)
|
|
916
|
+
return void 0;
|
|
917
|
+
const out = {};
|
|
918
|
+
for (const key of Object.keys(attrs).sort()) {
|
|
919
|
+
if (key === "genHash" || key === "uid")
|
|
920
|
+
continue;
|
|
921
|
+
const value = attrs[key];
|
|
922
|
+
if (key === "src" && typeof value === "string") {
|
|
923
|
+
out[key] = identity(value);
|
|
924
|
+
} else {
|
|
925
|
+
out[key] = typeof value === "string" ? chipSpans ? normalizeChipSpans(normalizeImgTags(value, identity)) : normalizeImgTags(value, identity) : value;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return out;
|
|
929
|
+
}
|
|
930
|
+
function normalize(node, identity, chipSpans = true) {
|
|
931
|
+
switch (node.type) {
|
|
932
|
+
case "variableChip":
|
|
933
|
+
return ["var", node.attrs?.variableId ?? null];
|
|
934
|
+
case "topicNumber":
|
|
935
|
+
return ["topic", node.attrs?.group ?? null, !!node.attrs?.ref];
|
|
936
|
+
case "footnoteRef":
|
|
937
|
+
return ["note", node.attrs?.text ?? "", node.attrs?.size ?? null];
|
|
938
|
+
case "text":
|
|
939
|
+
return ["text", node.text ?? "", (node.marks ?? []).map((mark) => [mark.type, normalizeAttrs(mark.attrs, identity, chipSpans)])];
|
|
940
|
+
}
|
|
941
|
+
return [node.type, normalizeAttrs(node.attrs, identity, chipSpans), (node.content ?? []).map((child) => normalize(child, identity, chipSpans))];
|
|
942
|
+
}
|
|
943
|
+
function fnv1a(input) {
|
|
944
|
+
let hash = 2166136261;
|
|
945
|
+
for (let i = 0; i < input.length; i++) {
|
|
946
|
+
hash ^= input.charCodeAt(i);
|
|
947
|
+
hash = Math.imul(hash, 16777619);
|
|
948
|
+
}
|
|
949
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
950
|
+
}
|
|
951
|
+
function minutaBlockFingerprint(blockJson, identity = (src) => src) {
|
|
952
|
+
const body = JSON.stringify((blockJson.content ?? []).map((child) => normalize(child, identity)));
|
|
953
|
+
return `${fnv1a(body)}${body.length.toString(16)}`;
|
|
954
|
+
}
|
|
955
|
+
function minutaBlockFingerprintV1(blockJson, identity = (src) => src) {
|
|
956
|
+
const body = JSON.stringify((blockJson.content ?? []).map((child) => normalize(child, identity, false)));
|
|
957
|
+
return `${fnv1a(body)}${body.length.toString(16)}`;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function readChecklist(doc) {
|
|
961
|
+
const stored = doc.attrs.checklist;
|
|
962
|
+
return stored ?? { wanted: [...docBlocks(doc).keys()], pending: [] };
|
|
963
|
+
}
|
|
964
|
+
const defaultImageIdentity = (src) => src;
|
|
965
|
+
function blockHash(node, identity = defaultImageIdentity) {
|
|
966
|
+
return minutaBlockFingerprint(node.toJSON(), identity);
|
|
967
|
+
}
|
|
968
|
+
function isBlockUntouched(node, identity = defaultImageIdentity) {
|
|
969
|
+
const genHash = node.attrs.genHash;
|
|
970
|
+
if (!genHash)
|
|
971
|
+
return false;
|
|
972
|
+
return genHash === blockHash(node, identity) || genHash === minutaBlockFingerprintV1(node.toJSON(), identity);
|
|
973
|
+
}
|
|
974
|
+
function docBlocks(doc) {
|
|
975
|
+
const map = /* @__PURE__ */ new Map();
|
|
976
|
+
doc.forEach((node, offset) => {
|
|
977
|
+
const id = node.attrs.blockId;
|
|
978
|
+
if (node.type.name === "minutaBlock" && id && !map.has(id))
|
|
979
|
+
map.set(id, { pos: offset, node });
|
|
980
|
+
});
|
|
981
|
+
return map;
|
|
982
|
+
}
|
|
983
|
+
function parseGeneratedBlocks(schema, html, identity = defaultImageIdentity) {
|
|
984
|
+
const wrapper = document.createElement("div");
|
|
985
|
+
wrapper.innerHTML = html;
|
|
986
|
+
const doc = DOMParser.fromSchema(schema).parse(wrapper);
|
|
987
|
+
const blocks = [];
|
|
988
|
+
doc.forEach((node) => {
|
|
989
|
+
if (node.type.name === "minutaBlock" && node.attrs.blockId)
|
|
990
|
+
blocks.push(node.type.create({ ...node.attrs, genHash: blockHash(node, identity) }, node.content, node.marks));
|
|
991
|
+
});
|
|
992
|
+
return blocks;
|
|
993
|
+
}
|
|
994
|
+
function stampGeneratedBlocks(editor, identity = defaultImageIdentity) {
|
|
995
|
+
const tr = editor.state.tr;
|
|
996
|
+
let stamped = 0;
|
|
997
|
+
editor.state.doc.forEach((node, offset) => {
|
|
998
|
+
if (node.type.name !== "minutaBlock")
|
|
999
|
+
return;
|
|
1000
|
+
const genHash = blockHash(node, identity);
|
|
1001
|
+
if (node.attrs.genHash !== genHash) {
|
|
1002
|
+
tr.setNodeMarkup(offset, void 0, { ...node.attrs, genHash });
|
|
1003
|
+
stamped++;
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
if (tr.docChanged)
|
|
1007
|
+
editor.view.dispatch(tr);
|
|
1008
|
+
return stamped;
|
|
1009
|
+
}
|
|
1010
|
+
function baseChipKey(variableId, parentBlockId) {
|
|
1011
|
+
return `${parentBlockId ?? ""}|${variableId ?? ""}`;
|
|
1012
|
+
}
|
|
1013
|
+
function chipKey(variableId, parentBlockId, loopIndex) {
|
|
1014
|
+
return `${baseChipKey(variableId, parentBlockId)}|${loopIndex ?? ""}`;
|
|
1015
|
+
}
|
|
1016
|
+
function collectChips(html) {
|
|
1017
|
+
const wrapper = document.createElement("div");
|
|
1018
|
+
wrapper.innerHTML = html;
|
|
1019
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1020
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
1021
|
+
wrapper.querySelectorAll("span[data-variable-id]").forEach((el) => {
|
|
1022
|
+
const variableId = el.getAttribute("data-variable-id");
|
|
1023
|
+
const parentBlockId = el.getAttribute("data-parent-block-id");
|
|
1024
|
+
const loopIndex = el.getAttribute("data-loop-index") ?? el.getAttribute("data-variable-index");
|
|
1025
|
+
const value = { html: el.outerHTML, text: el.textContent ?? "", loopIndex };
|
|
1026
|
+
for (const [map, key] of [[byKey, chipKey(variableId, parentBlockId, loopIndex)], [byBase, baseChipKey(variableId, parentBlockId)]]) {
|
|
1027
|
+
const list = map.get(key) ?? [];
|
|
1028
|
+
list.push(value);
|
|
1029
|
+
map.set(key, list);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
return { byKey, byBase };
|
|
1033
|
+
}
|
|
1034
|
+
function matchByValue(docChips, generated) {
|
|
1035
|
+
const pairs = [];
|
|
1036
|
+
const count = (list, text) => list.filter((t) => t === text).length;
|
|
1037
|
+
const docTexts = docChips.map((chip) => chip.node.attrs.text);
|
|
1038
|
+
const genTexts = generated.map((value) => value.text);
|
|
1039
|
+
const freeDoc = new Set(docChips.keys());
|
|
1040
|
+
const freeGen = new Set(generated.keys());
|
|
1041
|
+
docChips.forEach((chip, i) => {
|
|
1042
|
+
const text = docTexts[i];
|
|
1043
|
+
if (count(docTexts, text) !== 1 || count(genTexts, text) !== 1)
|
|
1044
|
+
return;
|
|
1045
|
+
const j = genTexts.indexOf(text);
|
|
1046
|
+
pairs.push({ ...chip, next: generated[j] });
|
|
1047
|
+
freeDoc.delete(i);
|
|
1048
|
+
freeGen.delete(j);
|
|
1049
|
+
});
|
|
1050
|
+
if (freeDoc.size === 1 && freeGen.size === 1) {
|
|
1051
|
+
const [i] = freeDoc;
|
|
1052
|
+
const [j] = freeGen;
|
|
1053
|
+
pairs.push({ ...docChips[i], next: generated[j] });
|
|
1054
|
+
}
|
|
1055
|
+
return pairs;
|
|
1056
|
+
}
|
|
1057
|
+
function refreshChips(tr, chips) {
|
|
1058
|
+
const inDoc = /* @__PURE__ */ new Map();
|
|
1059
|
+
tr.doc.descendants((node, pos) => {
|
|
1060
|
+
if (node.type.name !== "variableChip")
|
|
1061
|
+
return;
|
|
1062
|
+
const key = chipKey(node.attrs.variableId, node.attrs.parentBlockId, node.attrs.loopIndex);
|
|
1063
|
+
const list = inDoc.get(key) ?? [];
|
|
1064
|
+
list.push({ pos, node });
|
|
1065
|
+
inDoc.set(key, list);
|
|
1066
|
+
});
|
|
1067
|
+
let changed = 0;
|
|
1068
|
+
for (const [key, docChips] of inDoc) {
|
|
1069
|
+
let generated = chips.byKey.get(key);
|
|
1070
|
+
if (!generated?.length && docChips[0].node.attrs.loopIndex == null) {
|
|
1071
|
+
const { variableId, parentBlockId } = docChips[0].node.attrs;
|
|
1072
|
+
generated = chips.byBase.get(baseChipKey(variableId, parentBlockId));
|
|
1073
|
+
}
|
|
1074
|
+
if (!generated?.length)
|
|
1075
|
+
continue;
|
|
1076
|
+
const values = generated;
|
|
1077
|
+
const uniform = values.every((value) => value.html === values[0].html);
|
|
1078
|
+
const pairs = uniform ? docChips.map((chip) => ({ ...chip, next: values[0] })) : matchByValue(docChips, values);
|
|
1079
|
+
for (const { pos, node, next } of pairs) {
|
|
1080
|
+
const loopIndex = uniform ? node.attrs.loopIndex : next.loopIndex ?? node.attrs.loopIndex;
|
|
1081
|
+
if (next.html === node.attrs.html && loopIndex === node.attrs.loopIndex)
|
|
1082
|
+
continue;
|
|
1083
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, html: next.html, text: next.text, loopIndex });
|
|
1084
|
+
if (next.text !== node.attrs.text)
|
|
1085
|
+
changed++;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return changed;
|
|
1089
|
+
}
|
|
1090
|
+
function refreshIslandChips(tr, chips) {
|
|
1091
|
+
let changed = 0;
|
|
1092
|
+
tr.doc.descendants((node, pos) => {
|
|
1093
|
+
if (node.type.name !== "htmlIsland" || !String(node.attrs.html ?? "").includes("data-variable-id"))
|
|
1094
|
+
return;
|
|
1095
|
+
const tpl = document.createElement("template");
|
|
1096
|
+
tpl.innerHTML = node.attrs.html;
|
|
1097
|
+
let touched = false;
|
|
1098
|
+
tpl.content.querySelectorAll("span[data-variable-id]").forEach((el) => {
|
|
1099
|
+
const key = chipKey(
|
|
1100
|
+
el.getAttribute("data-variable-id"),
|
|
1101
|
+
el.getAttribute("data-parent-block-id"),
|
|
1102
|
+
el.getAttribute("data-loop-index") ?? el.getAttribute("data-variable-index")
|
|
1103
|
+
);
|
|
1104
|
+
const values = chips.byKey.get(key);
|
|
1105
|
+
if (!values?.length || values.some((value) => value.html !== values[0].html))
|
|
1106
|
+
return;
|
|
1107
|
+
if (el.outerHTML !== values[0].html) {
|
|
1108
|
+
if (el.textContent !== values[0].text)
|
|
1109
|
+
changed++;
|
|
1110
|
+
el.outerHTML = values[0].html;
|
|
1111
|
+
touched = true;
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
if (touched)
|
|
1115
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, html: tpl.innerHTML });
|
|
1116
|
+
return false;
|
|
1117
|
+
});
|
|
1118
|
+
return changed;
|
|
1119
|
+
}
|
|
1120
|
+
function insertPos(doc, blockId, order) {
|
|
1121
|
+
const present = docBlocks(doc);
|
|
1122
|
+
const index = order.indexOf(blockId);
|
|
1123
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
1124
|
+
const prev = present.get(order[i]);
|
|
1125
|
+
if (prev)
|
|
1126
|
+
return prev.pos + prev.node.nodeSize;
|
|
1127
|
+
}
|
|
1128
|
+
return 0;
|
|
1129
|
+
}
|
|
1130
|
+
function listConflicts(doc) {
|
|
1131
|
+
return readChecklist(doc).pending.map(({ blockId, kind, title }) => ({ blockId, blockTitle: title, kind }));
|
|
1132
|
+
}
|
|
1133
|
+
function applyChecklistToDocument(editor, generatedHtml, options = {}) {
|
|
1134
|
+
const identity = options.imageIdentity ?? defaultImageIdentity;
|
|
1135
|
+
const generated = parseGeneratedBlocks(editor.schema, generatedHtml, identity);
|
|
1136
|
+
const genById = new Map(generated.map((node) => [node.attrs.blockId, node]));
|
|
1137
|
+
const order = generated.map((node) => node.attrs.blockId);
|
|
1138
|
+
const wanted = new Set(order);
|
|
1139
|
+
const stored = readChecklist(editor.state.doc);
|
|
1140
|
+
const before = new Set(stored.wanted);
|
|
1141
|
+
const previousPending = new Map(stored.pending.map((entry) => [entry.blockId, entry]));
|
|
1142
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1143
|
+
const toggled = options.toggled;
|
|
1144
|
+
const tr = editor.state.tr;
|
|
1145
|
+
const result = {
|
|
1146
|
+
inserted: [],
|
|
1147
|
+
removed: [],
|
|
1148
|
+
regenerated: [],
|
|
1149
|
+
variablesUpdated: 0,
|
|
1150
|
+
conflicts: [],
|
|
1151
|
+
toggledWithoutContent: false,
|
|
1152
|
+
lastBlockKept: false
|
|
1153
|
+
};
|
|
1154
|
+
const setConflict = (id, kind, node, title) => {
|
|
1155
|
+
pending.set(id, { blockId: id, kind, title, node: node ? node.toJSON() : null });
|
|
1156
|
+
};
|
|
1157
|
+
const ids = [...order, ...[...docBlocks(tr.doc).keys()].filter((id) => !wanted.has(id))];
|
|
1158
|
+
if (toggled && !ids.includes(toggled.blockId))
|
|
1159
|
+
ids.push(toggled.blockId);
|
|
1160
|
+
for (const id of ids) {
|
|
1161
|
+
const fresh = genById.get(id) ?? null;
|
|
1162
|
+
const explicit = toggled?.blockId === id;
|
|
1163
|
+
const turnedOn = explicit ? toggled.show : !before.has(id) && !!fresh;
|
|
1164
|
+
const turnedOff = explicit ? !toggled.show : before.has(id) && !fresh;
|
|
1165
|
+
const present = docBlocks(tr.doc).get(id);
|
|
1166
|
+
if (!present) {
|
|
1167
|
+
if (turnedOn && fresh) {
|
|
1168
|
+
tr.insert(insertPos(tr.doc, id, order), fresh);
|
|
1169
|
+
result.inserted.push(id);
|
|
1170
|
+
} else if (explicit && toggled.show && !fresh) {
|
|
1171
|
+
result.toggledWithoutContent = true;
|
|
1172
|
+
}
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const untouched = isBlockUntouched(present.node, identity);
|
|
1176
|
+
const title = present.node.attrs.blockTitle ?? null;
|
|
1177
|
+
if (!fresh || turnedOff) {
|
|
1178
|
+
if (!turnedOff) {
|
|
1179
|
+
const open = previousPending.get(id);
|
|
1180
|
+
if (!untouched && open?.kind === "remove")
|
|
1181
|
+
pending.set(id, open);
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (tr.doc.childCount <= 1) {
|
|
1185
|
+
result.lastBlockKept = true;
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (untouched) {
|
|
1189
|
+
tr.delete(present.pos, present.pos + present.node.nodeSize);
|
|
1190
|
+
result.removed.push(id);
|
|
1191
|
+
} else {
|
|
1192
|
+
setConflict(id, "remove", null, title);
|
|
1193
|
+
}
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (fresh.attrs.genHash === present.node.attrs.genHash)
|
|
1197
|
+
continue;
|
|
1198
|
+
if (untouched) {
|
|
1199
|
+
tr.replaceWith(present.pos, present.pos + present.node.nodeSize, fresh);
|
|
1200
|
+
result.regenerated.push(id);
|
|
1201
|
+
} else {
|
|
1202
|
+
setConflict(id, "replace", fresh, title);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const chips = collectChips(generatedHtml);
|
|
1206
|
+
result.variablesUpdated = refreshChips(tr, chips) + refreshIslandChips(tr, chips);
|
|
1207
|
+
const next = { wanted: [...wanted], pending: [...pending.values()] };
|
|
1208
|
+
if (JSON.stringify(next) !== JSON.stringify(stored) || tr.docChanged)
|
|
1209
|
+
tr.setDocAttribute("checklist", next);
|
|
1210
|
+
if (tr.docChanged) {
|
|
1211
|
+
editor.view.dispatch(closeHistory(tr));
|
|
1212
|
+
}
|
|
1213
|
+
result.conflicts = next.pending.map(({ blockId, kind, title }) => ({ blockId, blockTitle: title, kind }));
|
|
1214
|
+
return result;
|
|
1215
|
+
}
|
|
1216
|
+
function resolveChecklistConflict(editor, blockId, resolution) {
|
|
1217
|
+
const stored = readChecklist(editor.state.doc);
|
|
1218
|
+
const entry = stored.pending.find((item) => item.blockId === blockId);
|
|
1219
|
+
const present = docBlocks(editor.state.doc).get(blockId);
|
|
1220
|
+
const tr = editor.state.tr;
|
|
1221
|
+
const rest = { ...stored, pending: stored.pending.filter((item) => item.blockId !== blockId) };
|
|
1222
|
+
if (!entry || !present) {
|
|
1223
|
+
if (entry) {
|
|
1224
|
+
tr.setDocAttribute("checklist", rest);
|
|
1225
|
+
editor.view.dispatch(tr);
|
|
1226
|
+
}
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
if (resolution === "apply" && entry.kind === "remove" && editor.state.doc.childCount <= 1)
|
|
1230
|
+
return false;
|
|
1231
|
+
const fresh = entry.node ? editor.schema.nodeFromJSON(entry.node) : null;
|
|
1232
|
+
if (resolution === "apply") {
|
|
1233
|
+
if (entry.kind === "remove")
|
|
1234
|
+
tr.delete(present.pos, present.pos + present.node.nodeSize);
|
|
1235
|
+
else if (fresh)
|
|
1236
|
+
tr.replaceWith(present.pos, present.pos + present.node.nodeSize, fresh);
|
|
1237
|
+
} else if (entry.kind === "replace" && fresh) {
|
|
1238
|
+
tr.setNodeMarkup(present.pos, void 0, { ...present.node.attrs, genHash: fresh.attrs.genHash });
|
|
1239
|
+
}
|
|
1240
|
+
tr.setDocAttribute("checklist", rest);
|
|
1241
|
+
editor.view.dispatch(closeHistory(tr));
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
844
1245
|
function h(tag, attrs = {}, children = []) {
|
|
845
1246
|
const el = document.createElement(tag);
|
|
846
1247
|
if (attrs.class)
|
|
@@ -2144,6 +2545,32 @@ function createMinutaEditor(element, opts) {
|
|
|
2144
2545
|
return Promise.resolve(0);
|
|
2145
2546
|
return refreshVaultImages(ed, resolveUrl, { meta: { [POC_ASSET_SWAP]: true } });
|
|
2146
2547
|
},
|
|
2548
|
+
applyChecklist(generatedHtml, options) {
|
|
2549
|
+
const ed = tiptap;
|
|
2550
|
+
if (!ed || ed.isDestroyed)
|
|
2551
|
+
throw new Error("editor n\xE3o montado");
|
|
2552
|
+
return applyChecklistToDocument(ed, generatedHtml, options ?? {});
|
|
2553
|
+
},
|
|
2554
|
+
checklistConflicts() {
|
|
2555
|
+
const ed = tiptap;
|
|
2556
|
+
return ed && !ed.isDestroyed ? listConflicts(ed.state.doc) : [];
|
|
2557
|
+
},
|
|
2558
|
+
resolveChecklistConflict(blockId, resolution) {
|
|
2559
|
+
const ed = tiptap;
|
|
2560
|
+
if (!ed || ed.isDestroyed)
|
|
2561
|
+
return false;
|
|
2562
|
+
return resolveChecklistConflict(ed, blockId, resolution);
|
|
2563
|
+
},
|
|
2564
|
+
stampGenerated() {
|
|
2565
|
+
const ed = tiptap;
|
|
2566
|
+
if (!ed || ed.isDestroyed)
|
|
2567
|
+
return 0;
|
|
2568
|
+
return stampGeneratedBlocks(ed);
|
|
2569
|
+
},
|
|
2570
|
+
documentBlockIds() {
|
|
2571
|
+
const ed = tiptap;
|
|
2572
|
+
return ed && !ed.isDestroyed ? [...docBlocks(ed.state.doc).keys()] : [];
|
|
2573
|
+
},
|
|
2147
2574
|
schedulePaginate,
|
|
2148
2575
|
refreshChrome,
|
|
2149
2576
|
totalPages: () => totalPages,
|
|
@@ -2163,4 +2590,4 @@ function createMinutaEditor(element, opts) {
|
|
|
2163
2590
|
return api;
|
|
2164
2591
|
}
|
|
2165
2592
|
|
|
2166
|
-
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, PAGINATE_META, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPaginationPlugin, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, breaksEqual, buildPocExtensions, collectVaultRefs, computePocBreaks, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, formatTopicNumber, iconSvg, pocPageKey, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, swapVaultSrc, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
2593
|
+
export { A4_HEIGHT_PX, A4_WIDTH_PX, DEFAULT_MARGINS, DOC_FONTS, ICON_PATHS, PAGINATE_META, POC_ASSET_SWAP, PocDocument, PocFootnoteRef, PocHtmlIsland, PocInlineStyle, PocMinutaBlock, PocMissingImage, PocPaginationPlugin, PocStylePassthrough, PocStyledDiv, PocTemplateVariable, PocTopicNumber, PocTopicRenumber, PocUnderline, PocVariableChip, TOPIC_DEFAULT_GROUP, TOPIC_GROUP_ATTR, TOPIC_NUMBER_ATTR, TOPIC_REF_ATTR, TOPIC_ROMAN_ATTR, blockHash, breaksEqual, buildPocExtensions, collectVaultRefs, computePocBreaks, createMinutaEditor, decodeVaultAttr, dispatchVariableClick, escapeHtml, escapeVaultAttr, extractTemplateVariables, footnoteSizePt, formatTopicNumber, iconSvg, isBlockUntouched, minutaBlockFingerprint, pocPageKey, readTopicNumber, refreshVaultImages, renewVaultImageTags, renumberPocFootnotes, renumberPocTopics, renumberPocTopicsIn, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, swapVaultSrc, variableChipHtml, vaultImageHtml, vaultRefOf };
|
|
@@ -75,84 +75,6 @@ function sanitizeRichHtml(html) {
|
|
|
75
75
|
return tpl.innerHTML;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const TOPIC_NUMBER_ATTR = "data-topic-number";
|
|
79
|
-
const TOPIC_GROUP_ATTR = "data-topic-group";
|
|
80
|
-
const TOPIC_ROMAN_ATTR = "data-topic-roman";
|
|
81
|
-
const TOPIC_REF_ATTR = "data-topic-ref";
|
|
82
|
-
const TOPIC_DEFAULT_GROUP = "default";
|
|
83
|
-
function toRoman(num) {
|
|
84
|
-
if (!Number.isFinite(num) || num <= 0)
|
|
85
|
-
return "";
|
|
86
|
-
const numerals = [
|
|
87
|
-
[1e3, "M"],
|
|
88
|
-
[900, "CM"],
|
|
89
|
-
[500, "D"],
|
|
90
|
-
[400, "CD"],
|
|
91
|
-
[100, "C"],
|
|
92
|
-
[90, "XC"],
|
|
93
|
-
[50, "L"],
|
|
94
|
-
[40, "XL"],
|
|
95
|
-
[10, "X"],
|
|
96
|
-
[9, "IX"],
|
|
97
|
-
[5, "V"],
|
|
98
|
-
[4, "IV"],
|
|
99
|
-
[1, "I"]
|
|
100
|
-
];
|
|
101
|
-
let result = "";
|
|
102
|
-
let rest = Math.floor(num);
|
|
103
|
-
for (const [value, numeral] of numerals) {
|
|
104
|
-
while (rest >= value) {
|
|
105
|
-
result += numeral;
|
|
106
|
-
rest -= value;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
function fromRoman(text) {
|
|
112
|
-
const upper = text.trim().toUpperCase();
|
|
113
|
-
if (!upper || !/^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/.test(upper))
|
|
114
|
-
return null;
|
|
115
|
-
const values = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1e3 };
|
|
116
|
-
let total = 0;
|
|
117
|
-
for (let i = 0; i < upper.length; i++) {
|
|
118
|
-
const current = values[upper[i]];
|
|
119
|
-
const next = values[upper[i + 1] ?? ""] ?? 0;
|
|
120
|
-
total += current < next ? -current : current;
|
|
121
|
-
}
|
|
122
|
-
return total;
|
|
123
|
-
}
|
|
124
|
-
function formatTopicNumber(number, roman) {
|
|
125
|
-
return roman ? toRoman(number) : String(number);
|
|
126
|
-
}
|
|
127
|
-
function escapeAttr(value) {
|
|
128
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
129
|
-
}
|
|
130
|
-
function topicNumberHtml(info) {
|
|
131
|
-
let attrs = `${TOPIC_NUMBER_ATTR}="${info.number}" ${TOPIC_GROUP_ATTR}="${escapeAttr(info.group || TOPIC_DEFAULT_GROUP)}"`;
|
|
132
|
-
if (info.roman)
|
|
133
|
-
attrs += ` ${TOPIC_ROMAN_ATTR}="true"`;
|
|
134
|
-
if (info.ref)
|
|
135
|
-
attrs += ` ${TOPIC_REF_ATTR}="true"`;
|
|
136
|
-
return `<span ${attrs}>${formatTopicNumber(info.number, info.roman)}</span>`;
|
|
137
|
-
}
|
|
138
|
-
function readTopicNumber(el) {
|
|
139
|
-
const roman = el.getAttribute(TOPIC_ROMAN_ATTR) === "true";
|
|
140
|
-
const raw = el.getAttribute(TOPIC_NUMBER_ATTR) ?? "";
|
|
141
|
-
let number = Number.parseInt(raw, 10);
|
|
142
|
-
if (!Number.isFinite(number)) {
|
|
143
|
-
const text = el.textContent?.trim() ?? "";
|
|
144
|
-
number = Number.parseInt(text, 10);
|
|
145
|
-
if (!Number.isFinite(number))
|
|
146
|
-
number = fromRoman(text) ?? 1;
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
number,
|
|
150
|
-
group: el.getAttribute(TOPIC_GROUP_ATTR) || TOPIC_DEFAULT_GROUP,
|
|
151
|
-
roman,
|
|
152
|
-
ref: el.getAttribute(TOPIC_REF_ATTR) === "true"
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
78
|
const INLINE_TAGS = /* @__PURE__ */ new Set(["B", "STRONG", "I", "EM", "U", "SUP", "SUB", "BR"]);
|
|
157
79
|
function safeHref(value) {
|
|
158
80
|
const href = (value ?? "").trim();
|
|
@@ -208,12 +130,12 @@ function sanitizeFootnoteHtml(html) {
|
|
|
208
130
|
return style ? `<span style="${style}">${inner}</span>` : inner;
|
|
209
131
|
}
|
|
210
132
|
if (node.tagName === "DIV" || node.tagName === "P")
|
|
211
|
-
return inner ? `${inner}<br>` : "";
|
|
133
|
+
return inner.trim() ? `${inner}<br>` : "";
|
|
212
134
|
if (INLINE_TAGS.has(node.tagName))
|
|
213
135
|
return `<${node.tagName.toLowerCase()}>${inner}</${node.tagName.toLowerCase()}>`;
|
|
214
136
|
return inner;
|
|
215
137
|
};
|
|
216
|
-
return Array.from(root.childNodes).map(walk).join("").replace(/\s+/g, " ").replace(/(<br>\s*)+$/i, "").trim();
|
|
138
|
+
return Array.from(root.childNodes).map(walk).join("").replace(/\s+/g, " ").replace(/^(\s*<br>)+/i, "").replace(/(<br>\s*)+$/i, "").trim();
|
|
217
139
|
}
|
|
218
140
|
function footnoteHtmlToRuns(html) {
|
|
219
141
|
const runs = [];
|
|
@@ -271,6 +193,84 @@ function footnoteSizePt(style) {
|
|
|
271
193
|
return Number.isFinite(value) ? value : null;
|
|
272
194
|
}
|
|
273
195
|
|
|
196
|
+
const TOPIC_NUMBER_ATTR = "data-topic-number";
|
|
197
|
+
const TOPIC_GROUP_ATTR = "data-topic-group";
|
|
198
|
+
const TOPIC_ROMAN_ATTR = "data-topic-roman";
|
|
199
|
+
const TOPIC_REF_ATTR = "data-topic-ref";
|
|
200
|
+
const TOPIC_DEFAULT_GROUP = "default";
|
|
201
|
+
function toRoman(num) {
|
|
202
|
+
if (!Number.isFinite(num) || num <= 0)
|
|
203
|
+
return "";
|
|
204
|
+
const numerals = [
|
|
205
|
+
[1e3, "M"],
|
|
206
|
+
[900, "CM"],
|
|
207
|
+
[500, "D"],
|
|
208
|
+
[400, "CD"],
|
|
209
|
+
[100, "C"],
|
|
210
|
+
[90, "XC"],
|
|
211
|
+
[50, "L"],
|
|
212
|
+
[40, "XL"],
|
|
213
|
+
[10, "X"],
|
|
214
|
+
[9, "IX"],
|
|
215
|
+
[5, "V"],
|
|
216
|
+
[4, "IV"],
|
|
217
|
+
[1, "I"]
|
|
218
|
+
];
|
|
219
|
+
let result = "";
|
|
220
|
+
let rest = Math.floor(num);
|
|
221
|
+
for (const [value, numeral] of numerals) {
|
|
222
|
+
while (rest >= value) {
|
|
223
|
+
result += numeral;
|
|
224
|
+
rest -= value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function fromRoman(text) {
|
|
230
|
+
const upper = text.trim().toUpperCase();
|
|
231
|
+
if (!upper || !/^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/.test(upper))
|
|
232
|
+
return null;
|
|
233
|
+
const values = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1e3 };
|
|
234
|
+
let total = 0;
|
|
235
|
+
for (let i = 0; i < upper.length; i++) {
|
|
236
|
+
const current = values[upper[i]];
|
|
237
|
+
const next = values[upper[i + 1] ?? ""] ?? 0;
|
|
238
|
+
total += current < next ? -current : current;
|
|
239
|
+
}
|
|
240
|
+
return total;
|
|
241
|
+
}
|
|
242
|
+
function formatTopicNumber(number, roman) {
|
|
243
|
+
return roman ? toRoman(number) : String(number);
|
|
244
|
+
}
|
|
245
|
+
function escapeAttr(value) {
|
|
246
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
247
|
+
}
|
|
248
|
+
function topicNumberHtml(info) {
|
|
249
|
+
let attrs = `${TOPIC_NUMBER_ATTR}="${info.number}" ${TOPIC_GROUP_ATTR}="${escapeAttr(info.group || TOPIC_DEFAULT_GROUP)}"`;
|
|
250
|
+
if (info.roman)
|
|
251
|
+
attrs += ` ${TOPIC_ROMAN_ATTR}="true"`;
|
|
252
|
+
if (info.ref)
|
|
253
|
+
attrs += ` ${TOPIC_REF_ATTR}="true"`;
|
|
254
|
+
return `<span ${attrs}>${formatTopicNumber(info.number, info.roman)}</span>`;
|
|
255
|
+
}
|
|
256
|
+
function readTopicNumber(el) {
|
|
257
|
+
const roman = el.getAttribute(TOPIC_ROMAN_ATTR) === "true";
|
|
258
|
+
const raw = el.getAttribute(TOPIC_NUMBER_ATTR) ?? "";
|
|
259
|
+
let number = Number.parseInt(raw, 10);
|
|
260
|
+
if (!Number.isFinite(number)) {
|
|
261
|
+
const text = el.textContent?.trim() ?? "";
|
|
262
|
+
number = Number.parseInt(text, 10);
|
|
263
|
+
if (!Number.isFinite(number))
|
|
264
|
+
number = fromRoman(text) ?? 1;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
number,
|
|
268
|
+
group: el.getAttribute(TOPIC_GROUP_ATTR) || TOPIC_DEFAULT_GROUP,
|
|
269
|
+
roman,
|
|
270
|
+
ref: el.getAttribute(TOPIC_REF_ATTR) === "true"
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
274
|
function shouldMoveBreakToHeading(params) {
|
|
275
275
|
const { heading, item, pageStart, capacity } = params;
|
|
276
276
|
if (!heading || !heading.heading || heading.marker)
|
|
@@ -897,4 +897,4 @@ function PocPaginationPlugin(cfg) {
|
|
|
897
897
|
});
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
-
export { shouldMoveBreakToHeading as A, toRoman as B, fromRoman as C, topicNumberHtml as D, PocPaginationPlugin as P, TOPIC_DEFAULT_GROUP as T, formatTopicNumber as a, pocLooksLikeTitle as b,
|
|
900
|
+
export { shouldMoveBreakToHeading as A, toRoman as B, fromRoman as C, topicNumberHtml as D, PocPaginationPlugin as P, TOPIC_DEFAULT_GROUP as T, formatTopicNumber as a, pocLooksLikeTitle as b, pocNodeHasVisibleContent as c, pocNodePaintsBox as d, sanitizeFootnoteHtml as e, footnoteHtmlToRuns as f, footnoteSizePt as g, TOPIC_NUMBER_ATTR as h, TOPIC_GROUP_ATTR as i, TOPIC_ROMAN_ATTR as j, TOPIC_REF_ATTR as k, computePocBreaks as l, pocPageKey as m, breaksEqual as n, PAGINATE_META as o, pocLoneCellIndex as p, pocChromeFlowHeight as q, readTopicNumber as r, sanitizeRichHtml as s, isPocChromeCanvas as t, buildPocHeaderEl as u, buildPocFooterEl as v, buildPocNotesEl as w, pocDescendsInto as x, isPocFlexRow as y, computePocLoneRows as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/minuta-editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Editor de minuta do defesa.ai sem framework: folha A4 paginada, edição rica, placeholders de variável e export DOCX fiel ao preview — monta em qualquer app (React, Vue, vanilla)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|