Ideenquelle

Modification hexo

Je viens d’effectuer quelques changements sur le blog, propulsé par hexo. J’avoue que cela m’a pris un peu la tête.

Nouveau layout

Par défaut, hexo prévoit trois layouts.

Il est possible de prévoir de nouveaux layouts. Pour cela, il faut créer un fichier .md dans le dossier /scaffolds. Pour faire simple, un copier/coller de post.md vers link.md permet d’avoir un bon départ :

draft = false
+++
title = '{{ title }}'
date = '{{ date }}'
tags = []
draft = false
+++

Je l’ai modifié pour avoir un champs supplémentaire :

draft = false
+++
title = '{{ title }}'
date = '{{ date }}
layout : {{ layout }}
link = 
draft = false
+++

Rien de bien sorcier en somme.

Maintenant, j’ai modifié mon template /themes/mon-template/layout/_partial/article-excerpt.ejs pour que, lors de la génération de mon résumé d’article, il fasse apparaitre clairement au lecteur qu’il s’agisse d’un lien au moyen d’une icone.

<div class="blog-post">

  <div class="blog-header separate">


    <% if(item.layout == "thought"){ %>
      <blockquote>
        <%- item.content %>
      </blockquote>
    <% } else { %>
      <!-- Title -->
      <h2 class="blog-post-title">
        <% if(item.layout == "link") { %>
          <i data-eva="link-2-outline"></i> <a href="<%- item.link %>"><%- item.title %></a>
        <% } else { %>
          <%- item.title %>
        <% } %>
        </h2>
    <% } %>

Le code n’est pas complet. Il manque la fin de la page. Mais l’idée n’est pas de recréer un tutorial pour créer un thème. L’idée est juste de partager des tips&tricks que je n’ai pas trouvé facilement/simplement.

Article avec une fin de vie

J’ai également créé des posts liés à mon humeur. Toutefois, l’idée n’est pas que ces posts soient éternelles. J’ai envie de leur donner une date limite d’utilisation. J’avoue que j’ai galéré à mettre cela en place. La documentation n’est pas bien faite et il y a peu d’exemples sur ce type de modification. J’ai péché : j’ai fini par utiliser Gemini qui, à mon grand étonnement, a très bien répondu (mieux que ChatGPT sur un autre projet).

Ma première idée était de mettre une variable dans ma configuration indiquant la date limite de vie (30 jours). Lors de la génération des pages, si le layout est d’un type spécifique et si la date est passée, je ne l’affiche pas. Cela se fait dans le template. Il faut gérer la date (et là, j’ai sué, même si c’est affreusement simple rétrospectivement mais la documentation de hexo est trop faible sur ce point-là pour des néophytes) :

<% var diffInDays = (new Date()  - new Date(item.date)) / (1000*60*60*24) %>

<% if(item.layout == "thought" && !(diffInDays > config.lifespan_thought ) ){ %>

<% } else { %>
    <%- partial('_partial/article-excerpt', {item: item, index:true}) %>
<% } %>

Ce qui m’a entravé l’esprit, c’est que hexo permet d’utiliser les dates via moment.js mais que dans le template, la plupart des méthodes ne fonctionnent pas. Bref, j’ai trouvé une solution.

L’idée est plaisante. Toutefois, le post existe toujours en direct. Donc je veux restreindre la création même. Pour cela, il faut intervenir au niveau de la génération. Cool ! Et je fais comment ? J’ai donc créé un dossier /scripts à la racine du projet puis un fichier unpublish_expired_post.js :

const moment = require('moment')
const fs = require('hexo-fs')
const path = require('path');

'use strict';

function deleteFolderRecursive(folderPath) {
  if (fs.existsSync(folderPath)) {
    fs.readdirSync(folderPath).forEach((file) => {
      const curPath = path.join(folderPath, file);
      if (fs.lstatSync(curPath).isDirectory()) {
        deleteFolderRecursive(curPath);
      } else {
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(folderPath);
  }
}

// If a Post is expired,
//  - set the parameter punlished to false
//  - delete the public file
//  - remove the Post from the actual generating list

hexo.extend.filter.register('before_generate', function () {
  const Post = hexo.model('Post');
  if (!Post) return;

  const now = new Date();

  // Identify the expired Posts
  const expiredPosts = Post.find({
    end_date = '{ $lt: now } // Warehouse allows the use of query operators.
  });

  if (expiredPosts.length === 0) return;

  expiredPosts.forEach(post => {
    // Safety check for the date
    const endDate = new Date(post.end_date);
    if (isNaN(endDate)) return;

    // Disable the post for the actual generating process.
    post.published = false;

    // Remove it from the model .
    Post.removeById(post._id);

    hexo.log.info(`Post expiré masqué : ${post.title} (expiré le ${moment(post.end_date).format('YYYY-MM-DD')})`);


    // Remove the published file
    try {
      const postFolder = path.join(hexo.public_dir, post.path);

      deleteFolderRecursive(postFolder);
      hexo.log.info(`Post expiré supprimé: ${post.title} `);

    } catch (err) {

      hexo.log.warn(`Impossible de supprimer ${post.path}: ${err.message}`);
    }

    // Full path to the source file (.md)
    const fullPath = path.join(hexo.source_dir, post.source);

    // Set the status to unpublished
    if (fs.existsSync(fullPath)) {
      let content = fs.readFileSync(fullPath);

      // Remplace published: true to false, or add the line if missing
      if (content.includes('published: true')) {
        content = content.replace('published: true', 'published: false');
      } else if (!content.includes('published:')) {
        // Insert after the first "+++"
        content = content.replace('+++', '+++\npublished: false');
      }

      fs.writeFileSync(fullPath, content, 'utf8');
      hexo.log.info(`Fichier source mis à jour (Draft) : ${post.source}`);
    }

  });
});

J’ai sorti l’artillerie lourde :

J’ai également rajouté un autre script (set_endDate.js) pour que certains types d’articles aient automatiquement une date de fin ajustée selon la configuration de hexo:

'use strict';

const moment = require('moment')
const fs = require('hexo-fs')

hexo.on('new', function(data){

    // Retrieve the parameter from _config.yml (or set a default value).
    const settings = hexo.config.lifespan_settings || {}
    const layoutMatch = data.content.match(/layout:\s*(\w+)/);
    const currentLayout = layoutMatch ? layoutMatch[1] : 'post';

    // Verify if the actual layout has a specific lifespan
    if (!settings.hasOwnProperty(currentLayout)) {
        return;
    }

    // Retrieve the lifespan for the layout used for the new post
    const lifespan = settings[currentLayout]


    // calculate the end date of the post
    const endDate = moment().add(lifespan,'days').format('YYYY-MM-DD HH:mm:ss');

    // replace 'end_date' default by the calculated one
    const newContent = data.content.replace(/end_date = '2400-12-31 01\:00\:00/g, 'end_date = ''+endDate);

    // write the change in the file
    fs.writeFile(data.path, newContent, function(err){
        if (err) {
            console.log("Error when writing 'endDate' variable");
        } else {
            console.log("'endDate' variable added");
        }
    });


});
Tags: