@meistrari/minuta-nuxt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +106 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.mts +18 -0
  4. package/dist/module.d.ts +18 -0
  5. package/dist/module.json +9 -0
  6. package/dist/module.mjs +45 -0
  7. package/dist/runtime/components/MinutaAnnotationsGutter.vue +425 -0
  8. package/dist/runtime/components/MinutaChatEditCard.vue +124 -0
  9. package/dist/runtime/components/MinutaChromeCanvas.vue +549 -0
  10. package/dist/runtime/components/MinutaChromeEditor.vue +100 -0
  11. package/dist/runtime/components/MinutaDocumentViewer.vue +90 -0
  12. package/dist/runtime/components/MinutaDocxEditor.vue +1158 -0
  13. package/dist/runtime/components/MinutaEmbed.vue +783 -0
  14. package/dist/runtime/components/MinutaIcon.vue +53 -0
  15. package/dist/runtime/composables/annotationAnchors.d.ts +73 -0
  16. package/dist/runtime/composables/annotationAnchors.js +243 -0
  17. package/dist/runtime/composables/chatEditOps.d.ts +37 -0
  18. package/dist/runtime/composables/chatEditOps.js +61 -0
  19. package/dist/runtime/composables/docxExport.d.ts +24 -0
  20. package/dist/runtime/composables/docxExport.js +1292 -0
  21. package/dist/runtime/composables/editorExtensions.d.ts +56 -0
  22. package/dist/runtime/composables/editorExtensions.js +466 -0
  23. package/dist/runtime/composables/paginationPlugin.d.ts +126 -0
  24. package/dist/runtime/composables/paginationPlugin.js +421 -0
  25. package/dist/runtime/composables/useMinutaApi.d.ts +112 -0
  26. package/dist/runtime/composables/useMinutaApi.js +68 -0
  27. package/dist/runtime/composables/vaultImages.d.ts +12 -0
  28. package/dist/runtime/composables/vaultImages.js +85 -0
  29. package/dist/runtime/composables/versionDiff.d.ts +34 -0
  30. package/dist/runtime/composables/versionDiff.js +62 -0
  31. package/dist/runtime/server/api/minuta/[...path].d.ts +2 -0
  32. package/dist/runtime/server/api/minuta/[...path].js +6 -0
  33. package/dist/runtime/server/api/minuta/index.d.ts +2 -0
  34. package/dist/runtime/server/api/minuta/index.js +3 -0
  35. package/dist/runtime/server/utils/minuta-proxy.d.ts +10 -0
  36. package/dist/runtime/server/utils/minuta-proxy.js +63 -0
  37. package/dist/runtime/utils/footnoteHtml.d.ts +26 -0
  38. package/dist/runtime/utils/footnoteHtml.js +117 -0
  39. package/dist/runtime/utils/pocKeepWithNext.d.ts +44 -0
  40. package/dist/runtime/utils/pocKeepWithNext.js +9 -0
  41. package/dist/runtime/utils/sanitizeRichHtml.d.ts +10 -0
  42. package/dist/runtime/utils/sanitizeRichHtml.js +73 -0
  43. package/dist/types.d.mts +7 -0
  44. package/dist/types.d.ts +7 -0
  45. package/package.json +100 -0
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import {
4
+ PhArrowUUpLeft,
5
+ PhArrowUUpRight,
6
+ PhArrowsOutLineHorizontal,
7
+ PhArrowsOutLineVertical,
8
+ PhEraser,
9
+ PhHighlighter,
10
+ PhImage,
11
+ PhListBullets,
12
+ PhListNumbers,
13
+ PhTextAlignCenter,
14
+ PhTextAlignJustify,
15
+ PhTextAlignLeft,
16
+ PhTextAlignRight,
17
+ PhNote,
18
+ PhTextT,
19
+ PhRectangle,
20
+ PhTrash,
21
+ PhX,
22
+ } from '@phosphor-icons/vue'
23
+
24
+ const props = defineProps<{ name: string }>()
25
+
26
+ /** Substituto local do TelaIcon (tela-build) — só os ícones que o editor usa. */
27
+ const ICONS: Record<string, unknown> = {
28
+ 'i-ph-arrow-u-up-left': PhArrowUUpLeft,
29
+ 'i-ph-arrow-u-up-right': PhArrowUUpRight,
30
+ 'i-ph-arrows-out-line-horizontal': PhArrowsOutLineHorizontal,
31
+ 'i-ph-arrows-out-line-vertical': PhArrowsOutLineVertical,
32
+ 'i-ph-eraser': PhEraser,
33
+ 'i-ph-highlighter': PhHighlighter,
34
+ 'i-ph-image': PhImage,
35
+ 'i-ph-list-bullets': PhListBullets,
36
+ 'i-ph-list-numbers': PhListNumbers,
37
+ 'i-ph-text-align-center': PhTextAlignCenter,
38
+ 'i-ph-text-align-justify': PhTextAlignJustify,
39
+ 'i-ph-text-align-left': PhTextAlignLeft,
40
+ 'i-ph-text-align-right': PhTextAlignRight,
41
+ 'i-ph-note': PhNote,
42
+ 'i-ph-text-t': PhTextT,
43
+ 'i-ph-rectangle': PhRectangle,
44
+ 'i-ph-trash': PhTrash,
45
+ 'i-ph-x': PhX,
46
+ }
47
+
48
+ const icon = computed(() => ICONS[props.name] ?? null)
49
+ </script>
50
+
51
+ <template>
52
+ <component :is="icon" v-if="icon" :size="16" />
53
+ </template>
@@ -0,0 +1,73 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ /**
3
+ * Âncoras das anotações da análise (comentários de margem estilo Google Docs):
4
+ * localiza no documento o trecho exato (ou o título da seção) de cada item,
5
+ * dá as coordenadas pro gutter posicionar os cards e faz o highlight/scroll
6
+ * quando um card é clicado.
7
+ */
8
+ export interface AnnotationTarget {
9
+ /** trecho verbatim da minuta (preferido — vem do agente) */
10
+ trecho?: string | null;
11
+ /** título da seção onde o item aparece (fallback) */
12
+ onde?: string | null;
13
+ }
14
+ /** range PM de um recorte verbatim do documento (busca whitespace-tolerante) */
15
+ export declare function findTextRange(editor: Editor, needle: string): {
16
+ from: number;
17
+ to: number;
18
+ } | null;
19
+ export interface LocatedAnnotation {
20
+ range: {
21
+ from: number;
22
+ to: number;
23
+ };
24
+ /** como o range foi encontrado: trecho verbatim ou título da seção */
25
+ matchedBy: 'trecho' | 'onde';
26
+ }
27
+ /** localiza o range do item, dizendo por qual caminho: trecho verbatim primeiro, título da seção depois */
28
+ export declare function locateAnnotationDetailed(editor: Editor, target: AnnotationTarget): LocatedAnnotation | null;
29
+ /** localiza o range do item: trecho verbatim primeiro, título da seção depois */
30
+ export declare function locateAnnotation(editor: Editor, target: AnnotationTarget): {
31
+ from: number;
32
+ to: number;
33
+ } | null;
34
+ /** y do range relativo a um elemento-referência (para posicionar o card) */
35
+ export declare function annotationY(editor: Editor, from: number, reference: HTMLElement): number | null;
36
+ /** `manual` = trecho que o usuário editou — a âncora original não vale mais */
37
+ export type AnnotationKind = 'tese' | 'jurisprudencia' | 'citacao' | 'fato' | 'manual';
38
+ export interface AnnotationDecoration {
39
+ /** identidade estável do item (pra recuperar o range mapeado após edições) */
40
+ id: string;
41
+ from: number;
42
+ to: number;
43
+ kind: AnnotationKind;
44
+ /** card ativo na margem → highlight mais forte */
45
+ active?: boolean;
46
+ }
47
+ /** aplica (ou substitui) o conjunto de highlights da análise no documento */
48
+ export declare function applyAnnotationDecorations(editor: Editor, items: AnnotationDecoration[] | null): void;
49
+ /**
50
+ * Ranges atuais das decorações, MAPEADOS pelas edições desde a última
51
+ * aplicação — é assim que um trecho editado continua rastreável mesmo quando
52
+ * a busca verbatim já não o encontra.
53
+ */
54
+ export declare function getAnnotationDecorationRanges(editor: Editor): Map<string, {
55
+ from: number;
56
+ to: number;
57
+ }>;
58
+ /** meta que marca transações programáticas (chat, restore, troca de versão) */
59
+ export declare const PROGRAMMATIC_META = "minutaProgrammatic";
60
+ /** liga o rastro de edições do usuário (idempotente) */
61
+ export declare function trackUserEdits(editor: Editor): void;
62
+ /** ranges atuais do texto adicionado pelo usuário */
63
+ export declare function getUserEditRanges(editor: Editor): Array<{
64
+ from: number;
65
+ to: number;
66
+ }>;
67
+ /** substitui o rastro (rehydratação a partir da analysis persistida) */
68
+ export declare function setUserEditRanges(editor: Editor, ranges: Array<{
69
+ from: number;
70
+ to: number;
71
+ }>): void;
72
+ /** rola o container até o range da anotação */
73
+ export declare function scrollToAnnotation(editor: Editor, from: number, scrollContainer?: HTMLElement | null): void;
@@ -0,0 +1,243 @@
1
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
2
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
3
+ const norm = (text) => text.toLowerCase().replace(/\s+/g, " ").trim();
4
+ function buildTextIndex(editor) {
5
+ const chunks = [];
6
+ const posAt = [];
7
+ editor.state.doc.descendants((node, pos) => {
8
+ if (node.isText && node.text) {
9
+ for (let i = 0; i < node.text.length; i++) {
10
+ chunks.push(node.text[i]);
11
+ posAt.push(pos + i);
12
+ }
13
+ } else if (node.isBlock && chunks.length && chunks[chunks.length - 1] !== " ") {
14
+ chunks.push(" ");
15
+ posAt.push(pos);
16
+ }
17
+ return true;
18
+ });
19
+ return { text: chunks.join(""), posAt };
20
+ }
21
+ function findRange(text, posAt, needle) {
22
+ const target = norm(needle);
23
+ if (!target)
24
+ return null;
25
+ const lower = text.toLowerCase();
26
+ const idx = lower.indexOf(target);
27
+ if (idx >= 0)
28
+ return { from: posAt[idx], to: posAt[idx + target.length - 1] + 1 };
29
+ const isWs = (c) => /\s/.test(c);
30
+ for (let start = 0; start < lower.length; start++) {
31
+ if (lower[start] !== target[0])
32
+ continue;
33
+ let i = start;
34
+ let j = 0;
35
+ while (i < lower.length && j < target.length) {
36
+ if (lower[i] === target[j]) {
37
+ i++;
38
+ j++;
39
+ } else if (isWs(lower[i]) && target[j] === " ") {
40
+ while (i < lower.length && isWs(lower[i])) i++;
41
+ j++;
42
+ } else if (isWs(lower[i])) {
43
+ i++;
44
+ } else {
45
+ break;
46
+ }
47
+ }
48
+ if (j === target.length)
49
+ return { from: posAt[start], to: posAt[i - 1] + 1 };
50
+ }
51
+ return null;
52
+ }
53
+ export function findTextRange(editor, needle) {
54
+ const { text, posAt } = buildTextIndex(editor);
55
+ if (!text.length)
56
+ return null;
57
+ return findRange(text, posAt, needle);
58
+ }
59
+ export function locateAnnotationDetailed(editor, target) {
60
+ const { text, posAt } = buildTextIndex(editor);
61
+ if (!text.length)
62
+ return null;
63
+ if (target.trecho) {
64
+ const range = findRange(text, posAt, target.trecho);
65
+ if (range)
66
+ return { range, matchedBy: "trecho" };
67
+ }
68
+ if (target.onde) {
69
+ const candidates = [target.onde, target.onde.replace(/^[IVXLCDM\d]+\s*[—\-–.)]\s*/i, "")];
70
+ for (const candidate of candidates) {
71
+ if (!candidate.trim())
72
+ continue;
73
+ const range = findRange(text, posAt, candidate);
74
+ if (range)
75
+ return { range, matchedBy: "onde" };
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ export function locateAnnotation(editor, target) {
81
+ return locateAnnotationDetailed(editor, target)?.range ?? null;
82
+ }
83
+ export function annotationY(editor, from, reference) {
84
+ try {
85
+ const coords = editor.view.coordsAtPos(Math.min(from, editor.state.doc.content.size));
86
+ const refRect = reference.getBoundingClientRect();
87
+ return coords.top - refRect.top;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+ const highlightKey = new PluginKey("minutaAnnotationHighlight");
93
+ const editorsWithPlugin = /* @__PURE__ */ new WeakSet();
94
+ function ensurePlugin(editor) {
95
+ if (editorsWithPlugin.has(editor))
96
+ return;
97
+ editorsWithPlugin.add(editor);
98
+ editor.registerPlugin(new Plugin({
99
+ key: highlightKey,
100
+ state: {
101
+ init: () => DecorationSet.empty,
102
+ apply(tr, old) {
103
+ const meta = tr.getMeta(highlightKey);
104
+ if (meta === null)
105
+ return DecorationSet.empty;
106
+ if (meta) {
107
+ const max = tr.doc.content.size;
108
+ const decorations = meta.filter((item) => item.from < item.to && item.to <= max).map((item) => Decoration.inline(item.from, item.to, {
109
+ class: `minuta-anno minuta-anno--${item.kind}${item.active ? " minuta-anno--active" : ""}`
110
+ }, { annotationId: item.id }));
111
+ return DecorationSet.create(tr.doc, decorations);
112
+ }
113
+ return old.map(tr.mapping, tr.doc);
114
+ }
115
+ },
116
+ props: {
117
+ decorations(state) {
118
+ return highlightKey.getState(state);
119
+ }
120
+ }
121
+ }));
122
+ }
123
+ export function applyAnnotationDecorations(editor, items) {
124
+ if (editor.isDestroyed)
125
+ return;
126
+ ensurePlugin(editor);
127
+ editor.view.dispatch(editor.state.tr.setMeta(highlightKey, items ?? null));
128
+ }
129
+ export function getAnnotationDecorationRanges(editor) {
130
+ const out = /* @__PURE__ */ new Map();
131
+ if (editor.isDestroyed)
132
+ return out;
133
+ const set = highlightKey.getState(editor.state);
134
+ if (!set)
135
+ return out;
136
+ for (const deco of set.find()) {
137
+ const id = deco.spec.annotationId;
138
+ if (id && deco.from < deco.to)
139
+ out.set(id, { from: deco.from, to: deco.to });
140
+ }
141
+ return out;
142
+ }
143
+ export const PROGRAMMATIC_META = "minutaProgrammatic";
144
+ const userEditsKey = new PluginKey("minutaUserEdits");
145
+ const editorsWithUserEdits = /* @__PURE__ */ new WeakSet();
146
+ function mergeRanges(ranges) {
147
+ const sorted = [...ranges].filter((r) => r.to > r.from).sort((a, b) => a.from - b.from);
148
+ const out = [];
149
+ for (const range of sorted) {
150
+ const last = out[out.length - 1];
151
+ if (last && range.from <= last.to)
152
+ last.to = Math.max(last.to, range.to);
153
+ else
154
+ out.push({ ...range });
155
+ }
156
+ return out;
157
+ }
158
+ function userEditDecorations(doc, ranges) {
159
+ const max = doc.content.size;
160
+ return DecorationSet.create(doc, mergeRanges(ranges).filter((r) => r.from < r.to && r.to <= max).map((r) => Decoration.inline(r.from, r.to, { class: "minuta-anno minuta-anno--manual" }, { userEdit: true })));
161
+ }
162
+ function ensureUserEditsPlugin(editor) {
163
+ if (editorsWithUserEdits.has(editor))
164
+ return;
165
+ editorsWithUserEdits.add(editor);
166
+ editor.registerPlugin(new Plugin({
167
+ key: userEditsKey,
168
+ state: {
169
+ init: () => DecorationSet.empty,
170
+ apply(tr, old) {
171
+ const meta = tr.getMeta(userEditsKey);
172
+ if (meta)
173
+ return userEditDecorations(tr.doc, meta.set);
174
+ if (!tr.docChanged)
175
+ return old;
176
+ if (tr.getMeta(PROGRAMMATIC_META))
177
+ return old.map(tr.mapping, tr.doc);
178
+ const beforeSize = tr.before.content.size;
179
+ let fullReplace = false;
180
+ const inserted = [];
181
+ tr.mapping.maps.forEach((stepMap, index) => {
182
+ stepMap.forEach((fromA, toA, fromB, toB) => {
183
+ if (fromA <= 1 && toA >= beforeSize - 1)
184
+ fullReplace = true;
185
+ if (toB > fromB) {
186
+ let from = fromB;
187
+ let to = toB;
188
+ for (let j = index + 1; j < tr.mapping.maps.length; j++) {
189
+ from = tr.mapping.maps[j].map(from, 1);
190
+ to = tr.mapping.maps[j].map(to, -1);
191
+ }
192
+ if (to > from)
193
+ inserted.push({ from, to });
194
+ }
195
+ });
196
+ });
197
+ if (fullReplace)
198
+ return DecorationSet.empty;
199
+ const carried = old.map(tr.mapping, tr.doc).find().map((deco) => ({ from: deco.from, to: deco.to }));
200
+ return userEditDecorations(tr.doc, [...carried, ...inserted]);
201
+ }
202
+ },
203
+ props: {
204
+ decorations(state) {
205
+ return userEditsKey.getState(state);
206
+ }
207
+ }
208
+ }));
209
+ }
210
+ export function trackUserEdits(editor) {
211
+ ensureUserEditsPlugin(editor);
212
+ }
213
+ export function getUserEditRanges(editor) {
214
+ if (editor.isDestroyed)
215
+ return [];
216
+ const set = userEditsKey.getState(editor.state);
217
+ if (!set)
218
+ return [];
219
+ return mergeRanges(set.find().map((deco) => ({ from: deco.from, to: deco.to })));
220
+ }
221
+ export function setUserEditRanges(editor, ranges) {
222
+ if (editor.isDestroyed)
223
+ return;
224
+ ensureUserEditsPlugin(editor);
225
+ editor.view.dispatch(editor.state.tr.setMeta(userEditsKey, { set: ranges }));
226
+ }
227
+ export function scrollToAnnotation(editor, from, scrollContainer) {
228
+ try {
229
+ const coords = editor.view.coordsAtPos(from);
230
+ if (scrollContainer) {
231
+ const rect = scrollContainer.getBoundingClientRect();
232
+ scrollContainer.scrollTo({
233
+ top: scrollContainer.scrollTop + (coords.top - rect.top) - rect.height * 0.3,
234
+ behavior: "smooth"
235
+ });
236
+ } else {
237
+ const dom = editor.view.domAtPos(from);
238
+ const el = dom.node instanceof HTMLElement ? dom.node : dom.node.parentElement;
239
+ el?.scrollIntoView({ behavior: "smooth", block: "center" });
240
+ }
241
+ } catch {
242
+ }
243
+ }
@@ -0,0 +1,37 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ import type { MinutaAnalysis } from './useMinutaApi.js';
3
+ /**
4
+ * Aplicador das edições propostas pelo assistente de chat da minuta.
5
+ * O agente emite blocos minuta-edit com operações ancoradas em recortes
6
+ * verbatim do texto; aqui elas viram transações do editor (undoáveis).
7
+ */
8
+ export interface MinutaChatOp {
9
+ op: 'substituir' | 'inserir_apos' | 'remover';
10
+ trecho: string;
11
+ novo?: string;
12
+ }
13
+ export interface MinutaChatAnnotation {
14
+ tipo: 'tese' | 'jurisprudencia' | 'citacao' | 'fato';
15
+ trecho?: string;
16
+ onde?: string;
17
+ titulo?: string;
18
+ fundamento?: string;
19
+ referencia?: string;
20
+ tese?: string;
21
+ como?: string;
22
+ dado?: string;
23
+ documento?: string;
24
+ pagina?: number;
25
+ }
26
+ export interface MinutaChatEdit {
27
+ descricao?: string;
28
+ ops?: MinutaChatOp[];
29
+ anotacoes?: MinutaChatAnnotation[];
30
+ }
31
+ export interface ApplyChatEditResult {
32
+ applied: number;
33
+ failures: string[];
34
+ }
35
+ export declare function applyChatEditOps(editor: Editor, ops: MinutaChatOp[]): ApplyChatEditResult;
36
+ /** mescla as anotações propostas pelo agente na análise existente */
37
+ export declare function mergeChatAnnotations(analysis: MinutaAnalysis | null | undefined, anotacoes: MinutaChatAnnotation[]): MinutaAnalysis;
@@ -0,0 +1,61 @@
1
+ import { findTextRange, PROGRAMMATIC_META } from "./annotationAnchors.js";
2
+ function paragraphsFor(text) {
3
+ return text.split(/\n{2,}/).map((part) => part.trim()).filter(Boolean).map((part) => ({ type: "paragraph", content: [{ type: "text", text: part }] }));
4
+ }
5
+ export function applyChatEditOps(editor, ops) {
6
+ let applied = 0;
7
+ const failures = [];
8
+ for (const op of ops) {
9
+ const trecho = op.trecho?.trim();
10
+ if (!trecho) {
11
+ failures.push("Opera\xE7\xE3o sem trecho de refer\xEAncia");
12
+ continue;
13
+ }
14
+ const range = findTextRange(editor, trecho);
15
+ if (!range) {
16
+ failures.push(`Trecho n\xE3o encontrado: \u201C${trecho.slice(0, 60)}${trecho.length > 60 ? "\u2026" : ""}\u201D`);
17
+ continue;
18
+ }
19
+ try {
20
+ if (op.op === "substituir") {
21
+ editor.chain().focus(void 0, { scrollIntoView: false }).setMeta(PROGRAMMATIC_META, true).insertContentAt(range, op.novo ?? "").run();
22
+ } else if (op.op === "remover") {
23
+ editor.chain().focus(void 0, { scrollIntoView: false }).setMeta(PROGRAMMATIC_META, true).deleteRange(range).run();
24
+ } else if (op.op === "inserir_apos") {
25
+ const $to = editor.state.doc.resolve(Math.min(range.to, editor.state.doc.content.size));
26
+ const after = $to.after(Math.max(1, $to.depth));
27
+ editor.chain().focus(void 0, { scrollIntoView: false }).setMeta(PROGRAMMATIC_META, true).insertContentAt(after, paragraphsFor(op.novo ?? "")).run();
28
+ } else {
29
+ failures.push(`Opera\xE7\xE3o desconhecida: ${String(op.op)}`);
30
+ continue;
31
+ }
32
+ applied++;
33
+ } catch (error) {
34
+ console.error("minuta chat edit: falha ao aplicar op", op, error);
35
+ failures.push(`Falha ao aplicar em \u201C${trecho.slice(0, 40)}\u2026\u201D`);
36
+ }
37
+ }
38
+ return { applied, failures };
39
+ }
40
+ export function mergeChatAnnotations(analysis, anotacoes) {
41
+ const merged = {
42
+ ...analysis ?? {},
43
+ teses: [...analysis?.teses ?? []],
44
+ jurisprudencias: [...analysis?.jurisprudencias ?? []],
45
+ citacoes: [...analysis?.citacoes ?? []],
46
+ documentos: [...analysis?.documentos ?? []],
47
+ fatos: [...analysis?.fatos ?? []]
48
+ };
49
+ for (const anno of anotacoes) {
50
+ if (anno.tipo === "tese" && anno.titulo) {
51
+ merged.teses.push({ titulo: anno.titulo, fundamento: anno.fundamento ?? "", onde: anno.onde ?? "", trecho: anno.trecho });
52
+ } else if (anno.tipo === "jurisprudencia" && anno.referencia) {
53
+ merged.jurisprudencias.push({ referencia: anno.referencia, tese: anno.tese ?? "", onde: anno.onde ?? "", trecho: anno.trecho });
54
+ } else if (anno.tipo === "citacao" && anno.referencia) {
55
+ merged.citacoes.push({ referencia: anno.referencia, como: anno.como ?? "", onde: anno.onde ?? "", trecho: anno.trecho });
56
+ } else if (anno.tipo === "fato" && anno.dado) {
57
+ merged.fatos.push({ dado: anno.dado, trecho: anno.trecho, documento: anno.documento, pagina: anno.pagina, onde: anno.onde });
58
+ }
59
+ }
60
+ return merged;
61
+ }
@@ -0,0 +1,24 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ import type { PocPageBreak } from './paginationPlugin.js';
3
+ export interface PocDocxChrome {
4
+ headerText?: string;
5
+ footerText?: string;
6
+ headerLogoUrl?: string | null;
7
+ headerPosition?: 'left' | 'center' | 'right';
8
+ /** cabeçalho em todas as páginas ou só na primeira */
9
+ headerScope?: 'all' | 'first';
10
+ /** conteúdo custom do rodapé (o nº de página aparece sempre) */
11
+ footerScope?: 'all' | 'first';
12
+ /** numeração "Página N de M" no rodapé (default: ligada) */
13
+ showPageNumber?: boolean;
14
+ }
15
+ export interface PocDocxExportOptions {
16
+ /** quebras calculadas pelo engine de paginação do preview → paginação 1:1 */
17
+ breaks?: PocPageBreak[];
18
+ }
19
+ export declare function pocExportDocx(editor: Editor, margins: {
20
+ top: number;
21
+ right: number;
22
+ bottom: number;
23
+ left: number;
24
+ }, chrome?: PocDocxChrome, options?: PocDocxExportOptions): Promise<Blob>;