CKEditor Document Outline/Esquema de documento GRATIS

Video thumbnail

El document outline en CKEditor, que es justamente esta característica que ves aquí, es parte de la versión premium (es decir, es de pago). Básicamente, se trata de este árbol lateral que tenemos a la izquierda, que en español podríamos traducir como esquema de documento.

Por qué lo necesitaba (y por qué no lo pago)

Para mí, esta es una característica bastante deseada. Recuerda que en otro video te comenté que, finalmente, por los dioses, logré exportar mis libros directamente desde mi aplicación, sin tener que pasar por Word (que en realidad nunca usé) o Google Docs. Tampoco tengo que depender del plugin de Kindle que me permitía exportar a formato .pub.

Ahora tengo todo el contenido en un solo lugar, y lo puedo exportar directamente desde ahí. Por eso quiero pulir más esta herramienta que utilizo por detrás como base: el SecEditor, que uso para contenido enriquecido.

Aquí puedes ver, por ejemplo, uno de los capítulos de un libro que todavía no tiene fecha de publicación, donde enseño a crear una tienda en línea con Laravel. Como puedes ver, tengo mucho contenido —y créeme, tengo capítulos mucho más largos que este.

Implementación del esquema de documento hecho en casa

Lo que hice fue muy simple. Te lo voy a mostrar usando esta versión local de mi aplicación. Recuerda que está hecha en Laravel, aunque esto lo puedes adaptar fácilmente a cualquier sistema que use CKEDITOR.

Entonces, dentro de la aplicación, tengo el CKEDITOR ya instalado. Si quieres ver cómo instalarlo, tengo otros videos en mi canal donde explico paso a paso cómo generar el build con la herramienta que viste en pantalla, e integrarlo a tu proyecto.

Para hacerlo flexible, usé un componente de Laravel (aunque también puedes usar una simple vista). Básicamente, lo importo, y lo que ves aquí es el botoncito negro. Yo le puse una "X", pero puedes ponerle lo que quieras:

    <div x-data="{ open: false }">>
        <button class="top-0 p-0 fixed z-40 btn-sm h-10 bg-gray-800 transform -translate-x-8" @click="open = !open">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-9 w-9 text-black" fill="none" viewBox="0 0 24 24"
                stroke="#FFF" stroke-width="2">
                <path v-if="open" stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
            </svg>
        </button>

        <div class="top-5 left-2 border shadow-md card max-w-96 fixed z-30 overflow-auto "
            :class="{ '-translate-x-96': !open }">
            <div id="tocCkeditor">
                <!-- Aquí se generará la tabla de contenido -->
            </div>
        </div>
    </div>

Uso AlpineJS (que Laravel ya incluye por defecto). Si usas Vue, también es prácticamente igual.

Declaro una variable que indica si el panel está abierto o cerrado, y con eso le aplico una transición translateX para ocultarlo o mostrarlo. También agrego overflow: auto para que crezca si es necesario.

Generando el esquema con JavaScript

El contenido del esquema lo genero con JavaScript. Es muy sencillo:

function generateTableOfContents() {
    const editorContainer = document.getElementById('editor');
    const tocContainer = document.getElementById('tocCkeditor');
    const headers = editorContainer.querySelectorAll('h1, h2, h3');

    tocContainer.innerHTML = ''; // Limpiar TOC

    headers.forEach((header, index) => {
        if (!header.id) {
            // Generar ID único
            const id = `heading-${index}`;
            header.id = id;
        }

        const link = document.createElement('a');
        link.href = `#${header.id}`;
        link.textContent = header.textContent;
        link.style.display = 'block';

        // Estilo opcional según nivel
        if (header.tagName === 'H2') link.style.marginLeft = '10px';
        if (header.tagName === 'H3') link.style.marginLeft = '20px';

        tocContainer.appendChild(link);


    });

La lógica busca los encabezados H1, H2, y H3. A los encabezados les asigno un margen para simular tabulación y los itero todos.

También genero un identificador (id) para cada uno.

Lo que hago es modificar directamente el contenido HTML del editor, que está dentro de un div editable. SecEditor no elimina estas etiquetas, a diferencia de otros HTML no permitidos.

Leo el texto del encabezado (el título), y genero un enlace con ese contenido. Le aplico display: block, defino el id, y lo enlazo con una navegación HTML clásica (<a href="#id">).

Estos enlaces son exactamente lo que ves aquí. Si revisas el HTML, son enlaces que apuntan a #algo. Ese “algo” es el token generado con JavaScript.

La doble instancia de CKEditor

Un detalle importante que me costó encontrar es que, por alguna razón misteriosa, SecEditor genera dos veces el mismo contenido. Tiene un div con id="editor" que no es el documento real, y otro div (que sí es el documento válido) donde realmente está el contenido.

El falso tiene display: none, y aunque no afecta nada, al tener el mismo id duplicado, rompe la navegación, así que lo elimino:

setTimeout(() => {
    const editor = document.getElementById('editor');
    if (editor && editor.style.display === 'none') {
        // editor.style.display = 'block';
        document.getElementById('editor').remove()
    }
}, 2000);

Así que nada, si tú también quieres tener tu propio esquema de documentos online, sin pagar por el plugin premium de Documenter, ya sabes cómo hacerlo: con un poquito de Alpine, un componente en Laravel, algo de JavaScript para leer encabezados, y enlaces HTML clásicos.

Funciona perfecto, no cuesta nada, y lo puedes personalizar como te dé la gana.

Acepto recibir anuncios de interes sobre este Blog.

Te muestro como puedes generar un esquema de documentos en CKEditor totalmente GRATIS

- Andrés Cruz

In english