Barra de ferramentas, visualização ao vivo e WYSIWYG na área de texto que você já possui
Última atualização:
Um editor de markdown que aprimora sua área de texto em vez de substituí-la, para que o envio de formulários, a validação e os campos obrigatórios continuem funcionando. Modos WYSIWYG e Markdown simples, visualização ao vivo, localização e substituição, suporte RTL, modo escuro. Funciona de forma independente com Django, Laravel, Rails, Node.js, PHP e qualquer pilha.
Não é necessário Frutjam e nem Tailwind. Os estilos são agrupados, então o editor pode ser usado em qualquer projeto.
Instalação
NPM (pacotes: Vite, webpack, Rollup, etc.)
npm install markdown-text-editor |
1 2 | import MarkdownEditor from 'markdown-text-editor'; new MarkdownEditor('#markdown-editor'); |
As definições TypeScript são fornecidas com o pacote, portanto, opções, entradas da barra de ferramentas e formas variáveis são verificadas e preenchidas automaticamente sem instalação extra.
CDN: módulo ES
1 2 3 4 | <script type="module"> import MarkdownEditor from 'https://cdn.jsdelivr.net/npm/markdown-text-editor/+esm'; new MarkdownEditor('#markdown-editor'); </script> |
CDN: etiqueta de script global (IIFE)
Nenhuma importação necessária: MarkdownEditor está disponível automaticamente como uma variável global.
1 2 3 4 5 6 7 8 9 | <form action="/api/save" method="POST"> <textarea id="markdown-editor" name="content"># Hello World</textarea> <button type="submit">Save Content</button> </form> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script> new MarkdownEditor('#markdown-editor'); </script> |
Demonstração do Editor de Markdown
Início rápido
Passe um objeto de opções para personalizar o editor. Todas as opções são opcionais: omita qualquer uma para usar o valor padrão.
1 2 3 4 | const editor = new MarkdownEditor('#markdown-editor', { placeholder: 'Write your markdown...', toolbar: ['heading', 'bold', 'italic', 'strikethrough', 'ul', 'ol', 'checklist', 'blockquote', 'link', 'preview'], }); |
A Filosofia: "Nativo Primeiro"
A maioria dos editores quebra o fluxo de trabalho padrão da web. MarkdownEditor adota isso. Porque fica diretamente em cima de um <textarea>, você não precisa aprender uma nova maneira de lidar com dados.
- Não é necessária vinculação de dados: Funciona com
<form method="POST">fora da caixa - Acesso padrão: Usar
document.getElementById('editor').valueassim como uma entrada normal. - Backend Agnóstico: Funciona com qualquer backend (Python, Node.js, PHP, etc.) como um campo de formulário normal
MarkdownEditor vs EasyMDE/SimpleMDE
A maioria dos editores de markdown JavaScript (EasyMDE, SimpleMDE, editores baseados em CodeMirror) ocultam seu <textarea> e edite uma cópia, devolvendo o conteúdo quando o formulário for enviado. Isso funciona até que algo mais precise do valor: a required campo o navegador não consegue focar bloqueia totalmente o envio e .value ou FormData read before submit retorna uma string vazia, que quebra as proteções htmx, Turbo, autosave e unsaved-changes. MarkdownEditor é diferente. Ele estiliza a área de texto que você já possui e a deixa como o campo em que você digita, para que o valor esteja correto a cada momento.
| Recurso | Editor Markdown | EasyMDE/SimpleMDE |
|---|---|---|
| Textarea nativa preservada | ✅ | ❌ Oculto, editado como cópia |
Funciona com required campos | ✅ | ❌ O navegador bloqueia o envio |
.value corrija antes de enviar | ✅ | ❌ Vazio até o formulário ser enviado |
Serializa com FormData, htmx, turbo | ✅ | ❌ Precisa da própria API do editor |
| Modo híbrido WYSIWYG | ✅ | ❌ |
| Localizar e substituir integrado | ✅ | ❌ |
| Suporte RTL | ✅ Embutido | Através do CodeMirror direction opção |
| Manipuladores de eventos embutidos (CSP) | ✅ Nenhum | Alguns |
| Modo escuro / tema | ✅ | Limitado |
| Tamanho do pacote, compactado | 53 KB | 107 KB (JS + CSS) |
Integração de Estrutura
Porque o MarkdownEditor preserva o nativo <textarea>, ele se integra a todas as estruturas de back-end sem nenhum código extra. Seu servidor recebe o conteúdo do markdown exatamente como receberia de qualquer campo de formulário padrão. Para React e Vue são necessárias algumas linhas, abordadas no final desta seção.
Django
Adicione um class ao seu widget textarea e inicialize o editor: request.POST['content'] funciona sem etapas extras.
1 2 3 4 5 6 7 8 | # forms.py class PostForm(forms.ModelForm): class Meta: model = Post fields = ['content'] widgets = { 'content': forms.Textarea(attrs={'class': 'markdown-editor'}), } |
1 2 | new MarkdownEditor('.markdown-editor'); // request.POST['content'] contains the markdown on submit |
Laravel
Usar f.text_area com uma aula: $request->input('content') recebe a redução diretamente.
1 2 | <textarea name="content" class="markdown-editor">{{ old('content') }}</textarea> <script>new MarkdownEditor('.markdown-editor');</script> |
Ruby nos trilhos
Funciona com form_with fora da caixa: params[:content] contém a marcação. Para Turbo Drive, use turbo:load em vez de DOMContentLoaded.
1 2 3 4 5 6 | document.addEventListener('turbo:load', () => { document.querySelectorAll('.markdown-editor:not([data-mde-init])').forEach(el => { el.setAttribute('data-mde-init', 'true'); new MarkdownEditor(el); }); }); |
Node.js/Expresso
req.body.content recebe a redução no envio do formulário: sem etapa de sincronização, sem extração personalizada.
1 2 3 | <textarea name="content" class="markdown-editor"></textarea> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script>new MarkdownEditor('.markdown-editor');</script> |
PHP
$_POST['content'] funciona exatamente como qualquer textarea padrão: insira-o e o manuseio do formulário existente não exigirá nenhuma alteração.
1 2 3 4 5 6 | <form method="POST" action="save.php"> <textarea name="content" class="markdown-editor"></textarea> <button type="submit">Save</button> </form> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> <script>new MarkdownEditor('.markdown-editor');</script> |
Reagir
Crie o editor em um efeito e destrua-o ao desmontar. Usar defaultValue em vez de value: o editor grava diretamente na área de texto, portanto, uma ligação controlada substituiria o que o usuário está digitando.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import { useEffect, useRef } from 'react'; import MarkdownEditor from 'markdown-text-editor'; function MarkdownField({ name, defaultValue = '', onChange }) { const ref = useRef(null); useEffect(() => { const editor = new MarkdownEditor(ref.current, { onChange }); return () => editor.destroy(); }, []); return <textarea ref={ref} name={name} defaultValue={defaultValue} />; } |
Retornando editor.destroy() do efeito também abrange StrictMode, que executa efeitos duas vezes no desenvolvimento e, de outra forma, deixaria dois editores em uma área de texto. A matriz de dependência vazia é deliberada: as opções são lidas uma vez quando o editor é criado, portanto, executar novamente o efeito o destruiria e o reconstruiria a cada alteração.
Vista
A mesma ideia. Defina o valor inicial uma vez onMounted e não ligue :value, ou cada pressionamento de tecla emitido de volta v-model substituiria a área de texto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; import MarkdownEditor from 'markdown-text-editor'; const el = ref(null); const props = defineProps({ modelValue: { type: String, default: '' } }); const emit = defineEmits(['update:modelValue']); let editor = null; onMounted(() => { el.value.value = props.modelValue; editor = new MarkdownEditor(el.value, { onChange: value => emit('update:modelValue', value) }); }); onBeforeUnmount(() => editor?.destroy()); </script> <template><textarea ref="el"></textarea></template> |
Usado como <MarkdownField v-model="content" />. De forma simples você pode pular v-model inteiramente e leia o valor da área de texto no envio, como acontece com qualquer outra estrutura.
Configuração
Você pode personalizar totalmente o comportamento e a interface do editor passando um options objeto. Se você omitir uma opção, o valor padrão será usado.
As opções são lidas uma vez, quando o editor é construído. Alterá-los posteriormente não tem efeito: ligue destroy() e crie um novo editor.
| Propriedade | Tipo | Padrão | Propósito |
|---|---|---|---|
mode | string | 'plain' | Define a visualização inicial. Use híbrido para uma experiência WYSIWYG ou simples para sintaxe bruta. |
placeholder | string | 'Write...' | Texto mostrado quando o editor está vazio. |
toolbar | array | [...] | Define quais ferramentas aparecem e em que ordem. |
footer | falso | objeto | tudo visível | Controla a barra de status mostrada abaixo do editor. Definir como false para ocultá-lo completamente ou passar um objeto para alternar estatísticas individuais. |
theme | string | inherited | Define explicitamente o tema do editor (claro, escuro, snowberry, darkberry). Se omitido, o editor herda data-theme do elemento ancestral mais próximo ou do <textarea> em si. |
minHeight | number | 200 | Altura mínima em pixels para a qual o editor diminuirá quando o conteúdo for curto. Pares com maxHeight para definir o intervalo de crescimento automático. |
maxHeight | number | 500 | Altura máxima em pixels que o editor pode atingir fora do modo de tela cheia. Quando o conteúdo exceder essa altura, uma barra de rolagem aparecerá dentro do editor. O editor também possui uma alça de arrastar para que os usuários possam redimensioná-lo manualmente além desse limite. |
renderer | function | marked | Substitui o analisador de redução usado para a visualização. Recebe a string markdown e deve retornar uma string HTML. |
sanitizer | function | DOMPurify | Substitui o desinfetante HTML. Recebe o HTML renderizado e deve retornar o HTML seguro para exibição. |
onChange | function | undefined | Retorno de chamada disparado em cada alteração de conteúdo: digitação, ações na barra de ferramentas, desfazer/refazer e continuação de lista. Recebe a string de redução atual como seu único argumento. |
🛠 Personalização da barra de ferramentas
A barra de ferramentas é modular. Você pode criar uma experiência mínima ou um conjunto de recursos completo modificando o array.
Ferramentas disponíveis
| Categoria | Chaves de ferramentas |
|---|---|
| Tipografia | heading, bold, italic, strikethrough, blockquote |
| Listas | ul (bullet), ol (numbered), checklist |
| Código | code (em linha), codeblock (bloco cercado) |
| Inserções | hr (regra horizontal), table (modelo de tabela) |
| Mídia | link, image |
| Edição | undo, redo, indent, outdent |
| Visualizar | preview |
| Modelos | { variables: [...] }, configurado em linha |
Referência de ferramenta
| Ferramenta | Descrição |
|---|---|
heading | Abre um menu suspenso para selecionar o nível de título H1–H6 |
bold | Ativa a formatação de texto em negrito. |
italic | Ativa a formatação de texto em itálico. |
strikethrough | Permite tachado de texto. |
ol | (Lista ordenada): Converte texto em formato de lista numerada. |
ul | (Lista não ordenada): converte o texto em uma lista com marcadores. |
checklist | Adiciona caixas de seleção ao seu texto, tornando-o excelente para tarefas, listas de tarefas ou acompanhamento do status de conclusão. |
blockquote | Destaque o texto citado ou enfatizado. |
code | Quebra o texto selecionado em crases únicos para código embutido. Clicar novamente remove os crases. |
codeblock | Quebra o texto selecionado em um bloco de código protegido com crase triplo. Clicar novamente remove as cercas. |
hr | Insere um --- regra horizontal na posição do cursor em sua própria linha. |
table | Insere um modelo inicial de tabela de descontos 2x3 na posição do cursor. |
image | Permite inserir imagens via sintaxe de markdown. |
link | Permite adicionar hiperlinks ao seu texto. |
undo | Para reverter as últimas alterações. |
redo | Para reaplicar as últimas alterações desfeitas. |
indent | Para aumentar o nível de recuo. |
outdent | Para diminuir o nível de recuo. |
preview | Alterna uma visualização lado a lado em tela cheia. As caixas de seleção no painel de visualização são clicáveis e atualizam a fonte de redução instantaneamente. Pressione Escape para sair da tela cheia. Se um cabeçalho fixo cobrir o editor em tela cheia, consulte --mte-fullscreen-z-index. |
💡 Dicas de implementação:
- Reordenação: os botões aparecem na ordem exata em que você os listou na matriz
- Removendo: Simplesmente omita qualquer tecla (como
image) da matriz para desabilitar esse recurso inteiramente para o usuário - Backup nativo: se você não fornecer um
placeholderem JS, o plugin usará automaticamente oplaceholderatributo do seu HTML<textarea>
📊 Rodapé (barra de status)
O rodapé fica abaixo do editor e mostra a linha, a coluna do cursor, a contagem de caracteres do documento e, opcionalmente, a contagem de palavras, tudo atualizado em tempo real. É visível por padrão e cada estatística pode ser alternada de forma independente.
| Chave | Tipo | Padrão | Descrição |
|---|---|---|---|
line | boolean | true | Mostra o número da linha atual. |
col | boolean | true | Mostrar o número da coluna atual. |
chars | boolean | true | Mostrar a contagem total de caracteres. |
words | boolean | false | Mostre a contagem total de palavras. Desativado por padrão: definido como true para habilitar. |
Exemplos de uso
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Default — line, col, and chars visible new MarkdownEditor('#editor'); // Disable the footer entirely new MarkdownEditor('#editor', { footer: false }); // Hide only character count new MarkdownEditor('#editor', { footer: { chars: false } }); // Hide line and column, keep character count new MarkdownEditor('#editor', { footer: { line: false, col: false } }); // Show only line number new MarkdownEditor('#editor', { footer: { col: false, chars: false } }); // Enable word count alongside the defaults new MarkdownEditor('#editor', { footer: { words: true } }); // Show word count only new MarkdownEditor('#editor', { footer: { line: false, col: false, chars: false, words: true } }); |
🔀 Modos de edição
MarkdownEditor oferece duas maneiras distintas de escrever e formatar seu conteúdo. Você pode alternar entre uma visualização tradicional focada na sintaxe ou uma experiência moderna com foco no visual.
plain(Padrão): Um ambiente Markdown limpo e de alto desempenho onde a sintaxe (como**bold**ou# heading) é visível. Ideal para desenvolvedores e puristas do Markdownhybrid: uma experiência inspirada em WYSIWYG que renderiza a formatação (negrito, itálico, títulos) em tempo real enquanto você digita, mantendo a estrutura Markdown subjacente.
1 2 3 4 5 6 7 8 9 10 11 12 | // Default initialization (Plain Mode) new MarkdownEditor('#markdown-editor'); // Explicit Plain Mode new MarkdownEditor('#markdown-editor', { mode: 'plain' }); // Hybrid (Visual) Mode new MarkdownEditor('#markdown-editor', { mode: 'hybrid' }); |
Visualização do modo híbrido e simples:
Modo Híbrido
A formatação visual é renderizada em tempo real enquanto você digita.
Modo Simples (Padrão)
Concentra-se na sintaxe bruta do Markdown para uma experiência leve.
🌙 Tema
MarkdownEditor herda automaticamente seu tema da página ao redor: nenhuma configuração é necessária. O editor lê data-theme do ancestral mais próximo na inicialização, para que fique sincronizado com o tema do seu site imediatamente.
Como o tema é resolvido (ordem de prioridade)
themeopção: substituição explícita passada no objeto de opçõesdata-themeno<textarea>: definido diretamente no elemento- **
data-themeem qualquer ancestral **: por ex.<html>,<body>ou um invólucro<div>
Temas disponíveis
'light' (padrão), 'dark', 'snowberry', 'darkberry'
Opção 1, herdar de <html> ou qualquer ancestral (configuração zero)
1 2 3 4 5 6 | <html data-theme="dark"> ... <textarea id="markdown-editor"></textarea> <script> new MarkdownEditor('#markdown-editor'); // picks up dark automatically </script> |
Opção 2: definir data-theme diretamente no <textarea>
1 2 3 4 | <textarea id="markdown-editor" data-theme="dark"></textarea> <script> new MarkdownEditor('#markdown-editor'); </script> |
Opção 3: explícito theme opção (substitui tudo)
1 2 3 | new MarkdownEditor('#markdown-editor', { theme: 'dark' }); |
🎨 Tema personalizado por meio de variáveis CSS
Você pode personalizar totalmente a aparência do editor substituindo suas variáveis CSS no .markdown-editor-wrapper elemento ou qualquer [data-theme] seletor. Todas as cores usam o Espaço de cores OKLCH para resultados perceptualmente uniformes.
| Variável | Propósito | Padrão claro | Padrão escuro |
|---|---|---|---|
--color-base | Plano de fundo do editor | oklch(100% 0 0) | oklch(10.9% 0 0) |
--color-on-base | Cor do texto principal | oklch(22% 0 0) | oklch(98% 0 0) |
--color-primary | Acento primário (barra de ferramentas ativa, links) | oklch(51.1% .262 277) | oklch(66.4% .184 286) |
--color-on-primary | Texto em superfícies de cores primárias | oklch(96.2% .018 272) | oklch(10% .01 270) |
--color-secondary | Sotaque secundário | oklch(59.1% .293 323) | oklch(65% .18 220) |
--color-accent | Realçar acento (código embutido, itálico) | oklch(54.1% .281 293) | oklch(75% .18 50) |
--color-neutral | Superfícies neutras (bordas, divisórias) | oklch(15% 0 0) | oklch(85% 0 0) |
--color-error | Cor do estado de erro | oklch(57.7% .245 27) | oklch(60% .22 30) |
--border-radius | Arredondamento dos cantos da moldura do editor | 0.25rem |
Exemplo de tema personalizado
Substitua qualquer variável em .markdown-editor-wrapper após a inicialização do editor ou defina um personalizado [data-theme] bloco na sua folha de estilo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* Override individual variables */ .markdown-editor-wrapper { --color-primary: oklch(60% 0.2 30); /* orange accent */ --border-radius: 0.5rem; } /* Or define a full custom theme */ [data-theme="brand"] .markdown-editor-wrapper, .markdown-editor-wrapper[data-theme="brand"] { --color-base: oklch(15% 0.01 250); --color-on-base: oklch(95% 0 0); --color-primary: oklch(65% 0.22 145); /* green */ --color-on-primary: oklch(10% 0 0); --color-accent: oklch(75% 0.18 60); --color-neutral: oklch(80% 0 0); --border-radius: 0.75rem; } |
new MarkdownEditor('#markdown-editor', { theme: 'brand' }); |
🏷 Variáveis
Adiciona um menu suspenso na barra de ferramentas para inserir espaços reservados. Útil quando a pessoa que escreve um documento não é o desenvolvedor que definiu a sintaxe do espaço reservado, como acontece com modelos de e-mail, fatura ou contrato: eles escolhem um nome legível e a sintaxe correta é inserida para eles.
1 2 3 4 5 6 7 8 9 | new MarkdownEditor('#markdown-editor', { toolbar: ['bold', 'italic', 'link', { variables: [ { label: 'Customer Name', value: '{{customer.name}}' }, { label: 'Invoice No', value: '{{invoice.number}}' } ]}, 'preview' ] }); |
A ferramenta é configurada inline no toolbar array, então sua posição entre os outros botões depende de você. O botão lista os rótulos e clicar em um deles insere seu value no cursor, substituindo qualquer seleção. Nada será renderizado se a lista estiver vazia.
Valores de amostra na visualização
Uma entrada dada uma sample mostra essa amostra na visualização, enquanto a área de texto mantém o espaço reservado real. As entradas sem uma aparecem como estão escritas, então você pode misturar as duas.
1 2 3 4 5 6 7 | { variables: [ { label: 'Customer Name', value: '{{customer.name}}', sample: 'Hannes' }, { label: 'Invoice No', value: '{{invoice.number}}' } ]} // textarea : Hi {{customer.name}}, invoice {{invoice.number}} // preview : Hi Hannes, invoice {{invoice.number}} |
Agrupamento
Dê uma entrada items em vez de um value para renderizar uma seção com cabeçalho. Entradas agrupadas e simples podem ser misturadas em uma lista.
1 2 3 4 5 6 7 8 9 10 | { variables: [ { label: 'Today', value: '{{today}}', sample: '10 September 2026' }, { label: 'Customer', items: [ { label: 'Name', value: '{{customer.name}}', sample: 'Hannes' }, { label: 'Email', value: '{{customer.email}}', sample: 'hannes@example.com' } ]}, { label: 'Invoice', items: [ { label: 'Number', value: '{{invoice.number}}' } ]} ]} |
Coisas para saber
- O editor nunca resolve variáveis. Ele insere texto e seu aplicativo substitui valores reais posteriormente, geralmente no lado do servidor.
sampleafeta apenas o que a visualização exibe. - Os rótulos são mostrados como texto simples, portanto a marcação em um rótulo nunca é renderizada.
- Entradas malformadas são ignoradas em vez de lançadas, e o botão não é renderizado quando nada utilizável é configurado.
🖋 Renderizador personalizado
A visualização é renderizada com marcado e higienizado com DOMPurificar. Ambos podem ser substituídos. Use isto quando seu aplicativo já renderiza markdown com outra biblioteca e você deseja que a visualização corresponda exatamente à produção.
Ambas as opções são funções simples que pegam uma string e retornam uma string, portanto, qualquer analisador e sanitizador funcionam.
1 2 3 4 5 6 7 8 9 | import MarkdownIt from 'markdown-it'; import taskLists from 'markdown-it-task-lists'; import MarkdownEditor from 'markdown-text-editor'; const md = new MarkdownIt({ linkify: true, breaks: true }).use(taskLists); new MarkdownEditor('#markdown-editor', { renderer: markdown => md.render(markdown) }); |
DOMPurify ainda roda na saída, então você mantém a proteção XSS sem configurar nada.
Desinfetante personalizado
Necessário apenas quando o padrão remove algo que seu renderizador emite. DOMPurify remove <iframe> por padrão, portanto, as incorporações de vídeo precisam ser permitidas explicitamente. DOMPurify deve ser importado em seu próprio código, pois a cópia que acompanha o editor é interna.
1 2 3 4 5 6 7 8 9 | import DOMPurify from 'dompurify'; new MarkdownEditor('#markdown-editor', { renderer: markdown => md.render(markdown), sanitizer: html => DOMPurify.sanitize(html, { ADD_TAGS: ['iframe'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder'] }) }); |
Usando uma tag de script em vez de um bundler, carregue DOMPurify junto com o editor:
1 2 | <script src="https://cdn.jsdelivr.net/npm/dompurify"></script> <script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script> |
Coisas para saber
- Apenas visualização. A formatação ao vivo do modo híbrido usa um renderizador interno separado e não é afetada.
- As listas de tarefas precisam de suporte de plug-in. As caixas de seleção clicáveis são encontradas procurando por
input[type="checkbox"]na saída, então markdown-é necessáriomarkdown-it-task-lists. O editor registra um aviso se detectar a sintaxe da lista de tarefas e nenhuma caixa de seleção. - Ambos devem ser síncronos e retornar uma string. Um
asyncescrita de função[object Promise]na visualização. - Substituir o desinfetante substitui sua proteção. Uma passagem como
html => htmldesativa totalmente a higienização e é seguro apenas para conteúdo totalmente confiável.
🎨 Estilização de elementos internos
Variáveis CSS cobrem a maioria dos temas porque são herdadas, portanto, definir uma .markdown-editor-wrapper chega à barra de ferramentas, botões, visualização e rodapé. Procure um nome de classe somente quando nenhuma variável expõe o que você precisa.
1 2 3 4 5 | /* Variables set on the wrapper inherit down to every child */ .markdown-editor-wrapper { --border-radius: 12px; /* rounds the editor and its toolbar buttons */ --color-primary: oklch(60% 0.2 30); } |
Referência de classe
Esses nomes de classe são estáveis e seguros para estilização.
| Aula | Elemento |
|---|---|
.markdown-editor-wrapper | Contêiner externo envolvendo todo o editor |
.toolbar | Faixa da barra de ferramentas acima da área de edição |
.markdown-btn | Botão individual da barra de ferramentas |
.preview-btn | O botão de alternância de visualização/tela cheia |
.editor-layout | Grade contendo a área de edição e visualização lado a lado |
.textarea-wrapper | Wrapper em torno da área de edição |
.editor-textarea | O elemento textarea subjacente |
.display-layer | Camada de formatação renderizada, somente modo híbrido |
.preview-wrapper | Coluna de visualização |
.preview-content | Markdown renderizado dentro da visualização |
.editor-footer | Barra de status abaixo do editor |
.find-replace-panel | Encontre e substitua o painel |
1 2 3 4 5 6 7 8 9 10 | /* Some variables are set by the component on itself, which beats an inherited value. Target the element directly for those. */ .markdown-editor-wrapper .markdown-btn { --btn--font-size: 0.875rem; } /* And use classes for anything no variable exposes */ .markdown-editor-wrapper .toolbar { border-bottom: 2px solid oklch(60% 0.2 30); } |
Visando um editor
Com vários editores em uma página, direcione um deles com data-editor. O wrapper espelha o id de seu <textarea>, então <textarea id="notes"> te dá [data-editor="notes"]. O próprio id permanece na área de texto, então getElementById continua funcionando.
1 2 3 4 | /* one editor only */ [data-editor="notes"] { --border-radius: 0; } |
🪟 Tela cheia e camadas (índice z)
Em tela cheia o editor usa z-index: 10000, que limpa as camadas que a maioria das estruturas de UI reserva para cabeçalhos fixos e sobreposições. Se um cabeçalho fixo ou barra lateral ainda cobrir o editor, aumente esse valor.
Substitua-o por --mte-fullscreen-z-index. O valor se aplica apenas em tela cheia e é herdado, portanto, defini-lo em qualquer ancestral abrange todos os editores abaixo dele.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* every editor on the page */ .markdown-editor-wrapper { --mte-fullscreen-z-index: 20000; } /* one section only - the value inherits down */ #admin-panel { --mte-fullscreen-z-index: 20000; } /* a single editor, by its textarea id */ [data-editor="notes"] { --mte-fullscreen-z-index: 20000; } |
API de conteúdo
Um dos principais pontos fortes do MarkdownEditor é que ele mantém o <textarea> perfeitamente sincronizado. Esteja você usando uma estrutura JavaScript moderna ou um back-end tradicional como Django, PHP ou Laravel, o fluxo de trabalho permanece simples e nativo.
Lendo e escrevendo conteúdo
1. O jeito nativo (recomendado)
Como o editor aprimora uma área de texto padrão, você pode usar métodos DOM familiares. Esta é a maneira mais rápida de interagir com seus dados sem aprender uma nova API.
1 2 3 4 5 | // Retrieve content via ID const markdown = document.getElementById('markdown-editor').value; // Set content via ID (The editor UI updates automatically) document.getElementById('markdown-editor').value = "# New Heading Content"; |
2. Usando uma referência de variável
Se você tiver uma referência ao elemento textarea, poderá usá-lo diretamente: não é necessária nenhuma API específica da biblioteca.
1 2 3 4 5 6 7 | const textarea = document.getElementById('markdown-editor'); // Retrieve content const markdown = textarea.value; // Set content (the editor UI reflects this immediately) textarea.value = "## Updated via JS"; |
3. Configurando o conteúdo inicial do lado do servidor
A maneira recomendada de definir o conteúdo inicial é diretamente no <textarea> HTML: funciona naturalmente com todos os frameworks de backend (Django, Laravel, Rails, PHP, etc.) e o editor o renderiza automaticamente no init.
1 2 | <!-- Recommended: set content server-side --> <textarea id="markdown-editor"># Hello World</textarea> |
Para ler ou atualizar conteúdo em tempo de execução, use o nativo textarea valor. Chamar editor.render() após uma atualização para atualizar a visualização e a camada híbrida.
1 2 3 4 5 6 7 8 | const textarea = document.getElementById('markdown-editor'); // Read const markdown = textarea.value; // Update at runtime textarea.value = '# New content'; editor.render(); |
4. Derrubando o editor: destroy()
Chamar editor.destroy() para remover o wrapper DOM do editor e restaurar o original <textarea> à sua posição no documento. Útil em aplicativos de página única ao desmontar uma visualização.
1 2 3 4 | const editor = new MarkdownEditor('#markdown-editor'); // Remove the editor and restore the plain textarea editor.destroy(); |
Reagindo às mudanças com onChange
Passe um onChange retorno de chamada para ser notificado sobre cada alteração de conteúdo. Recebe a string de redução atual.
1 2 3 4 5 | const editor = new MarkdownEditor('#markdown-editor', { onChange(value) { console.log('Content changed:', value.length, 'characters'); } }); |
Rascunho salvo automaticamente com localStorage
Usar onChange para salvar um rascunho a cada pressionamento de tecla. Restaure-o preenchendo previamente a área de texto antes de inicializar o editor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const DRAFT_KEY = 'my-page-draft'; // Restore saved draft before init (only if textarea starts empty) const textarea = document.getElementById('markdown-editor'); const saved = localStorage.getItem(DRAFT_KEY); if (saved && !textarea.value) textarea.value = saved; // Save on every change const editor = new MarkdownEditor('#markdown-editor', { onChange(value) { localStorage.setItem(DRAFT_KEY, value); } }); // Clear draft after successful form submission document.querySelector('form').addEventListener('submit', () => { localStorage.removeItem(DRAFT_KEY); }); |
Envio de formulário
Como o MarkdownEditor é criado diretamente no software nativo <textarea>, é compatível com todos os frameworks de back-end (Django, Laravel, PHP, Ruby on Rails, etc.) imediatamente.
É aqui que brilha a filosofia “Native-First”. Você não precisa sincronizar os dados manualmente antes de enviar um formulário. O navegador trata o editor exatamente como um campo de entrada padrão.
1 2 3 4 5 6 7 8 9 10 11 12 | <form method="POST" action="/api/submit"> <textarea id="markdown-editor" name="content" class="h-48" rows="5"> # Initial Content </textarea> <button type="submit">Submit to Server</button> </form> <script> // Just initialize it. That's it. new MarkdownEditor('#markdown-editor'); </script> |
Observação: Inicialização do plugin MarkdownEditor obrigatória
Basta usar um HTML padrão <form>. O name O atributo na textarea é o que seu servidor usará para identificar o conteúdo.
🚀 Por que é uma virada de jogo para back-ends
Como o editor preserva o nativo <textarea> comportamento, seu servidor manipula os dados como uma string padrão. Não há necessidade de lógica extra: não preventDefault() e sem manual FormData construção.
💡 Por que este é um "recurso matador":
A maioria dos editores (como Quill, Editor.js, simpleMDE, easyMDE) salva dados em estruturas JSON complexas. Se um desenvolvedor usar isso, ele terá que reescrever o esquema do banco de dados e a lógica de renderização.
Com o MarkdownEditor, um desenvolvedor pode pegar um site antigo e substituir um site simples <textarea> com seu editor, e o backend nem sabe que mudou. Ele apenas recebe o mesmo texto bruto de sempre, mas o usuário obtém uma experiência 10 vezes melhor.
| Estrutura / Linguagem | Como acessar o conteúdo do Markdown |
|---|---|
| PHP | $_POST['content'] |
| Django | request.POST.get('content') |
| Node.js (Expresso) | req.body.content |
| Laravel | $request->input('content') |
| Ruby nos trilhos | params[:content] |
🖼️ Upload avançado de imagens
Lidar com uploads de imagens nativamente: em vez de depender de strings Base64 lentas e com muita memória, é uma vitória significativa tanto para o desempenho quanto para o SEO.
Opções de configuração
A ferramenta de imagem suporta um fileInput configuração para lidar com uploads diretos do servidor.
accept: Defina uma matriz de formatos de imagem permitidos (por exemplo, 'webp', 'avif')uploadUrl: especifique o endpoint de back-end onde oFileobjeto será enviado viaPOSTparams: objeto opcional para enviar dados adicionais (como tokens CSRF, IDs de usuários ou nomes de pastas) junto com o arquivo de imagem
Exemplo de uso (configuração completa)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const options = { placeholder: 'Start writing...', toolbar: [ 'link', { image: { fileInput: { accept: ['webp', 'avif'], // restrict the image upload format uploadUrl: '/api/upload', // Your upload endpoint params: { _token: 'your_csrf_token_here', // Essential for Laravel/Django folder: 'blog_posts' } }, // Supports boolean: true/false OR object: { required: true } altInput: { required: true } } }, 'preview' ], } const editor = new MarkdownEditor('#markdown-editor', options); |
📡 Integração de Servidor
O Pedido
O editor envia um POST solicitar como multipart/form-data. Por padrão, inclui:
image_file: O objeto de arquivo realimage_alt: O texto alternativo inserido pelo usuário- ...mais quaisquer dados personalizados definidos no
paramsobjeto
A resposta necessária
Para confirmar um upload bem-sucedido e inserir a imagem no editor, seu servidor deve retornar a seguinte estrutura JSON:
1 2 3 4 | { "success": true, "image_path": "https://cdn.yourdomain.com/uploads/image.webp" } |
Observação: Certifique-se de usar a chave image_path para o URL da imagem enviada.
Validação de texto alternativo da imagem (altInput)
Para garantir que seu conteúdo permaneça acessível e otimizado para SEO, o MarkdownEditor aplica a validação de texto alternativo por padrão.
- Comportamento padrão: Se
altInputnão está definido, o padrão é{ required: true } - Aplicar acessibilidade: os usuários serão impedidos de inserir uma imagem até que uma descrição alternativa seja fornecida
1. Padrão (nenhuma configuração necessária)
1 2 3 4 | // Alt text is REQUIRED by default image: { fileInput: { uploadUrl: '/api/upload' } } |
2. Abreviação (desativar validação)
Se você quiser permitir imagens sem descrições, basta definir o booleano como false.
1 2 3 | image: { altInput: false // Users can now skip the alt text field } |
3. Baseado em objeto (explícito)
1 2 3 4 5 | image: { altInput: { required: false // Disables alt text validation — users can skip the alt field } } |
Uso de imagem padrão (não fileInput)
Se fileInput não estiver configurado, o editor usará como padrão um modal simples baseado em URL. Isso é ideal se seus usuários estiverem vinculando principalmente a hosts de imagens externos.
1 2 3 4 5 6 7 8 | const options = { toolbar: [ 'link', 'image', 'preview' ], } const editor = new MarkdownEditor('#markdown-editor', options); |
💡 Por que usar parâmetros?
Em frameworks como Laravel ou Django, você não pode fazer upload de arquivos sem um token CSRF. Ao adicionar _token para o params objeto, sua solicitação passará perfeitamente pelo middleware de segurança do back-end, mantendo a filosofia "Zero Logic" para seus controladores do lado do servidor.
⌨️ Atalhos de teclado
Ações comuns de formatação podem ser acionadas diretamente no teclado, sem tocar na barra de ferramentas. Cada atalho também é mostrado na dica de ferramenta do botão da barra de ferramentas correspondente.
| Atalho | Ação |
|---|---|
Ctrl + B / ⌘ B | Alternar Negrito |
Ctrl + I / ⌘ I | Alternar Itálico |
Ctrl + K / ⌘ K | Inserir link |
Ctrl + ` / ⌘ ` | Alternar in-line Code |
Ctrl + Shift + S / ⌘ ⇧ S | Alternar |
Ctrl + 1 / ⌘ 1 | Título 1 |
Ctrl + 2 / ⌘ 2 | Título 2 |
Ctrl + 3 / ⌘ 3 | Título 3 |
Ctrl + L / ⌘ L | Alternar lista com marcadores |
Ctrl + Z / ⌘ Z | Desfazer |
Ctrl + Shift + Z / ⌘ ⇧ Z | Refazer |
Tab | Recuar linhas selecionadas |
Shift + Tab | Recuar linhas selecionadas |
Ctrl + F / ⌘ F | Abra o painel Localizar |
Ctrl + H / ⌘ H | Abra o painel Localizar e Substituir |
Ctrl + Shift + F / ⌘ ⇧ F | Alternar visualização em tela cheia |
F11 | Alternar visualização em tela cheia |
Escape | Fechar painel Localizar / Sair da visualização em tela cheia |
🔍 Localizar e substituir
Um painel integrado de localização e substituição está disponível dentro do editor: nenhuma extensão de navegador ou ferramenta separada é necessária.
- Imprensa
Ctrl + F(ou⌘ F) para abrir o painel Localizar - Imprensa
Ctrl + H(ou⌘ H) para abrir o painel Localizar e substituir - A pesquisa não diferencia maiúsculas de minúsculas e mostra um contador de partidas ao vivo (por exemplo, 3 de 12)
- Navegue pelas partidas com os botões ▲ / ▼ ou
Enter/Shift + Enter - Substituir substitui a correspondência realçada atual; Substituir tudo substitui todas as ocorrências de uma só vez
- Imprensa
Escapepara fechar o painel e retornar o foco ao editor
O painel flutua no canto superior direito da área de conteúdo do editor e não interrompe a escrita.
Características
🔌 Integração de formulário nativo
Funciona exatamente como um padrão <textarea>. Sem APIs complexas: basta usar o value ou name atributo. Ele "simplesmente funciona" com envios de formulários HTML padrão em PHP, Django ou Node.js.
🖼️ Upload avançado de imagens
Configure uploads de servidores nativos via API. Evite strings Base64 pesadas para garantir carregamentos de página mais rápidos e SEO superior hospedando imagens em seu próprio CDN.
🔀 Modos Híbrido e Simples
Alternar entre um Híbrido (WYSIWYG) experiência em edição visual ou Remarcação simples modo para uma sensação de codificação tradicional.
🚀 Alto desempenho
UM 53 KB compactados pacote (252 KB minificado, CSS incluído) otimizado para "Conteúdo Pesado". Lida com documentos e arquivos grandes sem qualquer atraso de entrada ou queda de desempenho. Atualizações de visualização eliminadas, cálculos de estilo em cache e manuseio de teclado sem conflitos: portanto, Tab e Enter sempre fazem exatamente uma coisa.
🌍 Suporte RTL integrado
Suporte nativo para idiomas da direita para a esquerda, como árabe, urdu e farsi. Perfeito para criar aplicativos acessíveis globalmente.
✨ Destaque de sintaxe
Legibilidade aprimorada com código claro e formatação remarcada.
🌙 Tema Adaptativo
Inclui suporte automático ao Modo Escuro. Ele sincroniza com as configurações do sistema ou com o IU do Frutjam biblioteca para uma experiência visual perfeita.
📝 Edição Inteligente
Continuação de lista automática no estilo GitHub para listas ordenadas, listas não ordenadas e listas de verificação: pressione Enter e o editor continua o padrão. As caixas de seleção no painel de visualização podem ser clicadas e sincronizadas instantaneamente com a fonte de redução.
📱 Totalmente responsivo
Uma interface de usuário fluida e voltada para dispositivos móveis que se adapta perfeitamente a desktops, tablets e smartphones para edição em qualquer lugar.
📦 Suporte universal
Compatível com ESM, UMD, CommonJS e IIFE. Funciona imediatamente via CDN (<script src>), npm ou qualquer bundler (Vite, webpack, Rollup): nenhuma configuração extra necessária.
♿ Acessível por padrão
Suporte completo para ARIA integrado: ponto de referência da barra de ferramentas, região de visualização rotulada, botões fáceis de ler na tela, aria-pressed no botão de visualização, disabled e aria-disabled em ferramentas inativas e restauração correta do foco quando os modais fecham.
🛡️ Zero conflitos de CSS
Os estilos do editor têm escopo total para .markdown-editor-wrapper. A comprovação global do Tailwind é excluída para que o editor viva com segurança ao lado do Bootstrap, Tailwind ou qualquer outra estrutura sem quebrar seus estilos.
⌨️ Atalhos de teclado
Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+`, Ctrl+Shift+S: ações comuns de formatação sem tocar no mouse. Cada atalho é mostrado na dica de ferramenta do botão da barra de ferramentas.
🔍 Localizar e substituir
Imprensa Ctrl+F encontrar ou Ctrl+H para abrir localizar e substituir. Pesquisa sem distinção entre maiúsculas e minúsculas com contador de correspondências ao vivo, navegação seguinte/anterior, substituição única e substituição de todas: sem sair do editor.
🔒 Visualização segura do XSS
A visualização renderizada é higienizada via DOMPurificar antes de ser gravado no DOM. Tags de script, manipuladores de eventos in-line e URLs maliciosos na entrada de markdown criada são removidos automaticamente: nenhuma configuração é necessária.
▶️ Visualização em tempo real
Veja sua redução renderizada instantaneamente enquanto você digita.
🔗 Fácil integração
Integre-se perfeitamente a qualquer projeto web com configuração mínima.
🛠️ Barra de ferramentas personalizável
Configure e reordene dinamicamente as opções da barra de ferramentas, como negrito, itálico e muito mais.
Exemplo de configuração completa
Use este exemplo abrangente para inicializar o MarkdownEditor com todos os recursos principais, incluindo ordenação personalizada da barra de ferramentas e gerenciamento avançado de upload de imagens.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | const editor = new MarkdownEditor('#markdown-editor', { mode: 'hybrid', placeholder: 'Start writing...', footer: { line: true, col: true, chars: true, words: true, }, onChange(value) { console.log('Content updated:', value.length, 'characters'); }, toolbar: [ 'heading', 'bold', 'italic', 'strikethrough', 'blockquote', 'ul', 'ol', 'checklist', 'code', 'codeblock', 'hr', 'table', { image: { fileInput: { accept: ['webp', 'avif', 'png'], uploadUrl: '/api/upload' } } }, 'link', 'undo', 'redo', 'indent', 'outdent', 'preview' ], }); // Read content natively const markdown = document.getElementById('markdown-editor').value; // destroy() when the view unmounts (SPAs) // editor.destroy(); |
Achou isso útil?
Uma estrela do GitHub ajuda outros desenvolvedores a descobrir o editor. Faz parte Frutjam. Uma estrela ali também ajuda.