Skip to main content

Barra de herramientas, vista previa en vivo y WYSIWYG en el área de texto que ya tiene

Última actualización:

Un editor de rebajas que mejora su área de texto en lugar de reemplazarla, por lo que el envío de formularios, la validación y los campos obligatorios siguen funcionando. Modos WYSIWYG y Simple Markdown, vista previa en vivo, buscar y reemplazar, soporte RTL, modo oscuro. Funciona de forma independiente con Django, Laravel, Rails, Node.js, PHP y cualquier pila.

No se requiere Frutjam ni viento de cola. Los estilos están agrupados, por lo que el editor se inserta en cualquier proyecto.

Instalación

NPM (bundlers: Vite, webpack, Rollup, etc.)

bash
npm install markdown-text-editor
javascript
1
2
import MarkdownEditor from 'markdown-text-editor';
new MarkdownEditor('#markdown-editor');

Las definiciones de TypeScript se envían con el paquete, por lo que las opciones, las entradas de la barra de herramientas y las formas variables se comprueban y se completan automáticamente sin instalación adicional.

CDN: módulo ES

html
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)

No se necesita importar: MarkdownEditor está disponible como variable global automáticamente.

html
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>

Markdown Editor

Inicio rápido

Pasa un objeto de opciones para personalizar el editor. Todas las opciones son opcionales: omita cualquiera para usar el valor predeterminado.

javascript
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'],
});

La filosofía: "Native-First"

La mayoría de los editores rompen el flujo de trabajo web estándar. MarkdownEditor lo acepta. Porque se encuentra directamente encima de un <textarea>, no necesitas aprender una nueva forma de manejar los datos.

  • No se necesita vinculación de datos: Funciona con <form method="POST"> Al abrir la caja
  • ** Acceso Estándar:** Uso document.getElementById('editor').value como una entrada normal.
  • Backend Agnostic: Funciona con cualquier backend (Python, Node.js, PHP, etc.) al igual que un campo de formulario normal

MarkdownEditor vs EasyMDE / SimpleMDE

La mayoría de los editores de rebajas de JavaScript (EasyMDE, SimpleMDE, editores basados en CodeMirror) * ocultan sus <textarea>* y editar una copia, escribiendo el contenido cuando se envíe el formulario. Eso funciona hasta que algo más necesita el valor: a required campo en el que el navegador no puede enfocar bloquea el envío por completo, y .value o FormData leer antes de enviar devuelve una cadena vacía, que rompe las protecciones htmx, Turbo, autoguardado y cambios no guardados. MarkdownEditor es diferente. Estiliza el área de texto que ya tienes y la deja como el campo en el que escribes, por lo que el valor es correcto en cada momento.

ResaltarMarkdownEditorEasyMDE / SimpleMDE
Texto nativoárea conservada✅❌ Oculto, editado como copia
Tiene compatibilidad con required campos✅❌ El navegador bloquea el envío
.value corregir antes de enviar✅❌ Vacío hasta que se envíe el formulario
Serializa con FormData, htmx, Turbo✅❌ Necesita la API propia del editor
Modo híbrido WYSIWYG✅❌
Búsqueda y sustitución integradas✅❌
Implementación RTLFuera del estandardA través de CodeMirror's direction opción
Controladores de eventos en línea (CSP)NingunoAlgunas
Modo oscuro/ tematización✅Edición limitada
Tamaño del paquete, gzipped53 KB107 KB (JS + CSS)

Integración del marco

Porque MarkdownEditor preserva el nativo <textarea>, se integra con todos los marcos de back-end sin ningún código adicional. Su servidor recibe el contenido rebajado exactamente como lo haría desde cualquier campo de formulario estándar. Para React y Vue se necesitan algunas líneas, cubiertas al final de esta sección.

Django

Añada un class a su widget de área de texto e inicializar el editor: request.POST['content'] funciona sin pasos adicionales.

python
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'}),
        }
javascript
1
2
new MarkdownEditor('.markdown-editor');
// request.POST['content'] contains the markdown on submit

Laravel

Usar f.text_area con una clase: $request->input('content') recibe la rebaja directamente.

html
1
2
<textarea name="content" class="markdown-editor">{{ old('content') }}</textarea>
<script>new MarkdownEditor('.markdown-editor');</script>

Ruby on Rails

Tiene compatibilidad con form_with Al abrir la caja params[:content] contiene la rebaja. Para Turbo Drive, use turbo:load en vez de DOMContentLoaded.

javascript
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 / Express

req.body.content recibe la rebaja al enviar el formulario: sin paso de sincronización, sin extracción personalizada.

html
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 exactamente como con cualquier área de texto estándar: colóquela y el manejo de su formulario existente no requiere cambios.

html
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>

Reaccione

Crea el editor en un efecto y destrúyelo al desmontarlo. Usa defaultValue En lugar de dejar value: el editor escribe directamente en el área de texto, por lo que un enlace controlado sobrescribiría lo que el usuario está escribiendo.

javascript
 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 &lt;textarea ref={ref} name={name} defaultValue={defaultValue} /&gt;;
}

Volviendo editor.destroy() del efecto también cubre StrictMode, que ejecuta efectos dos veces en desarrollo y, de lo contrario, dejaría dos editores en un área de texto. La matriz de dependencias vacía es deliberada: las opciones se leen una vez cuando se crea el editor, por lo que volver a ejecutar el efecto lo derribaría y lo reconstruiría en cada cambio.

VUE

Misma idea. Establece el valor inicial una vez en onMounted y no obligan :value, o cada pulsación de tecla emitida de vuelta a través de v-model sobrescribiría el área de texto.

javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
&lt;script setup&gt;
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 =&gt; emit('update:modelValue', value)
    });
});

onBeforeUnmount(() =&gt; editor?.destroy());
&lt;/script&gt;

&lt;template&gt;&lt;textarea ref="el"&gt;&lt;/textarea&gt;&lt;/template&gt;

Utilizado como <MarkdownField v-model="content" />. En una forma simple se puede saltar v-model en su totalidad y leer el valor del área de texto en enviar, como con cualquier otro marco.

Configuración

Puede personalizar completamente el comportamiento y la interfaz del editor pasando un options objeto. Si omite una opción, se utiliza el valor ** predeterminado * *.

Las opciones se leen una vez, cuando se construye el editor. Cambiarlos después no tiene efecto: llamar destroy() y crear un nuevo editor en su lugar.

PropiedadTipoPredeterminadoPropósito
modestring'plain'Establece la vista inicial. Utilice Hybrid para una experiencia WYSIWYG o Plain para la sintaxis RAW.
placeholderstring'Write...'Texto que se muestra cuando el editor está vacío.
toolbararray[...]Define qué herramientas aparecen y en qué orden.
footerFalsoobjetoTodo visible
themestringinheritedEstablece explícitamente el tema del editor (claro, oscuro, baya de nieve, baya oscura). Si se omite, el editor hereda data-theme del elemento ancestro más cercano o el <textarea> en sí misma.
minHeightnumber200Altura mínima en píxeles a la que el editor se reducirá cuando el contenido sea corto. Empareja con maxHeight para establecer el rango de crecimiento automático.
maxHeightnumber500Altura máxima en píxeles a la que el editor puede crecer en modo sin pantalla completa. Una vez que el contenido supera esta altura, aparece una barra de desplazamiento dentro del editor. El editor también tiene un controlador de arrastre para que los usuarios puedan redimensionarlo manualmente más allá de este límite.
rendererfunctionmarkedSustituye al analizador de rebajas utilizado para la vista previa. Recibe la cadena de descuento y debe devolver una cadena HTML.
sanitizerfunctionDOMPurifySustituye al desinfectante HTML. Recibe el HTML renderizado y debe devolver el HTML seguro para mostrar.
onChangefunctionundefinedLa devolución de llamada se activa en cada cambio de contenido: escritura, acciones de la barra de herramientas, deshacer/rehacer y continuación de la lista. Recibe la cadena de descuento actual como su único argumento.

Personalización 🛠 de la barra de herramientas

La barra de herramientas es modular. Puedes crear una experiencia mínima o un conjunto completo de funciones modificando la matriz.

Herramientas disponibles

CategoriaTeclas de herramientas
Tipografíaheading, bold, italic, strikethrough, blockquote
Listasul (bullet), ol (numbered), checklist
Códigocode En línea codeblock (bloque vallado)
Insercioneshr Línea horizontal table (plantilla de tabla)
Medios de comunicaciónlink, image
Editarundo, redo, indent, outdent
Visualizaciónpreview
Plantillas{ variables: [...] }, configurado en línea

Referencia de herramientas

HerramientaExplicación
headingAbre un menú desplegable para seleccionar el nivel de encabezado H1–H6
boldHabilita el formato de texto en negrita.
italicHabilita el formato de texto en cursiva.
strikethroughPermite tachar el texto.
ol(Ordered List): convierte el texto en un formato de lista numerada.
ul(Lista desordenada): convierte el texto en una lista de viñetas.
checklistAñade casillas de verificación a tu texto, lo que lo hace ideal para tareas, listas de tareas pendientes o seguimiento del estado de finalización.
blockquoteResalte el texto citado o enfatizado.
codeAjusta el texto seleccionado en backticks individuales para el código en línea. Al hacer clic de nuevo se eliminan los backticks.
codeblockAjusta el texto seleccionado en un bloque de código vallado de triple retroceso. Al hacer clic de nuevo, se eliminan las cercas.
hrInserta un --- regla horizontal en la posición del cursor en su propia línea.
tableInserta una plantilla de tabla de rebajas 2x3 de arranque en la posición del cursor.
imageLe permite insertar imágenes a través de la sintaxis de rebaja.
linkLe permite añadir hipervínculos a su texto.
undoPara revertir los últimos cambios.
redoPara volver a aplicar los últimos cambios deshechos.
indentPara aumentar el nivel de sangría.
outdentPara disminuir el nivel de sangría.
previewActiva o desactiva una vista previa de pantalla completa lado a lado. Se puede hacer clic en las casillas de verificación del panel de vista previa y actualizar el origen de las rebajas al instante. Pulse Escape para salir de la pantalla completa. Si un encabezado fijo cubre el editor en pantalla completa, consulte --mte-fullscreen-z-index.
Consejos de implementación
  • Reordenar: Los botones aparecen en el orden exacto en que los enumeras en la matriz
  • Eliminar: Simplemente omita cualquier clave (como image) de la matriz para deshabilitar esa función por completo para el usuario
  • Native Fallback: Si no proporciona un placeholder en JS, el plugin usará automáticamente el placeholder atributo de tu HTML <textarea>

📊 Pie de página (barra de estado)

El pie de página se encuentra debajo del editor y muestra la **línea * * del cursor, la * * columna * *, el ** recuento de caracteres ** del documento y, opcionalmente, el ** recuento de palabras **, todo actualizado en tiempo real. Es visible de forma predeterminada y cada estadística se puede alternar de forma independiente.

LlaveTipoPredeterminadoExplicación
linebooleantrueMostrar el número de línea actual.
colbooleantrueMostrar el número de columna actual.
charsbooleantrueMostrar el recuento total de caracteres.
wordsbooleanfalseMostrar el recuento total de palabras. Desactivado de forma predeterminada: establecido en true activar

Ejemplos de uso

javascript
 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 edición

MarkdownEditor ofrece dos formas distintas de escribir y formatear tu contenido. Puedes alternar entre una vista tradicional centrada en la sintaxis o una experiencia moderna y visual.

  • plain (Predeterminado): Un entorno de Markdown limpio y de alto rendimiento donde la sintaxis (como **bold** o # heading) es visible. Ideal para desarrolladores y puristas de Markdown
  • hybrid: una experiencia inspirada en WYSIWYG que renderiza el formato (negrita, cursiva, encabezados) en tiempo real a medida que escribe, sin dejar de mantener la estructura de Markdown subyacente.
javascript
 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'
});

Vista previa de modo híbrido y simple:

Modo híbrido

El formato visual se representa en tiempo real mientras escribe.

Modo normal (predeterminado)

Se centra en la sintaxis cruda de Markdown para una experiencia ligera.

&Temas

MarkdownEditor hereda automáticamente su tema de la página circundante: no se requiere configuración. El editor lee data-theme desde el antepasado más cercano en la inicialización, por lo que se mantiene sincronizado con el tema de su sitio fuera de la caja.

Cómo se resuelve el tema (orden de prioridad)

  1. theme option: anulación explícita pasada en el objeto options
  2. data-theme en el <textarea>: se establece directamente en el elemento
  3. data-theme en cualquier antepasado: por ejemplo, <html>, <body>, o un envoltorio <div>

Temas disponibles

'light' (predeterminado) 'dark', 'snowberry', 'darkberry'

Opción 1, heredar de <html> o cualquier antepasado (configuración cero)
html
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>
Opción 2: data-theme Directamente sobre la cubeta. <textarea>
html
1
2
3
4
<textarea id="markdown-editor" data-theme="dark"></textarea>
<script>
  new MarkdownEditor('#markdown-editor');
</script>
Opción 3: explícita theme option (anula todo)
javascript
1
2
3
new MarkdownEditor('#markdown-editor', {
    theme: 'dark'
});

Tema 🎨 personalizado a través de variables CSS

Puede personalizar completamente el aspecto del editor anulando sus variables CSS en el .markdown-editor-wrapper elemento o cualquier [data-theme] selector. Todos los colores utilizan el Espacio de color OKLCH para obtener resultados perceptualmente uniformes.

VariablePropósitoLight / DefaultDark default
--color-baseFondo del editoroklch(100% 0 0)oklch(10.9% 0 0)
--color-on-baseColor de texto primariooklch(22% 0 0)oklch(98% 0 0)
--color-primaryAcento principal (barra de herramientas activa, enlaces)oklch(51.1% .262 277)oklch(66.4% .184 286)
--color-on-primaryTexto en superficies de color primariooklch(96.2% .018 272)oklch(10% .01 270)
--color-secondaryAcento secundariooklch(59.1% .293 323)oklch(65% .18 220)
--color-accentDestacar acento (código en línea, cursiva)oklch(54.1% .281 293)oklch(75% .18 50)
--color-neutralSuperficies neutras (bordes, divisores)oklch(15% 0 0)oklch(85% 0 0)
--color-errorColor de estado de erroroklch(57.7% .245 27)oklch(60% .22 30)
--border-radiusRedondeo de esquinas del marco del editor0.25rem
Ejemplo de tema personalizado

Anular cualquier variable en .markdown-editor-wrapper después de que el editor inicializa, o define una [data-theme] en tu hoja de estilo:

css
 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;
}
javascript
new MarkdownEditor('#markdown-editor', { theme: 'brand' });

Variables

Añade un menú desplegable de la barra de herramientas para insertar marcadores de posición. Útil cuando la persona que escribe un documento no es el desarrollador que definió la sintaxis del marcador de posición, como con las plantillas de correo electrónico, factura o contrato: eligen un nombre legible y se inserta la sintaxis correcta para ellos.

javascript
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'
    ]
});

La herramienta se configura en línea en el toolbar matriz, por lo que su posición entre los otros botones depende de usted. El botón enumera las etiquetas y, al hacer clic en una, inserta su value en el cursor, reemplazando cualquier selección. No se representa nada si la lista está vacía.

Valores de muestra en la vista previa

Una entrada dada un sample muestra esa muestra en la vista previa, mientras que el área de texto mantiene el marcador de posición real. Las entradas sin uno aparecen tal como están escritas, por lo que puedes mezclar ambas.

javascript
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}}

Agrupamiento

dar una entrada items en lugar de un value para representar una sección con encabezado. Las entradas agrupadas y planas se pueden combinar en una lista.

javascript
 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}}' }
    ]}
]}

Cosas que debes saber

  • El editor nunca resuelve variables. Inserta texto y su aplicación sustituye valores reales más adelante, normalmente en el lado del servidor. sample solo afecta lo que muestra la vista previa.
  • Las etiquetas se muestran como texto sin formato, por lo que el marcado en una etiqueta nunca se representa.
  • Las entradas con formato incorrecto se omiten en lugar de descartarse, y el botón no se representa en absoluto cuando no se configura nada utilizable.

🖋 Representador personalizado

La vista previa se representa con marcado y desinfectado con DOMPurificar. Ambos pueden ser reemplazados. Úselo cuando su aplicación ya muestre rebajas con otra biblioteca y desee que la vista previa coincida exactamente con la producción.

Ambas opciones son funciones simples que toman una cadena y devuelven una cadena, por lo que cualquier analizador y desinfectante funciona.

javascript
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 aún se ejecuta en la salida, por lo que mantiene la protección XSS sin configurar nada.

desinfectante personalizado

Solo es necesario cuando el valor predeterminado elimina algo que emite su renderizador. DOMPurify elimina <iframe> de forma predeterminada, por lo que las incrustaciones de vídeo deben permitirse explícitamente. DOMPurify debe importarse en su propio código, ya que la copia incluida con el editor es interna.

javascript
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 una etiqueta de script en lugar de un paquete, cargue DOMPurify junto con el editor:

html
1
2
<script src="https://cdn.jsdelivr.net/npm/dompurify"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script>

Cosas que debes saber

  • Solo vista previa. El formato en vivo del modo híbrido utiliza un procesador interno separado y no se ve afectado.
  • Las listas de tareas necesitan compatibilidad con complementos. Las casillas de verificación en las que se puede hacer clic se encuentran buscando input[type="checkbox"] en la salida, por lo que se necesita rebajas markdown-it-task-lists. El editor registra una advertencia si detecta la sintaxis de la lista de tareas y no hay casillas de verificación.
  • Ambos deben ser sincrónicos y devolver una cadena. Un async función escribe [object Promise] en la vista previa.
  • Reemplazar el desinfectante reemplaza su protección. Un paso como html => html desactiva la desinfección por completo y solo es seguro para contenido totalmente confiable.

🎨 Diseño de elementos internos

Las variables CSS cubren la mayoría de los temas porque heredan, por lo que establecer una en .markdown-editor-wrapper Llega a la barra de herramientas, botones, vista previa y pie de página. Busque un nombre de clase sólo cuando ninguna variable exponga lo que necesita.

css
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);
}

referencia de clase

Estos nombres de clases son estables y seguros para diseñarlos.

ClaseElemento
.markdown-editor-wrapperContenedor exterior que envuelve todo el editor.
.toolbarBarra de herramientas encima del área de edición
.markdown-btnBotón de barra de herramientas individual
.preview-btnEl botón de alternancia de vista previa/pantalla completa
.editor-layoutCuadrícula que contiene el área de edición y vista previa una al lado de la otra
.textarea-wrapperEnvoltura alrededor del área de edición
.editor-textareaEl elemento de área de texto subyacente
.display-layerCapa de formato renderizada, solo modo híbrido
.preview-wrapperColumna de vista previa
.preview-contentRebaja renderizada dentro de la vista previa
.editor-footerBarra de estado debajo del editor
.find-replace-panelBuscar y reemplazar panel
css
 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);
}

Dirigirse a un editor

Con varios editores en una página, apunte a uno de ellos con data-editor. El contenedor refleja la identificación de su <textarea>, entonces <textarea id="notes"> te da [data-editor="notes"]. La identificación en sí permanece en el área de texto, por lo que getElementById sigue funcionando.

css
1
2
3
4
/* one editor only */
[data-editor="notes"] {
    --border-radius: 0;
}

🪟 Pantalla completa y capas (índice z)

En pantalla completa el editor usa z-index: 10000, que borra las capas que la mayoría de los marcos de UI reservan para encabezados fijos y superposiciones. Si un encabezado fijo o una barra lateral aún cubre el editor, aumente este valor.

Anularlo con --mte-fullscreen-z-index. El valor se aplica solo en pantalla completa y se hereda, por lo que establecerlo en cualquier antecesor cubre todos los editores que se encuentran debajo de él.

css
 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 contenido

Una de las principales fortalezas de MarkdownEditor es que mantiene la base <textarea> perfectamente sincronizado. Ya sea que esté utilizando un marco de JavaScript moderno o un backend tradicional como Django, PHP o Laravel, el flujo de trabajo sigue siendo simple y nativo.

Contenido de lectura y escritura

1. El estilo nativo (recomendado)

Debido a que el editor mejora un área de texto estándar, puede utilizar métodos DOM familiares. Esta es la forma más rápida de interactuar con sus datos sin necesidad de aprender una nueva API.

javascript
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 una referencia variable

Si tiene una referencia al elemento de área de texto, puede usarla directamente: no se necesita una API específica de la biblioteca.

javascript
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. Configuración del contenido inicial del lado del servidor

La forma recomendada de configurar el contenido inicial es directamente en el <textarea> HTML: esto funciona de forma natural con todos los frameworks backend (Django, Laravel, Rails, PHP, etc.) y el editor lo renderiza automáticamente al iniciar.

html
1
2
<!-- Recommended: set content server-side -->
<textarea id="markdown-editor"># Hello World</textarea>

Para leer o actualizar contenido en tiempo de ejecución, use el nativo textarea valor. Llamar editor.render() después de una actualización para actualizar la vista previa y la capa híbrida.

javascript
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. Derribando al editor: destroy()

Llamar editor.destroy() para eliminar el contenedor DOM del editor y restaurar el original <textarea> a su posición en el documento. Útil en aplicaciones de una sola página al desmontar una vista.

javascript
1
2
3
4
const editor = new MarkdownEditor('#markdown-editor');

// Remove the editor and restore the plain textarea
editor.destroy();

Reaccionar a los cambios con onChange

pasar un onChange devolución de llamada para recibir notificaciones sobre cada cambio de contenido. Recibe la cadena de rebajas actual.

javascript
1
2
3
4
5
const editor = new MarkdownEditor('#markdown-editor', {
    onChange(value) {
        console.log('Content changed:', value.length, 'characters');
    }
});

Borrador de guardado automático con localStorage

Usar onChange para guardar un borrador con cada pulsación de tecla. Restáurelo completando previamente el área de texto antes de inicializar el editor.

javascript
 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);
});

Envío de formulario

Porque MarkdownEditor está construido directamente en el nativo <textarea>, es compatible con todos los marcos de backend (Django, Laravel, PHP, Ruby on Rails, etc.) desde el primer momento.

Aquí es donde brilla la filosofía de "los nativos primero". No es necesario sincronizar datos manualmente antes de enviar un formulario. El navegador trata al editor exactamente como un campo de entrada estándar.

html
 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>

Nota: Inicialización obligatoria del complemento MarkdownEditor

Simplemente use un HTML estándar <form>. El name El atributo en el área de texto es lo que su servidor utilizará para identificar el contenido.

🚀 Por qué cambia las reglas del juego para los backends

Dado que el editor conserva el nativo. <textarea> comportamiento, su servidor maneja los datos como una cadena estándar. No se requiere lógica adicional: no preventDefault() y sin manual FormData construcción.

💡 Por qué esta es una "función excelente":

La mayoría de los editores (como Quill, Editor.js, simpleMDE, easyMDE) guardan datos en estructuras JSON complejas. Si un desarrollador los usa, debe reescribir el esquema de su base de datos y su lógica de representación.

Con MarkdownEditor, un desarrollador puede tomar un sitio web antiguo y reemplazarlo por uno simple. <textarea> con su editor, y el backend ni siquiera sabe que cambió. Simplemente recibe el mismo texto sin formato de siempre, pero el usuario obtiene una experiencia 10 veces mejor.

Marco / IdiomaCómo acceder al contenido de Markdown
PHP$_POST['content']
Djangorequest.POST.get('content')
Node.js (Rápido)req.body.content
Laravel$request->input('content')
Ruby on Railsparams[:content]

🖼️ Carga de imágenes avanzada

Manejar la carga de imágenes de forma nativa: en lugar de depender de cadenas Base64 lentas y con mucha memoria, es una ganancia significativa tanto para el rendimiento como para el SEO.

Opciones de configuración

La herramienta de imagen admite una fileInput configuración para manejar cargas directas al servidor.

  • accept: Defina una variedad de formatos de imagen permitidos (por ejemplo, 'webp', 'avif')
  • uploadUrl: especifique el punto final de backend donde File El objeto será enviado a través de POST
  • params: Objeto opcional para enviar datos adicionales (como tokens CSRF, ID de usuario o nombres de carpetas) junto con el archivo de imagen.

Ejemplo de uso (configuración completa)

javascript
 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);

📡 Integración del servidor

La Solicitud

El editor envía un POST solicitar como multipart/form-data. Por defecto, incluye:

  • image_file: El objeto de archivo real
  • image_alt: El texto alternativo ingresado por el usuario.
  • ...más cualquier dato personalizado definido en el params objeto

La respuesta requerida

Para confirmar una carga exitosa e insertar la imagen en el editor, su servidor debe devolver la siguiente estructura JSON:

json
1
2
3
4
{
  "success": true,
  "image_path": "https://cdn.yourdomain.com/uploads/image.webp"
}

Nota: asegúrese de utilizar la clave image_path para la URL de la imagen cargada.

Validación de texto alternativo de imagen (altInput)

Para garantizar que su contenido siga siendo accesible y compatible con SEO, MarkdownEditor aplica la validación de texto alternativo de forma predeterminada.

  • Comportamiento predeterminado: si altInput no está definido, por defecto es { required: true }
  • Hacer cumplir la accesibilidad: se impedirá que los usuarios inserten una imagen hasta que se proporcione una descripción alternativa.

1. Predeterminado (no se necesita configuración)

javascript
1
2
3
4
// Alt text is REQUIRED by default
image: {
  fileInput: { uploadUrl: '/api/upload' }
}

2. Taquigrafía (desactivar validación)

Si desea permitir imágenes sin descripciones, simplemente configure el valor booleano en false.

javascript
1
2
3
image: {
  altInput: false // Users can now skip the alt text field
}

3. Basado en objetos (explícito)

javascript
1
2
3
4
5
image: {
    altInput: {
        required: false // Disables alt text validation — users can skip the alt field
    }
}

Uso de imagen estándar (No fileInput)

Si fileInput no está configurado, el editor utiliza de forma predeterminada un modal simple basado en URL. Esto es ideal si sus usuarios se vinculan principalmente a servidores de imágenes externos.

javascript
1
2
3
4
5
6
7
8
const options = {
  toolbar: [
    'link',
    'image',
    'preview'
  ],
}
const editor = new MarkdownEditor('#markdown-editor', options);

💡 ¿Por qué utilizar parámetros?

En frameworks como Laravel o Django, no puedes cargar archivos sin un token CSRF. Al agregar _token hacia params objeto, su solicitud pasará a través del middleware de seguridad del backend sin problemas, manteniendo la filosofía de "Lógica Cero" para sus controladores del lado del servidor.

⌨️ Atajos de teclado

Las acciones de formato comunes se pueden activar directamente desde el teclado sin tocar la barra de herramientas. Cada acceso directo también se muestra en la información sobre herramientas del botón de la barra de herramientas correspondiente.

AtajoAcción
Ctrl + B  /  ⌘ BAlternar Negrita
Ctrl + I  /  ⌘ IAlternar Cursiva
Ctrl + K  /  ⌘ KInsertar enlace
Ctrl + `  /  ⌘ `Alternar en línea Code
Ctrl + Shift + S  /  ⌘ ⇧ SAlternar Tachado
Ctrl + 1  /  ⌘ 1Título 1
Ctrl + 2  /  ⌘ 2Título 2
Ctrl + 3  /  ⌘ 3Título 3
Ctrl + L  /  ⌘ LAlternar lista de viñetas
Ctrl + Z  /  ⌘ ZDeshacer
Ctrl + Shift + Z  /  ⌘ ⇧ ZRehacer
TabSangrar líneas seleccionadas
Shift + TabEliminar sangría de líneas seleccionadas
Ctrl + F  /  ⌘ FAbrir panel de búsqueda
Ctrl + H  /  ⌘ HAbrir el panel Buscar y reemplazar
Ctrl + Shift + F  /  ⌘ ⇧ FAlternar vista previa a pantalla completa
F11Alternar vista previa a pantalla completa
EscapeCerrar el panel Buscar/Salir de la vista previa en pantalla completa

🔍 Buscar y reemplazar

Un panel integrado de búsqueda y reemplazo está disponible dentro del editor: no se necesita extensión del navegador ni herramienta separada.

  • Prensa Ctrl + F (o ⌘ F) para abrir el panel Buscar
  • Prensa Ctrl + H (o ⌘ H) para abrir el panel Buscar y reemplazar
  • La búsqueda no distingue entre mayúsculas y minúsculas y muestra un contador de partidos en vivo (por ejemplo, 3 de 12)
  • Navegar partidos con los botones ▲ / ▼ o Enter / Shift + Enter
  • Reemplazar reemplaza la coincidencia resaltada actual; Reemplazar todo reemplaza cada ocurrencia a la vez
  • Prensa Escape para cerrar el panel y devolver el foco al editor

El panel flota en la esquina superior derecha del área de contenido del editor y no interrumpe la escritura.

Características

🔌 Integración de formularios nativos

Funciona exactamente como un estándar <textarea>. Sin API complejas: solo use el value o name atributo. "Simplemente funciona" con envíos de formularios HTML estándar en PHP, Django o Node.js.

🖼️ Carga de imágenes avanzada

Configure las cargas del servidor nativo a través de API. Evite cadenas Base64 pesadas para garantizar cargas de página más rápidas y un SEO superior al alojar imágenes en su propia CDN.

🔀 Modos híbridos y simples

Cambiar entre un Híbrido (WYSIWYG) experiencia en edición visual o Rebaja simple Modo para una sensación de codificación tradicional.

🚀 Alto rendimiento

A 53 KB comprimidos paquete (252 KB minimizado, CSS incluido) optimizado para "contenido pesado". Maneja documentos y archivos de gran tamaño sin ningún retraso en la entrada ni caída del rendimiento. Actualizaciones de vista previa sin rebote, cálculos de estilo en caché y manejo del teclado sin conflictos: por lo que Tab y Enter siempre hacen exactamente una cosa.

🌍 Soporte RTL incorporado

Soporte nativo para idiomas de derecha a izquierda como árabe, urdu y farsi. Perfecto para crear aplicaciones accesibles globalmente.

✨ Resaltado de sintaxis

Legibilidad mejorada con código claro y formato de rebajas.

🌙 Temas adaptativos

Incluye soporte automático para el modo oscuro. Se sincroniza con la configuración de su sistema o con el UI de mermelada de frutas Biblioteca para una experiencia visual perfecta.

📝 Edición inteligente

Continuación automática de listas estilo GitHub para listas ordenadas, listas desordenadas y listas de verificación: presione Enter y el editor continúa el patrón. Se puede hacer clic en las casillas de verificación en el panel de vista previa y se sincronizan con la fuente de rebajas al instante.

📱 Totalmente receptivo

Una interfaz de usuario fluida y pensada para dispositivos móviles que se adapta perfectamente a computadoras de escritorio, tabletas y teléfonos inteligentes para editar sobre la marcha.

📦 Soporte universal

compatible con ESM, UMD, CommonJS y IIFE. Funciona de inmediato a través de CDN (<script src>), npm o cualquier paquete (Vite, webpack, Rollup): no se necesita configuración adicional.

♿ Accesible por defecto

Compatibilidad total con ARIA integrada: punto de referencia de la barra de herramientas, región de vista previa etiquetada, botones fáciles de leer en pantalla, aria-pressed en la palanca de vista previa, disabled y aria-disabled en herramientas inactivas y restauración correcta del enfoque cuando se cierran los modales.

🛡️ Cero conflictos de CSS

Los estilos del editor tienen un alcance completo para .markdown-editor-wrapper. La verificación previa global de Tailwind está excluida, por lo que el editor convive de forma segura junto con Bootstrap, Tailwind o cualquier otro marco sin alterar sus estilos.

⌨️ Atajos de teclado

Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+`, Ctrl+Shift+S: acciones de formato comunes sin tocar el mouse. Cada acceso directo se muestra en la información sobre herramientas del botón de la barra de herramientas.

🔍 Buscar y reemplazar

Prensa Ctrl+F encontrar o Ctrl+H para abrir buscar y reemplazar. Búsqueda que no distingue entre mayúsculas y minúsculas con contador de partidos en vivo, navegación siguiente/anterior, reemplazo único y reemplazar todo: sin salir del editor.

🔒 Vista previa segura XSS

La vista previa renderizada se desinfecta mediante DOMPurificar antes de escribirse en el DOM. Las etiquetas de script, los controladores de eventos en línea y las URL maliciosas en las entradas de rebajas diseñadas se eliminan automáticamente: no se requiere configuración.

▶️ Vista previa en tiempo real

Vea su descuento renderizado instantáneamente a medida que escribe.

🔗 Fácil integración

Integre perfectamente en cualquier proyecto web con una configuración mínima.

🛠️ Barra de herramientas personalizable

Configure y reordene dinámicamente las opciones de la barra de herramientas como negrita, cursiva y más.

Ejemplo de configuración completa

Utilice este ejemplo completo para inicializar MarkdownEditor con todas las funciones principales, incluido el orden personalizado de la barra de herramientas y el manejo avanzado de carga de imágenes.

javascript
 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();

¿Encontró esto útil?

Una estrella de GitHub ayuda a otros desarrolladores a descubrir el editor. es parte de mermelada de frutas. Una estrella allí también ayuda.

Estrella en GitHub
Edit page

Última actualización: