@meistrari/minuta-editor 1.0.2 → 1.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/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.mjs +79 -5
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -232,6 +232,12 @@ interface MinutaEditorOptions {
|
|
|
232
232
|
toolbar?: boolean;
|
|
233
233
|
/** realce de proveniência de bloco no hover (só faz sentido no modo estruturado) */
|
|
234
234
|
showBlockOrigin?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Dispara `onBlockClick` também com o editor editável. Desligado por
|
|
237
|
+
* padrão: na tela do defesa.ai o clique em edição é para posicionar o
|
|
238
|
+
* cursor, e abrir o bloco do checklist a cada clique atrapalharia.
|
|
239
|
+
*/
|
|
240
|
+
blockClickWhileEditing?: boolean;
|
|
235
241
|
onReady?: (tiptap: Editor) => void;
|
|
236
242
|
/** o documento mudou por edição (troca de imagem fria não conta) */
|
|
237
243
|
onChange?: () => void;
|
|
@@ -274,6 +280,8 @@ interface MinutaEditor {
|
|
|
274
280
|
setToolbar: (on: boolean) => void;
|
|
275
281
|
setMargins: (margins: MinutaMargins) => void;
|
|
276
282
|
setShowBlockOrigin: (on: boolean) => void;
|
|
283
|
+
/** liga/desliga `onBlockClick` em edição depois de criado (ver `blockClickWhileEditing`) */
|
|
284
|
+
setBlockClickWhileEditing: (on: boolean) => void;
|
|
277
285
|
/**
|
|
278
286
|
* Ponte formulário → documento: rola até a primeira ocorrência da variável
|
|
279
287
|
* e a destaca por um instante. `false` se ela não está no documento.
|
|
@@ -286,7 +294,13 @@ interface MinutaEditor {
|
|
|
286
294
|
* editou o texto em volta. Devolve quantas ocorrências mudaram.
|
|
287
295
|
*/
|
|
288
296
|
setVariableValue: (variableId: string, value: string) => number;
|
|
289
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* Troca a imagem de uma variável de imagem: o `<img data-variable-id>` ganha
|
|
299
|
+
* o `src` novo, e um aviso de imagem faltando com esse id vira a imagem.
|
|
300
|
+
* Devolve quantas ocorrências mudaram.
|
|
301
|
+
*/
|
|
302
|
+
setVariableImage: (variableId: string, src: string) => number;
|
|
303
|
+
/** ids marcados no documento, na ordem, sem repetição: chips de texto, imagens e imagens faltando */
|
|
290
304
|
variablesInDocument: () => string[];
|
|
291
305
|
/** repagina (o host chama após mudar algo que afeta altura fora do editor) */
|
|
292
306
|
schedulePaginate: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -232,6 +232,12 @@ interface MinutaEditorOptions {
|
|
|
232
232
|
toolbar?: boolean;
|
|
233
233
|
/** realce de proveniência de bloco no hover (só faz sentido no modo estruturado) */
|
|
234
234
|
showBlockOrigin?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Dispara `onBlockClick` também com o editor editável. Desligado por
|
|
237
|
+
* padrão: na tela do defesa.ai o clique em edição é para posicionar o
|
|
238
|
+
* cursor, e abrir o bloco do checklist a cada clique atrapalharia.
|
|
239
|
+
*/
|
|
240
|
+
blockClickWhileEditing?: boolean;
|
|
235
241
|
onReady?: (tiptap: Editor) => void;
|
|
236
242
|
/** o documento mudou por edição (troca de imagem fria não conta) */
|
|
237
243
|
onChange?: () => void;
|
|
@@ -274,6 +280,8 @@ interface MinutaEditor {
|
|
|
274
280
|
setToolbar: (on: boolean) => void;
|
|
275
281
|
setMargins: (margins: MinutaMargins) => void;
|
|
276
282
|
setShowBlockOrigin: (on: boolean) => void;
|
|
283
|
+
/** liga/desliga `onBlockClick` em edição depois de criado (ver `blockClickWhileEditing`) */
|
|
284
|
+
setBlockClickWhileEditing: (on: boolean) => void;
|
|
277
285
|
/**
|
|
278
286
|
* Ponte formulário → documento: rola até a primeira ocorrência da variável
|
|
279
287
|
* e a destaca por um instante. `false` se ela não está no documento.
|
|
@@ -286,7 +294,13 @@ interface MinutaEditor {
|
|
|
286
294
|
* editou o texto em volta. Devolve quantas ocorrências mudaram.
|
|
287
295
|
*/
|
|
288
296
|
setVariableValue: (variableId: string, value: string) => number;
|
|
289
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* Troca a imagem de uma variável de imagem: o `<img data-variable-id>` ganha
|
|
299
|
+
* o `src` novo, e um aviso de imagem faltando com esse id vira a imagem.
|
|
300
|
+
* Devolve quantas ocorrências mudaram.
|
|
301
|
+
*/
|
|
302
|
+
setVariableImage: (variableId: string, src: string) => number;
|
|
303
|
+
/** ids marcados no documento, na ordem, sem repetição: chips de texto, imagens e imagens faltando */
|
|
290
304
|
variablesInDocument: () => string[];
|
|
291
305
|
/** repagina (o host chama após mudar algo que afeta altura fora do editor) */
|
|
292
306
|
schedulePaginate: () => void;
|
package/dist/index.mjs
CHANGED
|
@@ -1426,8 +1426,9 @@ function createMinutaEditor(element, opts) {
|
|
|
1426
1426
|
opts.onEditChrome?.(part);
|
|
1427
1427
|
window.dispatchEvent(new CustomEvent("poc:edit-chrome", { detail: { part, editor: tiptap } }));
|
|
1428
1428
|
}
|
|
1429
|
+
let blockClickWhileEditing = opts.blockClickWhileEditing === true;
|
|
1429
1430
|
function onSheetClick(event) {
|
|
1430
|
-
if (editable)
|
|
1431
|
+
if (editable && !blockClickWhileEditing)
|
|
1431
1432
|
return;
|
|
1432
1433
|
const target = event.target;
|
|
1433
1434
|
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")) {
|
|
@@ -1652,9 +1653,29 @@ function createMinutaEditor(element, opts) {
|
|
|
1652
1653
|
text: String(detail.text ?? "")
|
|
1653
1654
|
});
|
|
1654
1655
|
}
|
|
1656
|
+
function variableTargetElement(variableId) {
|
|
1657
|
+
const id = CSS.escape(variableId);
|
|
1658
|
+
const chip = sheet.querySelector(`.poc-varchip [data-variable-id="${id}"], .poc-varchip[data-variable-id="${id}"]`);
|
|
1659
|
+
if (chip)
|
|
1660
|
+
return chip.closest(".poc-varchip") ?? chip;
|
|
1661
|
+
const img = sheet.querySelector(`img[data-variable-id="${id}"]`);
|
|
1662
|
+
if (img)
|
|
1663
|
+
return img;
|
|
1664
|
+
const ed = tiptap;
|
|
1665
|
+
if (!ed || ed.isDestroyed)
|
|
1666
|
+
return null;
|
|
1667
|
+
let found = null;
|
|
1668
|
+
ed.state.doc.descendants((node, pos) => {
|
|
1669
|
+
if (found || node.type.name !== "missingImage" || node.attrs.variableId !== variableId)
|
|
1670
|
+
return;
|
|
1671
|
+
const dom = ed.view.nodeDOM(pos);
|
|
1672
|
+
if (dom instanceof HTMLElement)
|
|
1673
|
+
found = dom;
|
|
1674
|
+
});
|
|
1675
|
+
return found;
|
|
1676
|
+
}
|
|
1655
1677
|
function scrollToVariable(variableId) {
|
|
1656
|
-
const
|
|
1657
|
-
const chip = el?.closest(".poc-varchip") ?? el;
|
|
1678
|
+
const chip = variableTargetElement(variableId);
|
|
1658
1679
|
if (!chip)
|
|
1659
1680
|
return false;
|
|
1660
1681
|
chip.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
@@ -1682,17 +1703,66 @@ function createMinutaEditor(element, opts) {
|
|
|
1682
1703
|
ed.view.dispatch(tr);
|
|
1683
1704
|
return count;
|
|
1684
1705
|
}
|
|
1706
|
+
function setVariableImage(variableId, src) {
|
|
1707
|
+
const ed = tiptap;
|
|
1708
|
+
if (!ed || ed.isDestroyed)
|
|
1709
|
+
return 0;
|
|
1710
|
+
const islandType = ed.state.schema.nodes.htmlIsland;
|
|
1711
|
+
const tr = ed.state.tr;
|
|
1712
|
+
let count = 0;
|
|
1713
|
+
const hits = [];
|
|
1714
|
+
ed.state.doc.descendants((node, pos) => {
|
|
1715
|
+
if (node.type.name === "missingImage" && node.attrs.variableId === variableId)
|
|
1716
|
+
hits.push({ pos, node });
|
|
1717
|
+
else if (node.type.name === "htmlIsland" && String(node.attrs.html ?? "").includes(`data-variable-id="${variableId}"`))
|
|
1718
|
+
hits.push({ pos, node });
|
|
1719
|
+
});
|
|
1720
|
+
for (const { pos, node } of hits.reverse()) {
|
|
1721
|
+
if (node.type.name === "missingImage") {
|
|
1722
|
+
if (!islandType)
|
|
1723
|
+
continue;
|
|
1724
|
+
const html = `<img src="${escapeAttr(src)}" alt="${escapeAttr(variableId)}" data-variable-id="${escapeAttr(variableId)}" data-parent-block-id="${escapeAttr(String(node.attrs.parentBlockId ?? ""))}" style="max-width: 100%; height: auto;" />`;
|
|
1725
|
+
tr.replaceWith(pos, pos + node.nodeSize, islandType.create({ html }));
|
|
1726
|
+
count++;
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
const tpl = document.createElement("template");
|
|
1730
|
+
tpl.innerHTML = String(node.attrs.html ?? "");
|
|
1731
|
+
const imgs = tpl.content.querySelectorAll(`img[data-variable-id="${CSS.escape(variableId)}"]`);
|
|
1732
|
+
if (!imgs.length)
|
|
1733
|
+
continue;
|
|
1734
|
+
for (const img of imgs)
|
|
1735
|
+
img.setAttribute("src", src);
|
|
1736
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, html: tpl.innerHTML });
|
|
1737
|
+
count += imgs.length;
|
|
1738
|
+
}
|
|
1739
|
+
if (count)
|
|
1740
|
+
ed.view.dispatch(tr);
|
|
1741
|
+
return count;
|
|
1742
|
+
}
|
|
1685
1743
|
function variablesInDocument() {
|
|
1686
1744
|
const ed = tiptap;
|
|
1687
1745
|
const ids = [];
|
|
1688
1746
|
if (!ed || ed.isDestroyed)
|
|
1689
1747
|
return ids;
|
|
1748
|
+
const add = (id) => {
|
|
1749
|
+
if (typeof id === "string" && id && !ids.includes(id))
|
|
1750
|
+
ids.push(id);
|
|
1751
|
+
};
|
|
1690
1752
|
ed.state.doc.descendants((node) => {
|
|
1691
|
-
if (node.type.name === "variableChip"
|
|
1692
|
-
|
|
1753
|
+
if (node.type.name === "variableChip" || node.type.name === "missingImage") {
|
|
1754
|
+
add(node.attrs.variableId);
|
|
1755
|
+
} else if (node.type.name === "htmlIsland") {
|
|
1756
|
+
for (const m of String(node.attrs.html ?? "").matchAll(/data-variable-id="([^"]+)"/g)) {
|
|
1757
|
+
add(m[1]);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1693
1760
|
});
|
|
1694
1761
|
return ids;
|
|
1695
1762
|
}
|
|
1763
|
+
function escapeAttr(value) {
|
|
1764
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1765
|
+
}
|
|
1696
1766
|
function buildBubbleMenu() {
|
|
1697
1767
|
const TEXT_COLORS = [
|
|
1698
1768
|
{ label: "Preto", value: "#1C242D" },
|
|
@@ -1862,8 +1932,12 @@ function createMinutaEditor(element, opts) {
|
|
|
1862
1932
|
setShowBlockOrigin(on) {
|
|
1863
1933
|
toggleClass(root, "poc-no-block-origin", !on);
|
|
1864
1934
|
},
|
|
1935
|
+
setBlockClickWhileEditing(on) {
|
|
1936
|
+
blockClickWhileEditing = on;
|
|
1937
|
+
},
|
|
1865
1938
|
scrollToVariable,
|
|
1866
1939
|
setVariableValue,
|
|
1940
|
+
setVariableImage,
|
|
1867
1941
|
variablesInDocument,
|
|
1868
1942
|
schedulePaginate,
|
|
1869
1943
|
refreshChrome,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/minuta-editor",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Editor de minuta do defesa.ai sem framework: folha A4 paginada, edição rica, placeholders de variável e export DOCX fiel ao preview — monta em qualquer app (React, Vue, vanilla)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|