@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 ADDED
@@ -0,0 +1,141 @@
1
+ # @meistrari/minuta-editor
2
+
3
+ O editor de minuta do **defesa.ai** sem framework: a folha A4 paginada de
4
+ verdade, edição rica, cabeçalho/rodapé, placeholders de variável e export
5
+ DOCX com as mesmas quebras de página do preview. Monta em qualquer app —
6
+ React, Vue, Svelte, vanilla — com uma chamada:
7
+
8
+ ```ts
9
+ import { createMinutaEditor } from '@meistrari/minuta-editor'
10
+ import '@meistrari/minuta-editor/style.css'
11
+
12
+ const editor = createMinutaEditor(document.getElementById('minuta')!, {
13
+ html: documento.html,
14
+ onChange: () => salvar(editor.getHTML()),
15
+ })
16
+ ```
17
+
18
+ **Zero I/O.** Nada de fetch, autosave, histórico ou auth aqui — isso é do
19
+ app que monta. O que entra é HTML; o que sai é HTML e um `Blob` de `.docx`.
20
+
21
+ É o mesmo motor que o `@meistrari/minuta-nuxt` usa por dentro (o módulo Nuxt
22
+ é uma casca fina sobre este pacote), então o que se edita aqui sai idêntico
23
+ ao que o defesa.ai produz.
24
+
25
+ ## API
26
+
27
+ ```ts
28
+ const editor = createMinutaEditor(el, {
29
+ html, // documento inicial
30
+ editable: true,
31
+ margins: { top: 95, right: 113, bottom: 95, left: 113 }, // px @96dpi (2,5cm / 3cm)
32
+ header: { logoUrl: null, position: 'left' },
33
+ chrome: { header: '', footer: '', showPageNumber: true },
34
+ variables: [...], // catálogo → liga o modo template
35
+ uploadImage: async file => ({ src, vaultSrc }) | null,
36
+ toolbar: true, // false = o host monta a barra dele
37
+ onReady: tiptap => {},
38
+ onChange: () => {},
39
+ onEditChrome: part => {}, // clique no cabeçalho/rodapé
40
+ })
41
+
42
+ editor.getHTML() // → string
43
+ editor.exportDocx() // → Promise<Blob> (carrega a lib `docx` sob demanda)
44
+ editor.usedVariables() // → string[]
45
+ editor.setEditable(bool)
46
+ editor.setChrome({ header, footer, headerScope, footerScope, showPageNumber })
47
+ editor.setHeader({ logoUrl, position })
48
+ editor.setVariables(catalogo)
49
+ editor.schedulePaginate() // repagina (após mudar algo que afeta altura por fora)
50
+ editor.totalPages()
51
+ editor.destroy()
52
+
53
+ editor.tiptap // a instância do Tiptap — escotilha de fuga
54
+ editor.root / .container / .sheet // os elementos, para ancorar overlays
55
+ ```
56
+
57
+ ### Decisões que vale conhecer
58
+
59
+ **`exportDocx` devolve `Blob`, não baixa.** Quem decide o nome, se manda pro
60
+ backend ou se abre em outra aba é o host. Um pacote que dispara download
61
+ sozinho toma uma decisão que não é dele.
62
+
63
+ **`toolbar: false` + `editor.tiptap`.** Um app com design system próprio
64
+ (antd, Material…) monta a barra dele e fala com o Tiptap direto —
65
+ `editor.tiptap.chain().focus().toggleBold().run()`, `isActive('bold')`. É a API
66
+ do Tiptap, documentada, sem um wrapper de comandos que sempre falta um.
67
+
68
+ **CSS em arquivo, com namespace.** Tudo vive sob `.poc-editor-root`, inclusive
69
+ as utilities atômicas que o defesa.ai tira do UnoCSS. Importe
70
+ `@meistrari/minuta-editor/style.css` uma vez.
71
+
72
+ **A lib `docx` fica fora do entry principal.** É o maior pedaço do bundle
73
+ (~1 MB) e só serve no clique de exportar: `exportDocx()` faz `import()`
74
+ dinâmico, e quem precisa da função crua importa de
75
+ `@meistrari/minuta-editor/docx`.
76
+
77
+ ## Modo template (placeholders de variável)
78
+
79
+ Passe o catálogo e o editor vira editor de template: `/` no texto (ou o botão
80
+ `{ }` da barra) abre o seletor, busca pelo rótulo e insere um placeholder.
81
+
82
+ ```ts
83
+ createMinutaEditor(el, {
84
+ html,
85
+ variables: [
86
+ { key: 'PROCESSO', label: 'Nº do Processo', description: 'De info_processos.processo', group: 'Processo' },
87
+ { key: 'NOME_REU', label: 'Nome do Réu', group: 'Réu' },
88
+ ],
89
+ })
90
+ editor.usedVariables() // ['PROCESSO', 'NOME_REU']
91
+ ```
92
+
93
+ O placeholder é um **nó atômico** do Tiptap (`<span data-minuta-var="PROCESSO">`),
94
+ não texto: seleciona inteiro, apaga inteiro, formata inteiro. Negritar metade de
95
+ um `#PROCESSO` textual produziria `<b>#PRO</b>CESSO` e a substituição passaria
96
+ reto; com nó, isso não é representável.
97
+
98
+ Para resolver, `resolveTemplateVariables(html, { PROCESSO: '…' })` (exportado
99
+ daqui) — o que não tem valor continua placeholder vivo no documento.
100
+
101
+ ## React
102
+
103
+ ```tsx
104
+ import { useEffect, useRef } from 'react'
105
+ import { createMinutaEditor, type MinutaEditor } from '@meistrari/minuta-editor'
106
+ import '@meistrari/minuta-editor/style.css'
107
+
108
+ export function MinutaEditor({ html, onChange }: { html: string, onChange: (html: string) => void }) {
109
+ const host = useRef<HTMLDivElement>(null)
110
+ const editor = useRef<MinutaEditor | null>(null)
111
+
112
+ useEffect(() => {
113
+ editor.current = createMinutaEditor(host.current!, {
114
+ html,
115
+ onChange: () => onChange(editor.current!.getHTML()),
116
+ })
117
+ return () => editor.current?.destroy()
118
+ // o HTML inicial é lido uma vez: o editor é a fonte da verdade enquanto vive
119
+ // eslint-disable-next-line react-hooks/exhaustive-deps
120
+ }, [])
121
+
122
+ return <div ref={host} style={{ height: '100vh' }} />
123
+ }
124
+ ```
125
+
126
+ ## Vue / Nuxt
127
+
128
+ Use `@meistrari/minuta-nuxt` — ele já embrulha este pacote em componentes
129
+ (`<MinutaEmbed>`, `<MinutaTemplateEditor>`) e traz o proxy de API do defesa.ai.
130
+
131
+ ## Desenvolvimento
132
+
133
+ ```bash
134
+ bun install
135
+ bun run build # unbuild → dist/
136
+ bun run typecheck
137
+ ```
138
+
139
+ Publicado no npm pelo workflow `Publish @meistrari/minuta-editor`. Como o
140
+ `minuta-nuxt` depende deste pacote pelo npm, **publique este primeiro** quando
141
+ uma mudança atravessar os dois.