npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meistrari/minuta-editor

v1.4.0

Published

Editor de minuta do defesa.ai sem framework: folha A4 paginada, edição rica, placeholders de variável e export DOCX fiel ao preview — monta em qualquer app (React, Vue, vanilla)

Readme

@meistrari/minuta-editor

O editor de minuta do defesa.ai sem framework: a folha A4 paginada de verdade, edição rica, cabeçalho/rodapé, placeholders de variável e export DOCX que reflui no Word como na folha (só a quebra manual do template é forçada; título leva keep-with-next). Monta em qualquer app — React, Vue, Svelte, vanilla — com uma chamada:

import { createMinutaEditor } from '@meistrari/minuta-editor'
import '@meistrari/minuta-editor/style.css'

const editor = createMinutaEditor(document.getElementById('minuta')!, {
    html: documento.html,
    onChange: () => salvar(editor.getHTML()),
})

Zero I/O. Nada de fetch, autosave, histórico ou auth aqui — isso é do app que monta. O que entra é HTML; o que sai é HTML e um Blob de .docx.

É o mesmo motor que o @meistrari/minuta-nuxt usa por dentro (o módulo Nuxt é uma casca fina sobre este pacote), então o que se edita aqui sai idêntico ao que o defesa.ai produz.

API

const editor = createMinutaEditor(el, {
    html,                                   // documento inicial
    editable: true,
    margins: { top: 95, right: 113, bottom: 95, left: 113 }, // px @96dpi (2,5cm / 3cm)
    header: { logoUrl: null, position: 'left' },
    chrome: { header: '', footer: '', showPageNumber: true },
    variables: [...],                       // catálogo → liga o modo template
    uploadImage: async file => ({ src, vaultSrc }) | null,
    imageFallback: 'dataUri',               // 'none' = upload falhou → não insere, só onImageUploadError
    onImageUploadError: (error, file) => {},
    toolbar: true,                          // false = o host monta a barra dele
    onReady: tiptap => {},
    onChange: () => {},
    onEditChrome: part => {},               // clique no cabeçalho/rodapé
    onVariableClick: ({ variableId, parentBlockId, text, kind }) => {}, // kind: 'text' | 'image' | 'missingImage'
    onBlockClick: blockId => {},            // read-only (ou com blockClickWhileEditing)
})

editor.getHTML()          // → string
editor.exportDocx()       // → Promise<Blob>  (carrega a lib `docx` sob demanda)
editor.usedVariables()    // → string[]
editor.setEditable(bool)
editor.setChrome({ header, footer, headerScope, footerScope, showPageNumber })
editor.setHeader({ logoUrl, position })
editor.setVariables(catalogo)
editor.setBlockClickWhileEditing(bool)

// variáveis do case marcadas no documento (<span|img data-variable-id>, aviso de imagem faltando)
editor.scrollToVariable(id)              // → boolean (rola e dá um flash)
editor.setVariableValue(id, texto)       // → nº de ocorrências (o aviso de "faltando" vira chip)
editor.setVariableImage(id, src, vaultSrc?) // → nº de ocorrências (o aviso de imagem vira <img>; vaultSrc → data-vault-src)
editor.setImageFallback('dataUri' | 'none')
editor.variablesInDocument()             // → string[]
editor.refreshVaultImages(vaultUrl => Promise<string>) // renova o src das imagens do vault (data-vault-src ou src="vault://"); sem undo/onChange

editor.schedulePaginate() // repagina (após mudar algo que afeta altura por fora)
editor.totalPages()
editor.destroy()

editor.tiptap             // a instância do Tiptap — escotilha de fuga
editor.root / .container / .sheet   // os elementos, para ancorar overlays

Decisões que vale conhecer

exportDocx devolve Blob, não baixa. Quem decide o nome, se manda pro backend ou se abre em outra aba é o host. Um pacote que dispara download sozinho toma uma decisão que não é dele.

toolbar: false + editor.tiptap. Um app com design system próprio (antd, Material…) monta a barra dele e fala com o Tiptap direto — editor.tiptap.chain().focus().toggleBold().run(), isActive('bold'). É a API do Tiptap, documentada, sem um wrapper de comandos que sempre falta um.

CSS em arquivo, com namespace. Tudo vive sob .poc-editor-root, inclusive as utilities atômicas que o defesa.ai tira do UnoCSS. Importe @meistrari/minuta-editor/style.css uma vez.

A lib docx fica fora do entry principal. É o maior pedaço do bundle (~1 MB) e só serve no clique de exportar: exportDocx() faz import() dinâmico, e quem precisa da função crua importa de @meistrari/minuta-editor/docx.

Modo template (placeholders de variável)

Passe o catálogo e o editor vira editor de template: / no texto (ou o botão { } da barra) abre o seletor, busca pelo rótulo e insere um placeholder.

createMinutaEditor(el, {
    html,
    variables: [
        { key: 'PROCESSO', label: 'Nº do Processo', description: 'De info_processos.processo', group: 'Processo' },
        { key: 'NOME_REU', label: 'Nome do Réu', group: 'Réu' },
    ],
})
editor.usedVariables() // ['PROCESSO', 'NOME_REU']

O placeholder é um nó atômico do Tiptap (<span data-minuta-var="PROCESSO">), não texto: seleciona inteiro, apaga inteiro, formata inteiro. Negritar metade de um #PROCESSO textual produziria <b>#PRO</b>CESSO e a substituição passaria reto; com nó, isso não é representável.

Para resolver, resolveTemplateVariables(html, { PROCESSO: '…' }) (exportado daqui) — o que não tem valor continua placeholder vivo no documento.

React

import { useEffect, useRef } from 'react'
import { createMinutaEditor, type MinutaEditor } from '@meistrari/minuta-editor'
import '@meistrari/minuta-editor/style.css'

export function MinutaEditor({ html, onChange }: { html: string, onChange: (html: string) => void }) {
    const host = useRef<HTMLDivElement>(null)
    const editor = useRef<MinutaEditor | null>(null)

    useEffect(() => {
        editor.current = createMinutaEditor(host.current!, {
            html,
            onChange: () => onChange(editor.current!.getHTML()),
        })
        return () => editor.current?.destroy()
        // o HTML inicial é lido uma vez: o editor é a fonte da verdade enquanto vive
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    return <div ref={host} style={{ height: '100vh' }} />
}

Vue / Nuxt

Use @meistrari/minuta-nuxt — ele já embrulha este pacote em componentes (<MinutaEmbed>, <MinutaTemplateEditor>) e traz o proxy de API do defesa.ai.

Desenvolvimento

bun install
bun run build       # unbuild → dist/
bun run typecheck

Publicado no npm pelo workflow Publish @meistrari/minuta-editor. Como o minuta-nuxt depende deste pacote pelo npm, publique este primeiro quando uma mudança atravessar os dois.