@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 +141 -0
- package/dist/chunks/docxExport.mjs +1282 -0
- package/dist/docx.d.mts +5 -0
- package/dist/docx.d.ts +5 -0
- package/dist/docx.mjs +5 -0
- package/dist/index.d.mts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.mjs +1887 -0
- package/dist/shared/minuta-editor.b2512170.mjs +711 -0
- package/dist/shared/minuta-editor.e223a421.d.mts +166 -0
- package/dist/shared/minuta-editor.e223a421.d.ts +166 -0
- package/dist/style.css +1 -0
- package/package.json +97 -0
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.
|