@meistrari/minuta-nuxt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +18 -0
- package/dist/module.d.ts +18 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +45 -0
- package/dist/runtime/components/MinutaAnnotationsGutter.vue +425 -0
- package/dist/runtime/components/MinutaChatEditCard.vue +124 -0
- package/dist/runtime/components/MinutaChromeCanvas.vue +549 -0
- package/dist/runtime/components/MinutaChromeEditor.vue +100 -0
- package/dist/runtime/components/MinutaDocumentViewer.vue +90 -0
- package/dist/runtime/components/MinutaDocxEditor.vue +1158 -0
- package/dist/runtime/components/MinutaEmbed.vue +783 -0
- package/dist/runtime/components/MinutaIcon.vue +53 -0
- package/dist/runtime/composables/annotationAnchors.d.ts +73 -0
- package/dist/runtime/composables/annotationAnchors.js +243 -0
- package/dist/runtime/composables/chatEditOps.d.ts +37 -0
- package/dist/runtime/composables/chatEditOps.js +61 -0
- package/dist/runtime/composables/docxExport.d.ts +24 -0
- package/dist/runtime/composables/docxExport.js +1292 -0
- package/dist/runtime/composables/editorExtensions.d.ts +56 -0
- package/dist/runtime/composables/editorExtensions.js +466 -0
- package/dist/runtime/composables/paginationPlugin.d.ts +126 -0
- package/dist/runtime/composables/paginationPlugin.js +421 -0
- package/dist/runtime/composables/useMinutaApi.d.ts +112 -0
- package/dist/runtime/composables/useMinutaApi.js +68 -0
- package/dist/runtime/composables/vaultImages.d.ts +12 -0
- package/dist/runtime/composables/vaultImages.js +85 -0
- package/dist/runtime/composables/versionDiff.d.ts +34 -0
- package/dist/runtime/composables/versionDiff.js +62 -0
- package/dist/runtime/server/api/minuta/[...path].d.ts +2 -0
- package/dist/runtime/server/api/minuta/[...path].js +6 -0
- package/dist/runtime/server/api/minuta/index.d.ts +2 -0
- package/dist/runtime/server/api/minuta/index.js +3 -0
- package/dist/runtime/server/utils/minuta-proxy.d.ts +10 -0
- package/dist/runtime/server/utils/minuta-proxy.js +63 -0
- package/dist/runtime/utils/footnoteHtml.d.ts +26 -0
- package/dist/runtime/utils/footnoteHtml.js +117 -0
- package/dist/runtime/utils/pocKeepWithNext.d.ts +44 -0
- package/dist/runtime/utils/pocKeepWithNext.js +9 -0
- package/dist/runtime/utils/sanitizeRichHtml.d.ts +10 -0
- package/dist/runtime/utils/sanitizeRichHtml.js +73 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +100 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core';
|
|
2
|
+
import { Extension as TiptapExtension, Mark as TiptapMark, Node as TiptapNode } from '@tiptap/core';
|
|
3
|
+
import type { PocPageConfig } from './paginationPlugin.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extensões Tiptap da PoC do editor de minuta.
|
|
6
|
+
*
|
|
7
|
+
* Modelo: doc → minutaBlock+ → (paragraph | heading | styledDiv | htmlIsland | ...)
|
|
8
|
+
* - minutaBlock: wrapper com proveniência (blockId/título do bloco do template)
|
|
9
|
+
* - htmlIsland: tabelas/listas/imagens/svg preservados como ilhas não-editáveis (HTML cru)
|
|
10
|
+
* - variableChip: <span data-variable-id> vira nó atômico — a variável continua "viva"
|
|
11
|
+
* - inlineStyle/styledDiv/passthrough: preservam o CSS inline do pipeline de render
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Meta de transação: troca de imagem fria pelo blob recém-baixado (load
|
|
15
|
+
* progressivo). Não é edição do advogado — fica fora do undo e não pode
|
|
16
|
+
* marcar o documento como sujo (senão o autosave dispara sozinho no load).
|
|
17
|
+
*/
|
|
18
|
+
export declare const POC_ASSET_SWAP = "pocAssetSwap";
|
|
19
|
+
export declare const PocDocument: TiptapNode<any, any>;
|
|
20
|
+
export declare const PocMinutaBlock: TiptapNode<any, any>;
|
|
21
|
+
export declare const PocHtmlIsland: TiptapNode<any, any>;
|
|
22
|
+
/**
|
|
23
|
+
* Aviso de imagem faltando — a caixa tracejada que o render emite quando a
|
|
24
|
+
* variável de imagem não foi preenchida.
|
|
25
|
+
*
|
|
26
|
+
* Antes ela chegava aqui como um `styledDiv` comum: apagar exigia selecionar o
|
|
27
|
+
* texto por dentro, e a moldura sobrava no documento. Como nó próprio ela é
|
|
28
|
+
* atômica e selecionável (um clique + Backspace resolve), ganha um botão de
|
|
29
|
+
* remover e um alvo de clique para escolher a imagem ali mesmo.
|
|
30
|
+
*
|
|
31
|
+
* Na etapa de edição o DOCUMENTO é a verdade — o checklist fica travado —, então
|
|
32
|
+
* escolher a imagem insere no texto editado; não mexe na variável do caso.
|
|
33
|
+
*
|
|
34
|
+
* O nó guarda só o ID da variável e REDESENHA a caixa a partir dele. Guardar o
|
|
35
|
+
* `outerHTML` e devolvê-lo a um `innerHTML` faria do corpo da edição — que é
|
|
36
|
+
* dado persistido, editável por qualquer um do workspace — um vetor de stored
|
|
37
|
+
* XSS: bastaria uma `div.missing-image-warning` forjada com `onerror` para
|
|
38
|
+
* rodar script no editor de quem abrisse o caso depois.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PocMissingImage: TiptapNode<any, any>;
|
|
41
|
+
export declare const PocVariableChip: TiptapNode<any, any>;
|
|
42
|
+
/**
|
|
43
|
+
* Nota de rodapé: o render devolve `<sup data-footnote="texto">n</sup>` e o
|
|
44
|
+
* export transforma em nota de rodapé DE VERDADE no Word (o pipeline legado faz
|
|
45
|
+
* o mesmo via footnotes_filter.lua do conversor). Precisa ser um nó do schema —
|
|
46
|
+
* senão o Tiptap descarta o data-footnote no round-trip e o texto da nota some.
|
|
47
|
+
*/
|
|
48
|
+
export declare const PocFootnoteRef: TiptapNode<any, any>;
|
|
49
|
+
/** re-sequencia a numeração das chamadas de nota na ordem do documento */
|
|
50
|
+
export declare function renumberPocFootnotes(editor: Editor): void;
|
|
51
|
+
export declare const PocStyledDiv: TiptapNode<any, any>;
|
|
52
|
+
export declare const PocInlineStyle: TiptapMark<any, any>;
|
|
53
|
+
export declare const PocUnderline: TiptapMark<any, any>;
|
|
54
|
+
/** Preserva style/class inline nos blocos de texto (o pipeline de render inlina todo o CSS). */
|
|
55
|
+
export declare const PocStylePassthrough: TiptapExtension<any, any>;
|
|
56
|
+
export declare function buildPocExtensions(pageConfig?: PocPageConfig): (TiptapNode<any, any> | TiptapMark<any, any> | TiptapExtension<any, any>)[];
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { Extension as TiptapExtension, Mark as TiptapMark, Node as TiptapNode, mergeAttributes } from "@tiptap/core";
|
|
2
|
+
import TiptapStarterKit from "@tiptap/starter-kit";
|
|
3
|
+
import Table from "@tiptap/extension-table";
|
|
4
|
+
import TableCell from "@tiptap/extension-table-cell";
|
|
5
|
+
import TableHeader from "@tiptap/extension-table-header";
|
|
6
|
+
import TableRow from "@tiptap/extension-table-row";
|
|
7
|
+
import { sanitizeRichHtml } from "../utils/sanitizeRichHtml.js";
|
|
8
|
+
import { PocPaginationPlugin } from "./paginationPlugin.js";
|
|
9
|
+
export const POC_ASSET_SWAP = "pocAssetSwap";
|
|
10
|
+
function htmlToDom(html) {
|
|
11
|
+
const tpl = document.createElement("template");
|
|
12
|
+
tpl.innerHTML = sanitizeRichHtml(html);
|
|
13
|
+
return tpl.content.firstElementChild ?? document.createElement("div");
|
|
14
|
+
}
|
|
15
|
+
export const PocDocument = TiptapNode.create({
|
|
16
|
+
name: "doc",
|
|
17
|
+
topNode: true,
|
|
18
|
+
content: "minutaBlock+"
|
|
19
|
+
});
|
|
20
|
+
export const PocMinutaBlock = TiptapNode.create({
|
|
21
|
+
name: "minutaBlock",
|
|
22
|
+
group: "block",
|
|
23
|
+
content: "block+",
|
|
24
|
+
defining: true,
|
|
25
|
+
isolating: true,
|
|
26
|
+
addAttributes() {
|
|
27
|
+
return {
|
|
28
|
+
blockId: { default: null },
|
|
29
|
+
blockTitle: { default: null }
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
parseHTML() {
|
|
33
|
+
return [{
|
|
34
|
+
tag: "div[data-minuta-block]",
|
|
35
|
+
priority: 120,
|
|
36
|
+
getAttrs: (el) => ({
|
|
37
|
+
blockId: el.getAttribute("data-block-id"),
|
|
38
|
+
blockTitle: el.getAttribute("data-block-title")
|
|
39
|
+
})
|
|
40
|
+
}];
|
|
41
|
+
},
|
|
42
|
+
renderHTML({ HTMLAttributes, node }) {
|
|
43
|
+
return ["div", mergeAttributes(HTMLAttributes, {
|
|
44
|
+
"data-minuta-block": "",
|
|
45
|
+
"data-block-id": node.attrs.blockId,
|
|
46
|
+
"data-block-title": node.attrs.blockTitle,
|
|
47
|
+
"class": "poc-minuta-block"
|
|
48
|
+
}), 0];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
export const PocHtmlIsland = TiptapNode.create({
|
|
52
|
+
name: "htmlIsland",
|
|
53
|
+
group: "block",
|
|
54
|
+
atom: true,
|
|
55
|
+
selectable: true,
|
|
56
|
+
// atomos arrastáveis: mover imagens/tabelas pelo documento
|
|
57
|
+
draggable: true,
|
|
58
|
+
addAttributes() {
|
|
59
|
+
return { html: { default: "" } };
|
|
60
|
+
},
|
|
61
|
+
parseHTML() {
|
|
62
|
+
const capture = (el) => ({ html: el.outerHTML });
|
|
63
|
+
return ["ul", "ol", "img", "figure", "svg"].map((tag) => ({
|
|
64
|
+
tag,
|
|
65
|
+
priority: 100,
|
|
66
|
+
getAttrs: capture
|
|
67
|
+
}));
|
|
68
|
+
},
|
|
69
|
+
renderHTML({ node }) {
|
|
70
|
+
return htmlToDom(node.attrs.html);
|
|
71
|
+
},
|
|
72
|
+
addNodeView() {
|
|
73
|
+
return ({ node, getPos, editor }) => {
|
|
74
|
+
const dom = document.createElement("div");
|
|
75
|
+
dom.className = "poc-island";
|
|
76
|
+
dom.contentEditable = "false";
|
|
77
|
+
dom.innerHTML = sanitizeRichHtml(node.attrs.html);
|
|
78
|
+
const img = dom.children.length === 1 && dom.firstElementChild instanceof HTMLImageElement ? dom.firstElementChild : null;
|
|
79
|
+
if (img)
|
|
80
|
+
dom.classList.add("poc-island--img");
|
|
81
|
+
if (img && editor.isEditable) {
|
|
82
|
+
const handle = document.createElement("div");
|
|
83
|
+
handle.className = "poc-img-handle";
|
|
84
|
+
handle.title = "Arraste para redimensionar";
|
|
85
|
+
dom.appendChild(handle);
|
|
86
|
+
let startX = 0;
|
|
87
|
+
let startWidth = 0;
|
|
88
|
+
let scale = 1;
|
|
89
|
+
let maxWidth = Number.POSITIVE_INFINITY;
|
|
90
|
+
const onMove = (event) => {
|
|
91
|
+
const width = Math.round(startWidth + (event.clientX - startX) / scale);
|
|
92
|
+
img.style.width = `${Math.min(maxWidth, Math.max(40, width))}px`;
|
|
93
|
+
img.style.height = "auto";
|
|
94
|
+
};
|
|
95
|
+
const onUp = () => {
|
|
96
|
+
document.removeEventListener("mousemove", onMove);
|
|
97
|
+
document.removeEventListener("mouseup", onUp);
|
|
98
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
99
|
+
if (typeof pos === "number") {
|
|
100
|
+
const clone = dom.cloneNode(true);
|
|
101
|
+
clone.querySelector(".poc-img-handle")?.remove();
|
|
102
|
+
editor.view.dispatch(
|
|
103
|
+
editor.state.tr.setNodeMarkup(pos, void 0, { html: clone.innerHTML })
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
handle.addEventListener("mousedown", (event) => {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
startX = event.clientX;
|
|
111
|
+
startWidth = img.offsetWidth;
|
|
112
|
+
scale = img.offsetWidth ? img.getBoundingClientRect().width / img.offsetWidth : 1;
|
|
113
|
+
maxWidth = dom.parentElement?.clientWidth || Number.POSITIVE_INFINITY;
|
|
114
|
+
document.addEventListener("mousemove", onMove);
|
|
115
|
+
document.addEventListener("mouseup", onUp);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return { dom };
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
let missingImageSeq = 0;
|
|
123
|
+
function nextMissingImageUid() {
|
|
124
|
+
missingImageSeq += 1;
|
|
125
|
+
return `mi-${missingImageSeq}`;
|
|
126
|
+
}
|
|
127
|
+
const MISSING_IMAGE_BOX_STYLE = "box-sizing: border-box; max-width: 100%; border: 2px dashed rgba(223, 139, 13, 0.43); padding: 16px; margin: 12px 0; border-radius: 8px; background-color: rgba(254, 174, 0, 0.03); color: rgb(119, 54, 19); text-align: center; box-shadow: inset 0 0 8px #e6510011;";
|
|
128
|
+
const MISSING_IMAGE_ROW_STYLE = "display: flex; align-items: center; justify-content: center; gap: 8px;";
|
|
129
|
+
export const PocMissingImage = TiptapNode.create({
|
|
130
|
+
name: "missingImage",
|
|
131
|
+
group: "block",
|
|
132
|
+
atom: true,
|
|
133
|
+
selectable: true,
|
|
134
|
+
draggable: true,
|
|
135
|
+
addAttributes() {
|
|
136
|
+
return {
|
|
137
|
+
variableId: { default: null },
|
|
138
|
+
parentBlockId: { default: null },
|
|
139
|
+
/**
|
|
140
|
+
* Identidade do nó DENTRO desta sessão do editor.
|
|
141
|
+
*
|
|
142
|
+
* Um fluxo demorado (escolher arquivo + upload) precisa voltar
|
|
143
|
+
* exatamente ao aviso que foi clicado. Posição não serve: o
|
|
144
|
+
* documento muda no meio. Tipo + variável também não: o mesmo
|
|
145
|
+
* aviso pode aparecer várias vezes, e um delete+insert põe outro
|
|
146
|
+
* igual no mesmo lugar. Com um id por nó, achar é inequívoco —
|
|
147
|
+
* se ele sumiu, não achamos; se ele andou, achamos onde parou.
|
|
148
|
+
*
|
|
149
|
+
* Gerado no parse e NÃO serializado: vale enquanto o editor viver,
|
|
150
|
+
* que é o tempo de vida do fluxo. Colar um aviso passa pelo parse
|
|
151
|
+
* de novo e ganha id próprio, então nunca há dois iguais.
|
|
152
|
+
*/
|
|
153
|
+
uid: { default: null }
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
parseHTML() {
|
|
157
|
+
return [{
|
|
158
|
+
tag: "div.missing-image-warning",
|
|
159
|
+
// acima do styledDiv (40), senão a caixa vira container editável de novo
|
|
160
|
+
priority: 120,
|
|
161
|
+
getAttrs: (el) => ({
|
|
162
|
+
variableId: el.getAttribute("data-variable-id"),
|
|
163
|
+
parentBlockId: el.getAttribute("data-parent-block-id"),
|
|
164
|
+
uid: nextMissingImageUid()
|
|
165
|
+
})
|
|
166
|
+
}];
|
|
167
|
+
},
|
|
168
|
+
renderHTML({ node }) {
|
|
169
|
+
return [
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
"class": "missing-image-warning",
|
|
173
|
+
"data-variable-id": node.attrs.variableId ?? "",
|
|
174
|
+
"data-parent-block-id": node.attrs.parentBlockId ?? "",
|
|
175
|
+
"style": MISSING_IMAGE_BOX_STYLE
|
|
176
|
+
// `uid` fica de fora de propósito: é identidade de sessão, não dado
|
|
177
|
+
},
|
|
178
|
+
["span", { style: MISSING_IMAGE_ROW_STYLE }, `A imagem ${node.attrs.variableId ?? ""} est\xE1 faltando`]
|
|
179
|
+
];
|
|
180
|
+
},
|
|
181
|
+
addNodeView() {
|
|
182
|
+
return ({ node, getPos, editor }) => {
|
|
183
|
+
const dom = document.createElement("div");
|
|
184
|
+
dom.className = "poc-missing-image";
|
|
185
|
+
dom.contentEditable = "false";
|
|
186
|
+
const caixa = document.createElement("div");
|
|
187
|
+
caixa.className = "missing-image-warning";
|
|
188
|
+
caixa.setAttribute("style", MISSING_IMAGE_BOX_STYLE);
|
|
189
|
+
const linha = document.createElement("span");
|
|
190
|
+
linha.setAttribute("style", MISSING_IMAGE_ROW_STYLE);
|
|
191
|
+
linha.textContent = `\u26A0 A imagem ${node.attrs.variableId ?? ""} est\xE1 faltando`;
|
|
192
|
+
caixa.appendChild(linha);
|
|
193
|
+
dom.appendChild(caixa);
|
|
194
|
+
if (!editor.isEditable)
|
|
195
|
+
return { dom };
|
|
196
|
+
caixa.style.position = "relative";
|
|
197
|
+
caixa.style.cursor = "pointer";
|
|
198
|
+
caixa.title = "Clique para escolher uma imagem";
|
|
199
|
+
const remover = document.createElement("button");
|
|
200
|
+
remover.type = "button";
|
|
201
|
+
remover.className = "poc-missing-image__remove";
|
|
202
|
+
remover.title = "Remover este aviso";
|
|
203
|
+
remover.setAttribute("aria-label", "Remover este aviso");
|
|
204
|
+
remover.textContent = "\xD7";
|
|
205
|
+
remover.addEventListener("mousedown", (event) => {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
209
|
+
if (typeof pos === "number")
|
|
210
|
+
editor.view.dispatch(editor.state.tr.delete(pos, pos + node.nodeSize));
|
|
211
|
+
});
|
|
212
|
+
caixa.appendChild(remover);
|
|
213
|
+
dom.addEventListener("click", (event) => {
|
|
214
|
+
if (event.target.closest(".poc-missing-image__remove"))
|
|
215
|
+
return;
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
218
|
+
window.dispatchEvent(new CustomEvent("poc:pick-image", {
|
|
219
|
+
detail: { variableId: node.attrs.variableId, uid: node.attrs.uid, hintPos: pos }
|
|
220
|
+
}));
|
|
221
|
+
});
|
|
222
|
+
return { dom };
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
export const PocVariableChip = TiptapNode.create({
|
|
227
|
+
name: "variableChip",
|
|
228
|
+
inline: true,
|
|
229
|
+
group: "inline",
|
|
230
|
+
atom: true,
|
|
231
|
+
selectable: true,
|
|
232
|
+
addAttributes() {
|
|
233
|
+
return {
|
|
234
|
+
variableId: { default: null },
|
|
235
|
+
parentBlockId: { default: null },
|
|
236
|
+
html: { default: "" },
|
|
237
|
+
text: { default: "" }
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
parseHTML() {
|
|
241
|
+
return [{
|
|
242
|
+
tag: "span[data-variable-id]",
|
|
243
|
+
priority: 110,
|
|
244
|
+
getAttrs: (el) => ({
|
|
245
|
+
variableId: el.getAttribute("data-variable-id"),
|
|
246
|
+
parentBlockId: el.getAttribute("data-parent-block-id"),
|
|
247
|
+
html: el.outerHTML,
|
|
248
|
+
text: el.textContent ?? ""
|
|
249
|
+
})
|
|
250
|
+
}];
|
|
251
|
+
},
|
|
252
|
+
renderHTML({ node }) {
|
|
253
|
+
return htmlToDom(node.attrs.html);
|
|
254
|
+
},
|
|
255
|
+
addNodeView() {
|
|
256
|
+
return ({ node }) => {
|
|
257
|
+
const dom = document.createElement("span");
|
|
258
|
+
dom.className = "poc-varchip";
|
|
259
|
+
dom.contentEditable = "false";
|
|
260
|
+
dom.innerHTML = sanitizeRichHtml(node.attrs.html);
|
|
261
|
+
dom.addEventListener("click", (event) => {
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
window.dispatchEvent(new CustomEvent("poc:variable-click", {
|
|
264
|
+
detail: {
|
|
265
|
+
variableId: node.attrs.variableId,
|
|
266
|
+
parentBlockId: node.attrs.parentBlockId,
|
|
267
|
+
text: node.attrs.text
|
|
268
|
+
}
|
|
269
|
+
}));
|
|
270
|
+
});
|
|
271
|
+
return { dom };
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
export const PocFootnoteRef = TiptapNode.create({
|
|
276
|
+
name: "footnoteRef",
|
|
277
|
+
inline: true,
|
|
278
|
+
group: "inline",
|
|
279
|
+
atom: true,
|
|
280
|
+
selectable: false,
|
|
281
|
+
addAttributes() {
|
|
282
|
+
return {
|
|
283
|
+
number: { default: "" },
|
|
284
|
+
/** conteúdo da nota em HTML de ênfase (b/i/u/br) */
|
|
285
|
+
text: { default: "" },
|
|
286
|
+
/** corpo declarado no template (`font-size: 9pt`), em pontos */
|
|
287
|
+
size: { default: null }
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
parseHTML() {
|
|
291
|
+
return [{
|
|
292
|
+
tag: "sup[data-footnote]",
|
|
293
|
+
priority: 110,
|
|
294
|
+
getAttrs: (el) => ({
|
|
295
|
+
number: el.textContent ?? "",
|
|
296
|
+
text: el.getAttribute("data-footnote") ?? "",
|
|
297
|
+
size: Number.parseFloat(el.getAttribute("data-footnote-size") ?? "") || null
|
|
298
|
+
})
|
|
299
|
+
}];
|
|
300
|
+
},
|
|
301
|
+
renderHTML({ node }) {
|
|
302
|
+
const attrs = { "data-footnote": node.attrs.text, "class": "poc-footnote" };
|
|
303
|
+
if (node.attrs.size)
|
|
304
|
+
attrs["data-footnote-size"] = String(node.attrs.size);
|
|
305
|
+
return ["sup", attrs, node.attrs.number];
|
|
306
|
+
},
|
|
307
|
+
addNodeView() {
|
|
308
|
+
return ({ node, editor, getPos }) => {
|
|
309
|
+
const dom = document.createElement("sup");
|
|
310
|
+
dom.className = "poc-footnote";
|
|
311
|
+
dom.contentEditable = "false";
|
|
312
|
+
dom.textContent = node.attrs.number;
|
|
313
|
+
dom.title = node.attrs.text;
|
|
314
|
+
dom.addEventListener("click", (event) => {
|
|
315
|
+
if (!editor.isEditable || typeof getPos !== "function")
|
|
316
|
+
return;
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
const pos = getPos();
|
|
320
|
+
if (pos == null)
|
|
321
|
+
return;
|
|
322
|
+
window.dispatchEvent(new CustomEvent("poc:edit-footnote", { detail: { pos } }));
|
|
323
|
+
});
|
|
324
|
+
return { dom };
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
export function renumberPocFootnotes(editor) {
|
|
329
|
+
if (!editor || editor.isDestroyed)
|
|
330
|
+
return;
|
|
331
|
+
const tr = editor.state.tr;
|
|
332
|
+
let index = 0;
|
|
333
|
+
let touched = false;
|
|
334
|
+
editor.state.doc.descendants((node, pos) => {
|
|
335
|
+
if (node.type.name !== "footnoteRef")
|
|
336
|
+
return;
|
|
337
|
+
index++;
|
|
338
|
+
const number = String(index);
|
|
339
|
+
if (node.attrs.number !== number) {
|
|
340
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, number });
|
|
341
|
+
touched = true;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
if (touched)
|
|
345
|
+
editor.view.dispatch(tr);
|
|
346
|
+
}
|
|
347
|
+
export const PocStyledDiv = TiptapNode.create({
|
|
348
|
+
name: "styledDiv",
|
|
349
|
+
group: "block",
|
|
350
|
+
content: "block+",
|
|
351
|
+
addAttributes() {
|
|
352
|
+
const passthrough = (name) => ({
|
|
353
|
+
default: null,
|
|
354
|
+
parseHTML: (el) => el.getAttribute(name),
|
|
355
|
+
renderHTML: (attrs) => attrs[name] != null ? { [name]: attrs[name] } : {}
|
|
356
|
+
});
|
|
357
|
+
return {
|
|
358
|
+
"style": { default: null },
|
|
359
|
+
"class": { default: null },
|
|
360
|
+
// alinhamento de colunas do template (usado pelo CSS de centralização de imagens)
|
|
361
|
+
"data-horizontal-align": passthrough("data-horizontal-align"),
|
|
362
|
+
"data-vertical-align": passthrough("data-vertical-align"),
|
|
363
|
+
"data-width": passthrough("data-width")
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
parseHTML() {
|
|
367
|
+
return [{
|
|
368
|
+
tag: "div",
|
|
369
|
+
priority: 40,
|
|
370
|
+
getAttrs: (el) => {
|
|
371
|
+
if (el.hasAttribute("data-minuta-block"))
|
|
372
|
+
return false;
|
|
373
|
+
return { style: el.getAttribute("style"), class: el.getAttribute("class") };
|
|
374
|
+
}
|
|
375
|
+
}];
|
|
376
|
+
},
|
|
377
|
+
renderHTML({ HTMLAttributes }) {
|
|
378
|
+
return ["div", mergeAttributes(HTMLAttributes), 0];
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
export const PocInlineStyle = TiptapMark.create({
|
|
382
|
+
name: "inlineStyle",
|
|
383
|
+
addAttributes() {
|
|
384
|
+
return {
|
|
385
|
+
style: { default: null },
|
|
386
|
+
class: { default: null }
|
|
387
|
+
};
|
|
388
|
+
},
|
|
389
|
+
parseHTML() {
|
|
390
|
+
return [{
|
|
391
|
+
tag: "span",
|
|
392
|
+
priority: 40,
|
|
393
|
+
getAttrs: (el) => {
|
|
394
|
+
if (el.hasAttribute("data-variable-id"))
|
|
395
|
+
return false;
|
|
396
|
+
const style = el.getAttribute("style");
|
|
397
|
+
const cls = el.getAttribute("class");
|
|
398
|
+
if (!style && !cls)
|
|
399
|
+
return false;
|
|
400
|
+
return { style, class: cls };
|
|
401
|
+
}
|
|
402
|
+
}];
|
|
403
|
+
},
|
|
404
|
+
renderHTML({ HTMLAttributes }) {
|
|
405
|
+
return ["span", mergeAttributes(HTMLAttributes), 0];
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
export const PocUnderline = TiptapMark.create({
|
|
409
|
+
name: "underline",
|
|
410
|
+
parseHTML() {
|
|
411
|
+
return [
|
|
412
|
+
{ tag: "u" },
|
|
413
|
+
{ style: "text-decoration", consuming: false, getAttrs: (value) => value.includes("underline") ? {} : false }
|
|
414
|
+
];
|
|
415
|
+
},
|
|
416
|
+
renderHTML() {
|
|
417
|
+
return ["u", 0];
|
|
418
|
+
},
|
|
419
|
+
addCommands() {
|
|
420
|
+
return {
|
|
421
|
+
toggleUnderline: () => ({ commands }) => commands.toggleMark("underline")
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
export const PocStylePassthrough = TiptapExtension.create({
|
|
426
|
+
name: "pocStylePassthrough",
|
|
427
|
+
addGlobalAttributes() {
|
|
428
|
+
const attr = (name) => ({
|
|
429
|
+
default: null,
|
|
430
|
+
parseHTML: (el) => el.getAttribute(name),
|
|
431
|
+
renderHTML: (attrs) => attrs[name] ? { [name]: attrs[name] } : {}
|
|
432
|
+
});
|
|
433
|
+
return [{
|
|
434
|
+
types: ["paragraph", "heading", "blockquote", "table", "tableRow", "tableCell", "tableHeader"],
|
|
435
|
+
attributes: { style: attr("style"), class: attr("class") }
|
|
436
|
+
}];
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
export function buildPocExtensions(pageConfig) {
|
|
440
|
+
const extensions = [
|
|
441
|
+
PocDocument,
|
|
442
|
+
TiptapStarterKit.configure({ document: false }),
|
|
443
|
+
Table.configure({ resizable: true }),
|
|
444
|
+
TableRow,
|
|
445
|
+
TableCell,
|
|
446
|
+
TableHeader,
|
|
447
|
+
PocMinutaBlock,
|
|
448
|
+
PocHtmlIsland,
|
|
449
|
+
PocMissingImage,
|
|
450
|
+
PocVariableChip,
|
|
451
|
+
PocFootnoteRef,
|
|
452
|
+
PocStyledDiv,
|
|
453
|
+
PocInlineStyle,
|
|
454
|
+
PocUnderline,
|
|
455
|
+
PocStylePassthrough
|
|
456
|
+
];
|
|
457
|
+
if (pageConfig) {
|
|
458
|
+
extensions.push(TiptapExtension.create({
|
|
459
|
+
name: "pocPagination",
|
|
460
|
+
addProseMirrorPlugins() {
|
|
461
|
+
return [PocPaginationPlugin(pageConfig)];
|
|
462
|
+
}
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
return extensions;
|
|
466
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
export interface PocPageBreak {
|
|
59
|
+
pos: number;
|
|
60
|
+
filler: number;
|
|
61
|
+
pageNo: number;
|
|
62
|
+
/** notas cujos marcadores estão nesta página — desenhadas acima do rodapé */
|
|
63
|
+
notes?: PocPageNote[];
|
|
64
|
+
/** altura reservada pelas notas (já descontada do filler) */
|
|
65
|
+
notesH?: number;
|
|
66
|
+
}
|
|
67
|
+
interface PagState {
|
|
68
|
+
breaks: PocPageBreak[];
|
|
69
|
+
total: number;
|
|
70
|
+
/** parágrafos-marcador "PAGEBREAK" (quebra manual do template) */
|
|
71
|
+
markers: {
|
|
72
|
+
from: number;
|
|
73
|
+
to: number;
|
|
74
|
+
}[];
|
|
75
|
+
}
|
|
76
|
+
export declare const pocPageKey: PluginKey<PagState>;
|
|
77
|
+
export declare const PAGINATE_META = "poc-paginate";
|
|
78
|
+
export declare function pocChromeFlowHeight(cfg: PocPageConfig): number;
|
|
79
|
+
/**
|
|
80
|
+
* o chrome tem dois formatos: canvas (elementos posicionados, coordenadas no
|
|
81
|
+
* frame da PÁGINA — vem do editor estilo Word) e flow legado (texto corrido)
|
|
82
|
+
*/
|
|
83
|
+
export declare function isPocChromeCanvas(html: string | undefined | null): boolean;
|
|
84
|
+
export declare function buildPocHeaderEl(cfg: PocPageConfig): HTMLElement;
|
|
85
|
+
export declare function buildPocFooterEl(cfg: PocPageConfig, pageNo: number, total: number): HTMLElement;
|
|
86
|
+
/** área de notas de rodapé: filete + notas, no pé da área útil (como no Word) */
|
|
87
|
+
export declare function buildPocNotesEl(notes: PocPageNote[]): HTMLElement;
|
|
88
|
+
export declare function computePocBreaks(view: EditorView, cfg: PocPageConfig, sheet: HTMLElement): {
|
|
89
|
+
breaks: PocPageBreak[];
|
|
90
|
+
total: number;
|
|
91
|
+
markers: {
|
|
92
|
+
from: number;
|
|
93
|
+
to: number;
|
|
94
|
+
}[];
|
|
95
|
+
lastNotes: PocPageNote[];
|
|
96
|
+
};
|
|
97
|
+
export declare function isPocFlexRow(node: PMNode): boolean;
|
|
98
|
+
/** conteúdo visível = texto, imagem com src preenchido ou svg (imgs vazias do pipeline não contam) */
|
|
99
|
+
export declare function pocNodeHasVisibleContent(node: PMNode): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* A célula/linha PINTA alguma coisa (borda ou fundo)?
|
|
102
|
+
*
|
|
103
|
+
* Uma coluna sem texto mas com borda não é espaço sobrando: é estrutura. A
|
|
104
|
+
* folha de rosto da Telefônica desenha uma timeline com `border-right` numa
|
|
105
|
+
* coluna VAZIA (o eixo central) e `border-bottom` alternando entre as duas —
|
|
106
|
+
* colapsar a coluna "vazia" apaga o eixo e centraliza o que devia ficar
|
|
107
|
+
* alternando esquerda/direita.
|
|
108
|
+
*/
|
|
109
|
+
export declare function pocNodePaintsBox(node: PMNode): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Índice da única célula com conteúdo da linha, ou null se a linha não pode
|
|
112
|
+
* ser colapsada. Fonte única da regra: o preview (decorations .poc-lone-row)
|
|
113
|
+
* e o export DOCX chamam esta função — se divergirem, o Word deixa de bater
|
|
114
|
+
* com a tela.
|
|
115
|
+
*/
|
|
116
|
+
export declare function pocLoneCellIndex(row: PMNode): number | null;
|
|
117
|
+
export interface PocLoneRow {
|
|
118
|
+
rowFrom: number;
|
|
119
|
+
rowTo: number;
|
|
120
|
+
cellFrom: number;
|
|
121
|
+
cellTo: number;
|
|
122
|
+
}
|
|
123
|
+
export declare function computePocLoneRows(doc: PMNode): PocLoneRow[];
|
|
124
|
+
export declare function breaksEqual(a: PocPageBreak[], b: PocPageBreak[]): boolean;
|
|
125
|
+
export declare function PocPaginationPlugin(cfg: PocPageConfig): Plugin<PagState>;
|
|
126
|
+
export {};
|