@meistrari/minuta-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -0
- package/dist/chunks/docxExport.mjs +1282 -0
- package/dist/docx.d.mts +5 -0
- package/dist/docx.d.ts +5 -0
- package/dist/docx.mjs +5 -0
- package/dist/index.d.mts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.mjs +1887 -0
- package/dist/shared/minuta-editor.b2512170.mjs +711 -0
- package/dist/shared/minuta-editor.e223a421.d.mts +166 -0
- package/dist/shared/minuta-editor.e223a421.d.ts +166 -0
- package/dist/style.css +1 -0
- package/package.json +97 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1887 @@
|
|
|
1
|
+
import { Node as Node$1, mergeAttributes, Mark, Extension, Editor } from '@tiptap/core';
|
|
2
|
+
import BubbleMenu from '@tiptap/extension-bubble-menu';
|
|
3
|
+
import Bold from '@tiptap/extension-bold';
|
|
4
|
+
import Blockquote from '@tiptap/extension-blockquote';
|
|
5
|
+
import BulletList from '@tiptap/extension-bullet-list';
|
|
6
|
+
import Code from '@tiptap/extension-code';
|
|
7
|
+
import CodeBlock from '@tiptap/extension-code-block';
|
|
8
|
+
import Dropcursor from '@tiptap/extension-dropcursor';
|
|
9
|
+
import Gapcursor from '@tiptap/extension-gapcursor';
|
|
10
|
+
import HardBreak from '@tiptap/extension-hard-break';
|
|
11
|
+
import Heading from '@tiptap/extension-heading';
|
|
12
|
+
import History from '@tiptap/extension-history';
|
|
13
|
+
import HorizontalRule from '@tiptap/extension-horizontal-rule';
|
|
14
|
+
import Italic from '@tiptap/extension-italic';
|
|
15
|
+
import ListItem from '@tiptap/extension-list-item';
|
|
16
|
+
import OrderedList from '@tiptap/extension-ordered-list';
|
|
17
|
+
import Paragraph from '@tiptap/extension-paragraph';
|
|
18
|
+
import Strike from '@tiptap/extension-strike';
|
|
19
|
+
import Text from '@tiptap/extension-text';
|
|
20
|
+
import Table from '@tiptap/extension-table';
|
|
21
|
+
import TableCell from '@tiptap/extension-table-cell';
|
|
22
|
+
import TableHeader from '@tiptap/extension-table-header';
|
|
23
|
+
import TableRow from '@tiptap/extension-table-row';
|
|
24
|
+
import { Plugin } from '@tiptap/pm/state';
|
|
25
|
+
import { s as sanitizeRichHtml, P as PocPaginationPlugin, a as sanitizeFootnoteHtml, c as computePocBreaks, b as pocPageKey, d as breaksEqual, e as PAGINATE_META } from './shared/minuta-editor.b2512170.mjs';
|
|
26
|
+
export { j as buildPocFooterEl, h as buildPocHeaderEl, k as buildPocNotesEl, o as computePocLoneRows, f as footnoteHtmlToRuns, q as footnoteSizePt, i as isPocChromeCanvas, l as isPocFlexRow, g as pocChromeFlowHeight, p as pocLoneCellIndex, m as pocNodeHasVisibleContent, n as pocNodePaintsBox, r as shouldMoveBreakToHeading } from './shared/minuta-editor.b2512170.mjs';
|
|
27
|
+
import '@tiptap/pm/view';
|
|
28
|
+
|
|
29
|
+
const POC_ASSET_SWAP = "pocAssetSwap";
|
|
30
|
+
function htmlToDom(html) {
|
|
31
|
+
const tpl = document.createElement("template");
|
|
32
|
+
tpl.innerHTML = sanitizeRichHtml(html);
|
|
33
|
+
return tpl.content.firstElementChild ?? document.createElement("div");
|
|
34
|
+
}
|
|
35
|
+
const PocDocument = Node$1.create({
|
|
36
|
+
name: "doc",
|
|
37
|
+
topNode: true,
|
|
38
|
+
content: "minutaBlock+"
|
|
39
|
+
});
|
|
40
|
+
const PocMinutaBlock = Node$1.create({
|
|
41
|
+
name: "minutaBlock",
|
|
42
|
+
group: "block",
|
|
43
|
+
content: "block+",
|
|
44
|
+
defining: true,
|
|
45
|
+
isolating: true,
|
|
46
|
+
addAttributes() {
|
|
47
|
+
return {
|
|
48
|
+
blockId: { default: null },
|
|
49
|
+
blockTitle: { default: null }
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
parseHTML() {
|
|
53
|
+
return [{
|
|
54
|
+
tag: "div[data-minuta-block]",
|
|
55
|
+
priority: 120,
|
|
56
|
+
getAttrs: (el) => ({
|
|
57
|
+
blockId: el.getAttribute("data-block-id"),
|
|
58
|
+
blockTitle: el.getAttribute("data-block-title")
|
|
59
|
+
})
|
|
60
|
+
}];
|
|
61
|
+
},
|
|
62
|
+
renderHTML({ HTMLAttributes, node }) {
|
|
63
|
+
return ["div", mergeAttributes(HTMLAttributes, {
|
|
64
|
+
"data-minuta-block": "",
|
|
65
|
+
"data-block-id": node.attrs.blockId,
|
|
66
|
+
"data-block-title": node.attrs.blockTitle,
|
|
67
|
+
"class": "poc-minuta-block"
|
|
68
|
+
}), 0];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const PocHtmlIsland = Node$1.create({
|
|
72
|
+
name: "htmlIsland",
|
|
73
|
+
group: "block",
|
|
74
|
+
atom: true,
|
|
75
|
+
selectable: true,
|
|
76
|
+
// atomos arrastáveis: mover imagens/tabelas pelo documento
|
|
77
|
+
draggable: true,
|
|
78
|
+
addAttributes() {
|
|
79
|
+
return { html: { default: "" } };
|
|
80
|
+
},
|
|
81
|
+
parseHTML() {
|
|
82
|
+
const capture = (el) => ({ html: el.outerHTML });
|
|
83
|
+
return ["ul", "ol", "img", "figure", "svg"].map((tag) => ({
|
|
84
|
+
tag,
|
|
85
|
+
priority: 100,
|
|
86
|
+
getAttrs: capture
|
|
87
|
+
}));
|
|
88
|
+
},
|
|
89
|
+
renderHTML({ node }) {
|
|
90
|
+
return htmlToDom(node.attrs.html);
|
|
91
|
+
},
|
|
92
|
+
addNodeView() {
|
|
93
|
+
return ({ node, getPos, editor }) => {
|
|
94
|
+
const dom = document.createElement("div");
|
|
95
|
+
dom.className = "poc-island";
|
|
96
|
+
dom.contentEditable = "false";
|
|
97
|
+
dom.innerHTML = sanitizeRichHtml(node.attrs.html);
|
|
98
|
+
const img = dom.children.length === 1 && dom.firstElementChild instanceof HTMLImageElement ? dom.firstElementChild : null;
|
|
99
|
+
if (img)
|
|
100
|
+
dom.classList.add("poc-island--img");
|
|
101
|
+
if (img && editor.isEditable) {
|
|
102
|
+
const handle = document.createElement("div");
|
|
103
|
+
handle.className = "poc-img-handle";
|
|
104
|
+
handle.title = "Arraste para redimensionar";
|
|
105
|
+
dom.appendChild(handle);
|
|
106
|
+
let startX = 0;
|
|
107
|
+
let startWidth = 0;
|
|
108
|
+
let scale = 1;
|
|
109
|
+
let maxWidth = Number.POSITIVE_INFINITY;
|
|
110
|
+
const onMove = (event) => {
|
|
111
|
+
const width = Math.round(startWidth + (event.clientX - startX) / scale);
|
|
112
|
+
img.style.width = `${Math.min(maxWidth, Math.max(40, width))}px`;
|
|
113
|
+
img.style.height = "auto";
|
|
114
|
+
};
|
|
115
|
+
const onUp = () => {
|
|
116
|
+
document.removeEventListener("mousemove", onMove);
|
|
117
|
+
document.removeEventListener("mouseup", onUp);
|
|
118
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
119
|
+
if (typeof pos === "number") {
|
|
120
|
+
const clone = dom.cloneNode(true);
|
|
121
|
+
clone.querySelector(".poc-img-handle")?.remove();
|
|
122
|
+
editor.view.dispatch(
|
|
123
|
+
editor.state.tr.setNodeMarkup(pos, void 0, { html: clone.innerHTML })
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
handle.addEventListener("mousedown", (event) => {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
event.stopPropagation();
|
|
130
|
+
startX = event.clientX;
|
|
131
|
+
startWidth = img.offsetWidth;
|
|
132
|
+
scale = img.offsetWidth ? img.getBoundingClientRect().width / img.offsetWidth : 1;
|
|
133
|
+
maxWidth = dom.parentElement?.clientWidth || Number.POSITIVE_INFINITY;
|
|
134
|
+
document.addEventListener("mousemove", onMove);
|
|
135
|
+
document.addEventListener("mouseup", onUp);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { dom };
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
let missingImageSeq = 0;
|
|
143
|
+
function nextMissingImageUid() {
|
|
144
|
+
missingImageSeq += 1;
|
|
145
|
+
return `mi-${missingImageSeq}`;
|
|
146
|
+
}
|
|
147
|
+
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;";
|
|
148
|
+
const MISSING_IMAGE_ROW_STYLE = "display: flex; align-items: center; justify-content: center; gap: 8px;";
|
|
149
|
+
const PocMissingImage = Node$1.create({
|
|
150
|
+
name: "missingImage",
|
|
151
|
+
group: "block",
|
|
152
|
+
atom: true,
|
|
153
|
+
selectable: true,
|
|
154
|
+
draggable: true,
|
|
155
|
+
addAttributes() {
|
|
156
|
+
return {
|
|
157
|
+
variableId: { default: null },
|
|
158
|
+
parentBlockId: { default: null },
|
|
159
|
+
/**
|
|
160
|
+
* Identidade do nó DENTRO desta sessão do editor.
|
|
161
|
+
*
|
|
162
|
+
* Um fluxo demorado (escolher arquivo + upload) precisa voltar
|
|
163
|
+
* exatamente ao aviso que foi clicado. Posição não serve: o
|
|
164
|
+
* documento muda no meio. Tipo + variável também não: o mesmo
|
|
165
|
+
* aviso pode aparecer várias vezes, e um delete+insert põe outro
|
|
166
|
+
* igual no mesmo lugar. Com um id por nó, achar é inequívoco —
|
|
167
|
+
* se ele sumiu, não achamos; se ele andou, achamos onde parou.
|
|
168
|
+
*
|
|
169
|
+
* Gerado no parse e NÃO serializado: vale enquanto o editor viver,
|
|
170
|
+
* que é o tempo de vida do fluxo. Colar um aviso passa pelo parse
|
|
171
|
+
* de novo e ganha id próprio, então nunca há dois iguais.
|
|
172
|
+
*/
|
|
173
|
+
uid: { default: null }
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
parseHTML() {
|
|
177
|
+
return [{
|
|
178
|
+
tag: "div.missing-image-warning",
|
|
179
|
+
// acima do styledDiv (40), senão a caixa vira container editável de novo
|
|
180
|
+
priority: 120,
|
|
181
|
+
getAttrs: (el) => ({
|
|
182
|
+
variableId: el.getAttribute("data-variable-id"),
|
|
183
|
+
parentBlockId: el.getAttribute("data-parent-block-id"),
|
|
184
|
+
uid: nextMissingImageUid()
|
|
185
|
+
})
|
|
186
|
+
}];
|
|
187
|
+
},
|
|
188
|
+
renderHTML({ node }) {
|
|
189
|
+
return [
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
"class": "missing-image-warning",
|
|
193
|
+
"data-variable-id": node.attrs.variableId ?? "",
|
|
194
|
+
"data-parent-block-id": node.attrs.parentBlockId ?? "",
|
|
195
|
+
"style": MISSING_IMAGE_BOX_STYLE
|
|
196
|
+
// `uid` fica de fora de propósito: é identidade de sessão, não dado
|
|
197
|
+
},
|
|
198
|
+
["span", { style: MISSING_IMAGE_ROW_STYLE }, `A imagem ${node.attrs.variableId ?? ""} est\xE1 faltando`]
|
|
199
|
+
];
|
|
200
|
+
},
|
|
201
|
+
addNodeView() {
|
|
202
|
+
return ({ node, getPos, editor }) => {
|
|
203
|
+
const dom = document.createElement("div");
|
|
204
|
+
dom.className = "poc-missing-image";
|
|
205
|
+
dom.contentEditable = "false";
|
|
206
|
+
const caixa = document.createElement("div");
|
|
207
|
+
caixa.className = "missing-image-warning";
|
|
208
|
+
caixa.setAttribute("style", MISSING_IMAGE_BOX_STYLE);
|
|
209
|
+
const linha = document.createElement("span");
|
|
210
|
+
linha.setAttribute("style", MISSING_IMAGE_ROW_STYLE);
|
|
211
|
+
linha.textContent = `\u26A0 A imagem ${node.attrs.variableId ?? ""} est\xE1 faltando`;
|
|
212
|
+
caixa.appendChild(linha);
|
|
213
|
+
dom.appendChild(caixa);
|
|
214
|
+
if (!editor.isEditable)
|
|
215
|
+
return { dom };
|
|
216
|
+
caixa.style.position = "relative";
|
|
217
|
+
caixa.style.cursor = "pointer";
|
|
218
|
+
caixa.title = "Clique para escolher uma imagem";
|
|
219
|
+
const remover = document.createElement("button");
|
|
220
|
+
remover.type = "button";
|
|
221
|
+
remover.className = "poc-missing-image__remove";
|
|
222
|
+
remover.title = "Remover este aviso";
|
|
223
|
+
remover.setAttribute("aria-label", "Remover este aviso");
|
|
224
|
+
remover.textContent = "\xD7";
|
|
225
|
+
remover.addEventListener("mousedown", (event) => {
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
event.stopPropagation();
|
|
228
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
229
|
+
if (typeof pos === "number")
|
|
230
|
+
editor.view.dispatch(editor.state.tr.delete(pos, pos + node.nodeSize));
|
|
231
|
+
});
|
|
232
|
+
caixa.appendChild(remover);
|
|
233
|
+
dom.addEventListener("click", (event) => {
|
|
234
|
+
if (event.target.closest(".poc-missing-image__remove"))
|
|
235
|
+
return;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
238
|
+
window.dispatchEvent(new CustomEvent("poc:pick-image", {
|
|
239
|
+
detail: { variableId: node.attrs.variableId, uid: node.attrs.uid, hintPos: pos }
|
|
240
|
+
}));
|
|
241
|
+
});
|
|
242
|
+
return { dom };
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
const PocVariableChip = Node$1.create({
|
|
247
|
+
name: "variableChip",
|
|
248
|
+
inline: true,
|
|
249
|
+
group: "inline",
|
|
250
|
+
atom: true,
|
|
251
|
+
selectable: true,
|
|
252
|
+
addAttributes() {
|
|
253
|
+
return {
|
|
254
|
+
variableId: { default: null },
|
|
255
|
+
parentBlockId: { default: null },
|
|
256
|
+
html: { default: "" },
|
|
257
|
+
text: { default: "" }
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
parseHTML() {
|
|
261
|
+
return [{
|
|
262
|
+
tag: "span[data-variable-id]",
|
|
263
|
+
priority: 110,
|
|
264
|
+
getAttrs: (el) => ({
|
|
265
|
+
variableId: el.getAttribute("data-variable-id"),
|
|
266
|
+
parentBlockId: el.getAttribute("data-parent-block-id"),
|
|
267
|
+
html: el.outerHTML,
|
|
268
|
+
text: el.textContent ?? ""
|
|
269
|
+
})
|
|
270
|
+
}];
|
|
271
|
+
},
|
|
272
|
+
renderHTML({ node }) {
|
|
273
|
+
return htmlToDom(node.attrs.html);
|
|
274
|
+
},
|
|
275
|
+
addNodeView() {
|
|
276
|
+
return ({ node, editor }) => {
|
|
277
|
+
const dom = document.createElement("span");
|
|
278
|
+
dom.className = "poc-varchip";
|
|
279
|
+
dom.contentEditable = "false";
|
|
280
|
+
dom.innerHTML = sanitizeRichHtml(node.attrs.html);
|
|
281
|
+
dom.addEventListener("click", (event) => {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
editor.view.dom.dispatchEvent(new CustomEvent("poc:variable-click", {
|
|
284
|
+
bubbles: true,
|
|
285
|
+
detail: {
|
|
286
|
+
variableId: node.attrs.variableId,
|
|
287
|
+
parentBlockId: node.attrs.parentBlockId,
|
|
288
|
+
text: node.attrs.text
|
|
289
|
+
}
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
return { dom };
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
function variableChipHtml(currentHtml, text) {
|
|
297
|
+
const tpl = document.createElement("template");
|
|
298
|
+
tpl.innerHTML = sanitizeRichHtml(currentHtml);
|
|
299
|
+
const el = tpl.content.firstElementChild;
|
|
300
|
+
if (!el)
|
|
301
|
+
return currentHtml;
|
|
302
|
+
el.textContent = text;
|
|
303
|
+
return el.outerHTML;
|
|
304
|
+
}
|
|
305
|
+
function readCatalog(catalog) {
|
|
306
|
+
return typeof catalog === "function" ? catalog() : catalog;
|
|
307
|
+
}
|
|
308
|
+
const PocTemplateVariable = Node$1.create({
|
|
309
|
+
name: "templateVariable",
|
|
310
|
+
inline: true,
|
|
311
|
+
group: "inline",
|
|
312
|
+
atom: true,
|
|
313
|
+
selectable: true,
|
|
314
|
+
addOptions() {
|
|
315
|
+
return { catalog: [] };
|
|
316
|
+
},
|
|
317
|
+
addAttributes() {
|
|
318
|
+
return {
|
|
319
|
+
varKey: { default: null },
|
|
320
|
+
label: { default: null }
|
|
321
|
+
};
|
|
322
|
+
},
|
|
323
|
+
parseHTML() {
|
|
324
|
+
return [{
|
|
325
|
+
// acima do PocInlineStyle (40): é nó, não span estilizado
|
|
326
|
+
tag: "span[data-minuta-var]",
|
|
327
|
+
priority: 115,
|
|
328
|
+
getAttrs: (el) => {
|
|
329
|
+
const key = el.getAttribute("data-minuta-var")?.trim();
|
|
330
|
+
if (!key)
|
|
331
|
+
return false;
|
|
332
|
+
return {
|
|
333
|
+
varKey: key,
|
|
334
|
+
label: el.getAttribute("data-minuta-var-label")?.trim() || el.textContent?.trim() || key
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}];
|
|
338
|
+
},
|
|
339
|
+
renderHTML({ node }) {
|
|
340
|
+
const key = node.attrs.varKey ?? "";
|
|
341
|
+
const label = node.attrs.label || key;
|
|
342
|
+
return ["span", {
|
|
343
|
+
"data-minuta-var": key,
|
|
344
|
+
"data-minuta-var-label": label,
|
|
345
|
+
"class": "poc-tplvar"
|
|
346
|
+
}, label];
|
|
347
|
+
},
|
|
348
|
+
addNodeView() {
|
|
349
|
+
return ({ node }) => {
|
|
350
|
+
const key = node.attrs.varKey ?? "";
|
|
351
|
+
const catalog = readCatalog(this.options.catalog);
|
|
352
|
+
const def = catalog.find((item) => item.key === key);
|
|
353
|
+
const dom = document.createElement("span");
|
|
354
|
+
dom.className = "poc-tplvar";
|
|
355
|
+
dom.contentEditable = "false";
|
|
356
|
+
dom.setAttribute("data-minuta-var", key);
|
|
357
|
+
dom.textContent = def?.label || node.attrs.label || key;
|
|
358
|
+
dom.title = def?.description ? `${key} \u2014 ${def.description}` : key;
|
|
359
|
+
if (catalog.length && !def)
|
|
360
|
+
dom.classList.add("poc-tplvar--unknown");
|
|
361
|
+
return { dom };
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
addCommands() {
|
|
365
|
+
return {
|
|
366
|
+
insertTemplateVariable: (variable) => ({ commands }) => commands.insertContent({
|
|
367
|
+
type: "templateVariable",
|
|
368
|
+
attrs: { varKey: variable.key, label: variable.label }
|
|
369
|
+
})
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
const PocFootnoteRef = Node$1.create({
|
|
374
|
+
name: "footnoteRef",
|
|
375
|
+
inline: true,
|
|
376
|
+
group: "inline",
|
|
377
|
+
atom: true,
|
|
378
|
+
selectable: false,
|
|
379
|
+
addAttributes() {
|
|
380
|
+
return {
|
|
381
|
+
number: { default: "" },
|
|
382
|
+
/** conteúdo da nota em HTML de ênfase (b/i/u/br) */
|
|
383
|
+
text: { default: "" },
|
|
384
|
+
/** corpo declarado no template (`font-size: 9pt`), em pontos */
|
|
385
|
+
size: { default: null }
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
parseHTML() {
|
|
389
|
+
return [{
|
|
390
|
+
tag: "sup[data-footnote]",
|
|
391
|
+
priority: 110,
|
|
392
|
+
getAttrs: (el) => ({
|
|
393
|
+
number: el.textContent ?? "",
|
|
394
|
+
text: el.getAttribute("data-footnote") ?? "",
|
|
395
|
+
size: Number.parseFloat(el.getAttribute("data-footnote-size") ?? "") || null
|
|
396
|
+
})
|
|
397
|
+
}];
|
|
398
|
+
},
|
|
399
|
+
renderHTML({ node }) {
|
|
400
|
+
const attrs = { "data-footnote": node.attrs.text, "class": "poc-footnote" };
|
|
401
|
+
if (node.attrs.size)
|
|
402
|
+
attrs["data-footnote-size"] = String(node.attrs.size);
|
|
403
|
+
return ["sup", attrs, node.attrs.number];
|
|
404
|
+
},
|
|
405
|
+
addNodeView() {
|
|
406
|
+
return ({ node, editor, getPos }) => {
|
|
407
|
+
const dom = document.createElement("sup");
|
|
408
|
+
dom.className = "poc-footnote";
|
|
409
|
+
dom.contentEditable = "false";
|
|
410
|
+
dom.textContent = node.attrs.number;
|
|
411
|
+
dom.title = node.attrs.text;
|
|
412
|
+
dom.addEventListener("click", (event) => {
|
|
413
|
+
if (!editor.isEditable || typeof getPos !== "function")
|
|
414
|
+
return;
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
event.stopPropagation();
|
|
417
|
+
const pos = getPos();
|
|
418
|
+
if (pos == null)
|
|
419
|
+
return;
|
|
420
|
+
editor.view.dom.dispatchEvent(new CustomEvent("poc:edit-footnote", { detail: { pos }, bubbles: true }));
|
|
421
|
+
});
|
|
422
|
+
return { dom };
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
function renumberPocFootnotes(editor) {
|
|
427
|
+
if (!editor || editor.isDestroyed)
|
|
428
|
+
return;
|
|
429
|
+
const tr = editor.state.tr;
|
|
430
|
+
let index = 0;
|
|
431
|
+
let touched = false;
|
|
432
|
+
editor.state.doc.descendants((node, pos) => {
|
|
433
|
+
if (node.type.name !== "footnoteRef")
|
|
434
|
+
return;
|
|
435
|
+
index++;
|
|
436
|
+
const number = String(index);
|
|
437
|
+
if (node.attrs.number !== number) {
|
|
438
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, number });
|
|
439
|
+
touched = true;
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
if (touched)
|
|
443
|
+
editor.view.dispatch(tr);
|
|
444
|
+
}
|
|
445
|
+
const PocStyledDiv = Node$1.create({
|
|
446
|
+
name: "styledDiv",
|
|
447
|
+
group: "block",
|
|
448
|
+
content: "block+",
|
|
449
|
+
addAttributes() {
|
|
450
|
+
const passthrough = (name) => ({
|
|
451
|
+
default: null,
|
|
452
|
+
parseHTML: (el) => el.getAttribute(name),
|
|
453
|
+
renderHTML: (attrs) => attrs[name] != null ? { [name]: attrs[name] } : {}
|
|
454
|
+
});
|
|
455
|
+
return {
|
|
456
|
+
"style": { default: null },
|
|
457
|
+
"class": { default: null },
|
|
458
|
+
// alinhamento de colunas do template (usado pelo CSS de centralização de imagens)
|
|
459
|
+
"data-horizontal-align": passthrough("data-horizontal-align"),
|
|
460
|
+
"data-vertical-align": passthrough("data-vertical-align"),
|
|
461
|
+
"data-width": passthrough("data-width")
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
parseHTML() {
|
|
465
|
+
return [{
|
|
466
|
+
tag: "div",
|
|
467
|
+
priority: 40,
|
|
468
|
+
getAttrs: (el) => {
|
|
469
|
+
if (el.hasAttribute("data-minuta-block"))
|
|
470
|
+
return false;
|
|
471
|
+
return { style: el.getAttribute("style"), class: el.getAttribute("class") };
|
|
472
|
+
}
|
|
473
|
+
}];
|
|
474
|
+
},
|
|
475
|
+
renderHTML({ HTMLAttributes }) {
|
|
476
|
+
return ["div", mergeAttributes(HTMLAttributes), 0];
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
const PocInlineStyle = Mark.create({
|
|
480
|
+
name: "inlineStyle",
|
|
481
|
+
addAttributes() {
|
|
482
|
+
return {
|
|
483
|
+
style: { default: null },
|
|
484
|
+
class: { default: null }
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
parseHTML() {
|
|
488
|
+
return [{
|
|
489
|
+
tag: "span",
|
|
490
|
+
priority: 40,
|
|
491
|
+
getAttrs: (el) => {
|
|
492
|
+
if (el.hasAttribute("data-variable-id") || el.hasAttribute("data-minuta-var"))
|
|
493
|
+
return false;
|
|
494
|
+
const style = el.getAttribute("style");
|
|
495
|
+
const cls = el.getAttribute("class");
|
|
496
|
+
if (!style && !cls)
|
|
497
|
+
return false;
|
|
498
|
+
return { style, class: cls };
|
|
499
|
+
}
|
|
500
|
+
}];
|
|
501
|
+
},
|
|
502
|
+
renderHTML({ HTMLAttributes }) {
|
|
503
|
+
return ["span", mergeAttributes(HTMLAttributes), 0];
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
const PocUnderline = Mark.create({
|
|
507
|
+
name: "underline",
|
|
508
|
+
parseHTML() {
|
|
509
|
+
return [
|
|
510
|
+
{ tag: "u" },
|
|
511
|
+
{ style: "text-decoration", consuming: false, getAttrs: (value) => value.includes("underline") ? {} : false }
|
|
512
|
+
];
|
|
513
|
+
},
|
|
514
|
+
renderHTML() {
|
|
515
|
+
return ["u", 0];
|
|
516
|
+
},
|
|
517
|
+
addCommands() {
|
|
518
|
+
return {
|
|
519
|
+
toggleUnderline: () => ({ commands }) => commands.toggleMark("underline")
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
const PocStylePassthrough = Extension.create({
|
|
524
|
+
name: "pocStylePassthrough",
|
|
525
|
+
addGlobalAttributes() {
|
|
526
|
+
const attr = (name) => ({
|
|
527
|
+
default: null,
|
|
528
|
+
parseHTML: (el) => el.getAttribute(name),
|
|
529
|
+
renderHTML: (attrs) => attrs[name] ? { [name]: attrs[name] } : {}
|
|
530
|
+
});
|
|
531
|
+
return [{
|
|
532
|
+
types: ["paragraph", "heading", "blockquote", "table", "tableRow", "tableCell", "tableHeader"],
|
|
533
|
+
attributes: { style: attr("style"), class: attr("class") }
|
|
534
|
+
}];
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
function buildPocExtensions(pageConfig, options) {
|
|
538
|
+
const extensions = [
|
|
539
|
+
PocDocument,
|
|
540
|
+
// As mesmas extensões, na mesma ordem, que `StarterKit.configure({ document: false })`
|
|
541
|
+
// instanciava (2.11.9) — a ordem entra no schema (precedência de marks) e na
|
|
542
|
+
// ordem dos plugins. Individuais, e não o meta-pacote, por causa de quem
|
|
543
|
+
// INSTALA: o starter-kit declara as extensions com `^2.11.9`, e o `npm install`
|
|
544
|
+
// de um consumidor resolvia versões mais novas, que importam do core símbolos
|
|
545
|
+
// que o 2.11.9 não tem (`canInsertNode`). `overrides` não viajam no pacote;
|
|
546
|
+
// dependência direta pinada viaja.
|
|
547
|
+
Bold,
|
|
548
|
+
Blockquote,
|
|
549
|
+
BulletList,
|
|
550
|
+
Code,
|
|
551
|
+
CodeBlock,
|
|
552
|
+
Dropcursor,
|
|
553
|
+
Gapcursor,
|
|
554
|
+
HardBreak,
|
|
555
|
+
Heading,
|
|
556
|
+
History,
|
|
557
|
+
HorizontalRule,
|
|
558
|
+
Italic,
|
|
559
|
+
ListItem,
|
|
560
|
+
OrderedList,
|
|
561
|
+
Paragraph,
|
|
562
|
+
Strike,
|
|
563
|
+
Text,
|
|
564
|
+
Table.configure({ resizable: true }),
|
|
565
|
+
TableRow,
|
|
566
|
+
TableCell,
|
|
567
|
+
TableHeader,
|
|
568
|
+
PocMinutaBlock,
|
|
569
|
+
PocHtmlIsland,
|
|
570
|
+
PocMissingImage,
|
|
571
|
+
PocVariableChip,
|
|
572
|
+
PocTemplateVariable.configure({ catalog: options?.variables ?? [] }),
|
|
573
|
+
PocFootnoteRef,
|
|
574
|
+
PocStyledDiv,
|
|
575
|
+
PocInlineStyle,
|
|
576
|
+
PocUnderline,
|
|
577
|
+
PocStylePassthrough
|
|
578
|
+
];
|
|
579
|
+
const handleKeyDown = options?.handleKeyDown;
|
|
580
|
+
if (handleKeyDown) {
|
|
581
|
+
extensions.push(Extension.create({
|
|
582
|
+
name: "pocKeyDownFirst",
|
|
583
|
+
// acima do StarterKit (100): o ExtensionManager ordena os plugins
|
|
584
|
+
// por prioridade, e é essa ordem que o someProp percorre
|
|
585
|
+
priority: 1e3,
|
|
586
|
+
addProseMirrorPlugins() {
|
|
587
|
+
return [new Plugin({ props: { handleKeyDown } })];
|
|
588
|
+
}
|
|
589
|
+
}));
|
|
590
|
+
}
|
|
591
|
+
if (pageConfig) {
|
|
592
|
+
extensions.push(Extension.create({
|
|
593
|
+
name: "pocPagination",
|
|
594
|
+
addProseMirrorPlugins() {
|
|
595
|
+
return [PocPaginationPlugin(pageConfig)];
|
|
596
|
+
}
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
return extensions;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const VARIABLE_TAG_RE = /<span\b[^>]*\bdata-minuta-var="([^"]*)"[^>]*>[^<]*<\/span>/g;
|
|
603
|
+
function escapeHtml(value) {
|
|
604
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
605
|
+
}
|
|
606
|
+
function decodeAttr(value) {
|
|
607
|
+
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
608
|
+
}
|
|
609
|
+
function extractTemplateVariables(html) {
|
|
610
|
+
if (!html)
|
|
611
|
+
return [];
|
|
612
|
+
const keys = [];
|
|
613
|
+
for (const match of html.matchAll(VARIABLE_TAG_RE)) {
|
|
614
|
+
const key = decodeAttr(match[1] ?? "").trim();
|
|
615
|
+
if (key && !keys.includes(key))
|
|
616
|
+
keys.push(key);
|
|
617
|
+
}
|
|
618
|
+
return keys;
|
|
619
|
+
}
|
|
620
|
+
function resolveTemplateVariables(html, values) {
|
|
621
|
+
if (!html)
|
|
622
|
+
return { html: "", missing: [] };
|
|
623
|
+
const missing = [];
|
|
624
|
+
const resolved = html.replace(VARIABLE_TAG_RE, (tag, rawKey) => {
|
|
625
|
+
const key = decodeAttr(rawKey ?? "").trim();
|
|
626
|
+
if (!key)
|
|
627
|
+
return tag;
|
|
628
|
+
const value = values?.[key];
|
|
629
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
630
|
+
if (!missing.includes(key))
|
|
631
|
+
missing.push(key);
|
|
632
|
+
return tag;
|
|
633
|
+
}
|
|
634
|
+
return escapeHtml(value).replace(/\r?\n/g, "<br>");
|
|
635
|
+
});
|
|
636
|
+
return { html: resolved, missing };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function escapeAttr(value) {
|
|
640
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
641
|
+
}
|
|
642
|
+
function escapeVaultAttr(value) {
|
|
643
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
644
|
+
}
|
|
645
|
+
function decodeVaultAttr(value) {
|
|
646
|
+
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
647
|
+
}
|
|
648
|
+
function vaultImageHtml(src, vaultUrl) {
|
|
649
|
+
return `<img src="${escapeAttr(src)}" data-vault-src="${escapeVaultAttr(vaultUrl)}" style="width: 420px; height: auto; max-width: 100%;">`;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function h(tag, attrs = {}, children = []) {
|
|
653
|
+
const el = document.createElement(tag);
|
|
654
|
+
if (attrs.class)
|
|
655
|
+
el.className = attrs.class;
|
|
656
|
+
if (attrs.title)
|
|
657
|
+
el.title = attrs.title;
|
|
658
|
+
if (attrs.type && "type" in el)
|
|
659
|
+
el.type = attrs.type;
|
|
660
|
+
if (typeof attrs.style === "string")
|
|
661
|
+
el.setAttribute("style", attrs.style);
|
|
662
|
+
else if (attrs.style)
|
|
663
|
+
Object.assign(el.style, attrs.style);
|
|
664
|
+
if (attrs.attrs) {
|
|
665
|
+
for (const [k, v] of Object.entries(attrs.attrs))
|
|
666
|
+
el.setAttribute(k, v);
|
|
667
|
+
}
|
|
668
|
+
if (attrs.text != null)
|
|
669
|
+
el.textContent = attrs.text;
|
|
670
|
+
if (attrs.html != null)
|
|
671
|
+
el.innerHTML = attrs.html;
|
|
672
|
+
if (attrs.on) {
|
|
673
|
+
for (const [name, fn] of Object.entries(attrs.on)) {
|
|
674
|
+
if (fn)
|
|
675
|
+
el.addEventListener(name, fn);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
for (const child of children) {
|
|
679
|
+
if (child === null || child === void 0 || child === false)
|
|
680
|
+
continue;
|
|
681
|
+
el.append(typeof child === "string" ? document.createTextNode(child) : child);
|
|
682
|
+
}
|
|
683
|
+
return el;
|
|
684
|
+
}
|
|
685
|
+
function replaceChildren(el, children) {
|
|
686
|
+
el.replaceChildren(...children.filter((c) => c !== null && c !== void 0 && c !== false));
|
|
687
|
+
}
|
|
688
|
+
function toggleClass(el, cls, on) {
|
|
689
|
+
el.classList.toggle(cls, on);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const ICON_PATHS = {
|
|
693
|
+
"arrow-u-up-left": "M232,144a64.07,64.07,0,0,1-64,64H80a8,8,0,0,1,0-16h88a48,48,0,0,0,0-96H51.31l34.35,34.34a8,8,0,0,1-11.32,11.32l-48-48a8,8,0,0,1,0-11.32l48-48A8,8,0,0,1,85.66,45.66L51.31,80H168A64.07,64.07,0,0,1,232,144Z",
|
|
694
|
+
"arrow-u-up-right": "M170.34,130.34,204.69,96H88a48,48,0,0,0,0,96h88a8,8,0,0,1,0,16H88A64,64,0,0,1,88,80H204.69L170.34,45.66a8,8,0,0,1,11.32-11.32l48,48a8,8,0,0,1,0,11.32l-48,48a8,8,0,0,1-11.32-11.32Z",
|
|
695
|
+
"highlighter": "M253.66,106.34a8,8,0,0,0-11.32,0L192,156.69,107.31,72l50.35-50.34a8,8,0,1,0-11.32-11.32L96,60.69A16,16,0,0,0,93.18,79.5L72,100.69a16,16,0,0,0,0,22.62L76.69,128,18.34,186.34a8,8,0,0,0,3.13,13.25l72,24A7.88,7.88,0,0,0,96,224a8,8,0,0,0,5.66-2.34L136,187.31l4.69,4.69a16,16,0,0,0,22.62,0l21.19-21.18A16,16,0,0,0,203.31,168l50.35-50.34A8,8,0,0,0,253.66,106.34ZM93.84,206.85l-55-18.35L88,139.31,124.69,176ZM152,180.69,83.31,112,104,91.31,172.69,160Z",
|
|
696
|
+
"text-align-left": "M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z",
|
|
697
|
+
"text-align-center": "M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64ZM64,96a8,8,0,0,0,0,16H192a8,8,0,0,0,0-16Zm152,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-24,40H64a8,8,0,0,0,0,16H192a8,8,0,0,0,0-16Z",
|
|
698
|
+
"text-align-right": "M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64ZM216,96H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z",
|
|
699
|
+
"text-align-justify": "M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64ZM216,96H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,40H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z",
|
|
700
|
+
"list-bullets": "M80,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H88A8,8,0,0,1,80,64Zm136,56H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM44,52A12,12,0,1,0,56,64,12,12,0,0,0,44,52Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,116Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,180Z",
|
|
701
|
+
"list-numbers": "M224,128a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM104,72H216a8,8,0,0,0,0-16H104a8,8,0,0,0,0,16ZM216,184H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM43.58,55.16,48,52.94V104a8,8,0,0,0,16,0V40a8,8,0,0,0-11.58-7.16l-16,8a8,8,0,0,0,7.16,14.32ZM79.77,156.72a23.73,23.73,0,0,0-9.6-15.95,24.86,24.86,0,0,0-34.11,4.7,23.63,23.63,0,0,0-3.57,6.46,8,8,0,1,0,15,5.47,7.84,7.84,0,0,1,1.18-2.13,8.76,8.76,0,0,1,12-1.59A7.91,7.91,0,0,1,63.93,159a7.64,7.64,0,0,1-1.57,5.78,1,1,0,0,0-.08.11L33.59,203.21A8,8,0,0,0,40,216H72a8,8,0,0,0,0-16H56l19.08-25.53A23.47,23.47,0,0,0,79.77,156.72Z",
|
|
702
|
+
"image": "M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z",
|
|
703
|
+
"brackets-curly": "M43.18,128a29.78,29.78,0,0,1,8,10.26c4.8,9.9,4.8,22,4.8,33.74,0,24.31,1,36,24,36a8,8,0,0,1,0,16c-17.48,0-29.32-6.14-35.2-18.26-4.8-9.9-4.8-22-4.8-33.74,0-24.31-1-36-24-36a8,8,0,0,1,0-16c23,0,24-11.69,24-36,0-11.72,0-23.84,4.8-33.74C50.68,38.14,62.52,32,80,32a8,8,0,0,1,0,16C57,48,56,59.69,56,84c0,11.72,0,23.84-4.8,33.74A29.78,29.78,0,0,1,43.18,128ZM240,120c-23,0-24-11.69-24-36,0-11.72,0-23.84-4.8-33.74C205.32,38.14,193.48,32,176,32a8,8,0,0,0,0,16c23,0,24,11.69,24,36,0,11.72,0,23.84,4.8,33.74a29.78,29.78,0,0,0,8,10.26,29.78,29.78,0,0,0-8,10.26c-4.8,9.9-4.8,22-4.8,33.74,0,24.31-1,36-24,36a8,8,0,0,0,0,16c17.48,0,29.32-6.14,35.2-18.26,4.8-9.9,4.8-22,4.8-33.74,0-24.31,1-36,24-36a8,8,0,0,0,0-16Z",
|
|
704
|
+
"note": "M88,96a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,96Zm8,40h64a8,8,0,0,0,0-16H96a8,8,0,0,0,0,16Zm32,16H96a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16ZM224,48V156.69A15.86,15.86,0,0,1,219.31,168L168,219.31A15.86,15.86,0,0,1,156.69,224H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H152V160a8,8,0,0,1,8-8h48V48H48Zm120-40v28.7L196.69,168Z",
|
|
705
|
+
"arrows-out-line-vertical": "M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM101.66,53.66,120,35.31V96a8,8,0,0,0,16,0V35.31l18.34,18.35a8,8,0,0,0,11.32-11.32l-32-32a8,8,0,0,0-11.32,0l-32,32a8,8,0,0,0,11.32,11.32Zm52.68,148.68L136,220.69V160a8,8,0,0,0-16,0v60.69l-18.34-18.35a8,8,0,0,0-11.32,11.32l32,32a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Z",
|
|
706
|
+
"eraser": "M225,80.4,183.6,39a24,24,0,0,0-33.94,0L31,157.66a24,24,0,0,0,0,33.94l30.06,30.06A8,8,0,0,0,66.74,224H216a8,8,0,0,0,0-16h-84.7L225,114.34A24,24,0,0,0,225,80.4ZM108.68,208H70.05L42.33,180.28a8,8,0,0,1,0-11.31L96,115.31,148.69,168Zm105-105L160,156.69,107.31,104,161,50.34a8,8,0,0,1,11.32,0l41.38,41.38a8,8,0,0,1,0,11.31Z"
|
|
707
|
+
};
|
|
708
|
+
function iconSvg(name, size = 16) {
|
|
709
|
+
const d = ICON_PATHS[name];
|
|
710
|
+
if (!d)
|
|
711
|
+
return "";
|
|
712
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="${d}"/></svg>`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const DOC_FONTS = ["Arial", "Times New Roman", "Calibri", "Cambria", "Georgia", "Garamond", "Verdana", "Courier New"];
|
|
716
|
+
const FOOTNOTE_SIZES = [7, 8, 9, 10, 11, 12];
|
|
717
|
+
const FOOTNOTE_COLORS = ["#000000", "#6b7280", "#b91c1c", "#1d4ed8", "#047857"];
|
|
718
|
+
function createFootnotePopover(opts) {
|
|
719
|
+
let state = null;
|
|
720
|
+
let el = null;
|
|
721
|
+
let input = null;
|
|
722
|
+
function exec(command) {
|
|
723
|
+
input?.focus();
|
|
724
|
+
document.execCommand(command);
|
|
725
|
+
}
|
|
726
|
+
function applyStyle(prop, value) {
|
|
727
|
+
const sel = window.getSelection();
|
|
728
|
+
if (!input || !sel || sel.rangeCount === 0)
|
|
729
|
+
return;
|
|
730
|
+
const range = sel.getRangeAt(0);
|
|
731
|
+
if (!input.contains(range.commonAncestorContainer) || range.collapsed)
|
|
732
|
+
return;
|
|
733
|
+
const span = document.createElement("span");
|
|
734
|
+
span.style.setProperty(prop, value);
|
|
735
|
+
span.appendChild(range.extractContents());
|
|
736
|
+
range.insertNode(span);
|
|
737
|
+
sel.removeAllRanges();
|
|
738
|
+
const next = document.createRange();
|
|
739
|
+
next.selectNodeContents(span);
|
|
740
|
+
sel.addRange(next);
|
|
741
|
+
input.focus();
|
|
742
|
+
}
|
|
743
|
+
function save() {
|
|
744
|
+
const ed = opts.editor;
|
|
745
|
+
const current = state;
|
|
746
|
+
const html = input ? sanitizeFootnoteHtml(input.innerHTML) : "";
|
|
747
|
+
close();
|
|
748
|
+
if (!current || ed.isDestroyed)
|
|
749
|
+
return;
|
|
750
|
+
const node = ed.state.doc.nodeAt(current.pos);
|
|
751
|
+
if (!node || node.type.name !== "footnoteRef")
|
|
752
|
+
return;
|
|
753
|
+
const text = html.trim();
|
|
754
|
+
if (!text)
|
|
755
|
+
ed.chain().focus().deleteRange({ from: current.pos, to: current.pos + node.nodeSize }).run();
|
|
756
|
+
else
|
|
757
|
+
ed.view.dispatch(ed.state.tr.setNodeMarkup(current.pos, void 0, { ...node.attrs, text }));
|
|
758
|
+
renumberPocFootnotes(ed);
|
|
759
|
+
}
|
|
760
|
+
function cancel() {
|
|
761
|
+
const ed = opts.editor;
|
|
762
|
+
const current = state;
|
|
763
|
+
close();
|
|
764
|
+
if (current?.isNew && !ed.isDestroyed) {
|
|
765
|
+
const node = ed.state.doc.nodeAt(current.pos);
|
|
766
|
+
if (node?.type.name === "footnoteRef" && !node.attrs.text)
|
|
767
|
+
ed.chain().focus().deleteRange({ from: current.pos, to: current.pos + node.nodeSize }).run();
|
|
768
|
+
renumberPocFootnotes(ed);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function remove() {
|
|
772
|
+
if (input)
|
|
773
|
+
input.innerHTML = "";
|
|
774
|
+
save();
|
|
775
|
+
}
|
|
776
|
+
function build(isNew) {
|
|
777
|
+
const stopMouse = (e) => e.preventDefault();
|
|
778
|
+
const fontSelect = h("select", { class: "poc-footnote-pop__select", title: "Fonte do trecho selecionado", on: {
|
|
779
|
+
mousedown: (e) => e.stopPropagation(),
|
|
780
|
+
change: () => {
|
|
781
|
+
if (fontSelect.value)
|
|
782
|
+
applyStyle("font-family", fontSelect.value);
|
|
783
|
+
fontSelect.value = "";
|
|
784
|
+
}
|
|
785
|
+
} }, [h("option", { text: "Fonte", attrs: { value: "" } }), ...DOC_FONTS.map((f) => h("option", { text: f, attrs: { value: f }, style: { fontFamily: f } }))]);
|
|
786
|
+
const sizeSelect = h("select", { class: "poc-footnote-pop__select poc-footnote-pop__select--narrow", title: "Tamanho do trecho selecionado", on: {
|
|
787
|
+
mousedown: (e) => e.stopPropagation(),
|
|
788
|
+
change: () => {
|
|
789
|
+
if (sizeSelect.value)
|
|
790
|
+
applyStyle("font-size", `${sizeSelect.value}pt`);
|
|
791
|
+
sizeSelect.value = "";
|
|
792
|
+
}
|
|
793
|
+
} }, [h("option", { text: "Tam.", attrs: { value: "" } }), ...FOOTNOTE_SIZES.map((s) => h("option", { text: String(s), attrs: { value: String(s) } }))]);
|
|
794
|
+
input = h("div", {
|
|
795
|
+
class: "poc-footnote-pop__input",
|
|
796
|
+
attrs: { "contenteditable": "true", "data-placeholder": "Escreva a nota\u2026" },
|
|
797
|
+
on: {
|
|
798
|
+
keydown: (event) => {
|
|
799
|
+
const plainEnter = event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
800
|
+
if (!plainEnter && event.key !== "Escape")
|
|
801
|
+
return;
|
|
802
|
+
event.preventDefault();
|
|
803
|
+
if (plainEnter)
|
|
804
|
+
save();
|
|
805
|
+
else
|
|
806
|
+
cancel();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
return h("div", { class: "poc-footnote-pop", on: { click: (e) => e.stopPropagation() } }, [
|
|
811
|
+
h("div", { class: "poc-footnote-pop__head", text: "Nota de rodap\xE9" }),
|
|
812
|
+
h("div", { class: "poc-footnote-pop__toolbar" }, [
|
|
813
|
+
h("button", { type: "button", class: "poc-footnote-pop__tool font-bold", title: "Negrito", text: "B", on: { mousedown: stopMouse, click: () => exec("bold") } }),
|
|
814
|
+
h("button", { type: "button", class: "poc-footnote-pop__tool italic", title: "It\xE1lico", text: "I", on: { mousedown: stopMouse, click: () => exec("italic") } }),
|
|
815
|
+
h("button", { type: "button", class: "poc-footnote-pop__tool underline", title: "Sublinhado", text: "U", on: { mousedown: stopMouse, click: () => exec("underline") } }),
|
|
816
|
+
h("span", { class: "poc-footnote-pop__sep" }),
|
|
817
|
+
fontSelect,
|
|
818
|
+
sizeSelect,
|
|
819
|
+
h("span", { class: "poc-footnote-pop__sep" }),
|
|
820
|
+
...FOOTNOTE_COLORS.map((color) => h("button", {
|
|
821
|
+
type: "button",
|
|
822
|
+
class: "poc-footnote-pop__swatch",
|
|
823
|
+
title: `Cor ${color}`,
|
|
824
|
+
style: { background: color },
|
|
825
|
+
on: { mousedown: stopMouse, click: () => applyStyle("color", color) }
|
|
826
|
+
}))
|
|
827
|
+
]),
|
|
828
|
+
input,
|
|
829
|
+
h("div", { class: "poc-footnote-pop__actions" }, [
|
|
830
|
+
isNew ? null : h("button", { type: "button", class: "poc-footnote-pop__remove", text: "Remover", on: { click: remove } }),
|
|
831
|
+
h("span", { class: "poc-footnote-pop__spacer" }),
|
|
832
|
+
h("button", { type: "button", class: "poc-footnote-pop__cancel", text: "Cancelar", on: { click: cancel } }),
|
|
833
|
+
h("button", { type: "button", class: "poc-footnote-pop__save", text: "Salvar", on: { click: save } })
|
|
834
|
+
])
|
|
835
|
+
]);
|
|
836
|
+
}
|
|
837
|
+
function open(pos, isNew) {
|
|
838
|
+
const ed = opts.editor;
|
|
839
|
+
if (ed.isDestroyed)
|
|
840
|
+
return;
|
|
841
|
+
const node = ed.state.doc.nodeAt(pos);
|
|
842
|
+
if (!node || node.type.name !== "footnoteRef")
|
|
843
|
+
return;
|
|
844
|
+
const coords = opts.sheetCoords(pos);
|
|
845
|
+
if (!coords)
|
|
846
|
+
return;
|
|
847
|
+
close();
|
|
848
|
+
state = { pos, isNew };
|
|
849
|
+
el = build(isNew);
|
|
850
|
+
el.style.top = `${coords.top}px`;
|
|
851
|
+
el.style.left = `${Math.max(8, Math.min(coords.left, opts.host.clientWidth - 372))}px`;
|
|
852
|
+
opts.host.appendChild(el);
|
|
853
|
+
if (input) {
|
|
854
|
+
input.innerHTML = sanitizeFootnoteHtml(node.attrs.text ?? "");
|
|
855
|
+
input.focus();
|
|
856
|
+
const sel = window.getSelection();
|
|
857
|
+
if (sel) {
|
|
858
|
+
const range = document.createRange();
|
|
859
|
+
range.selectNodeContents(input);
|
|
860
|
+
range.collapse(false);
|
|
861
|
+
sel.removeAllRanges();
|
|
862
|
+
sel.addRange(range);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function close() {
|
|
867
|
+
if (!el)
|
|
868
|
+
return;
|
|
869
|
+
el.remove();
|
|
870
|
+
el = null;
|
|
871
|
+
input = null;
|
|
872
|
+
state = null;
|
|
873
|
+
opts.onClose?.();
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
open,
|
|
877
|
+
close,
|
|
878
|
+
isOpen: () => !!state,
|
|
879
|
+
destroy: close
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const PALETTE = [
|
|
884
|
+
"#000000",
|
|
885
|
+
"#434343",
|
|
886
|
+
"#666666",
|
|
887
|
+
"#999999",
|
|
888
|
+
"#B7B7B7",
|
|
889
|
+
"#CCCCCC",
|
|
890
|
+
"#D9D9D9",
|
|
891
|
+
"#EFEFEF",
|
|
892
|
+
"#F3F3F3",
|
|
893
|
+
"#FFFFFF",
|
|
894
|
+
"#980000",
|
|
895
|
+
"#FF0000",
|
|
896
|
+
"#FF9900",
|
|
897
|
+
"#FFFF00",
|
|
898
|
+
"#00FF00",
|
|
899
|
+
"#00FFFF",
|
|
900
|
+
"#4A86E8",
|
|
901
|
+
"#0000FF",
|
|
902
|
+
"#9900FF",
|
|
903
|
+
"#FF00FF",
|
|
904
|
+
"#E6B8AF",
|
|
905
|
+
"#F4CCCC",
|
|
906
|
+
"#FCE5CD",
|
|
907
|
+
"#FFF2CC",
|
|
908
|
+
"#D9EAD3",
|
|
909
|
+
"#D0E0E3",
|
|
910
|
+
"#C9DAF8",
|
|
911
|
+
"#CFE2F3",
|
|
912
|
+
"#D9D2E9",
|
|
913
|
+
"#EAD1DC",
|
|
914
|
+
"#CC4125",
|
|
915
|
+
"#E06666",
|
|
916
|
+
"#F6B26B",
|
|
917
|
+
"#FFD966",
|
|
918
|
+
"#93C47D",
|
|
919
|
+
"#76A5AF",
|
|
920
|
+
"#6D9EEB",
|
|
921
|
+
"#6FA8DC",
|
|
922
|
+
"#8E7CC3",
|
|
923
|
+
"#C27BA0",
|
|
924
|
+
"#A61C00",
|
|
925
|
+
"#CC0000",
|
|
926
|
+
"#E69138",
|
|
927
|
+
"#F1C232",
|
|
928
|
+
"#6AA84F",
|
|
929
|
+
"#45818E",
|
|
930
|
+
"#3C78D8",
|
|
931
|
+
"#3D85C6",
|
|
932
|
+
"#674EA7",
|
|
933
|
+
"#A64D79"
|
|
934
|
+
];
|
|
935
|
+
const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36];
|
|
936
|
+
function currentFontSizePt(ed) {
|
|
937
|
+
const markStyle = ed.getAttributes("inlineStyle")?.style;
|
|
938
|
+
const fromMark = markStyle?.match(/font-size:\s*([\d.]+)\s*(pt|px)/i);
|
|
939
|
+
if (fromMark) {
|
|
940
|
+
const value = Number.parseFloat(fromMark[1]);
|
|
941
|
+
return Math.round(fromMark[2].toLowerCase() === "pt" ? value : value / (96 / 72));
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const { node, offset } = ed.view.domAtPos(ed.state.selection.from);
|
|
945
|
+
const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node.childNodes[offset] instanceof HTMLElement ? node.childNodes[offset] : node;
|
|
946
|
+
if (el instanceof HTMLElement)
|
|
947
|
+
return Math.round(Number.parseFloat(getComputedStyle(el).fontSize) / (96 / 72));
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
function currentFontFamily(ed) {
|
|
953
|
+
const markStyle = ed.getAttributes("inlineStyle")?.style;
|
|
954
|
+
const fromMark = markStyle?.match(/font-family:\s*([^;]+)/i);
|
|
955
|
+
if (fromMark)
|
|
956
|
+
return fromMark[1].split(",")[0].trim().replace(/["']/g, "");
|
|
957
|
+
try {
|
|
958
|
+
const { node } = ed.view.domAtPos(ed.state.selection.from);
|
|
959
|
+
const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
|
960
|
+
const family = el ? getComputedStyle(el).fontFamily : "";
|
|
961
|
+
return family.split(",")[0]?.trim().replace(/["']/g, "") || null;
|
|
962
|
+
} catch {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function createToolbar(actions) {
|
|
967
|
+
const ed = () => actions.editor();
|
|
968
|
+
let openPalette = null;
|
|
969
|
+
const tool = (title, content, onClick, extraClass = "") => {
|
|
970
|
+
const btn = h("button", { type: "button", class: `poc-tool ${extraClass}`.trim(), title, on: { click: onClick } });
|
|
971
|
+
if (typeof content === "string" && content.startsWith("<svg"))
|
|
972
|
+
btn.innerHTML = content;
|
|
973
|
+
else
|
|
974
|
+
btn.append(content);
|
|
975
|
+
return btn;
|
|
976
|
+
};
|
|
977
|
+
const sep = () => h("div", { class: "poc-tool-sep" });
|
|
978
|
+
const icon = (name) => iconSvg(name);
|
|
979
|
+
const fontSelect = h("select", { class: "poc-tool-select w-118px", title: "Fonte", on: {
|
|
980
|
+
change: () => {
|
|
981
|
+
if (fontSelect.value)
|
|
982
|
+
actions.mergeInlineStyle("font-family", fontSelect.value);
|
|
983
|
+
}
|
|
984
|
+
} });
|
|
985
|
+
const sizeSelect = h("select", { class: "poc-tool-select w-64px", title: "Tamanho da fonte", on: {
|
|
986
|
+
change: () => {
|
|
987
|
+
if (sizeSelect.value)
|
|
988
|
+
actions.mergeInlineStyle("font-size", `${sizeSelect.value}pt`);
|
|
989
|
+
}
|
|
990
|
+
} });
|
|
991
|
+
function fillSelect(select, placeholder, options, current, style) {
|
|
992
|
+
select.replaceChildren();
|
|
993
|
+
const ph = h("option", { text: placeholder, attrs: { value: "" } });
|
|
994
|
+
ph.disabled = true;
|
|
995
|
+
select.append(ph);
|
|
996
|
+
if (current != null && !options.map(String).includes(String(current)))
|
|
997
|
+
select.append(h("option", { text: String(current), attrs: { value: String(current) } }));
|
|
998
|
+
for (const opt of options)
|
|
999
|
+
select.append(h("option", { text: String(opt), attrs: { value: String(opt) }, style: style?.(String(opt)) }));
|
|
1000
|
+
select.value = current != null ? String(current) : "";
|
|
1001
|
+
}
|
|
1002
|
+
function palette(apply) {
|
|
1003
|
+
return h("div", { class: "poc-palette" }, [
|
|
1004
|
+
...PALETTE.map((c) => h("button", { type: "button", class: "poc-palette-swatch", title: c, style: { background: c }, on: { click: () => {
|
|
1005
|
+
apply(c);
|
|
1006
|
+
closePalettes();
|
|
1007
|
+
} } })),
|
|
1008
|
+
h("label", { class: "poc-palette-custom" }, [
|
|
1009
|
+
"Personalizada",
|
|
1010
|
+
h("input", { type: "color", on: { change: (e) => {
|
|
1011
|
+
apply(e.target.value);
|
|
1012
|
+
closePalettes();
|
|
1013
|
+
} } })
|
|
1014
|
+
])
|
|
1015
|
+
]);
|
|
1016
|
+
}
|
|
1017
|
+
const colorPalette = palette((c) => actions.mergeInlineStyle("color", c));
|
|
1018
|
+
const highlightPalette = palette((c) => actions.mergeInlineStyle("background-color", c));
|
|
1019
|
+
colorPalette.hidden = true;
|
|
1020
|
+
highlightPalette.hidden = true;
|
|
1021
|
+
function togglePalette(which) {
|
|
1022
|
+
openPalette = openPalette === which ? null : which;
|
|
1023
|
+
colorPalette.hidden = openPalette !== "color";
|
|
1024
|
+
highlightPalette.hidden = openPalette !== "highlight";
|
|
1025
|
+
}
|
|
1026
|
+
function closePalettes() {
|
|
1027
|
+
openPalette = null;
|
|
1028
|
+
colorPalette.hidden = true;
|
|
1029
|
+
highlightPalette.hidden = true;
|
|
1030
|
+
}
|
|
1031
|
+
const colorBtn = tool("Cor do texto", h("span", { class: "font-semibold", text: "A" }), () => togglePalette("color"));
|
|
1032
|
+
colorBtn.append(h("span", { class: "poc-color-underline" }));
|
|
1033
|
+
const highlightBtn = tool("Cor de destaque", icon("highlighter"), () => togglePalette("highlight"));
|
|
1034
|
+
const boldBtn = tool("Negrito", "B", () => ed()?.chain().focus().toggleBold().run(), "font-bold");
|
|
1035
|
+
const italicBtn = tool("It\xE1lico", "I", () => ed()?.chain().focus().toggleItalic().run(), "italic");
|
|
1036
|
+
const underlineBtn = tool("Sublinhado", "U", () => ed()?.chain().focus().toggleMark("underline").run(), "underline");
|
|
1037
|
+
const strikeBtn = tool("Tachado", "S", () => ed()?.chain().focus().toggleStrike().run(), "line-through");
|
|
1038
|
+
const bulletBtn = tool("Lista com marcadores", icon("list-bullets"), () => ed()?.chain().focus().toggleBulletList().run());
|
|
1039
|
+
const orderedBtn = tool("Lista numerada", icon("list-numbers"), () => ed()?.chain().focus().toggleOrderedList().run());
|
|
1040
|
+
const variableBtn = tool("Inserir vari\xE1vel (ou digite / no texto)", icon("brackets-curly"), actions.openVariablePicker);
|
|
1041
|
+
const el = h("div", { class: "poc-toolbar", on: { click: (e) => e.stopPropagation() } }, [
|
|
1042
|
+
tool("Desfazer", icon("arrow-u-up-left"), () => ed()?.chain().focus().undo().run()),
|
|
1043
|
+
tool("Refazer", icon("arrow-u-up-right"), () => ed()?.chain().focus().redo().run()),
|
|
1044
|
+
sep(),
|
|
1045
|
+
fontSelect,
|
|
1046
|
+
sizeSelect,
|
|
1047
|
+
sep(),
|
|
1048
|
+
boldBtn,
|
|
1049
|
+
italicBtn,
|
|
1050
|
+
underlineBtn,
|
|
1051
|
+
strikeBtn,
|
|
1052
|
+
sep(),
|
|
1053
|
+
h("div", { class: "relative" }, [colorBtn, colorPalette]),
|
|
1054
|
+
h("div", { class: "relative" }, [highlightBtn, highlightPalette]),
|
|
1055
|
+
sep(),
|
|
1056
|
+
tool("Alinhar \xE0 esquerda", icon("text-align-left"), () => actions.setAlignment("left")),
|
|
1057
|
+
tool("Centralizar", icon("text-align-center"), () => actions.setAlignment("center")),
|
|
1058
|
+
tool("Alinhar \xE0 direita", icon("text-align-right"), () => actions.setAlignment("right")),
|
|
1059
|
+
tool("Justificar", icon("text-align-justify"), () => actions.setAlignment("justify")),
|
|
1060
|
+
sep(),
|
|
1061
|
+
bulletBtn,
|
|
1062
|
+
orderedBtn,
|
|
1063
|
+
sep(),
|
|
1064
|
+
tool("Inserir imagem", icon("image"), actions.pickImage),
|
|
1065
|
+
variableBtn,
|
|
1066
|
+
tool("Inserir nota de rodap\xE9", icon("note"), actions.insertFootnote),
|
|
1067
|
+
tool("Inserir quebra de p\xE1gina", icon("arrows-out-line-vertical"), actions.insertPageBreak),
|
|
1068
|
+
tool("Limpar formata\xE7\xE3o", icon("eraser"), actions.clearFormatting)
|
|
1069
|
+
]);
|
|
1070
|
+
function sync() {
|
|
1071
|
+
const e = ed();
|
|
1072
|
+
variableBtn.hidden = !actions.hasVariables();
|
|
1073
|
+
toggleClass(variableBtn, "poc-tool--on", actions.isVariablePickerOpen());
|
|
1074
|
+
if (!e || e.isDestroyed) {
|
|
1075
|
+
for (const b of [boldBtn, italicBtn, underlineBtn, strikeBtn, bulletBtn, orderedBtn])
|
|
1076
|
+
toggleClass(b, "poc-tool--on", false);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
toggleClass(boldBtn, "poc-tool--on", e.isActive("bold"));
|
|
1080
|
+
toggleClass(italicBtn, "poc-tool--on", e.isActive("italic"));
|
|
1081
|
+
toggleClass(underlineBtn, "poc-tool--on", e.isActive("underline"));
|
|
1082
|
+
toggleClass(strikeBtn, "poc-tool--on", e.isActive("strike"));
|
|
1083
|
+
toggleClass(bulletBtn, "poc-tool--on", e.isActive("bulletList"));
|
|
1084
|
+
toggleClass(orderedBtn, "poc-tool--on", e.isActive("orderedList"));
|
|
1085
|
+
const family = currentFontFamily(e);
|
|
1086
|
+
if (fontSelect.dataset.current !== String(family)) {
|
|
1087
|
+
fontSelect.dataset.current = String(family);
|
|
1088
|
+
fillSelect(fontSelect, "Fonte", DOC_FONTS, family, (f) => ({ fontFamily: f }));
|
|
1089
|
+
}
|
|
1090
|
+
const size = currentFontSizePt(e);
|
|
1091
|
+
if (sizeSelect.dataset.current !== String(size)) {
|
|
1092
|
+
sizeSelect.dataset.current = String(size);
|
|
1093
|
+
fillSelect(sizeSelect, "Tam.", FONT_SIZES, size);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
sync();
|
|
1097
|
+
return { el, sync, closePalettes, destroy: () => el.remove() };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function createVariablePicker(opts) {
|
|
1101
|
+
let search = "";
|
|
1102
|
+
let query = opts.query;
|
|
1103
|
+
let activeIndex = 0;
|
|
1104
|
+
const term = () => (query ?? search).trim().toLowerCase();
|
|
1105
|
+
function filtered() {
|
|
1106
|
+
const all = opts.variables();
|
|
1107
|
+
const t = term();
|
|
1108
|
+
if (!t)
|
|
1109
|
+
return all;
|
|
1110
|
+
return all.filter(
|
|
1111
|
+
(v) => v.label.toLowerCase().includes(t) || v.key.toLowerCase().includes(t) || (v.description?.toLowerCase().includes(t) ?? false)
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
const list = h("div", { class: "poc-varpicker__list" });
|
|
1115
|
+
const hint = h("div", { class: "poc-varpicker__hint" });
|
|
1116
|
+
const input = h("input", {
|
|
1117
|
+
class: "poc-varpicker__search",
|
|
1118
|
+
attrs: { placeholder: "Buscar vari\xE1vel\u2026", spellcheck: "false" },
|
|
1119
|
+
on: {
|
|
1120
|
+
input: () => {
|
|
1121
|
+
search = input.value;
|
|
1122
|
+
activeIndex = 0;
|
|
1123
|
+
render();
|
|
1124
|
+
},
|
|
1125
|
+
keydown: (event) => {
|
|
1126
|
+
const handlers = {
|
|
1127
|
+
ArrowDown: () => move(1),
|
|
1128
|
+
ArrowUp: () => move(-1),
|
|
1129
|
+
Enter: choose,
|
|
1130
|
+
Escape: opts.onClose
|
|
1131
|
+
};
|
|
1132
|
+
const handler = handlers[event.key];
|
|
1133
|
+
if (!handler)
|
|
1134
|
+
return;
|
|
1135
|
+
event.preventDefault();
|
|
1136
|
+
handler();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
const el = h("div", {
|
|
1141
|
+
class: "poc-varpicker",
|
|
1142
|
+
style: { top: `${opts.top}px`, left: `${opts.left}px` },
|
|
1143
|
+
on: { click: (e) => e.stopPropagation() }
|
|
1144
|
+
}, [query === void 0 ? input : hint, list]);
|
|
1145
|
+
list.addEventListener("mousedown", (e) => e.preventDefault());
|
|
1146
|
+
function render() {
|
|
1147
|
+
const items = filtered();
|
|
1148
|
+
if (query !== void 0) {
|
|
1149
|
+
replaceChildren(hint, ["Vari\xE1vel", query ? ": " : "", query ? h("b", { text: query }) : null]);
|
|
1150
|
+
}
|
|
1151
|
+
if (!items.length) {
|
|
1152
|
+
replaceChildren(list, [h("p", { class: "poc-varpicker__empty", text: "Nenhuma vari\xE1vel encontrada." })]);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const rows = [];
|
|
1156
|
+
let lastGroup = null;
|
|
1157
|
+
items.forEach((variable, index) => {
|
|
1158
|
+
const group = variable.group ?? null;
|
|
1159
|
+
if (group && group !== lastGroup)
|
|
1160
|
+
rows.push(h("div", { class: "poc-varpicker__group", text: group }));
|
|
1161
|
+
lastGroup = group;
|
|
1162
|
+
const btn = h("button", {
|
|
1163
|
+
type: "button",
|
|
1164
|
+
class: "poc-varpicker__item",
|
|
1165
|
+
on: {
|
|
1166
|
+
mouseenter: () => {
|
|
1167
|
+
activeIndex = index;
|
|
1168
|
+
syncActive();
|
|
1169
|
+
},
|
|
1170
|
+
click: () => opts.onSelect(variable)
|
|
1171
|
+
}
|
|
1172
|
+
}, [
|
|
1173
|
+
h("span", { class: "poc-varpicker__label", text: variable.label }),
|
|
1174
|
+
h("span", { class: "poc-varpicker__key", text: variable.key }),
|
|
1175
|
+
variable.description ? h("span", { class: "poc-varpicker__desc", text: variable.description }) : null
|
|
1176
|
+
]);
|
|
1177
|
+
btn.dataset.index = String(index);
|
|
1178
|
+
rows.push(btn);
|
|
1179
|
+
});
|
|
1180
|
+
replaceChildren(list, rows);
|
|
1181
|
+
syncActive();
|
|
1182
|
+
}
|
|
1183
|
+
function syncActive() {
|
|
1184
|
+
for (const item of list.querySelectorAll(".poc-varpicker__item"))
|
|
1185
|
+
toggleClass(item, "poc-varpicker__item--on", item.dataset.index === String(activeIndex));
|
|
1186
|
+
}
|
|
1187
|
+
function move(delta) {
|
|
1188
|
+
const total = filtered().length;
|
|
1189
|
+
if (!total)
|
|
1190
|
+
return;
|
|
1191
|
+
activeIndex = (activeIndex + delta + total) % total;
|
|
1192
|
+
syncActive();
|
|
1193
|
+
}
|
|
1194
|
+
function choose() {
|
|
1195
|
+
const variable = filtered()[activeIndex];
|
|
1196
|
+
if (variable)
|
|
1197
|
+
opts.onSelect(variable);
|
|
1198
|
+
}
|
|
1199
|
+
render();
|
|
1200
|
+
return {
|
|
1201
|
+
el,
|
|
1202
|
+
move,
|
|
1203
|
+
choose,
|
|
1204
|
+
hasResults: () => filtered().length > 0,
|
|
1205
|
+
setQuery: (q) => {
|
|
1206
|
+
query = q;
|
|
1207
|
+
activeIndex = 0;
|
|
1208
|
+
render();
|
|
1209
|
+
},
|
|
1210
|
+
setPosition: (top, left) => {
|
|
1211
|
+
el.style.top = `${top}px`;
|
|
1212
|
+
el.style.left = `${left}px`;
|
|
1213
|
+
},
|
|
1214
|
+
destroy: () => el.remove()
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function focusVariablePicker(picker) {
|
|
1218
|
+
picker.el.querySelector(".poc-varpicker__search")?.focus();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const A4_WIDTH_PX = 794;
|
|
1222
|
+
const A4_HEIGHT_PX = 1123;
|
|
1223
|
+
const PAGE_GAP_PX = 32;
|
|
1224
|
+
const DEFAULT_MARGINS = { top: 95, right: 113, bottom: 95, left: 113 };
|
|
1225
|
+
function createMinutaEditor(element, opts) {
|
|
1226
|
+
let editable = opts.editable ?? true;
|
|
1227
|
+
const margins = { ...opts.margins ?? DEFAULT_MARGINS };
|
|
1228
|
+
let header = { logoUrl: opts.header?.logoUrl ?? null, position: opts.header?.position ?? "left" };
|
|
1229
|
+
const chrome = {
|
|
1230
|
+
header: opts.chrome?.header ?? "",
|
|
1231
|
+
footer: opts.chrome?.footer ?? "",
|
|
1232
|
+
headerScope: opts.chrome?.headerScope ?? "all",
|
|
1233
|
+
footerScope: opts.chrome?.footerScope ?? "all",
|
|
1234
|
+
showPageNumber: opts.chrome?.showPageNumber !== false
|
|
1235
|
+
};
|
|
1236
|
+
let variables = opts.variables;
|
|
1237
|
+
const readVariables = () => typeof variables === "function" ? variables() : variables ?? [];
|
|
1238
|
+
const hasVariables = () => readVariables().length > 0;
|
|
1239
|
+
let tiptap = null;
|
|
1240
|
+
let toolbar = null;
|
|
1241
|
+
let variablePicker = null;
|
|
1242
|
+
let totalPages = 1;
|
|
1243
|
+
let lastPageNotes = [];
|
|
1244
|
+
let zoom = 1;
|
|
1245
|
+
const pageConfig = {
|
|
1246
|
+
pageHeight: A4_HEIGHT_PX,
|
|
1247
|
+
margins,
|
|
1248
|
+
gap: PAGE_GAP_PX,
|
|
1249
|
+
headerLogoUrl: header.logoUrl,
|
|
1250
|
+
headerPosition: header.position,
|
|
1251
|
+
headerText: chrome.header,
|
|
1252
|
+
footerText: chrome.footer,
|
|
1253
|
+
headerScope: chrome.headerScope ?? "all",
|
|
1254
|
+
footerScope: chrome.footerScope ?? "all",
|
|
1255
|
+
showPageNumber: chrome.showPageNumber !== false,
|
|
1256
|
+
version: 0
|
|
1257
|
+
};
|
|
1258
|
+
const firstHeader = h("div", { class: "poc-first-header", title: "Clique para editar o cabe\xE7alho", on: { click: () => editChrome("header") } });
|
|
1259
|
+
const errorEl = h("p", { class: "body-13-regular text-red-600" });
|
|
1260
|
+
errorEl.hidden = true;
|
|
1261
|
+
const editorHost = h("div", { on: { click: onSheetClick } });
|
|
1262
|
+
const notesEl = h("div", { class: "poc-page-notes poc-last-notes" });
|
|
1263
|
+
notesEl.hidden = true;
|
|
1264
|
+
const lastFooter = h("div", { class: "poc-last-footer", title: "Clique para editar o rodap\xE9", on: { click: () => editChrome("footer") } });
|
|
1265
|
+
const sheet = h("div", { class: "poc-sheet relative mx-auto bg-white shadow-md", on: { click: onNotesClick } }, [
|
|
1266
|
+
firstHeader,
|
|
1267
|
+
errorEl,
|
|
1268
|
+
editorHost,
|
|
1269
|
+
notesEl,
|
|
1270
|
+
lastFooter
|
|
1271
|
+
]);
|
|
1272
|
+
const container = h("div", {
|
|
1273
|
+
class: "relative min-h-0 w-full flex-1 overflow-y-auto bg-gray-120 py-24px",
|
|
1274
|
+
on: { click: () => {
|
|
1275
|
+
toolbar?.closePalettes();
|
|
1276
|
+
closeVariablePicker();
|
|
1277
|
+
} }
|
|
1278
|
+
}, [sheet]);
|
|
1279
|
+
const root = h("div", { class: "poc-editor-root" }, [container]);
|
|
1280
|
+
toggleClass(root, "poc-no-block-origin", opts.showBlockOrigin === false);
|
|
1281
|
+
const bubbleEl = buildBubbleMenu();
|
|
1282
|
+
const imageInput = h("input", { type: "file", class: "hidden", attrs: { accept: "image/*" }, on: { change: onImageFile } });
|
|
1283
|
+
function mountToolbar() {
|
|
1284
|
+
if (toolbar)
|
|
1285
|
+
return;
|
|
1286
|
+
toolbar = createToolbar({
|
|
1287
|
+
editor: () => tiptap,
|
|
1288
|
+
hasVariables,
|
|
1289
|
+
mergeInlineStyle,
|
|
1290
|
+
setAlignment,
|
|
1291
|
+
pickImage: () => imageInput.click(),
|
|
1292
|
+
insertFootnote,
|
|
1293
|
+
insertPageBreak: () => tiptap?.chain().focus().insertContent("<p>PAGEBREAK</p>").run(),
|
|
1294
|
+
clearFormatting: () => tiptap?.chain().focus().unsetAllMarks().run(),
|
|
1295
|
+
openVariablePicker,
|
|
1296
|
+
isVariablePickerOpen: () => !!variablePicker
|
|
1297
|
+
});
|
|
1298
|
+
toolbar.el.append(imageInput);
|
|
1299
|
+
root.prepend(toolbar.el);
|
|
1300
|
+
toolbar.el.hidden = !editable;
|
|
1301
|
+
}
|
|
1302
|
+
function unmountToolbar() {
|
|
1303
|
+
if (!toolbar)
|
|
1304
|
+
return;
|
|
1305
|
+
toolbar.destroy();
|
|
1306
|
+
toolbar = null;
|
|
1307
|
+
root.append(imageInput);
|
|
1308
|
+
}
|
|
1309
|
+
if (opts.toolbar !== false)
|
|
1310
|
+
mountToolbar();
|
|
1311
|
+
else
|
|
1312
|
+
root.append(imageInput);
|
|
1313
|
+
element.append(root);
|
|
1314
|
+
function applySheetStyle() {
|
|
1315
|
+
Object.assign(sheet.style, {
|
|
1316
|
+
width: `${A4_WIDTH_PX}px`,
|
|
1317
|
+
minHeight: `${A4_HEIGHT_PX}px`,
|
|
1318
|
+
padding: `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`,
|
|
1319
|
+
boxSizing: "border-box",
|
|
1320
|
+
zoom: String(zoom)
|
|
1321
|
+
});
|
|
1322
|
+
Object.assign(firstHeader.style, {
|
|
1323
|
+
height: `${margins.top}px`,
|
|
1324
|
+
left: `${margins.left}px`,
|
|
1325
|
+
right: `${margins.right}px`,
|
|
1326
|
+
textAlign: header.position,
|
|
1327
|
+
lineHeight: `${margins.top}px`
|
|
1328
|
+
});
|
|
1329
|
+
Object.assign(lastFooter.style, { height: `${margins.bottom}px`, left: `${margins.left}px`, right: `${margins.right}px` });
|
|
1330
|
+
Object.assign(notesEl.style, { position: "absolute", bottom: `${margins.bottom}px`, left: `${margins.left}px`, right: `${margins.right}px` });
|
|
1331
|
+
}
|
|
1332
|
+
const isChromeCanvas = (html) => !!html && html.includes("data-el=");
|
|
1333
|
+
const chromeCanvasStyle = () => `position: absolute; top: 0; left: ${-margins.left}px; width: 794px; height: 100%; pointer-events: none;`;
|
|
1334
|
+
function chromeNode(html) {
|
|
1335
|
+
return isChromeCanvas(html) ? h("div", { class: "poc-chrome-canvas", style: chromeCanvasStyle(), html: sanitizeRichHtml(html) }) : h("div", { class: "poc-chrome-text", html: sanitizeRichHtml(html) });
|
|
1336
|
+
}
|
|
1337
|
+
function renderChromeBands() {
|
|
1338
|
+
const logo = header.logoUrl ? h("img", { class: "inline-block align-middle", attrs: { src: header.logoUrl }, style: { maxHeight: `${Math.max(24, margins.top - 36)}px`, maxWidth: "50%" } }) : null;
|
|
1339
|
+
replaceChildren(firstHeader, [logo, chromeNode(chrome.header)]);
|
|
1340
|
+
const footerHidden = chrome.footerScope === "first" && totalPages > 1;
|
|
1341
|
+
replaceChildren(lastFooter, [
|
|
1342
|
+
footerHidden ? h("div", { class: "poc-chrome-text" }) : chromeNode(chrome.footer),
|
|
1343
|
+
chrome.showPageNumber !== false ? h("span", { class: "ml-auto", text: `P\xE1gina ${totalPages} de ${totalPages}` }) : null
|
|
1344
|
+
]);
|
|
1345
|
+
}
|
|
1346
|
+
function renderNotes() {
|
|
1347
|
+
notesEl.hidden = !lastPageNotes.length;
|
|
1348
|
+
replaceChildren(notesEl, lastPageNotes.map((note) => h("p", {
|
|
1349
|
+
class: "poc-page-note",
|
|
1350
|
+
style: note.size ? { fontSize: `${note.size}pt` } : void 0,
|
|
1351
|
+
html: `<sup>${note.n}</sup> ${sanitizeFootnoteHtml(note.text)}`
|
|
1352
|
+
})));
|
|
1353
|
+
}
|
|
1354
|
+
function refreshChrome() {
|
|
1355
|
+
pageConfig.headerLogoUrl = header.logoUrl;
|
|
1356
|
+
pageConfig.headerPosition = header.position;
|
|
1357
|
+
pageConfig.headerText = chrome.header;
|
|
1358
|
+
pageConfig.footerText = chrome.footer;
|
|
1359
|
+
pageConfig.headerScope = chrome.headerScope ?? "all";
|
|
1360
|
+
pageConfig.footerScope = chrome.footerScope ?? "all";
|
|
1361
|
+
pageConfig.showPageNumber = chrome.showPageNumber !== false;
|
|
1362
|
+
pageConfig.version = (pageConfig.version ?? 0) + 1;
|
|
1363
|
+
applySheetStyle();
|
|
1364
|
+
renderChromeBands();
|
|
1365
|
+
const ed = tiptap;
|
|
1366
|
+
if (!ed || ed.isDestroyed)
|
|
1367
|
+
return;
|
|
1368
|
+
const current = pocPageKey.getState(ed.state);
|
|
1369
|
+
if (current)
|
|
1370
|
+
ed.view.dispatch(ed.state.tr.setMeta(PAGINATE_META, { ...current, breaks: [...current.breaks] }));
|
|
1371
|
+
}
|
|
1372
|
+
let paginateScheduled = false;
|
|
1373
|
+
function schedulePaginate() {
|
|
1374
|
+
if (paginateScheduled || !tiptap)
|
|
1375
|
+
return;
|
|
1376
|
+
paginateScheduled = true;
|
|
1377
|
+
requestAnimationFrame(() => {
|
|
1378
|
+
paginateScheduled = false;
|
|
1379
|
+
const ed = tiptap;
|
|
1380
|
+
if (!ed || ed.isDestroyed)
|
|
1381
|
+
return;
|
|
1382
|
+
const { breaks, total, markers, lastNotes } = computePocBreaks(ed.view, pageConfig, sheet);
|
|
1383
|
+
const notesChanged = lastNotes.length !== lastPageNotes.length;
|
|
1384
|
+
if (notesChanged) {
|
|
1385
|
+
lastPageNotes = lastNotes;
|
|
1386
|
+
renderNotes();
|
|
1387
|
+
}
|
|
1388
|
+
const current = pocPageKey.getState(ed.state);
|
|
1389
|
+
if (current && breaksEqual(current.breaks, breaks) && current.total === total && current.markers.length === markers.length && !notesChanged) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (totalPages !== total) {
|
|
1393
|
+
totalPages = total;
|
|
1394
|
+
renderChromeBands();
|
|
1395
|
+
}
|
|
1396
|
+
ed.view.dispatch(ed.state.tr.setMeta(PAGINATE_META, { breaks, total, markers }));
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
1400
|
+
const breathing = container.clientWidth < 640 ? 12 : 48;
|
|
1401
|
+
const next = Math.min(1, (container.clientWidth - breathing) / A4_WIDTH_PX);
|
|
1402
|
+
if (next !== zoom) {
|
|
1403
|
+
zoom = next;
|
|
1404
|
+
sheet.style.zoom = String(zoom);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
resizeObserver.observe(container);
|
|
1408
|
+
function sheetCoords(pos) {
|
|
1409
|
+
const ed = tiptap;
|
|
1410
|
+
if (!ed || ed.isDestroyed)
|
|
1411
|
+
return null;
|
|
1412
|
+
let coords;
|
|
1413
|
+
try {
|
|
1414
|
+
coords = ed.view.coordsAtPos(pos);
|
|
1415
|
+
} catch {
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
const rect = sheet.getBoundingClientRect();
|
|
1419
|
+
const z = zoom || 1;
|
|
1420
|
+
return {
|
|
1421
|
+
top: (coords.bottom - rect.top) / z + 6,
|
|
1422
|
+
left: (coords.left - rect.left) / z
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function editChrome(part) {
|
|
1426
|
+
opts.onEditChrome?.(part);
|
|
1427
|
+
window.dispatchEvent(new CustomEvent("poc:edit-chrome", { detail: { part, editor: tiptap } }));
|
|
1428
|
+
}
|
|
1429
|
+
function onSheetClick(event) {
|
|
1430
|
+
if (editable)
|
|
1431
|
+
return;
|
|
1432
|
+
const target = event.target;
|
|
1433
|
+
if (target.closest(".poc-varchip") || target.closest(".poc-page-headerarea") || target.closest(".poc-page-footerarea") || target.closest(".poc-first-header") || target.closest(".poc-last-footer")) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const block = target.closest("[data-block-id]");
|
|
1437
|
+
if (block)
|
|
1438
|
+
opts.onBlockClick?.(block.getAttribute("data-block-id"));
|
|
1439
|
+
}
|
|
1440
|
+
function mergeInlineStyle(prop, value) {
|
|
1441
|
+
const ed = tiptap;
|
|
1442
|
+
if (!ed)
|
|
1443
|
+
return;
|
|
1444
|
+
const current = ed.getAttributes("inlineStyle")?.style || "";
|
|
1445
|
+
const cleaned = current.replace(new RegExp(`${prop}:\\s*[^;]+;?`, "gi"), "").trim();
|
|
1446
|
+
const style = `${cleaned}${cleaned && !cleaned.endsWith(";") ? ";" : ""} ${prop}: ${value};`.trim();
|
|
1447
|
+
ed.chain().focus().setMark("inlineStyle", { style }).run();
|
|
1448
|
+
}
|
|
1449
|
+
function setAlignment(align) {
|
|
1450
|
+
const ed = tiptap;
|
|
1451
|
+
if (!ed)
|
|
1452
|
+
return;
|
|
1453
|
+
const { state, view } = ed;
|
|
1454
|
+
const { from, to } = state.selection;
|
|
1455
|
+
const tr = state.tr;
|
|
1456
|
+
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
1457
|
+
if (node.type.name === "paragraph" || node.type.name === "heading") {
|
|
1458
|
+
const style = (node.attrs.style || "").replace(/text-align:\s*[^;]+;?/gi, "").trim();
|
|
1459
|
+
const merged = `${style}${style && !style.endsWith(";") ? ";" : ""} text-align: ${align};`.trim();
|
|
1460
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, style: merged });
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
view.dispatch(tr);
|
|
1464
|
+
ed.commands.focus();
|
|
1465
|
+
}
|
|
1466
|
+
function insertDataUriImage(file) {
|
|
1467
|
+
const reader = new FileReader();
|
|
1468
|
+
reader.onload = () => {
|
|
1469
|
+
tiptap?.chain().focus().insertContent(
|
|
1470
|
+
`<img src="${reader.result}" style="width: 420px; height: auto; max-width: 100%;">`
|
|
1471
|
+
).run();
|
|
1472
|
+
};
|
|
1473
|
+
reader.readAsDataURL(file);
|
|
1474
|
+
}
|
|
1475
|
+
function onImageFile(event) {
|
|
1476
|
+
const input = event.target;
|
|
1477
|
+
const file = input.files?.[0];
|
|
1478
|
+
if (!file || !file.type.startsWith("image/"))
|
|
1479
|
+
return;
|
|
1480
|
+
if (opts.uploadImage) {
|
|
1481
|
+
void (async () => {
|
|
1482
|
+
try {
|
|
1483
|
+
const uploaded = await opts.uploadImage(file);
|
|
1484
|
+
if (uploaded) {
|
|
1485
|
+
tiptap?.chain().focus().insertContent(vaultImageHtml(uploaded.src, uploaded.vaultSrc)).run();
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
console.error("[minuta-editor] upload de imagem falhou, embutindo como data URI", error);
|
|
1490
|
+
}
|
|
1491
|
+
insertDataUriImage(file);
|
|
1492
|
+
})();
|
|
1493
|
+
} else {
|
|
1494
|
+
insertDataUriImage(file);
|
|
1495
|
+
}
|
|
1496
|
+
input.value = "";
|
|
1497
|
+
}
|
|
1498
|
+
let footnotePopover = null;
|
|
1499
|
+
function insertFootnote() {
|
|
1500
|
+
const ed = tiptap;
|
|
1501
|
+
if (!ed || !footnotePopover)
|
|
1502
|
+
return;
|
|
1503
|
+
const to = ed.state.selection.to;
|
|
1504
|
+
ed.chain().focus().setTextSelection(to).insertContent({ type: "footnoteRef", attrs: { number: "?", text: "" } }).run();
|
|
1505
|
+
renumberPocFootnotes(ed);
|
|
1506
|
+
const pos = ed.state.selection.from - 1;
|
|
1507
|
+
footnotePopover.open(pos, true);
|
|
1508
|
+
}
|
|
1509
|
+
function onEditFootnoteEvent(event) {
|
|
1510
|
+
if (!editable)
|
|
1511
|
+
return;
|
|
1512
|
+
const { pos } = event.detail || {};
|
|
1513
|
+
if (typeof pos === "number")
|
|
1514
|
+
footnotePopover?.open(pos, false);
|
|
1515
|
+
}
|
|
1516
|
+
function onNotesClick(event) {
|
|
1517
|
+
if (!editable)
|
|
1518
|
+
return;
|
|
1519
|
+
const line = event.target.closest?.(".poc-page-note");
|
|
1520
|
+
if (!line)
|
|
1521
|
+
return;
|
|
1522
|
+
const number = line.querySelector("sup")?.textContent?.trim();
|
|
1523
|
+
const ed = tiptap;
|
|
1524
|
+
if (!number || !ed)
|
|
1525
|
+
return;
|
|
1526
|
+
let target = null;
|
|
1527
|
+
ed.state.doc.descendants((node, pos) => {
|
|
1528
|
+
if (target != null)
|
|
1529
|
+
return false;
|
|
1530
|
+
if (node.type.name === "footnoteRef" && node.attrs.number === number) {
|
|
1531
|
+
target = pos;
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
return true;
|
|
1535
|
+
});
|
|
1536
|
+
if (target != null)
|
|
1537
|
+
footnotePopover?.open(target, false);
|
|
1538
|
+
}
|
|
1539
|
+
let variableTriggerFrom = null;
|
|
1540
|
+
const VARIABLE_TRIGGER_RE = /(?:^|[\s(\[«"'])\/([\p{L}\d_]*)$/u;
|
|
1541
|
+
const VARIABLE_TRIGGER_MAX_QUERY = 24;
|
|
1542
|
+
function closeVariablePicker() {
|
|
1543
|
+
if (!variablePicker)
|
|
1544
|
+
return;
|
|
1545
|
+
variablePicker.destroy();
|
|
1546
|
+
variablePicker = null;
|
|
1547
|
+
variableTriggerFrom = null;
|
|
1548
|
+
toolbar?.sync();
|
|
1549
|
+
}
|
|
1550
|
+
function mountPicker(top, left, from, query) {
|
|
1551
|
+
closeVariablePicker();
|
|
1552
|
+
variableTriggerFrom = from;
|
|
1553
|
+
variablePicker = createVariablePicker({
|
|
1554
|
+
variables: readVariables,
|
|
1555
|
+
top,
|
|
1556
|
+
left: Math.max(8, Math.min(left, A4_WIDTH_PX - 330)),
|
|
1557
|
+
query,
|
|
1558
|
+
onSelect: insertVariable,
|
|
1559
|
+
onClose: closeVariablePicker
|
|
1560
|
+
});
|
|
1561
|
+
sheet.append(variablePicker.el);
|
|
1562
|
+
toolbar?.sync();
|
|
1563
|
+
}
|
|
1564
|
+
function syncVariableTrigger(docChanged) {
|
|
1565
|
+
const ed = tiptap;
|
|
1566
|
+
if (!ed || ed.isDestroyed || !hasVariables() || !editable) {
|
|
1567
|
+
closeVariablePicker();
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (variablePicker && variableTriggerFrom === null)
|
|
1571
|
+
return;
|
|
1572
|
+
const { from, empty } = ed.state.selection;
|
|
1573
|
+
if (!empty) {
|
|
1574
|
+
closeVariablePicker();
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const blockStart = ed.state.doc.resolve(from).start();
|
|
1578
|
+
const textBefore = ed.state.doc.textBetween(blockStart, from, "\n", "\uFFFC");
|
|
1579
|
+
const match = textBefore.match(VARIABLE_TRIGGER_RE);
|
|
1580
|
+
if (!match || (match[1]?.length ?? 0) > VARIABLE_TRIGGER_MAX_QUERY) {
|
|
1581
|
+
closeVariablePicker();
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (!variablePicker && !docChanged)
|
|
1585
|
+
return;
|
|
1586
|
+
const slashPos = from - match[0].length + match[0].indexOf("/");
|
|
1587
|
+
const coords = sheetCoords(slashPos);
|
|
1588
|
+
if (!coords) {
|
|
1589
|
+
closeVariablePicker();
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
if (variablePicker && variableTriggerFrom === slashPos) {
|
|
1593
|
+
variablePicker.setQuery(match[1] ?? "");
|
|
1594
|
+
variablePicker.setPosition(coords.top, Math.max(8, Math.min(coords.left, A4_WIDTH_PX - 330)));
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
mountPicker(coords.top, coords.left, slashPos, match[1] ?? "");
|
|
1598
|
+
}
|
|
1599
|
+
function openVariablePicker() {
|
|
1600
|
+
const ed = tiptap;
|
|
1601
|
+
if (!ed || ed.isDestroyed || !hasVariables())
|
|
1602
|
+
return;
|
|
1603
|
+
const coords = sheetCoords(ed.state.selection.from);
|
|
1604
|
+
if (!coords)
|
|
1605
|
+
return;
|
|
1606
|
+
mountPicker(coords.top, coords.left, null, void 0);
|
|
1607
|
+
if (variablePicker)
|
|
1608
|
+
focusVariablePicker(variablePicker);
|
|
1609
|
+
}
|
|
1610
|
+
function insertVariable(variable) {
|
|
1611
|
+
const ed = tiptap;
|
|
1612
|
+
if (!ed || ed.isDestroyed)
|
|
1613
|
+
return;
|
|
1614
|
+
const from = variableTriggerFrom;
|
|
1615
|
+
const chain = ed.chain().focus();
|
|
1616
|
+
if (from != null)
|
|
1617
|
+
chain.deleteRange({ from, to: ed.state.selection.from });
|
|
1618
|
+
chain.insertContent({ type: "templateVariable", attrs: { varKey: variable.key, label: variable.label } }).run();
|
|
1619
|
+
closeVariablePicker();
|
|
1620
|
+
}
|
|
1621
|
+
function onEditorKeyDown(_view, event) {
|
|
1622
|
+
const picker = variablePicker;
|
|
1623
|
+
if (!picker)
|
|
1624
|
+
return false;
|
|
1625
|
+
if (event.key === "ArrowDown") {
|
|
1626
|
+
picker.move(1);
|
|
1627
|
+
return true;
|
|
1628
|
+
}
|
|
1629
|
+
if (event.key === "ArrowUp") {
|
|
1630
|
+
picker.move(-1);
|
|
1631
|
+
return true;
|
|
1632
|
+
}
|
|
1633
|
+
if (event.key === "Escape") {
|
|
1634
|
+
closeVariablePicker();
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
1638
|
+
if (!picker.hasResults())
|
|
1639
|
+
return false;
|
|
1640
|
+
picker.choose();
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
return false;
|
|
1644
|
+
}
|
|
1645
|
+
function onVariableClickEvent(event) {
|
|
1646
|
+
const detail = event.detail || {};
|
|
1647
|
+
if (typeof detail.variableId !== "string")
|
|
1648
|
+
return;
|
|
1649
|
+
opts.onVariableClick?.({
|
|
1650
|
+
variableId: detail.variableId,
|
|
1651
|
+
parentBlockId: detail.parentBlockId ?? null,
|
|
1652
|
+
text: String(detail.text ?? "")
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
function scrollToVariable(variableId) {
|
|
1656
|
+
const el = sheet.querySelector(`.poc-varchip [data-variable-id="${CSS.escape(variableId)}"], .poc-varchip[data-variable-id="${CSS.escape(variableId)}"]`);
|
|
1657
|
+
const chip = el?.closest(".poc-varchip") ?? el;
|
|
1658
|
+
if (!chip)
|
|
1659
|
+
return false;
|
|
1660
|
+
chip.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
1661
|
+
chip.classList.add("poc-varchip--flash");
|
|
1662
|
+
setTimeout(() => chip.classList.remove("poc-varchip--flash"), 1200);
|
|
1663
|
+
return true;
|
|
1664
|
+
}
|
|
1665
|
+
function setVariableValue(variableId, value) {
|
|
1666
|
+
const ed = tiptap;
|
|
1667
|
+
if (!ed || ed.isDestroyed)
|
|
1668
|
+
return 0;
|
|
1669
|
+
const tr = ed.state.tr;
|
|
1670
|
+
let count = 0;
|
|
1671
|
+
ed.state.doc.descendants((node, pos) => {
|
|
1672
|
+
if (node.type.name !== "variableChip" || node.attrs.variableId !== variableId)
|
|
1673
|
+
return;
|
|
1674
|
+
tr.setNodeMarkup(pos, void 0, {
|
|
1675
|
+
...node.attrs,
|
|
1676
|
+
text: value,
|
|
1677
|
+
html: variableChipHtml(String(node.attrs.html ?? ""), value)
|
|
1678
|
+
});
|
|
1679
|
+
count++;
|
|
1680
|
+
});
|
|
1681
|
+
if (count)
|
|
1682
|
+
ed.view.dispatch(tr);
|
|
1683
|
+
return count;
|
|
1684
|
+
}
|
|
1685
|
+
function variablesInDocument() {
|
|
1686
|
+
const ed = tiptap;
|
|
1687
|
+
const ids = [];
|
|
1688
|
+
if (!ed || ed.isDestroyed)
|
|
1689
|
+
return ids;
|
|
1690
|
+
ed.state.doc.descendants((node) => {
|
|
1691
|
+
if (node.type.name === "variableChip" && node.attrs.variableId && !ids.includes(node.attrs.variableId))
|
|
1692
|
+
ids.push(node.attrs.variableId);
|
|
1693
|
+
});
|
|
1694
|
+
return ids;
|
|
1695
|
+
}
|
|
1696
|
+
function buildBubbleMenu() {
|
|
1697
|
+
const TEXT_COLORS = [
|
|
1698
|
+
{ label: "Preto", value: "#1C242D" },
|
|
1699
|
+
{ label: "Vermelho", value: "#DC2626" },
|
|
1700
|
+
{ label: "Azul", value: "#2563EB" },
|
|
1701
|
+
{ label: "Verde", value: "#16A34A" },
|
|
1702
|
+
{ label: "Laranja", value: "#EA580C" }
|
|
1703
|
+
];
|
|
1704
|
+
const btn = (cls, text, mark, run) => {
|
|
1705
|
+
const b = h("button", { type: "button", class: `poc-bubble-btn ${cls}`, text, on: { click: run } });
|
|
1706
|
+
b.dataset.mark = mark;
|
|
1707
|
+
return b;
|
|
1708
|
+
};
|
|
1709
|
+
const eraser = h("button", { type: "button", class: "poc-bubble-btn", title: "Remover cor", on: { click: () => tiptap?.chain().focus().unsetMark("inlineStyle").run() } });
|
|
1710
|
+
eraser.innerHTML = iconSvg("eraser");
|
|
1711
|
+
const el = h("div", { class: "poc-bubble-menu flex items-center gap-2px rounded-8px border border-gray-200 bg-white p-2px shadow-lg" }, [
|
|
1712
|
+
btn("font-bold", "B", "bold", () => tiptap?.chain().focus().toggleBold().run()),
|
|
1713
|
+
btn("italic", "I", "italic", () => tiptap?.chain().focus().toggleItalic().run()),
|
|
1714
|
+
btn("underline", "U", "underline", () => tiptap?.chain().focus().toggleMark("underline").run()),
|
|
1715
|
+
h("div", { class: "mx-2px h-16px w-1px bg-gray-200" }),
|
|
1716
|
+
...TEXT_COLORS.map((color) => h("button", {
|
|
1717
|
+
type: "button",
|
|
1718
|
+
class: "poc-color-swatch",
|
|
1719
|
+
title: color.label,
|
|
1720
|
+
style: { background: color.value },
|
|
1721
|
+
on: { click: () => tiptap?.chain().focus().setMark("inlineStyle", { style: `color: ${color.value}` }).run() }
|
|
1722
|
+
})),
|
|
1723
|
+
eraser
|
|
1724
|
+
]);
|
|
1725
|
+
el.style.visibility = "hidden";
|
|
1726
|
+
return el;
|
|
1727
|
+
}
|
|
1728
|
+
function syncBubbleMenu() {
|
|
1729
|
+
const ed = tiptap;
|
|
1730
|
+
if (!ed)
|
|
1731
|
+
return;
|
|
1732
|
+
for (const b of bubbleEl.querySelectorAll("[data-mark]"))
|
|
1733
|
+
toggleClass(b, "poc-bubble-btn--active", ed.isActive(b.dataset.mark));
|
|
1734
|
+
}
|
|
1735
|
+
applySheetStyle();
|
|
1736
|
+
renderChromeBands();
|
|
1737
|
+
editorHost.addEventListener("poc:edit-footnote", onEditFootnoteEvent);
|
|
1738
|
+
editorHost.addEventListener("poc:variable-click", onVariableClickEvent);
|
|
1739
|
+
let contentObserver = null;
|
|
1740
|
+
try {
|
|
1741
|
+
tiptap = new Editor({
|
|
1742
|
+
element: editorHost,
|
|
1743
|
+
content: opts.html,
|
|
1744
|
+
editable,
|
|
1745
|
+
extensions: [
|
|
1746
|
+
...buildPocExtensions(pageConfig, {
|
|
1747
|
+
variables: readVariables,
|
|
1748
|
+
handleKeyDown: onEditorKeyDown
|
|
1749
|
+
}),
|
|
1750
|
+
BubbleMenu.configure({
|
|
1751
|
+
element: bubbleEl,
|
|
1752
|
+
// dentro do root: o CSS do pacote é todo escopado sob
|
|
1753
|
+
// `.poc-editor-root`, e o default do tippy (document.body)
|
|
1754
|
+
// deixaria o menu sem estilo
|
|
1755
|
+
tippyOptions: { duration: 100, appendTo: () => root },
|
|
1756
|
+
// o menu só existe na edição (o Vue montava com v-if="editable")
|
|
1757
|
+
shouldShow: ({ editor: ed, state, from, to }) => {
|
|
1758
|
+
if (!editable || !ed.isEditable)
|
|
1759
|
+
return false;
|
|
1760
|
+
const { empty } = state.selection;
|
|
1761
|
+
return !empty && from !== to && !ed.isActive("htmlIsland");
|
|
1762
|
+
}
|
|
1763
|
+
})
|
|
1764
|
+
],
|
|
1765
|
+
onCreate: ({ editor: ed }) => {
|
|
1766
|
+
bubbleEl.style.visibility = "";
|
|
1767
|
+
opts.onReady?.(ed);
|
|
1768
|
+
requestAnimationFrame(() => {
|
|
1769
|
+
if (ed.isDestroyed)
|
|
1770
|
+
return;
|
|
1771
|
+
schedulePaginate();
|
|
1772
|
+
const pmEl = ed.view.dom;
|
|
1773
|
+
contentObserver = new ResizeObserver(() => schedulePaginate());
|
|
1774
|
+
contentObserver.observe(pmEl);
|
|
1775
|
+
});
|
|
1776
|
+
},
|
|
1777
|
+
onTransaction: ({ transaction }) => {
|
|
1778
|
+
toolbar?.sync();
|
|
1779
|
+
syncBubbleMenu();
|
|
1780
|
+
opts.onTransaction?.();
|
|
1781
|
+
syncVariableTrigger(transaction.docChanged);
|
|
1782
|
+
if (transaction.docChanged) {
|
|
1783
|
+
if (!transaction.getMeta(POC_ASSET_SWAP))
|
|
1784
|
+
opts.onChange?.();
|
|
1785
|
+
schedulePaginate();
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
toggleClass(editorHost, "poc-review-cursor", !editable);
|
|
1790
|
+
footnotePopover = createFootnotePopover({ editor: tiptap, host: sheet, sheetCoords });
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
const message = error?.message ?? String(error);
|
|
1793
|
+
errorEl.textContent = `Falha ao montar o editor: ${message}`;
|
|
1794
|
+
errorEl.hidden = false;
|
|
1795
|
+
console.error("[minuta-editor] falha ao criar editor:", error);
|
|
1796
|
+
opts.onError?.(message);
|
|
1797
|
+
}
|
|
1798
|
+
const api = {
|
|
1799
|
+
get tiptap() {
|
|
1800
|
+
return tiptap;
|
|
1801
|
+
},
|
|
1802
|
+
root,
|
|
1803
|
+
container,
|
|
1804
|
+
sheet,
|
|
1805
|
+
getHTML: () => tiptap?.getHTML() ?? "",
|
|
1806
|
+
async exportDocx() {
|
|
1807
|
+
const ed = tiptap;
|
|
1808
|
+
if (!ed || ed.isDestroyed)
|
|
1809
|
+
throw new Error("editor n\xE3o montado");
|
|
1810
|
+
const { pocExportDocx } = await import('./chunks/docxExport.mjs');
|
|
1811
|
+
const breaks = pocPageKey.getState(ed.state)?.breaks ?? [];
|
|
1812
|
+
return pocExportDocx(ed, margins, {
|
|
1813
|
+
headerText: chrome.header,
|
|
1814
|
+
footerText: chrome.footer,
|
|
1815
|
+
headerScope: chrome.headerScope,
|
|
1816
|
+
footerScope: chrome.footerScope,
|
|
1817
|
+
headerLogoUrl: header.logoUrl,
|
|
1818
|
+
headerPosition: header.position,
|
|
1819
|
+
showPageNumber: chrome.showPageNumber
|
|
1820
|
+
}, { breaks });
|
|
1821
|
+
},
|
|
1822
|
+
usedVariables: () => extractTemplateVariables(tiptap?.getHTML() ?? ""),
|
|
1823
|
+
setEditable(value) {
|
|
1824
|
+
editable = value;
|
|
1825
|
+
tiptap?.setEditable(value);
|
|
1826
|
+
toggleClass(editorHost, "poc-review-cursor", !value);
|
|
1827
|
+
if (toolbar)
|
|
1828
|
+
toolbar.el.hidden = !value;
|
|
1829
|
+
if (!value) {
|
|
1830
|
+
closeVariablePicker();
|
|
1831
|
+
footnotePopover?.close();
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
setChrome(next) {
|
|
1835
|
+
Object.assign(chrome, next);
|
|
1836
|
+
chrome.headerScope = chrome.headerScope ?? "all";
|
|
1837
|
+
chrome.footerScope = chrome.footerScope ?? "all";
|
|
1838
|
+
chrome.showPageNumber = chrome.showPageNumber !== false;
|
|
1839
|
+
refreshChrome();
|
|
1840
|
+
},
|
|
1841
|
+
setHeader(next) {
|
|
1842
|
+
header = { ...next };
|
|
1843
|
+
refreshChrome();
|
|
1844
|
+
},
|
|
1845
|
+
setVariables(next) {
|
|
1846
|
+
variables = next;
|
|
1847
|
+
if (!hasVariables())
|
|
1848
|
+
closeVariablePicker();
|
|
1849
|
+
toolbar?.sync();
|
|
1850
|
+
},
|
|
1851
|
+
setToolbar(on) {
|
|
1852
|
+
if (on)
|
|
1853
|
+
mountToolbar();
|
|
1854
|
+
else
|
|
1855
|
+
unmountToolbar();
|
|
1856
|
+
},
|
|
1857
|
+
setMargins(next) {
|
|
1858
|
+
Object.assign(margins, next);
|
|
1859
|
+
refreshChrome();
|
|
1860
|
+
schedulePaginate();
|
|
1861
|
+
},
|
|
1862
|
+
setShowBlockOrigin(on) {
|
|
1863
|
+
toggleClass(root, "poc-no-block-origin", !on);
|
|
1864
|
+
},
|
|
1865
|
+
scrollToVariable,
|
|
1866
|
+
setVariableValue,
|
|
1867
|
+
variablesInDocument,
|
|
1868
|
+
schedulePaginate,
|
|
1869
|
+
refreshChrome,
|
|
1870
|
+
totalPages: () => totalPages,
|
|
1871
|
+
destroy() {
|
|
1872
|
+
editorHost.removeEventListener("poc:edit-footnote", onEditFootnoteEvent);
|
|
1873
|
+
editorHost.removeEventListener("poc:variable-click", onVariableClickEvent);
|
|
1874
|
+
contentObserver?.disconnect();
|
|
1875
|
+
resizeObserver.disconnect();
|
|
1876
|
+
closeVariablePicker();
|
|
1877
|
+
footnotePopover?.destroy();
|
|
1878
|
+
unmountToolbar();
|
|
1879
|
+
tiptap?.destroy();
|
|
1880
|
+
tiptap = null;
|
|
1881
|
+
root.remove();
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
return api;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
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, PocUnderline, PocVariableChip, breaksEqual, buildPocExtensions, computePocBreaks, createMinutaEditor, decodeVaultAttr, escapeHtml, escapeVaultAttr, extractTemplateVariables, iconSvg, pocPageKey, renumberPocFootnotes, resolveTemplateVariables, sanitizeFootnoteHtml, sanitizeRichHtml, variableChipHtml, vaultImageHtml };
|