@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 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
- /** as variáveis presentes no documento, na ordem, sem repetição */
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
- /** as variáveis presentes no documento, na ordem, sem repetição */
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 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;
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" && node.attrs.variableId && !ids.includes(node.attrs.variableId))
1692
- ids.push(node.attrs.variableId);
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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.2",
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",