@meistrari/minuta-nuxt 1.1.1 → 1.2.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/module.json +1 -1
- package/dist/runtime/components/MinutaChromeCanvas.vue +1 -1
- package/dist/runtime/components/MinutaChromeEditor.vue +1 -1
- package/dist/runtime/components/MinutaDocxEditor.vue +64 -1302
- package/dist/runtime/components/MinutaEmbed.vue +2 -2
- package/dist/runtime/components/MinutaTemplateEditor.vue +2 -2
- package/dist/runtime/composables/useMinutaApi.d.ts +2 -2
- package/dist/runtime/composables/useMinutaApi.js +1 -1
- package/dist/runtime/composables/vaultImages.d.ts +1 -5
- package/dist/runtime/composables/vaultImages.js +2 -12
- package/package.json +2 -1
- package/dist/runtime/components/MinutaVariablePicker.vue +0 -149
- package/dist/runtime/composables/docxExport.d.ts +0 -24
- package/dist/runtime/composables/docxExport.js +0 -1304
- package/dist/runtime/composables/editorExtensions.d.ts +0 -93
- package/dist/runtime/composables/editorExtensions.js +0 -548
- package/dist/runtime/composables/paginationPlugin.d.ts +0 -139
- package/dist/runtime/composables/paginationPlugin.js +0 -505
- package/dist/runtime/utils/footnoteHtml.d.ts +0 -26
- package/dist/runtime/utils/footnoteHtml.js +0 -117
- package/dist/runtime/utils/pocKeepWithNext.d.ts +0 -44
- package/dist/runtime/utils/pocKeepWithNext.js +0 -9
- package/dist/runtime/utils/sanitizeRichHtml.d.ts +0 -10
- package/dist/runtime/utils/sanitizeRichHtml.js +0 -73
- package/dist/runtime/utils/templateVariables.d.ts +0 -49
- package/dist/runtime/utils/templateVariables.js +0 -36
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
const INLINE_TAGS = /* @__PURE__ */ new Set(["B", "STRONG", "I", "EM", "U", "SUP", "SUB", "BR"]);
|
|
2
|
-
function safeHref(value) {
|
|
3
|
-
const href = (value ?? "").trim();
|
|
4
|
-
return /^https?:\/\//i.test(href) ? href : null;
|
|
5
|
-
}
|
|
6
|
-
function rgbToHex(value) {
|
|
7
|
-
const match = value.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
8
|
-
if (!match)
|
|
9
|
-
return null;
|
|
10
|
-
return `#${match.slice(1, 4).map((n) => Number(n).toString(16).padStart(2, "0")).join("")}`;
|
|
11
|
-
}
|
|
12
|
-
function safeSpanStyle(style) {
|
|
13
|
-
if (!style)
|
|
14
|
-
return null;
|
|
15
|
-
const out = [];
|
|
16
|
-
const family = style.match(/font-family\s*:\s*([^;]+)/i)?.[1]?.trim();
|
|
17
|
-
if (family && /^[\w\s,'"-]+$/.test(family))
|
|
18
|
-
out.push(`font-family: ${family.replace(/"/g, "'")}`);
|
|
19
|
-
const size = style.match(/font-size\s*:\s*([\d.]+)\s*pt/i)?.[1];
|
|
20
|
-
if (size)
|
|
21
|
-
out.push(`font-size: ${size}pt`);
|
|
22
|
-
const rawColor = style.match(/(?:^|;)\s*color\s*:\s*([^;]+)/i)?.[1]?.trim();
|
|
23
|
-
if (rawColor) {
|
|
24
|
-
const hex = rawColor.startsWith("#") ? /^#[0-9a-f]{6}$/i.test(rawColor) ? rawColor : null : rgbToHex(rawColor);
|
|
25
|
-
if (hex)
|
|
26
|
-
out.push(`color: ${hex}`);
|
|
27
|
-
}
|
|
28
|
-
return out.length ? out.join("; ") : null;
|
|
29
|
-
}
|
|
30
|
-
export function sanitizeFootnoteHtml(html) {
|
|
31
|
-
if (!html)
|
|
32
|
-
return "";
|
|
33
|
-
const doc = new DOMParser().parseFromString(`<div>${html}</div>`, "text/html");
|
|
34
|
-
const root = doc.body.firstElementChild;
|
|
35
|
-
if (!root)
|
|
36
|
-
return "";
|
|
37
|
-
const walk = (node) => {
|
|
38
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
39
|
-
return (node.textContent ?? "").replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c]);
|
|
40
|
-
if (!(node instanceof Element))
|
|
41
|
-
return "";
|
|
42
|
-
if (node.tagName === "SCRIPT" || node.tagName === "STYLE")
|
|
43
|
-
return "";
|
|
44
|
-
const inner = Array.from(node.childNodes).map(walk).join("");
|
|
45
|
-
if (node.tagName === "BR")
|
|
46
|
-
return "<br>";
|
|
47
|
-
if (node.tagName === "A") {
|
|
48
|
-
const href = safeHref(node.getAttribute("href"));
|
|
49
|
-
return href ? `<a href="${href.replace(/"/g, """)}">${inner}</a>` : inner;
|
|
50
|
-
}
|
|
51
|
-
if (node.tagName === "SPAN" || node.tagName === "FONT") {
|
|
52
|
-
const style = safeSpanStyle(node.getAttribute("style"));
|
|
53
|
-
return style ? `<span style="${style}">${inner}</span>` : inner;
|
|
54
|
-
}
|
|
55
|
-
if (node.tagName === "DIV" || node.tagName === "P")
|
|
56
|
-
return inner ? `${inner}<br>` : "";
|
|
57
|
-
if (INLINE_TAGS.has(node.tagName))
|
|
58
|
-
return `<${node.tagName.toLowerCase()}>${inner}</${node.tagName.toLowerCase()}>`;
|
|
59
|
-
return inner;
|
|
60
|
-
};
|
|
61
|
-
return Array.from(root.childNodes).map(walk).join("").replace(/\s+/g, " ").replace(/(<br>\s*)+$/i, "").trim();
|
|
62
|
-
}
|
|
63
|
-
export function footnoteHtmlToRuns(html) {
|
|
64
|
-
const runs = [];
|
|
65
|
-
if (!html)
|
|
66
|
-
return runs;
|
|
67
|
-
const doc = new DOMParser().parseFromString(`<div>${html}</div>`, "text/html");
|
|
68
|
-
const root = doc.body.firstElementChild;
|
|
69
|
-
if (!root)
|
|
70
|
-
return runs;
|
|
71
|
-
const walk = (node, style) => {
|
|
72
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
73
|
-
const text = node.textContent ?? "";
|
|
74
|
-
if (text)
|
|
75
|
-
runs.push({ ...style, text });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (!(node instanceof Element))
|
|
79
|
-
return;
|
|
80
|
-
if (node.tagName === "BR") {
|
|
81
|
-
runs.push({ text: "", break: true });
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const next = { ...style };
|
|
85
|
-
if (node.tagName === "B" || node.tagName === "STRONG")
|
|
86
|
-
next.bold = true;
|
|
87
|
-
if (node.tagName === "I" || node.tagName === "EM")
|
|
88
|
-
next.italics = true;
|
|
89
|
-
if (node.tagName === "U")
|
|
90
|
-
next.underline = true;
|
|
91
|
-
if (node.tagName === "A") {
|
|
92
|
-
const href = safeHref(node.getAttribute("href"));
|
|
93
|
-
if (href)
|
|
94
|
-
next.href = href;
|
|
95
|
-
}
|
|
96
|
-
if (node.tagName === "SPAN") {
|
|
97
|
-
const spanStyle = node.getAttribute("style") ?? "";
|
|
98
|
-
const family = spanStyle.match(/font-family\s*:\s*([^;]+)/i)?.[1]?.split(",")[0]?.trim().replace(/['"]/g, "");
|
|
99
|
-
if (family)
|
|
100
|
-
next.font = family;
|
|
101
|
-
const size = spanStyle.match(/font-size\s*:\s*([\d.]+)\s*pt/i)?.[1];
|
|
102
|
-
if (size)
|
|
103
|
-
next.sizePt = Number.parseFloat(size);
|
|
104
|
-
const color = spanStyle.match(/color\s*:\s*#([0-9a-f]{6})/i)?.[1];
|
|
105
|
-
if (color)
|
|
106
|
-
next.color = color.toUpperCase();
|
|
107
|
-
}
|
|
108
|
-
node.childNodes.forEach((child) => walk(child, next));
|
|
109
|
-
};
|
|
110
|
-
root.childNodes.forEach((child) => walk(child, {}));
|
|
111
|
-
return runs;
|
|
112
|
-
}
|
|
113
|
-
export function footnoteSizePt(style) {
|
|
114
|
-
const match = style?.match(/font-size\s*:\s*([\d.]+)\s*pt/i);
|
|
115
|
-
const value = match ? Number.parseFloat(match[1]) : Number.NaN;
|
|
116
|
-
return Number.isFinite(value) ? value : null;
|
|
117
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regra de *keep-with-next* do motor de paginação: um título não pode ficar
|
|
3
|
-
* órfão no pé da página, então a quebra sobe para antes dele.
|
|
4
|
-
*
|
|
5
|
-
* Vive separada de usePocPaginationPlugin porque o laço que a usa tem
|
|
6
|
-
* RETROCESSO — ele volta o índice para reavaliar o item depois de mover a
|
|
7
|
-
* quebra — e uma decisão que não avança trava o laço para sempre. Sendo pura,
|
|
8
|
-
* a regra pode ser testada sem DOM nem ProseMirror.
|
|
9
|
-
*/
|
|
10
|
-
export interface PocKeepWithNextItem {
|
|
11
|
-
/** topo do item no fluxo normalizado (sem chrome), em px */
|
|
12
|
-
top: number;
|
|
13
|
-
/** altura do item em px */
|
|
14
|
-
height: number;
|
|
15
|
-
/** margem superior, que conta como parte da página que o item abre */
|
|
16
|
-
marginTop: number;
|
|
17
|
-
/** é um título (h1–h4)? */
|
|
18
|
-
heading: boolean;
|
|
19
|
-
/** é o parágrafo-marcador de quebra manual do template? */
|
|
20
|
-
marker: boolean;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* A quebra deve subir do item para o título imediatamente anterior?
|
|
24
|
-
*
|
|
25
|
-
* Duas condições, e as duas são necessárias para o laço TERMINAR:
|
|
26
|
-
*
|
|
27
|
-
* 1. o título ainda não abre a página corrente — comparado pelo topo COM
|
|
28
|
-
* MARGEM, que é exatamente o valor que vira o novo `pageStart`. Comparar
|
|
29
|
-
* `prev.top` puro faz um título com `margin-top > 0` satisfazer a condição
|
|
30
|
-
* para sempre: `pageStart` vira `prev.top - prev.marginTop`, que é sempre
|
|
31
|
-
* menor que `prev.top`, e a mesma quebra é reemitida em loop;
|
|
32
|
-
*
|
|
33
|
-
* 2. o par título+item precisa CABER na página nova. Se o item sozinho já
|
|
34
|
-
* estoura a página (uma tabela ou imagem grande vinda de DOCX), descer o
|
|
35
|
-
* título não resolve — a quebra não avança e o laço volta ao mesmo ponto.
|
|
36
|
-
*/
|
|
37
|
-
export declare function shouldMoveBreakToHeading(params: {
|
|
38
|
-
heading: PocKeepWithNextItem | undefined;
|
|
39
|
-
item: PocKeepWithNextItem;
|
|
40
|
-
/** topo da página corrente no fluxo normalizado */
|
|
41
|
-
pageStart: number;
|
|
42
|
-
/** altura útil disponível, já descontadas as notas de rodapé */
|
|
43
|
-
capacity: number;
|
|
44
|
-
}): boolean;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export function shouldMoveBreakToHeading(params) {
|
|
2
|
-
const { heading, item, pageStart, capacity } = params;
|
|
3
|
-
if (!heading || !heading.heading || heading.marker)
|
|
4
|
-
return false;
|
|
5
|
-
const headingStart = heading.top - heading.marginTop;
|
|
6
|
-
if (headingStart <= pageStart)
|
|
7
|
-
return false;
|
|
8
|
-
return item.top + item.height - headingStart <= capacity;
|
|
9
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sanitização do HTML rico de cabeçalho/rodapé (chrome) — conteúdo criado em
|
|
3
|
-
* contenteditable e PERSISTIDO (template.headers, case_edition.chrome). Sem
|
|
4
|
-
* isso, um usuário do workspace poderia armazenar markup executável que roda
|
|
5
|
-
* no browser de quem abrir o caso/preview (stored XSS).
|
|
6
|
-
*
|
|
7
|
-
* Aplicar em TODO sink (v-html / innerHTML) e ao persistir. Mantém a
|
|
8
|
-
* formatação suportada (b/i/u, spans com estilo, imagens data:/blob:/https).
|
|
9
|
-
*/
|
|
10
|
-
export declare function sanitizeRichHtml(html: string | null | undefined): string;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
const BLOCKED_TAGS = /* @__PURE__ */ new Set([
|
|
2
|
-
"SCRIPT",
|
|
3
|
-
"STYLE",
|
|
4
|
-
"IFRAME",
|
|
5
|
-
"OBJECT",
|
|
6
|
-
"EMBED",
|
|
7
|
-
"LINK",
|
|
8
|
-
"META",
|
|
9
|
-
"BASE",
|
|
10
|
-
"FORM",
|
|
11
|
-
"INPUT",
|
|
12
|
-
"BUTTON",
|
|
13
|
-
"TEXTAREA",
|
|
14
|
-
"SELECT",
|
|
15
|
-
"TEMPLATE",
|
|
16
|
-
"DIALOG",
|
|
17
|
-
// SMIL: anima/troca atributo de outro elemento DEPOIS da sanitização —
|
|
18
|
-
// <a><animate attributeName="href" to="javascript:…"> devolve o href
|
|
19
|
-
// proibido no clique, com a URL nunca passando pelo allowlist abaixo
|
|
20
|
-
"ANIMATE",
|
|
21
|
-
"ANIMATEMOTION",
|
|
22
|
-
"ANIMATETRANSFORM",
|
|
23
|
-
"SET",
|
|
24
|
-
"DISCARD",
|
|
25
|
-
// hospedam markup de outro namespace (HTML dentro de SVG, MathML) — o
|
|
26
|
-
// parser reinterpreta na 2ª passada e é o vetor clássico de mXSS
|
|
27
|
-
"FOREIGNOBJECT",
|
|
28
|
-
"ANNOTATION-XML",
|
|
29
|
-
"NOSCRIPT",
|
|
30
|
-
"NOEMBED",
|
|
31
|
-
"NOFRAMES",
|
|
32
|
-
"XMP",
|
|
33
|
-
"PLAINTEXT",
|
|
34
|
-
// <use> puxa e clona um nó por referência (inclusive de fora do documento)
|
|
35
|
-
"USE"
|
|
36
|
-
]);
|
|
37
|
-
const BLOCKED_ATTRS = /* @__PURE__ */ new Set(["attributename", "attributetype", "to", "values", "from", "by"]);
|
|
38
|
-
const SAFE_URL = /^(?:https?:|data:image\/|blob:|vault:)/i;
|
|
39
|
-
function scrubElement(el) {
|
|
40
|
-
for (const child of [...el.children]) {
|
|
41
|
-
if (BLOCKED_TAGS.has(child.tagName.toUpperCase())) {
|
|
42
|
-
child.remove();
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
for (const attr of [...child.attributes]) {
|
|
46
|
-
const name = attr.name.toLowerCase();
|
|
47
|
-
if (name.startsWith("on")) {
|
|
48
|
-
child.removeAttribute(attr.name);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (BLOCKED_ATTRS.has(name)) {
|
|
52
|
-
child.removeAttribute(attr.name);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if ((name === "src" || name === "href" || name === "xlink:href" || name === "formaction" || name === "srcdoc") && (name === "srcdoc" || !SAFE_URL.test(attr.value.trim()))) {
|
|
56
|
-
child.removeAttribute(attr.name);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (name === "style" && /url\s*\(/i.test(attr.value) && !/url\s*\(\s*['"]?(?:https?:|data:image\/|blob:)/i.test(attr.value)) {
|
|
60
|
-
child.removeAttribute(attr.name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
scrubElement(child);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
export function sanitizeRichHtml(html) {
|
|
67
|
-
if (!html)
|
|
68
|
-
return "";
|
|
69
|
-
const tpl = document.createElement("template");
|
|
70
|
-
tpl.innerHTML = html;
|
|
71
|
-
scrubElement(tpl.content);
|
|
72
|
-
return tpl.innerHTML;
|
|
73
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Variáveis de template da minuta — o formato do placeholder e a resolução dele.
|
|
3
|
-
*
|
|
4
|
-
* O template é um documento normal do editor; onde entra um valor há um NÓ
|
|
5
|
-
* atômico (`templateVariable`, ver editorExtensions), serializado como
|
|
6
|
-
* `<span data-minuta-var="CHAVE" data-minuta-var-label="Rótulo">Rótulo</span>`.
|
|
7
|
-
*
|
|
8
|
-
* Por que nó e não `#CHAVE` no texto: no editor rico o texto é fatiado em
|
|
9
|
-
* marks. Negritar metade de um `#PROCESSO` produz
|
|
10
|
-
* `<b>#PRO</b>CESSO`, e qualquer substituição textual passa reto — em silêncio,
|
|
11
|
-
* só naquele template, só naquela linha. Como nó atômico o placeholder não tem
|
|
12
|
-
* "metade": seleciona inteiro, apaga inteiro, formata inteiro.
|
|
13
|
-
*
|
|
14
|
-
* ATENÇÃO: este arquivo é DUPLICADO em
|
|
15
|
-
* `packages/server/src/standalone-minuta/template-variables.ts` — o servidor
|
|
16
|
-
* resolve na criação da minuta e não pode importar o pacote publicado no npm.
|
|
17
|
-
* Mudou aqui, muda lá (e vice-versa); há teste de paridade no server.
|
|
18
|
-
*/
|
|
19
|
-
/** Definição de uma variável disponível — vem do app hospedeiro, não do CDB. */
|
|
20
|
-
export type MinutaVariableDef = {
|
|
21
|
-
/** chave usada no documento e nos valores (ex.: `PROCESSO`) */
|
|
22
|
-
key: string;
|
|
23
|
-
/** rótulo exibido no chip e no seletor (ex.: `Nº do Processo`) */
|
|
24
|
-
label: string;
|
|
25
|
-
/** de onde o valor sai — vira o tooltip do chip */
|
|
26
|
-
description?: string;
|
|
27
|
-
/** agrupador opcional no seletor (ex.: `Processo`, `Réu`) */
|
|
28
|
-
group?: string;
|
|
29
|
-
};
|
|
30
|
-
export declare function escapeHtml(value: string): string;
|
|
31
|
-
/**
|
|
32
|
-
* Chaves das variáveis usadas no documento, na ordem em que aparecem e sem
|
|
33
|
-
* repetição. É o que substitui o `extractPeticaoVariaveisFromTexto` de quem
|
|
34
|
-
* hospeda: o host guarda esta lista junto com o template e não precisa ler HTML.
|
|
35
|
-
*/
|
|
36
|
-
export declare function extractTemplateVariables(html: string | null | undefined): string[];
|
|
37
|
-
/**
|
|
38
|
-
* Troca os placeholders pelos valores. O que NÃO tem valor continua sendo um
|
|
39
|
-
* placeholder vivo no documento — não vira texto vazio nem some.
|
|
40
|
-
*
|
|
41
|
-
* É de propósito: uma variável sem valor é um buraco na peça, e o lugar de
|
|
42
|
-
* mostrar isso é o próprio parágrafo em que ela está, não um botão desabilitado
|
|
43
|
-
* no rodapé do modal. Como o nó sobrevive, `extractTemplateVariables` na minuta
|
|
44
|
-
* gerada responde "o que ainda falta", e mandar os valores de novo resolve.
|
|
45
|
-
*/
|
|
46
|
-
export declare function resolveTemplateVariables(html: string | null | undefined, values: Record<string, string | null | undefined> | null | undefined): {
|
|
47
|
-
html: string;
|
|
48
|
-
missing: string[];
|
|
49
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const VARIABLE_TAG_RE = /<span\b[^>]*\bdata-minuta-var="([^"]*)"[^>]*>[^<]*<\/span>/g;
|
|
2
|
-
export function escapeHtml(value) {
|
|
3
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4
|
-
}
|
|
5
|
-
function decodeAttr(value) {
|
|
6
|
-
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
7
|
-
}
|
|
8
|
-
export function extractTemplateVariables(html) {
|
|
9
|
-
if (!html)
|
|
10
|
-
return [];
|
|
11
|
-
const keys = [];
|
|
12
|
-
for (const match of html.matchAll(VARIABLE_TAG_RE)) {
|
|
13
|
-
const key = decodeAttr(match[1] ?? "").trim();
|
|
14
|
-
if (key && !keys.includes(key))
|
|
15
|
-
keys.push(key);
|
|
16
|
-
}
|
|
17
|
-
return keys;
|
|
18
|
-
}
|
|
19
|
-
export function resolveTemplateVariables(html, values) {
|
|
20
|
-
if (!html)
|
|
21
|
-
return { html: "", missing: [] };
|
|
22
|
-
const missing = [];
|
|
23
|
-
const resolved = html.replace(VARIABLE_TAG_RE, (tag, rawKey) => {
|
|
24
|
-
const key = decodeAttr(rawKey ?? "").trim();
|
|
25
|
-
if (!key)
|
|
26
|
-
return tag;
|
|
27
|
-
const value = values?.[key];
|
|
28
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
29
|
-
if (!missing.includes(key))
|
|
30
|
-
missing.push(key);
|
|
31
|
-
return tag;
|
|
32
|
-
}
|
|
33
|
-
return escapeHtml(value).replace(/\r?\n/g, "<br>");
|
|
34
|
-
});
|
|
35
|
-
return { html: resolved, missing };
|
|
36
|
-
}
|