Barra de ferramentas, visualização ao vivo e WYSIWYG na área de texto que você já possui
Última atualização:
This translation is older than the English page and may be missing recent changes.
Read the English versionUm 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.
Os tipos também são importáveis, o que é importante sempre que suas opções não estão escritas in-line no site da chamada. O TypeScript verifica apenas chaves desconhecidas em um objeto literal passado diretamente, portanto, uma configuração mantida em sua própria variável tem uma chave com erro ortográfico ignorada silenciosamente. Anotá-lo coloca o cheque de volta.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import MarkdownEditor, { type MarkdownEditorOptions, type ToolbarEntry } from 'markdown-text-editor'; // Without the annotation this compiles, the editor renders with the default // toolbar, and nothing tells you why. const config: MarkdownEditorOptions = { placeholder: 'Write...', toolbr: ['bold', 'preview'], // Error: did you mean to write 'toolbar'? }; // Building a toolbar conditionally const toolbar: ToolbarEntry[] = ['bold', 'italic']; if (canUpload) toolbar.push({ image: { fileInput: { uploadUrl: '/api/upload' } } }); toolbar.push('preview'); |
O mesmo se aplica a um componente wrapper, onde MarkdownEditorOptions é o que você encaminha em vez de any, então quem usa seu wrapper mantém a verificação.
1 2 3 4 | interface EditorProps { value: string; options?: MarkdownEditorOptions; } |
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. |
labels | object | undefined | Traduções para a interface própria do editor, codificadas pela string em inglês. Tudo o que você deixa de fora permanece em inglês. |
🛠 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 de tabela inicial de redução 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>
🧩 Ferramentas personalizadas
Adicione seu próprio botão da barra de ferramentas sem bifurcar. Descreva-o e coloque-o no toolbar array, em qualquer posição que você quiser.
1 2 3 4 5 6 7 8 9 10 11 | new MarkdownEditor('#markdown-editor', { toolbar: ['bold', 'italic', { custom: { title: 'Callout', icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">…</svg>', action(editor) { editor.insertText('> [!NOTE]\n> Something worth noting'); } } }, 'preview'] }); |
O title torna-se a dica de ferramenta e a classe CSS do botão, então este pode ser estilizado como .callout-btn, exatamente como as ferramentas integradas.
Várias ferramentas
Adicione quantos quiser. Cada um é sua própria entrada na matriz, então você os coloca individualmente e eles podem ficar entre as ferramentas integradas, em vez de serem agrupados em uma extremidade.
1 2 3 4 5 6 7 | toolbar: [ 'bold', 'italic', { custom: { title: 'Callout', icon: svg1, action: insertCallout } }, 'ul', 'ol', { custom: { title: 'Signature', icon: svg2, action: insertSignature } }, 'preview' ] |
Dê a cada um um distinto title. Duas ferramentas compartilhando um título compartilhariam uma classe CSS, e uma ferramenta sem um retornaria para .custom-btn.
Opções
| Propriedade | Tipo | Propósito |
|---|---|---|
title | string | Obrigatório. Texto da dica de ferramenta e a origem da classe CSS do botão. Traduzível através labels. |
action | function | Obrigatório. Recebe (editor, event). O evento click é passado para que uma ferramenta que abre uma caixa de diálogo possa entregá-lo MarkdownEditor.modal. |
icon | string | SVG embutido para a face do botão. |
shortcut | string | Um atalho de teclado como Ctrl+Shift+K. Adicionado à dica de ferramenta e tem precedência sobre um integrado usando as mesmas teclas. |
Uma ferramenta sem action é ignorado com um aviso do console em vez de quebrar a barra de ferramentas. Ele não renderiza nenhum botão e seu shortcut, se declarou um, também não está registrado.
Trabalhando com o conteúdo
Usar editor.insertText() em vez de escrever você mesmo na área de texto. Ele substitui a seleção, restaura o foco, move o cursor, rola, renderiza novamente a visualização e dispara onChange. Desfazer continua funcionando porque a edição segue o caminho normal.
1 2 3 4 5 6 7 8 | action(editor) { const textarea = editor.usertextarea; const selected = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd); const wrapped = `==${selected || 'highlight'}==`; // (text, where the caret lands, characters to leave outside the selection) editor.insertText(wrapped, wrapped.length, 0); } |
Atalhos
1 2 3 4 5 6 | { custom: { title: 'Highlight', icon: '<svg>…</svg>', shortcut: 'Ctrl+Shift+H', action: (editor) => editor.insertText('====', 2, 2) }} |
A dica de ferramenta se torna "Destacar (Ctrl+Shift+H)". Os atalhos de ferramentas são verificados antes dos integrados, portanto, uma ferramenta personalizada pode assumir deliberadamente uma combinação que o editor manipularia de outra forma.
Ferramentas que precisam de marcação própria
Um menu suspenso ou caixa de diálogo não pode ser descrito declarativamente, então escreva uma classe. Estender MarkdownEditor.Tool, definir this.button no construtor e implementar applySyntax().
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 | class EmbedTool extends MarkdownEditor.Tool { constructor(editor) { super(editor, 'Embed video'); // tooltip, and .embed-video-btn this.shortcut = 'Ctrl+Shift+E'; // optional this.button = this.createButton('<svg>…</svg>'); } applySyntax(event) { const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', ` <input type="url" class="embed-url fj:me-input fj:w-full"> <button type="button" class="embed-go fj:me-btn fj:me-btn-sm">Insert</button> `, 'Embed'); dialog.querySelector('.embed-go').addEventListener('click', () => { const url = dialog.querySelector('.embed-url').value.trim(); if (url) this.editor.insertText(`\n<iframe src="${url}"></iframe>\n`); dialog.close(); }); } destroy() { // Called by editor.destroy(). Release anything you registered here. } } new MarkdownEditor('#markdown-editor', { toolbar: ['bold', EmbedTool, 'preview'] }); |
Passe a configuração com { tool: EmbedTool, config: {...} }, que chega como o segundo argumento do construtor. Contexto this.button para null não renderiza nada, o que é útil para uma ferramenta que só se aplica em algumas configurações.
Uma coisa para observar: Toolchamadas do construtor createButton(), então qualquer coisa que sua substituição precise deve vir de this.editor, não de um campo atribuído após super().
O auxiliar de diálogo
MarkdownEditor.modal é a caixa de diálogo que as ferramentas integradas de link e imagem usam, portanto, uma ferramenta sua que coleta informações se parece com o resto do editor, em vez de inventar seu próprio pop-up. Ele retorna o <dialog> elemento, já aberto, que você consulta para seus campos e fecha com dialog.close().
1 2 | // (event, size classes, body HTML, aria label) const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', '…', 'Embed'); |
Passe o event seu applySyntax() recebido. O auxiliar lê para descobrir de qual editor veio o clique, então a caixa de diálogo é anexada dentro do wrapper desse editor e escolhe seu tema. Ele substitui qualquer caixa de diálogo já aberta nesse editor e se remove do DOM quando fechado, portanto não há nada para limpar.
Renderizando você mesmo o markdown
Se a sua ferramenta incorporar conteúdo renderizado, um corpo de dica de ferramenta, por exemplo, editor.renderMarkdown() produz HTML exatamente como a visualização, honrando seu renderer e sanitizer.
const html = editor.renderMarkdown('**bold** text'); |
Uma barra de ferramentas para sua própria área de texto
Quando uma caixa de diálogo coleta rich text, forneça a ela as ferramentas reais de formatação em vez de reconstruí-las. A barra de ferramentas está vinculada à área de texto que você passa, seu labels são herdados e nenhum botão de visualização é adicionado.
1 2 3 | const body = dialog.querySelector('textarea'); const bar = editor.createToolbar(body, ['bold', 'italic', 'strikethrough']); dialog.querySelector('.body-toolbar').appendChild(bar); |
Isto não gerencia a vida útil da barra de ferramentas. Os botões são limpos com a caixa de diálogo quando ela é removida, mas se uma ferramenta nela registrar algo em toda a página, um ouvinte de documento ou um cronômetro, libere você mesmo quando a caixa de diálogo fechar.
Tradução
As ferramentas personalizadas são traduzidas como as integradas, codificadas pelo título que você deu a elas.
labels: { 'Callout': 'Aviso', 'Embed video': 'Insertar video' } |
A classe CSS não é afetada, então .callout-btn fica .callout-btn em todos os idiomas.
📊 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' }); |
🌐 Idioma da interface
O texto, dicas de ferramentas, itens de menu, campos modais e botões do próprio editor são em inglês por padrão. Passe um labels objeto de traduzi-lo. As chaves são as strings em inglês, a forma como o gettext funciona, então você lista apenas o que deseja alterar e tudo o que estiver faltando volta para o inglês.
1 2 3 4 5 6 7 8 9 10 11 12 13 | new MarkdownEditor('#markdown-editor', { labels: { 'Bold': 'Gras', 'Italic': 'Italique', 'Heading': 'Titre', 'Link': 'Lien', 'Image link': 'Lien de l\'image', 'URL': 'URL', 'Alt text': 'Texte alternatif', 'Apply': 'Appliquer', 'Uploading...': 'Téléversement...', }, }); |
Isso é mais importante junto com o conteúdo da direita para a esquerda. Árabe, urdu e farsi são apresentados corretamente por conta própria, mas uma barra de ferramentas que ainda diz "Negrito" no texto da direita para a esquerda está apenas parcialmente traduzida.
🏷 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
As 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.
Cole e solte
Cole uma captura de tela ou arraste um arquivo de imagem para o editor e ele será carregado por meio do mesmo terminal do botão da barra de ferramentas. Um espaço reservado aparece imediatamente para que o editor nunca pareça congelado e é substituído pelo caminho real assim que o upload chega. Se o upload falhar, o espaço reservado será substituído por um marcador visível em vez de desaparecer silenciosamente.
Isso precisa uploadUrl configurado abaixo. Sem ele, colar uma imagem não adianta nada e o editor registra um aviso explicando o que adicionar, pois a alternativa seria uma string Base64 em seu banco de dados.
Vários arquivos de uma vez são carregados, um de cada vez, para que as imagens cheguem na ordem em que chegaram.
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.
Você também pode retornar image_alt, que é usado como sugestão apenas quando o usuário deixou o campo alt vazio. Qualquer coisa que eles mesmos digitarem sempre vence, então um servidor que deriva o texto alternativo do nome do arquivo ou do reconhecimento de imagem pode oferecê-lo sem substituir uma descrição real.
1 2 3 4 5 | { "success": true, "image_path": "https://cdn.yourdomain.com/uploads/image.webp", "image_alt": "Two people looking at a laptop" } |
Em caso de falha, retorne success: false com um error ou message corda. Esse texto é apresentado ao usuário, então faça dele algo em que ele possa agir.
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.
🔗 Links de um URL colado
Selecione algum texto, cole um URL sobre ele e você obterá um link de desconto em vez do URL substituir o que você selecionou.
1 2 | Read the docs <- select "the docs", paste https://example.com Read [the docs](https://example.com) <- what you get |
A regra é deliberadamente rigorosa: apenas uma simples http ou https URL sem espaços em branco é tratado desta forma. Colar uma frase que contenha um URL ou colar sem nada selecionado se comporta como uma colagem comum.
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 34 35 36 37 38 | 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'); }, labels: { 'Bold': 'Gras', 'Italic': 'Italique', 'Uploading...': 'Téléversement...', }, 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.