Notxor tiene un blog

Defenestrando la vida

Tutorial para blogear como Notxor

Notxor
2022-11-17

A raíz de haber publicado en Mastodon que había añadido alguna característica más al código que soporta este blog. Me han preguntado cómo funciona y si puedo explicar un poco el flujo de trabajo. No hay nada más sencillo, lo único que hago es aprovecharme de toda la funcionalidad que ya proporciona org-mode para generar html. De esta manera, conseguir un sitio estático es sencillo. Pero empezaré por el principio y explicaré un poco el código.

Por cierto, el código lo podéis encontrar en Codeberg y descargarlo libremente. En el README explico en qué está basado y algunas modificaciones que le he ido haciendo según lo he ido necesitando. Concretamente las adicciones que le hice al código de org-static-blog durante el tiempo que estuvo sin mantenimiento. Después ha seguido evolucionando y añadiendo características como el hacer ficheros RSS por etiqueta, que es algo que yo hice también en su día. Por lo tanto, seguramente, os sea más cómodo instalar dicho paquete y configurarlo en vuestro Emacs. Así empecé yo este blog. Luego el código se ha ido modificando poco a poco para ajustarlo más a mis necesidades y ya no uso el paquete de ELPA.

Las características que añadí por mi cuenta a esa versión antigua de org-static-blog son principalmente tres:

  1. La posibilidad de navegar el blog completo, mediante la utilización de enlaces relativos, en un servidor de pruebas, como el que proporciona Emacs con el comando httpd-start. En aquellos tiempos no se podía hacer, se tenía que publicar directamente.
  2. La generación de ficheros rss.xml por etiquetas.
  3. Que tenga en cuenta y muestre el autor del artículo. Esto último ha sido a petición, pero ya que está lo dejo, en mi blog no tiene mucho sentido.

El resto es sólo generación de sitio estático puro y duro, pero en lugar de utilizar org-export se utilizan las funciones de publicación del código.

Además hay que recordar que mi código no es un paquete. Hay que cargarlo con (load-file ...), todo el código para generar los html está contenido en el fichero notxor-blog.el. Pero es posible que necesites más código que no está en mi repositorio. Ahí sólo encontrarás el código elisp y el archivo estilos.css que es el que da formato y color a las páginas. Pero además utilizo un par de archivos javascript, para el coloreo de sintaxis y el formateo de fórmulas matemáticas, que luego comentaré.

Como explicar en abstracto cómo funciona el código y cómo es la estructura de directorios, voy a poner el ejemplo que tengo más a mano: este blog. ¿Qué estructura tiene el sitio? ¿Dónde pongo los archivos auxiliares? ¿Dónde pone los generados la herramienta?

Trasteando con los archivos auxiliares

Primero, antes de entrar en materia, vamos a echar un vistazo a la estructura de directorios:

.
├── 2017
│   ├── ...
│   └── index.html
├── 2018
│   ├── ...
│   └── index.html
├── 2019
│   ├── ...
│   └── index.html
├── 2020
│   ├── ...
│   └── index.html
├── 2021
│   ├── ...
├── 2022
│   ├── ...
│   └── index.html
├── about
│   └── index.html
├── archivo.html
├── etiquetas.html
├── favicon.png
├── index.html
├── medios
│   ├── css
│   │   └── estilos.css
│   ├── fonts
│   │   ├── Cantarell-Bold.otf
│   │   ├── EBGaramond12-Regular.ttf
│   │   ├── FiraCode-Retina.ttf
│   │   └── Sugar & Vinegar.ttf
│   ├── img
│   │   ├── abatar.png
│   │   ├── ...
│   │   ├── mastodon-icono.png
│   │   └── search.png
│   ├── index.html
│   └── js
│       ├── es
│       │   ├── ...
│       ├── jquery-latest.min.js
│       ├── main.js
│       └── MathJax.js
├── rss.xml
└── tags
    ├── acoso
    │   ├── index.html
    │   └── rss.xml
    ├── ...
    └── zettelkasten
        ├── index.html
        └── rss.xml

1301 directories, 3521 files

Puede parecer un poco complejo, pero no lo es tanto. Los directorios 2017-2022 los crea la herramienta automáticamente cuando publicas un artículo lo guardará en una estructura de directorios tal que /año/mes/día/título.

Justo debajo de esos directorios, encontramos otro que se llama about, pero no es obligatorio llamarlo así, simplemente se pretende tener un espacio donde puedes presentar el blog o cualquier otra tontería (como tengo yo). Luego en el preámbulo de la página, donde está el menú se enlaza a ese sitio con un simple enlace html:

<li><a href=\"/about/\">Acerca de...</a></li>

Lo podéis llamarlo como queráis, pero recordad que siendo un enlace a un directorio, cargará automáticamente index.html.

Lo siguiente es el fichero archivo.html. Eso lo genera la herramienta y es un listado de todos los artículos publicados. Su nombre se puede modificar, modificando una variable:

(defcustom notxor-blog-archive-file "archivo.html"
  "File name of the list of all blog posts.
The archive page lists all posts as headlines."
  :type '(string)
  :safe t)

He visto también blogs que prefieren cargar directamente el archivo general. La lista son los títulos de los artículos y sus fechas, pero cuando se alarga la lista es un poco lento de carga. Prefiero el índice que muestra sólo unas pocas entradas. Esas se pueden configurar en la variable:

(defcustom notxor-blog-index-length 10
  "Number of articles to include on index page."
  :type '(integer)
  :safe t)

También está el fichero etiquetas.html. Este es un listado de las etiquetas y los archivos que las portan. Lo genera automáticamente la herramienta y también se pude configurar el nombre modificando una variable:

(defcustom notxor-blog-tags-file "etiquetas.html"
  "File name of the list of all blog posts by tag.
The tags page lists all posts as headlines."
  :type '(string)
  :safe t)

El fichero index.html es el índice de portada del blog. Lo genera la herramienta y también se puede configurar el nombre con una variable:

(defcustom notxor-blog-index-file "index.html"
  "File name of the blog landing page.
The index page contains the most recent
`notxor-blog-index-length` full-text posts."
  :type '(string)
  :Safe' t)

En este último caso está así hecho por si se quiere poner otra página de portada al cargar la URL principal y que haya un enlace al inicio del blog. He visto como otros usuarios, en blogs personales, más curriculares, que utilizan una carátula a modo de presentación o currículo y desde ahí, enlazan el blog. En mi caso, como no utilizo el blog para esos menesteres, prefiero que se muestre el índice directamente.

Después encuentras el directorio medios que dentro tiene más subdirectorios interesantes:

  • Un directorio para la hoja de estilos css.
  • Un directorio donde guardar las fuentes que utiliza el sitio.
  • Un directorio para meter el poco javascript que usa la página.

Voy a empezar por éste último. El javascript que utilizo es muy poco, fundamentalmente para dos cosas: el visionado de fórmulas matemáticas con MathJax.js y el coloreado de sintaxis, y también para los botones de paypal y LiberaPay. Por supuesto, cada uno es libre de si utilizar o no, las librerías js que quiera y considere oportuno. Para cargarlas, las llamo en otra variable:

(defcustom notxor-blog-page-postamble
  "<div>
    <script src=\"/medios/js/jquery-latest.min.js\"></script>
    <script src=\"https://polyfill.io/v3/polyfill.min.js?features=es6\"></script>
    <script id=\"MathJax-script\" async src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script>
    <script src=\"/medios/js/main.js\"></script>
    <div class=\"footer\">
        <div class=\"redes\">
            <p>
                <a rel=\"me\" href=\"https://masto.rocks/web/@Notxor\" target=\"_blank\"><img src=\"/medios/img/mastodon-icono.png\" width=\"24px\" float=\"left\" /></a>
                <a rel=\"me\" href=\"https://masto.nobigtech.es/@Notxor\" target=\"_blank\"><img src=\"/medios/img/mastodon-icono.png\" width=\"24px\" float=\"left\" /></a>
                <a href=\"https://diasp.org/people/15172490a88f01339a3f782bcb452bd5\" target=\"_blank\"><img src=\"/medios/img/diaspora-icono.png\" width=\"24px\" float=\"left\" /></a>
            </p>
        </div>
        <p>Generado con <code>notxor-blog</code> en <i>Emacs</i></p>
        <p>
            <img style=\"display: inline-block;\" src=\"/medios/img/cc-by-nc-sa.png\" align=\"middle\" />
               <br />2012 - <span id=\"footerYear\"></span> <a href=\"mailto:notxor@nueva-actitud.org\">Notxor</a>
            &nbsp;&nbsp;-&nbsp;&nbsp;
            Powered by <a href=\"https://codeberg.org/Notxor/org-static-blog\" target=\"_blank\">notxor-blog</a>
            <script type=\"text/javascript\">document.getElementById(\"footerYear\").innerHTML = (new Date()).getFullYear();</script>
        </p>
        <table>
            <tr>
                <td>
                    <b>Donar por Paypal</b>
                </td>
                <td>
                    <b>Donar por Liberapay</b>
                </td>
            </tr>
            <tr>
                <td>
                    <!-- Inserción de botón pypal -->
                    <form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_top\">
                        <input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" />
                        <input type=\"hidden\" name=\"hosted_button_id\" value=\"29VXCW8SRNJ78\" />
                        <input type=\"image\" src=\"https://www.paypalobjects.com/es_ES/ES/i/btn/btn_donateCC_LG.gif\" border=\"0\" name=\"submit\" title=\"PayPal - Donar online de forma segura.\" alt=\"Botón Donar con PayPal\" />
                        <img alt=\"\" border=\"0\" src=\"https://www.paypal.com/es_ES/i/scr/pixel.gif\" width=\"1\" height=\"1\" />
                    </form>
                </td>
                <td>
                    <!-- Inserción de botón liberapay -->
                    <script src=\"https://liberapay.com/Notxor/widgets/button.js\"></script>
                    <noscript>
                        <a href=\"https://liberapay.com/Notxor/donate\">
                            <img alt=\"Donate using Liberapay\" src=\"https://liberapay.com/assets/widgets/donate.svg\"></a>
                    </noscript>
                </td>
            </tr>
        </table>
    </div>
</div>"
  "HTML to put after the content of each page."
  :type '(string)
  :safe t)

He querido pone completo todo el final de la página. En las primeras líneas se aprecia la llamada a las librerías establecidas. La única que no está accesible en la red es main.js que hace unas pequeñas conversiones:

$(document).ready(function() {
    /*******************************************************************
     * 1. replace css class "src" and "example" with "prettyprint", for
     *    prettify.js to render
     * 2. replace detail language css class, e.g. "src-scheme" to
     *    "lang-scheme" per the description of prettify.js
     ******************************************************************/
    var $blocks = $('pre.src');
    $blocks.each(function(index) {
        var self = $(this);
        var classes = self.removeClass('src').attr('class').split(/\s+/);
        $.each(classes, function(idx, cls) {
            if (cls.substring(0, 4) === 'src-') {
                var lang = cls.substring(4);
                self.removeClass(cls).addClass('lang-' + lang);
            }
        });
        self.addClass('prettyprint');
    });
    $('pre.example').removeClass('example').addClass('prettyprint');

    /*******************************************************************
     * 1. remove all org exported line number spans
     * 2. add css class "linenums" to code block per the description of
     *    prettify.js
     ******************************************************************/
    var $lines = $('span.linenr');
    var $linedBlocks = $lines.parent();
    $lines.remove();
    $linedBlocks.each(function(index) {
        $(this).addClass('linenums');
    });

    /*******************************************************************
     * pretty print all code blocks
     ******************************************************************/
    prettyPrint();
});

Hace un tiempo tenía activados los comentarios y se gestionaban con una herramienta libre que se llama hashover. El lugar del código desde donde se pueden incluir llamadas a esa u otras herramientas es otra variable:

(defcustom notxor-blog-post-comments
  "<section>
      <h1>Comentarios</h1>
      <blockquote>
          <p>Debido a algunos ataques mailintencionados a través de la
              herramienta de comentarios, he decidido no proporcionar
              dicha opción en el <i>Blog</i>. Si alguien quiere comentar
              algo, me puede encontrar en <a rel=\"me\" href=\"https://masto.rocks/web/@Notxor\" target=\"_blank\"><i>Mastodon</i></a>
              y en <a href=\"https://diasp.org/people/15172490a88f01339a3f782bcb452bd5\" target=\"_blank\"><i>Diaspora</i></a>
              con el <i>nick</i> de <b>Notxor</b>.</p>
          <p>También se ha abierto un grupo en Telegram (<a href=\"https://t.me/+S4-CQG2Mewod_lvj\" target=\"_blank\">enlace de invitación al grupo</a>), para usuarios de esa red.</p>
          <p>Disculpen las molestias.</p>
      </blockquote>
  </section>"
  "HTML code for comments to put after each blog post."
  :type '(string)
  :safe t)

En esa variable se pude establecer cualquier código html que necesite la herramienta de comentarios para que se despliegue. Recomiendo HashOver porque es una herramienta libre, la descargas y la pones en tu sitio, no necesita que se creen una cuenta o se logueen en sitios de terceros cuando visiten tu página. El contenido se guarda en texto plano en el directorio /hashover del sitio y no necesitas montar tampoco una base de datos para ello1.

Siguiendo, vemos que también hay un fichero rss.xml donde se actualiza el índice de las últimas entradas. Este se actualiza cada vez que publicas en el blog y hace que tus fieles seguidores tengan noticia de lo último que vas publicando.

Flujo de trabajo

Una vez configurado tu sitio y funcionando todo. Si te gusta reflexionar antes de publicar y corregir y pulir lo que se publicará2 como a mí, sólo tienes que realizar los siguientes pasos.

  1. Tener una idea para un artículo.
  2. Crear un borrador con M-x notxor-blog-create-draft. Ese comando pregunta por el título, genera toda la estructura en un archivo que se guardará en el directorio de borradores. Ese directorio se guarda en la variable:

    (defcustom notxor-blog-drafts-directory "~/blog/borradores/"
      "Directory where unpublished ORG files are stored.
    When publishing, draft are rendered as HTML, but not included in
    the index, archive, tags, or RSS feed."
      :type '(directory))
    

    La estructura con la que comenzará dicho artículo está determinado también en el código y lo puedes ajustar a tu gusto:

    (defun notxor-blog-create-new-post (&optional draft)
      "Create a new blog post.
    Prompts for a title and proposes a file name.  The file name is
    only a suggestion; You can choose any other file name if you so
    choose.  if DRAFT, create a new blog draft."
      (interactive)
      (let ((title (read-string (notxor-blog-gettext 'title))))
        (find-file (concat
                    (if draft
                        notxor-blog-drafts-directory
                        notxor-blog-posts-directory)
                    (read-string (notxor-blog-gettext 'filename)
                                 (concat (format-time-string "%Y-%m-%d-" (current-time))
                                         (replace-regexp-in-string "\s" "-" (downcase title))
                                         ".org"))))
        (insert "#+title:    " title "\n"
                "#+date:     " (format-time-string "<%Y-%m-%d %H:%M>") "\n"
                "#+author:   " "\n"
                "#+filetags: " "\n"
                "#+options:  H:4 num:nil toc:nil \\n:nil ::t |:t ^:{} -:nil f:t *:t <:t")))
    
  3. Normalmente cuando estoy escribiendo en un artículo para el blog suelo cambiar a notxor-blog-mode. Ese modo es simplemente un derivado de org-mode pero me permite definir teclas directas para las acciones más habituales.
  4. Se escribe el borrador, se repasa y se edita las veces que hagan falta hasta que se considere publicable. En ese momento, el artículo debe pasar al directorio correspondiente. En mi caso dicho directorio se encuentra configurado como:

    (defcustom notxor-blog-posts-directory "~/blog/articulos/"
      "Directory where published ORG files are stored.
    When publishing, posts are rendered as HTML, and included in the
    index, archive, tags, and RSS feed."
      :type '(directory))
    
  5. Cuando se ha pasado el borrador a artículo, se invoca a la función notxor-blog-publish. Esta función genera el artículo, actualiza el index.html, archivo.html, rss.xml y actualiza los ficheros html de etiquetas, con el formato: /tags/etiqueta/index.html. Esos archivos los escribe en el directorio que esté configurado como salida:

    (defcustom notxor-blog-publish-directory "~/public_html/"
      "Directory where published HTML files are stored."
      :type '(directory))
    

    En mi caso los mando a un directorio local de mi ordenador. En él hago pruebas de visualización y compruebo que todo funciona: enlaces, imágenes, etc. Suelo hacer un servidor de pruebas desde línea de comandos con:

    python3 -m http.server 8080
    
  6. Si necesito un fichero rss.xml por etiquetas llamo a la función notxor-blog-assemble-rss-by-tag. Igual que en el paso anterior se generaban los índices de las etiquetas, ahora se generan los feed en /tags/etiqueta/rss.xml. Por tanto, puedes pasar la URL https://tags/etiqueta/rss.xml a quien esté interesado sólo en alguno de los temas que toca el blog.
  7. Después de comprobar que todo ha salido bien3 lo subo al sitio con FileZilla.

Posteriormente cuando la gente lo lee y encuentra los errores que se me han colado, vuelvo al original4, arreglo el error y utilizo la función notxor-blog-publish-file. Esto genera sólo el ejecutable y lo pone en su sitio, así que sólo hay que subir un archivo corregido.

Conclusiones

Veo más acertado que utilices una herramienta más general que la mía, que la tengo ya bastante acomodada a mí. En algunos casos se ha quedado atrás con respecto a las características que le han metido a las nuevas versiones de org-static-blog.

No sé si todo lo explicado hasta aquí ha quedado suficientemente claro. Todo lo que he contado es cómo me apaño yo con esto para mantener un blog pequeñito, con sólo html, css y un poquitín de javascript. Como tampoco soy diseñador, pues queda como queda: simple y sin grandes pretensiones.

Lo hago con Emacs porque es la herramienta, si pudiera engancharle una sartén también me haría unos huevos fritos.

Footnotes:

1

Sin embargo, eso no implica que haya un montón de imbéciles intentando hacer saltar tu base de datos inyectando SQL, sin conocimiento de lo que están haciendo. Hasta bloquear un servidor modesto como el que sostiene este humilde blog.

2

Aún así se te escaparán errores.

3

Por mucho cuidado que tengas siempre se te colará algún error.

4

En el directorio articulos no en el de borradores.

Categoría: emacs org-mode

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en esta cuenta de Mastodon, también en esta otra cuenta de Mastodon y en Diaspora con el nick de Notxor.

Si usas habitualmente XMPP (si no, te recomiendo que lo hagas), puedes encontrar también un pequeño grupo en el siguiente enlace: notxor-tiene-un-blog@salas.suchat.org

Disculpen las molestias.