Extender el editor
Modelo de tres tiers
Sección titulada «Modelo de tres tiers»| Tier | Qué | Cuándo se registra | Cómo |
|---|---|---|---|
| 1 — UI | Bubble menu, dropdowns, botones | Runtime | getEditorInstance() |
| 2 — Schema | Imágenes, tablas, embeds, mentions con búsqueda | Al crear el editor | features / customExtensions |
| 3 — Preset | Empaquetar un conjunto de features | Al crear el editor | Nuevo preset en presets.ts |
Tier 1 — Solo UI (sin cambios de schema)
Sección titulada «Tier 1 — Solo UI (sin cambios de schema)»Puedes manipular el editor en runtime obteniendo la instancia de Tiptap:
const el = document.querySelector('co-rich-text-editor');const editor = await el.getEditorInstance();
// Ejecutar comandoseditor.chain().focus().toggleBold().run();editor.chain().focus().toggleHeading({ level: 2 }).run();editor.chain().focus().setColor('#02A270').run();Ejemplo: Bubble menu custom (Vue)
Sección titulada «Ejemplo: Bubble menu custom (Vue)»<template> <div v-if="showBubble" :style="bubbleStyle" class="my-bubble"> <button @click="toggleBold">B</button> <button @click="toggleItalic">I</button> </div> <CoRichTextEditor ref="editorRef" preset="task" @co-ready="onReady" /></template>
<script setup>import { ref } from 'vue';import { CoRichTextEditor } from '@prolibu-suite/cobalt-vue';
const editorRef = ref();const editor = ref(null);const showBubble = ref(false);const bubbleStyle = ref({});
async function onReady() { editor.value = await editorRef.value.getEditorInstance(); editor.value.on('selectionUpdate', updateBubble);}
function updateBubble() { const { from, to } = editor.value.state.selection; if (from === to) { showBubble.value = false; return; } const coords = editor.value.view.coordsAtPos(from); bubbleStyle.value = { position: 'fixed', top: `${coords.top - 40}px`, left: `${coords.left}px` }; showBubble.value = true;}
function toggleBold() { editor.value.chain().focus().toggleBold().run(); }function toggleItalic() { editor.value.chain().focus().toggleItalic().run(); }</script>Tier 2 — Nuevos nodos/marks
Sección titulada «Tier 2 — Nuevos nodos/marks»Ruta A: features (built-in)
Sección titulada «Ruta A: features (built-in)»<!-- Activar variables y menciones en preset task --><co-rich-text-editor preset="task" features='{"variables": true, "mentions": true}'></co-rich-text-editor>Ruta B: customExtensions (extensiones propias)
Sección titulada «Ruta B: customExtensions (extensiones propias)»import { Table } from '@tiptap/extension-table';import { TableRow } from '@tiptap/extension-table-row';import { TableHeader } from '@tiptap/extension-table-header';import { TableCell } from '@tiptap/extension-table-cell';
const editor = document.querySelector('co-rich-text-editor');editor.customExtensions = [ Table.configure({ resizable: true }), TableRow, TableHeader, TableCell,];Variables y Menciones
Sección titulada «Variables y Menciones»VariableNode — nodo atómico <span data-variable="contact.firstName">:
// Insertar variable programáticamenteconst editor = await el.getEditorInstance();editor.chain().focus().insertVariable('contact.firstName', 'Nombre').run();MentionNode — nodo atómico <span data-mention-id data-mention-label>:
editor.chain().focus().insertMention('user-123', 'Juan Pérez').run();Imágenes
Sección titulada «Imágenes»Activadas por preset="document" o features='{"image": true}'. Tres modos de inserción:
- Botón de toolbar — abre un formulario inline (popover) para insertar por URL
- Paste — pegar imagen del portapapeles
- Drop — drag & drop de archivo
Para subir a CDN en lugar de usar base64:
const editor = document.querySelector('co-rich-text-editor');editor.imageUpload = async (file) => { const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body: formData }); const { url } = await res.json(); return url;};Tier 3 — Crear un nuevo preset
Sección titulada «Tier 3 — Crear un nuevo preset»- Agregar el nombre al tipo
RichTextPresetentypes.ts - Configurar
STARTERKIT_CONFIG,PRESET_TOOLBAR,PRESET_MAXLENGTHy extras enpresets.ts - Agregar whitelist de sanitización en
sanitizer.ts→SANITIZE_CONFIGS - Replicar whitelist en
co-rich-text-viewer.tsx→ALLOWED_TAGS - Agregar tests en
presets.test.tsysanitizer.test.ts
Checklist para nuevos nodos
Sección titulada «Checklist para nuevos nodos»- Registrar la extensión en
getExtensions(preset ocustomExtensions) - Permitir sus tags/attrs en
sanitizer.tsY en el viewer - Estilizar en
co-rich-text-editor.css(.co-rte-content .ProseMirror …) yco-rich-text-viewer.css - (Opcional) Agregar botón en
toolbar-config.ts+ handler enco-rich-text-toolbar.tsx - Ejecutar cadena de rebuild + tests