12k
All articles

Uma Primeira Análise do Wordgard, um Novo Editor de Texto

Wordgard é uma nova biblioteca de editor rich-text de Marijn Haverbeke, com transações de mudança única, correções, facets e seleção desenhada pela biblioteca.

OpenReplay Team
OpenReplay Team
Uma Primeira Análise do Wordgard, um Novo Editor de Texto

Wordgard é uma biblioteca JavaScript de Marijn Haverbeke, o autor do ProseMirror e do CodeMirror, para construir editores de texto rico cujos documentos obedecem a um schema; ela inclui um componente de UI de editor, mas não é um editor WYSIWYG ou HTML genérico e livre.

Manter uma integração com ProseMirror pode significar mapear posições através de uma lista de steps, ou escrever um comando “genérico” que precisa verificar content expressions a cada passo. O Wordgard é a resposta do mesmo autor a essas reclamações, construído a partir do zero em vez de enxertado sobre o ProseMirror.

Este artigo cobre o que a biblioteca muda: o modelo de mudanças, a remoção das restrições de conteúdo, o sistema de extensões baseado em facets e a seleção implementada na própria biblioteca, além de onde um primeiro release deste autor se posiciona ao lado de ProseMirror, TipTap e Lexical.

Pontos Principais

  • O Wordgard foi lançado inicialmente como 0.1.0 em 2 de julho de 2026 sob a licença MIT e é instalado a partir do npm como wordgard; o autor afirmou no lançamento que o projeto permanecerá em versões 0.x provavelmente por pelo menos um ano.
  • Uma transação do Wordgard carrega exatamente uma mudança, construída a partir de seções que mantêm um intervalo de tokens, o substituem, ou adicionam ou removem marks sobre ele, de modo que o intervalo afetado pode ser lido diretamente em vez de reconstruído a partir de uma lista de steps.
  • Os schemas do Wordgard podem restringir quais tipos de nó um pai pode conter, mas não a sua ordem; as corrections, funções observadoras que retornam specs de mudança corretivas, assumem invariantes como tabelas retangulares.
  • A configuração é uma árvore de extensões com precedência por valor e facets definíveis pelo usuário, copiada do CodeMirror 6.
  • O Wordgard trata a seleção por teclado e ponteiro na própria biblioteca e desenha o seu próprio cursor; a seleção por toque é deixada para o navegador.

O Que É o Wordgard?

O Wordgard é um sistema de editor de texto rico para conteúdo que se encaixa em um schema específico, não um componente WYSIWYG plug-and-play nem uma aplicação. De acordo com o System Guide, a superfície de edição pretende parecer WYSIWYG, mas o conteúdo e as ações de edição são nomeados pelo que significam (cabeçalhos, listas, ênfase) em vez de por como aparecem (família de fontes, indentação de parágrafo, negrito). A exportação principal da biblioteca é a classe de UI Wordgard. Abaixo dela estão os tipos para documentos, estado do editor e ações de edição, e a maioria deles funciona sem nenhum navegador à vista.

O anúncio do 0.1, datado de 2 de julho de 2026, informa a licença MIT, o nome do pacote npm wordgard, e que o código-fonte vive na instância Forgejo do autor. A página inicial do projeto confirma a licença e acrescenta que relatórios de bugs são bem-vindos, mas pull requests não são aceitos. A página inicial também lista documentos baseados em schema, extensões modulares, texto bidirecional, conteúdo estruturado como tabelas e listas aninhadas, e edição colaborativa como funcionalidades; trate esses itens como afirmações do próprio projeto.

Como Configurar um Editor Wordgard?

Um editor Wordgard mínimo é uma chamada a Wordgard.create com um documento, uma configuração e um elemento pai. Este é o exemplo de configuração do guia:

import {Wordgard, menuBar} from "wordgard/editor"
import {fullSchema} from "wordgard/schema"
import {history} from "wordgard/history"

let editor = Wordgard.create({
  doc: `<p>Starting content</p>`,
  config: [
    fullSchema(), // A predefined document schema
    history(),    // Enable the undo history
    menuBar()     // Show a menu
  ],
  parent: document.body
})

O array config é a árvore de extensões, e cada uma das três entradas é um pacote de extensões em vez de um objeto de schema, um plugin e um widget. fullSchema() traz todo o conjunto de elementos de schema de wordgard/schema, e sua própria documentação alerta que o conjunto pode incorporar mais elementos conforme a biblioteca ganha funcionalidades; os exemplos posteriores do guia usam basicSchema(), que agrupa um documento em blocos, parágrafos, cabeçalhos, quebras de linha e as marks de strong, emphasis e link. A string doc é parseada como HTML contra aquele schema. O pacote se divide em módulos como wordgard/doc, wordgard/state, wordgard/editor, wordgard/command, wordgard/history, wordgard/schema e wordgard/types, e o guia recomenda TypeScript por causa do quão fortemente as peças se interligam.

Como o Modelo de Mudanças do Wordgard Difere do ProseMirror?

No Wordgard uma transação carrega exatamente um objeto de mudança, construído a partir de seções que mantêm um trecho do documento, o substituem, ou adicionam ou removem marks sobre ele, de modo que o intervalo afetado por uma edição pode ser lido diretamente em vez de reconstruído a partir de uma lista de steps. No ProseMirror uma transação é uma lista ordenada de steps atômicos, cada um agindo sobre o documento produzido pelo anterior, o que força a aritmética de posições e a inspeção de intervalos a percorrer a cadeia.

A justificativa do anúncio é que o formato de delta do CodeMirror, ele mesmo derivado do ShareJS, é ao mesmo tempo mais simples e mais capaz. Uma mudança é uma sequência plana sobre o documento antigo. Considere um documento com dez tokens de comprimento: adicionar um token na posição 4 resulta em “mantenha 4, substitua 0 pelo token, mantenha 6”, e deixar as posições 3 a 6 em negrito resulta em “mantenha 3, atualize 3 adicionando a mark, mantenha 4”. A seção de atualização de mark é a extensão do Wordgard ao modelo do CodeMirror.

Isso funciona em uma árvore porque as posições são contadas em tokens. No index system do guia, cada abertura de plot, fechamento de plot, leaf não textual e caractere UTF-16 adiciona um à posição, a posição 0 fica diretamente antes do primeiro filho, e os próprios tokens de abertura e fechamento do nó do documento não são contados. Isso permite que uma mudança insira novas sequências de tokens no documento como se ele fosse plano, com o código de criação da mudança assumindo a tarefa de verificar que o resultado continua sendo uma árvore bem formada.

Quando várias mudanças são passadas juntas a ChangeSet.create, cada posição é interpretada em relação ao documento original e a biblioteca as desloca automaticamente. Uma mudança de mark não toca em nenhum conteúdo:

let makeStrong = ChangeSet.create(doc, {
  from: 1, to: 5,
  add: Strong
})

Os mesmos objetos suportam transformar mudanças umas sobre as outras, o que é a base do histórico de undo e da edição colaborativa.

O Que Substitui as Content Expressions do ProseMirror?

Os schemas do Wordgard podem restringir quais tipos de nó um pai pode conter, e se um block plot pode estar vazio, mas não a ordem em que os filhos aparecem; as content expressions em forma de expressão regular do ProseMirror não têm equivalente. O anúncio dá duas razões: código genérico de manipulação de documentos não pode ser escrito contra restrições arbitrárias de ordenação sem verificar cada operação, e restrições rígidas bloqueiam os estados intermediários desarrumados pelos quais a edição real passa.

Regras que o schema não consegue expressar são tratadas por corrections. Uma correction é um observador vinculado a uma consulta de nó; ela roda sempre que um nó correspondente muda ou aparece, e pode devolver um spec de mudança que a biblioteca adiciona à transação. Como uma correction é código, ela pode respeitar o que o usuário está fazendo no meio do caminho em vez de rejeitar mecanicamente a estrutura. O exemplo do guia usa Correction.onChildList(Doc, ...) para inserir um cabeçalho de nível 1 quando o documento não começa com um; o anúncio aponta as tabelas retangulares como o caso que as expressions do ProseMirror nunca conseguiriam expressar.

Por Que o Wordgard Usa Facets em Vez de Plugins?

O Wordgard substitui o plugin do ProseMirror como unidade de configuração e precedência por uma árvore de valores de extensão de granularidade fina, cada um dos quais pode carregar sua própria precedência. A reclamação do anúncio é precisa: um plugin do ProseMirror agrupa vários hooks sob uma única posição de precedência, então um plugin que precisa ter alta prioridade para um hook e baixa para outro não consegue ter ambas.

Na seção de configuração do guia, uma extensão é uma de três coisas: um valor de um dos tipos de extensão embutidos da biblioteca, qualquer objeto que carregue uma extensão em seu campo extension, ou um array contendo mais do mesmo. A precedência explícita vem das funções em GardState.prec; dentro de um nível, a ordem na árvore decide. Facets são pontos de extensão tipados que qualquer código pode definir, com uma função combine opcional para reduzir as entradas a uma saída, e compartments permitem que partes de uma configuração sejam trocadas sem descartar o estado. A palavra “plugin” não desapareceu: Wordgard.Plugin.define continua lá, para objetos que mantêm seu próprio estado e precisam ficar próximos do DOM, que é como os tooltips e painéis que acompanham a biblioteca são construídos.

Seleção Desenhada pela Biblioteca

O Wordgard trata a seleção por teclado e ponteiro na biblioteca e esconde o caret nativo para desenhar o seu próprio cursor, enquanto o realce de seleção nativo em si permanece visível. O anúncio atribui isso ao comportamento não confiável dos navegadores: um cursor que não se move além de certos conteúdos, que aterra no lugar errado ou nem é pintado, e seleção por arraste do mouse que falha. Assim a biblioteca constrói sua própria representação de como o conteúdo está disposto, faz seu próprio tratamento de texto bidirecional, e posiciona o cursor ela mesma. O exemplo de DOM do guia mostra um elemento dedicado de camada de cursor sobreposto ao conteúdo, e o documento de migração afirma que o realce nativo permanece porque deixá-lo em paz gera menos problemas.

À época do anúncio do 0.1, a seleção por toque é a única exceção e permanece nativa, porque reimplementá-la quebra o menu de contexto da plataforma. Essa linha se moveu desde então: o changelog registra seleção por toque na 0.5.0 para posições que a seleção nativa não consegue alcançar, e na 0.5.1 uma posição de cursor extra nas bordas de inline plots, o que dá à seleção por arraste em toque algum lugar para parar. O anúncio também apresenta o tratamento de entrada como provisório: o Wordgard trata beforeinput para tudo exceto composição e abandona o parsing de mutações do DOM do ProseMirror, pendente de testes no mundo real. Nenhuma matriz de suporte a navegadores foi publicada.

Wordgard ao Lado de ProseMirror, TipTap e Lexical

ProseMirrorTipTapLexicalWordgard
Modelo de mudançasSteps ordenadosHerda o do ProseMirrorModelo próprioMudança única baseada em seções
Forma do conteúdoContent expressions em regexHerda o do ProseMirrorModelo próprioConjuntos de tipos de filhos mais corrections
ConfiguraçãoPluginsExtensões sobre plugins do ProseMirrorModelo próprioExtensões de facet com precedência por valor
SeleçãoNativa do navegadorNativa do navegadorModelo próprioCursor desenhado pela biblioteca, toque nativo

O TipTap é uma camada de framework sobre o ProseMirror e herda seu modelo central; o Lexical é o framework de editor separado da Meta. Nenhum dos dois compartilha interfaces com o Wordgard.

Quem deve esperar: a maioria das equipes, por enquanto. O Wordgard foi lançado inicialmente como 0.1.0, e o pacote no npm já passou por vários releases desde então; a entrada mais recente no changelog é a 0.5.2, datada de 6 de setembro de 2026, e o changelog registra mudanças incompatíveis nas versões 0.2.0, 0.3.0, 0.4.0 e 0.5.0. O autor espera repensar partes da interface pública e permanecer em 0.x provavelmente por um ano ou mais. Não há caminho de atualização a partir do ProseMirror: o documento Migrating from ProseMirror do projeto mapeia cada pacote do ProseMirror para um módulo do Wordgard e afirma que nenhuma compatibilidade de interface foi tentada.

Veredito

O Wordgard é o primeiro editor da linhagem do ProseMirror a descartar steps, content expressions ordenadas e seleção controlada pelo navegador em um único design, e são essas três decisões que o tornam digno de atenção, não o nome do autor. Se você mantém um produto baseado em ProseMirror, leia o documento de migração e as seções de Changes e Corrections do guia, e então prototipe um invariante de schema incômodo como uma correction; esse exercício lhe dirá mais sobre adequação do que qualquer lista de funcionalidades.

Perguntas Frequentes

O Wordgard inclui edição colaborativa, ou preciso construir um servidor?

O Wordgard inclui uma extensão de edição colaborativa do lado do cliente em wordgard/collab, mas nenhum servidor. A extensão collab() rastreia mudanças locais não confirmadas; collab.sendableUpdate e collab.receive trocam atualizações com uma autoridade central que você implementa, e collab.transformUpdate (adicionado na 0.2.0) permite que esse servidor faça rebase de atualizações desatualizadas. As corrections ignoram transações remotas, então dê à configuração do cliente e à transformação do servidor as mesmas corrections, listadas na mesma ordem.

Qual é a diferença entre um plot e um leaf no Wordgard?

Um plot é um nó com conteúdo, como um parágrafo, lista, tabela, ou o documento; um leaf é um nó sem conteúdo, como texto, uma imagem, ou uma quebra de linha. São classes separadas, Plot e Leaf, e as propriedades isPlot e isLeaf fazem o estreitamento de tipo entre elas no TypeScript. Um leaf é a sua própria tag (type, parameter, marks), enquanto um plot mantém uma tag mais um array de conteúdo.

Posso criar ou modificar documentos Wordgard fora do navegador, por exemplo no Node?

Sim para o modelo de documento, não para o editor. Os módulos wordgard/doc, wordgard/state e wordgard/types são projetados para rodar sem um DOM, então você pode construir documentos, aplicar change sets, executar corrections e serializar para JSON no servidor; wordgard/types depende apenas de wordgard/doc. O wordgard/editor carrega fora do navegador, mas não faz nada útil, e um documento em string HTML precisa do parser do navegador, então passe JSON ou use jsdom.

O Wordgard suporta tabelas, e como ele as mantém retangulares?

Sim. O módulo wordgard/table exporta um pacote de extensões tables() que adiciona os elementos de schema de tabela, um tipo CellSelection para selecionar retângulos de células, handlers de colar e soltar, um menu de tabela, e tables.correction, uma correction embutida que repara tabelas cujas células não se alinham em um retângulo limpo. Suas opções são headerCells, cellSpanning e cellContent (inline ou block). Células mescladas usam as marks RowSpan e ColSpan.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.