@meistrari/minuta-react 1.2.0 → 1.4.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 CHANGED
@@ -35,6 +35,8 @@ um `Blob` de `.docx`. Buscar o documento, persistir a edição e baixar o arquiv
35
35
  | `chrome` | — | `{ header, footer, headerScope, footerScope, showPageNumber }` |
36
36
  | `variables` | — | catálogo `{ key, label, description?, group? }[]` → liga o modo template |
37
37
  | `uploadImage` | — | `(file) => Promise<{ src, vaultSrc } \| null>` |
38
+ | `imageFallback` | `'dataUri'` | `'none'` = upload falhou → não insere nada, só `onImageUploadError` |
39
+ | `onImageUploadError` | — | `(error, file) => void` |
38
40
  | `toolbar` | `true` | `false` = você monta a sua sobre `ref.current.tiptap` |
39
41
  | `blockClickWhileEditing` | `false` | dispara `onBlockClick` também em edição (padrão: só em leitura) |
40
42
  | `onChange` | — | `(html) => void`, a cada edição |
@@ -71,6 +73,28 @@ const t = editor.current?.tiptap
71
73
  <MinutaEditor ref={editor} html={html} toolbar={false} onTransaction={rerender} />
72
74
  ```
73
75
 
76
+ ### Numerar parágrafo
77
+
78
+ O número dos parágrafos da peça ("12. A Ré…") é um nó próprio, não texto:
79
+ continua a sequência do template e sai como numeração nativa no Word. Com a
80
+ toolbar do pacote há o botão **1.**; numa toolbar própria:
81
+
82
+ ```tsx
83
+ import { hasLeadingTopicNumber, toggleTopicNumber } from '@meistrari/minuta-react'
84
+
85
+ // `editor.current` é null até o mount: proteja a leitura no render
86
+ const tiptap = editor.current?.tiptap
87
+ <Button type={tiptap && hasLeadingTopicNumber(tiptap) ? 'primary' : 'default'}
88
+ onClick={() => tiptap && toggleTopicNumber(tiptap)}>1.</Button>
89
+ ```
90
+
91
+ (`toggleTopicNumber` equivale a `tiptap.chain().focus().toggleTopicNumber().run()`;
92
+ há também `setTopicNumber()` e `unsetTopicNumber()`.) Enter no fim de um
93
+ parágrafo numerado já cria o seguinte numerado, sem botão. **Não** use a
94
+ "lista numerada" (`toggleOrderedList`) para isso: ela cria uma lista com recuo
95
+ próprio, fora da sequência da peça.
96
+
97
+
74
98
  Os tipos de `chain().toggleBold()` viajam com o pacote — não precisa importar
75
99
  `@tiptap/*` no seu código.
76
100
 
@@ -81,13 +105,21 @@ de conta no defesa.ai:
81
105
 
82
106
  ```
83
107
  GET /data/case/:id/html → { html, chrome, margins, source, editionId, variablesMarked, markedVariables }
84
- POST /data/case/:id/editions { name, html } → cria a edição → { id, updatedAt, … }
85
- PATCH /data/case/:id/editions/:id → salva → { updatedAt, updatedBy, … }
108
+ POST /data/case/:id/editions { name, html } → cria a edição → { id, updatedAt, contentHash, … }
109
+ PATCH /data/case/:id/editions/:id → salva → { updatedAt, updatedBy, contentHash, … }
86
110
  { html, baseUpdatedAt } 400 sem o token; 409 se outro salvou antes
111
+ GET /data/case/:id/editions → [{ id, updatedAt, updatedBy, contentHash, … }] sem html
87
112
  POST /data/case/:id/editions/:id/versions → marco no histórico (o PATCH NÃO cria versão)
113
+ { comment?, baseUpdatedAt? } com baseUpdatedAt: 409 se a revisão mudou; já versionada → 200 reused
88
114
  POST /data/case/:id/docx → .docx do estado atual (a edição, se existir)
89
115
  ```
90
116
 
117
+ `contentHash` é o sha256 do `html` exatamente como foi gravado (o servidor não
118
+ normaliza): num save que deu timeout, compare com o hash do que você enviou
119
+ pela listagem, sem baixar o documento. `baseUpdatedAt` no `POST …/versions`
120
+ amarra o checkpoint à revisão que você viu: a versão nunca sai com o seu nome
121
+ e o texto que outra pessoa salvou depois, e repetir a chamada não duplica.
122
+
91
123
  ```tsx
92
124
  // abrir
93
125
  const doc = await api.get(`/case/${caseId}/html`)
@@ -175,20 +207,51 @@ Sobre o fluxo de "enviar variáveis de volta": com uma edição viva o documento
175
207
  modelo do defesa.ai), ou você mantém os dois vivos com `setVariableValue` +
176
208
  salva a edição. Decisão de UX do seu app; as duas funcionam.
177
209
 
210
+ ### Voltar ao checklist sem perder a edição
211
+
212
+ Esqueceu uma tese ou variável no meio da edição? Mude o caso (`PATCH
213
+ /data/case/:id`), busque o documento que o checklist gera agora e aplique sobre
214
+ o texto editado — só o que mudou entra, e trecho que o advogado editou vira
215
+ pergunta em vez de ser sobrescrito:
216
+
217
+ ```tsx
218
+ const { html } = await api.get(`/data/case/${caseId}/html?source=blocks`)
219
+ const result = editor.current!.applyChecklist(html)
220
+ for (const c of result.conflicts) // 'replace' | 'remove' — a UI é sua
221
+ editor.current!.resolveChecklistConflict(c.blockId, 'keep' /* ou 'apply' */)
222
+ // DEPOIS das decisões: bloco inserido ou aceito pode trazer imagem vault://
223
+ await editor.current!.refreshVaultImages(resolver)
224
+ ```
225
+
226
+ Para isso funcionar a edição precisa nascer marcada: depois de montar o editor
227
+ com o HTML do checklist, antes do primeiro save, chame
228
+ `editor.current!.stampGenerated()`.
229
+
178
230
  ### Imagens inseridas pelo advogado
179
231
 
180
232
  `uploadImage` devolve `{ src, vaultSrc }`: o editor grava a tag com o `src`
181
- (presignado, expira) e `data-vault-src` (a `vault://`, durável). Ao reabrir uma
182
- edição salva, renove os links antes de mostrar:
233
+ (presignado, expira) e `data-vault-src` (a `vault://`, durável). Persista o
234
+ `getHTML()` como está: o `POST /data/case/:id/docx` usa o `data-vault-src` e
235
+ resolve `vault://` sozinho. Ao reabrir uma edição salva, renove os links antes
236
+ de mostrar:
183
237
 
184
238
  ```tsx
185
239
  // resolver: POST /data/vault/download-url { vaultUrl } → { downloadUrl } (pelo seu backend)
186
240
  await editor.current!.refreshVaultImages(vaultUrl => api.post('/vault/download-url', { vaultUrl }).then(r => r.downloadUrl))
187
241
  ```
188
242
 
189
- Não entra no undo nem dispara `onChange`. O `exportDocx()` do browser baixa
190
- pelo `src`, então renove antes de exportar também; o `POST /data/case/:id/docx`
191
- resolve `vault://` sozinho.
243
+ Renova tanto `data-vault-src` quanto imagens salvas só com `src="vault://…"`
244
+ (que ganham o `data-vault-src`), mantendo a referência durável. Não entra no
245
+ undo nem dispara `onChange`: chame `getHTML()` depois se guarda uma cópia. O
246
+ `exportDocx()` do browser baixa pelo `src`, então renove antes de exportar.
247
+
248
+ Se o upload falhar (ou `uploadImage` devolver `null`), o padrão é embutir a
249
+ imagem em base64. Um app cujo documento canônico depende de `vault://` não
250
+ quer isso: `imageFallback="none"` não insere nada e `onImageUploadError` recebe
251
+ o erro e o arquivo para você mostrar e deixar tentar de novo.
252
+
253
+ `setVariableImage(id, src, vaultSrc)` grava o `data-vault-src` junto — use a
254
+ forma de três argumentos para a imagem sobreviver à expiração do `src`.
192
255
 
193
256
  ## Next.js (App Router)
194
257
 
package/dist/index.d.mts CHANGED
@@ -18,7 +18,7 @@
18
18
  import * as react from 'react';
19
19
  import { CSSProperties } from 'react';
20
20
  import { MinutaMargins, MinutaHeader, MinutaChromeText, MinutaVariableCatalog, MinutaEditorOptions, MinutaVariableRef, MinutaEditor as MinutaEditor$1 } from '@meistrari/minuta-editor';
21
- export { MinutaChromeText, MinutaEditor as MinutaEditorInstance, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, extractTemplateVariables, resolveTemplateVariables } from '@meistrari/minuta-editor';
21
+ export { MinutaChromeText, MinutaEditor as MinutaEditorInstance, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, extractTemplateVariables, hasLeadingTopicNumber, resolveTemplateVariables, toggleTopicNumber } from '@meistrari/minuta-editor';
22
22
 
23
23
  interface MinutaEditorProps {
24
24
  /**
@@ -32,6 +32,9 @@ interface MinutaEditorProps {
32
32
  chrome?: Partial<MinutaChromeText>;
33
33
  variables?: MinutaVariableCatalog;
34
34
  uploadImage?: MinutaEditorOptions['uploadImage'];
35
+ /** `'dataUri'` (default) embute em base64 quando o upload falha; `'none'` não insere e avisa por `onImageUploadError` */
36
+ imageFallback?: MinutaEditorOptions['imageFallback'];
37
+ onImageUploadError?: MinutaEditorOptions['onImageUploadError'];
35
38
  /** barra de formatação do pacote (default: true). `false` = monte a sua sobre `ref.tiptap` */
36
39
  toolbar?: boolean;
37
40
  showBlockOrigin?: boolean;
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  import * as react from 'react';
19
19
  import { CSSProperties } from 'react';
20
20
  import { MinutaMargins, MinutaHeader, MinutaChromeText, MinutaVariableCatalog, MinutaEditorOptions, MinutaVariableRef, MinutaEditor as MinutaEditor$1 } from '@meistrari/minuta-editor';
21
- export { MinutaChromeText, MinutaEditor as MinutaEditorInstance, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, extractTemplateVariables, resolveTemplateVariables } from '@meistrari/minuta-editor';
21
+ export { MinutaChromeText, MinutaEditor as MinutaEditorInstance, MinutaEditorOptions, MinutaHeader, MinutaMargins, MinutaVariableCatalog, MinutaVariableDef, MinutaVariableRef, extractTemplateVariables, hasLeadingTopicNumber, resolveTemplateVariables, toggleTopicNumber } from '@meistrari/minuta-editor';
22
22
 
23
23
  interface MinutaEditorProps {
24
24
  /**
@@ -32,6 +32,9 @@ interface MinutaEditorProps {
32
32
  chrome?: Partial<MinutaChromeText>;
33
33
  variables?: MinutaVariableCatalog;
34
34
  uploadImage?: MinutaEditorOptions['uploadImage'];
35
+ /** `'dataUri'` (default) embute em base64 quando o upload falha; `'none'` não insere e avisa por `onImageUploadError` */
36
+ imageFallback?: MinutaEditorOptions['imageFallback'];
37
+ onImageUploadError?: MinutaEditorOptions['onImageUploadError'];
35
38
  /** barra de formatação do pacote (default: true). `false` = monte a sua sobre `ref.tiptap` */
36
39
  toolbar?: boolean;
37
40
  showBlockOrigin?: boolean;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { forwardRef, useRef, useLayoutEffect, useEffect, useImperativeHandle, createElement } from 'react';
3
3
  import { createMinutaEditor } from '@meistrari/minuta-editor';
4
- export { extractTemplateVariables, resolveTemplateVariables } from '@meistrari/minuta-editor';
4
+ export { extractTemplateVariables, hasLeadingTopicNumber, resolveTemplateVariables, toggleTopicNumber } from '@meistrari/minuta-editor';
5
5
 
6
6
  const MinutaEditor = forwardRef((props, ref) => {
7
7
  const host = useRef(null);
@@ -21,6 +21,8 @@ const MinutaEditor = forwardRef((props, ref) => {
21
21
  variables: props.variables,
22
22
  // lido na hora do upload, não no mount: o host pode trocar a função
23
23
  uploadImage: (file) => callbacks.current.uploadImage ? callbacks.current.uploadImage(file) : Promise.resolve(null),
24
+ imageFallback: props.imageFallback,
25
+ onImageUploadError: (error, file) => callbacks.current.onImageUploadError?.(error, file),
24
26
  toolbar: props.toolbar,
25
27
  showBlockOrigin: props.showBlockOrigin,
26
28
  blockClickWhileEditing: props.blockClickWhileEditing,
@@ -64,6 +66,9 @@ const MinutaEditor = forwardRef((props, ref) => {
64
66
  useEffect(() => {
65
67
  instance.current?.setBlockClickWhileEditing(props.blockClickWhileEditing === true);
66
68
  }, [props.blockClickWhileEditing]);
69
+ useEffect(() => {
70
+ instance.current?.setImageFallback(props.imageFallback ?? "dataUri");
71
+ }, [props.imageFallback]);
67
72
  useImperativeHandle(ref, () => instance.current);
68
73
  return createElement("div", {
69
74
  ref: host,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/minuta-react",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Componente React do editor de minuta do defesa.ai — casca fina sobre @meistrari/minuta-editor",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "test:pack": "node scripts/verify-pack.mjs"
29
29
  },
30
30
  "dependencies": {
31
- "@meistrari/minuta-editor": "^1.0.0",
31
+ "@meistrari/minuta-editor": "^1.7.0",
32
32
  "@tiptap/core": "2.11.9",
33
33
  "@tiptap/extension-blockquote": "2.11.9",
34
34
  "@tiptap/extension-bold": "2.11.9",