Blocs de code : titre, icône et bouton copier

Page d'exemple du render hook codeblock : titre ou nom de fichier, icône du langage et bouton « copier le code » sur les blocs de code Markdown.

Cette page illustre le render hook layouts/_markup/render-codeblock.html : chaque bloc de code fenced Markdown peut afficher un en-tête avec un titre ou un nom de fichier, l’icône de son langage et un bouton « copier le code ». Les options se passent dans l’info string du bloc, sans shortcode.

Titre / nom de fichier

L’attribut title="..." affiche un en-tête avec un titre ou un nom de fichier :

index.html
<p>Hello World</p>

Icône du langage (mapping auto)

L’attribut icon=true affiche l’icône du langage du bloc, résolue par le partial layouts/_partials/func/GetCodeIcon.html (slugs Simple Icons, alias inclus) :

js
console.log('Hello');

Bouton copier

L’attribut copy=true ajoute un bouton « copier le code », avec un retour visuel de 2 s à la copie :

bash
echo "Hello World"

Tout combiné

styles.scss
body {
  color: rebeccapurple;
}

Sans option (rendu Hugo par défaut)

Un bloc sans option passe par transform.HighlightCodeBlock : rendu strictement identique à celui de Hugo, aucune régression.

html
<p>Hello World</p>

Icône explicite (Simple Icons)

L’attribut icon="..." force une icône donnée (slug Simple Icons) — utile pour un langage non mappé ou pour le texte brut :

text
content/
layouts/

Langage non mappé (pas d’icône)

Un langage absent du mapping n’affiche pas d’icône (zig ici) :

zig
const std = @import("std");

Opt-out explicite

copy=false (resp. icon=false) désactive l’option même si elle est activée globalement dans la configuration du site :

js
console.log('pas de bouton copier');

Récapitulatif

SyntaxeEffet
{title="index.html"}En-tête avec titre / nom de fichier
{icon=true}Icône du langage (mapping auto)
{icon="python"}Icône explicite (slug Simple Icons)
{icon=false}Désactive l’icône
{copy=true}Bouton « copier le code »
{copy=false}Désactive le bouton copier

Notes

  • Défauts globaux : params.code.copy et params.code.icon (config/_default/params.yaml) activent les options pour tout le site ; l’info string du bloc gagne toujours.
  • Syntaxe : un attribut sans valeur ({icon}, {copy}) est invalide pour le parseur d’attributs Markdown (Goldmark) — toujours écrire icon=true / copy=true.
  • Langages non reconnus par Chroma (drupal, flutter, mongodb…) : rendus en texte brut, mais l’icône du mapping s’affiche ; une icône explicite ({icon="drupal"}) reste la solution la plus propre.
  • Accessibilité : vrai <button> avec aria-label basculé vers « Code copié » pendant 2 s, fallback execCommand pour les contextes non sécurisés.
  • Icônes du bouton : Lucide copy / check, émises via le partial icon du module hugolify-theme-icons ; sans ce module, le bouton reste fonctionnel avec son libellé texte.

Icônes — un exemple par langage du mapping

Un bloc pour chaque langage mappé dans layouts/_partials/func/GetCodeIcon.html, dans l’ordre du mapping, alias inclus (js et javascript partagent la même icône). Les langages non reconnus par Chroma sont rendus en texte brut — l’icône du mapping s’affiche quand même.

angular
<button [disabled]="isLoading">Valider</button>
astro
---
layout: ../layouts/Base.astro
---
<h1>Bonjour</h1>
bash
echo "Hello World"
bootstrap
<button class="btn btn-primary">Valider</button>
css
.title { color: rebeccapurple; }
dart
void main() { print('Bonjour'); }
docker
FROM alpine:3
RUN apk add curl
dockerfile
FROM node:20-alpine
COPY . .
RUN npm ci
drupal
function demo_form_alter(&$form, $form_state) {
  $form['#validate'][] = 'demo_validate';
}
elixir
def hello, do: IO.puts("Bonjour")
elm
main =
  text "Bonjour"
erlang
hello() ->
  io:format("Bonjour~n").
flutter
const Text('Bonjour')
git
git commit -m "feat: blocs de code"
gitlab
image: node:20
script:
  - npm test
go
fmt.Println("Bonjour")
golang
func main() { fmt.Println("Bonjour") }
gql
query {
  user(id: 1) {
    name
  }
}
graphql
type Query {
  hello: String
}
haskell
main = putStrLn "Bonjour"
htm
<p>Bonjour</p>
html
<p>Bonjour le monde</p>
hugo
{{ range .Pages }}{{ .Title }}{{ end }}
java
System.out.println("Bonjour");
javascript
console.log("Bonjour");
js
const somme = (a, b) => a + b;
jsx
<Bouton label="Bonjour" onClick={valider} />
tsx
<Bouton label="Bonjour" onClick={valider} />
k8s
apiVersion: v1
kind: Pod
kotlin
fun main() = println("Bonjour")
kubernetes
kubectl get pods -n prod
laravel
Route::get('/bonjour', fn () => 'Bonjour');
less
@primaire: rebeccapurple;
.title { color: @primaire; }
lua
print("Bonjour")
markdown
**Bonjour le monde**
md
## Bonjour

Un titre de section.
mongodb
db.utilisateurs.find({ actif: true })
mysql
SELECT * FROM utilisateurs WHERE actif = 1;
next
export default function Page() {
  return <h1>Bonjour</h1>;
}
nextjs
export const metadata = { title: "Bonjour" };
nginx
location / {
  proxy_pass http://app:3000;
}
perl
print "Bonjour\n";
php
<?php echo "Bonjour";
postgres
SELECT count(*) FROM utilisateurs;
postgresql
SELECT NOW();
powershell
Get-ChildItem -Recurse -Filter *.log
ps1
Write-Host "Bonjour"
py
print("Bonjour")
python
print("Bonjour le monde")
r
x <- c(1, 2, 3)
mean(x)
rb
puts "Bonjour"
react
<button onClick={valider}>Valider</button>
redis
SET salut "Bonjour"
rs
println!("Bonjour");
rust
fn main() {
    println!("Bonjour le monde");
}
sass
$primaire: rebeccapurple
scss
.title {
  color: $primaire;
}
sh
ls -la
shell
echo "Bonjour"
solidity
contract Bonjour {
    string public salut = "Bonjour";
}
sqlite
SELECT * FROM utilisateurs LIMIT 5;
svg
<svg xmlns="http://www.w3.org/2000/svg"><circle r="10" /></svg>
svelte
<h1>{titre}</h1>
swift
print("Bonjour")
symfony
#[Route('/bonjour', name: 'bonjour')]
public function bonjour(): Response {}
tailwind
<div class="flex items-center gap-2">Bonjour</div>
tailwindcss
@tailwind utilities;
terraform
resource "aws_instance" "web" {
  ami = "ami-123456"
}
tf
resource "aws_s3_bucket" "cdn" {
  bucket = "cdn"
}
ts
const id: number = 1;
typescript
function direBonjour(nom: string): string {
  return "Bonjour " + nom;
}
vite
import { defineConfig } from "vite";

export default defineConfig({});
vue
<template>
  <p>{{ msg }}</p>
</template>
wordpress
add_action("init", "mon_init");
xml
<utilisateur>
  <nom>Thomas</nom>
</utilisateur>
zsh
echo $SHELL