feat(documents): editor de texto rico (Tiptap) para templates de documento
Substitui o textarea de texto puro por um editor visual real, seguindo o
print de referência do usuário do editor "Contrato de Software" do
OrçaFácil legado: toolbar completa (negrito/itálico/sublinhado/tachado,
alinhamento, listas, tabela, imagem, quebra de página), variáveis inseridas
como chip inline via catálogo fechado (nunca {{texto livre}}), painel
"Variáveis usadas neste modelo" (com badge obrigatório ao vivo) e
"Histórico de versões".
Backend: editor-content-renderer.ts e merge-engine.ts portados/reescritos
a partir do renderEditorContent.js/variableRegistry.js reais do legado
(achados em .tmp/orcafacil-reference) — resolve a árvore Tiptap
diretamente em vez de regex sobre HTML cacheado. Novo catálogo de
variáveis + GET /documents/variable-catalog.
Fora de escopo desta leva (documentado em docs/progress.md): nó
`conditional`, repetição de linha de tabela, bloqueio por variável
obrigatória ausente, e auto-resolução de dados reais do cadastro na hora
de gerar o documento.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,18 @@ describe("documents — PDF generation + download", () => {
|
||||
.patch(`/api/v1/documents/templates/${templateId}/draft`)
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({
|
||||
editorContent: { type: "doc", plainText: "Revenda: {{revenda.nome}}" },
|
||||
editorContent: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
content: [
|
||||
{ type: "text", text: "Revenda: " },
|
||||
{ type: "mergeField", attrs: { key: "revenda.nome", label: "Nome da Revenda" } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
cssContent: "p { color: red; }",
|
||||
})
|
||||
.expect(200);
|
||||
@@ -146,7 +157,21 @@ describe("documents — PDF generation + download", () => {
|
||||
.post(`/api/v1/documents/templates/${templateId}/draft/preview`)
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({
|
||||
editorContent: { plainText: "Olá {{cliente.nome}}, plano {{plano}}." },
|
||||
editorContent: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
content: [
|
||||
{ type: "text", text: "Olá " },
|
||||
{ type: "mergeField", attrs: { key: "cliente.nome", label: "Nome do Cliente" } },
|
||||
{ type: "text", text: ", plano " },
|
||||
{ type: "mergeField", attrs: { key: "plano", label: "Plano" } },
|
||||
{ type: "text", text: "." },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
cssContent: "p { color: blue; }",
|
||||
})
|
||||
.expect(201);
|
||||
@@ -158,7 +183,7 @@ describe("documents — PDF generation + download", () => {
|
||||
const call = renderPdfMock.mock.calls[0]![0];
|
||||
expect(call.html).toContain("[cliente.nome]");
|
||||
expect(call.html).toContain("[plano]");
|
||||
expect(call.html).not.toContain("{{");
|
||||
expect(call.html).not.toContain("merge-field-chip");
|
||||
expect(call.css).toBe("p { color: blue; }");
|
||||
|
||||
// Never persisted: no S3 upload, and the real (published) generation
|
||||
|
||||
Reference in New Issue
Block a user