Taggi pre-release
Prenez note que ce plugin est en alpha pré-release. Vous pouvez quand même l'essayer et bidouiller sur un forum test en attendant, et bien sûr me partager vos retours.
Taggi est un plugin qui permet d’ajouter des shortcodes personnalisés inspirés de WordPress, ou des balises basées sur des regex directement dans le titre d'un sujet.
Il remplace automatiquement lesdites balises par du HTML, avec la possibilité d’injecter leur rendu ailleurs dans le DOM (avant, après, ou dans un conteneur choisi dynamiquement).
Installer le plugin
Sur forum
Le plugin est compilé en un seul fichier et doit être installé en script externe. Ouvrez votre template overall_footer_end
(fin du bas de page) et juste avant la fermeture de la balise </body>
, insérez le code suivant :
<!-- Importation de Taggi.js / lien temporaire -->
<script src="https://cdn.jsdelivr.net/npm/@poumon/taggi@pre-release/dist/taggi.min.js"></script>
<script>
const taggi = new Taggi(config, {
defaultSelector: ".taggit", // (optionnel)
fallbackOutput: (content, tag) => `<span class="taggit" data-tag="${tag}">${content}</span>`
});
</script>
Si vous modifiez le DOM ou souhaitez parser Taggi depuis un DOM récupéré par fetch, relancez taggi.init(dom).
Comme module
Pour utiliser Taggi dans vos projets, vous pouvez utiliser la commande npm i @poumon/taggi
et l'importer comme module.
Utilisation
Le plugin Taggi se configure et s'appelle une seule fois. Ensuite, il sera actif sur toutes les pages de votre forum.
Structure d'un tag
const config = {
coord: {
selector: [".post-title", ".content"], // string ou array; sinon defaultSelector
// Variante 1 – Shortcode par tag
output: (inner, tagName, ctx) => `<span class="coord">${inner}</span>`,
inject: ".page-title", // voir détails ci-dessous
position: "afterbegin" // "afterbegin" ou "beforeend" (par défaut)
// Variante 2 – Regex par tag
// regex: /X:\s*(-?\d+(?:\.\d+)?)\s*Y:\s*(-?\d+(?:\.\d+)?)/,
// output: (x, y) => `<span class="coord">X:${x} Y:${y}</span>`,
},
};
Propriétés disponibles
selector
(string | string[]) | Où chercher le contenu à parser.output(inner, tagName, ctx)
(function) | Retourne du HTML à partir du texte capturé. Lorsque RegExp est utilisé, les paramètres sont les groupes capturés.regex
(RegExp, optionnel) | Motif pour extraire des éléments sans shortcode (ex.: @pseudo).inject
(string | function, optionnel) | Où injecter le rendu, autrement le remplacement ce fait dans l'élément selon la position par défaut (si défini, on retire le texte source lors du parsing, voir ci-dessous).".selector"
→document.querySelector('.selector')
"!.selector"
→el.closest('.selector')
forcé closest seulement.fn(el){...}
→ retourner un élément cible.position
(string, optionnel) | De quel côté injecter le contenu :"after"→"afterbegin"
,"before"→"beforeend"
.
Règle d'or de l'injection
L'injection des tags dans le DOM utilise une stratégie rendant le système idempotent. Les tags ne se dupliquent jamais lorsqu'ils sont injectés.
- Si un tag possède
</strong>inject
: le shortcode (ou la portion reconnue) est retiré du HTML source. Le rendu est uniquement écrit dans la cible inject, entre<!--taggi:start:nom-->
et<!--taggi:end:nom-->
. - Si un tag n’a pas inject : le rendu remplace le contenu directement dans la source.
<h1 class="page-title">
<!--taggi:start:coord-->
<span class="coord">X:48.8588443 Y:2.8588443</span>
<!--taggi:end:coord-->
<a href="/t1-...">Votre 1er sujet</a>
</h1>
const taggi = new Taggi({
icon: {
selector: ".content",
output: (name) => `<i class="i i-${name}"></i>`,
// pas d'inject → remplacement inline dans l'élément
}
});
// Dans le HTML : "[icon star]" devient <i class="i i-star"></i>
const taggi = new Taggi({
coord: {
selector: [".post-title", ".content"],
output: (inner) => `${inner}`,
inject: "!.page-coords", // forcer el.closest('.page-coords')
position: "after"
}
});
const taggi = new Taggi({
mention: {
selector: ".content",
regex: /(^|\s)@([a-z0-9_]+)/i,
output: (_, username) => ` <a class="mention" href="/u/${username}">@${username}</a>`,
inject: ".mentions-bar", // la barre dédiée aux mentions
position: "beforeend"
}
});
- Toute mention
@user
trouvée dans.content
est retirée de la source, puis ajoutée à.mentions-bar
.
Exemple complet
<h1 class="page-title"><a href="#">Titre</a></h1>
<div class="content">[coord X:48.8588443 Y:2.8588443] — Bonjour @alice</div>
<div class="mentions-bar"></div>
<script>
const taggi = new Taggi({
coord: {
selector: ".content",
output: (inner) => `<span class="coord">${inner}</span>`,
inject: "!.page-title",
position: "afterbegin",
},
mention: {
selector: ".content",
regex: /(^|\s)@([a-z0-9_]+)/i,
output: (_, user) => ` <a class="mention" href="/u/${user}">@${user}</a>`,
inject: ".mentions-bar"
}
});
</script>
Résultat attendu :
- Le contenu
[coord ...]
disparaît de.content
et apparaît au début du H1 entre<span class="coord">...</span>
. - La mention
@alice
disparaît de.content
et apparaît dans.mentions-bar
.
new Taggi(config: Record<string, Tag>, options?: Options)
interface Options {
defaultSelector?: string | string[];
fallbackOutput?: (content: string, tagName: string, ctx?: Ctx) => string;
}
interface Tag {
selector?: string | string[];
output?: (...parts: string[]) => string; // shortcodes: (inner, tagName, ctx) | regex: (...groups)
regex?: RegExp; // si fourni, parsing par motif
inject?: string | ((el: HTMLElement) => HTMLElement | null); // "!selector" => closest-only
position?: "afterbegin" | "beforeend" | "after" | "before";
}
interface Ctx {
el: HTMLElement;
token?: unknown; // si vous l’exposez depuis votre output personnalisée
}