Files
folio/src/helpers.php
T
cedricAbonnel 1dbe6d8dd3 feat : versionnage semver, migrations contenu, bandeau mise à jour admin
- CHANGELOG.md : structure semver (1.0.0 / 1.1.0 / 1.2.0) remplace le journal non versionné
- public/version.txt : généré à chaque push depuis la première entrée CHANGELOG
- scripts/push.sh : extrait la version CHANGELOG avant git add
- src/UpdateChecker.php : compare version déployée vs version Gitea (raw file), cache 1 h
- templates/layout.php : bandeau alerte admin (nouvelle version / migrations en attente)
- templates/admin.php : dashboard moteur Folio (version déployée / disponible)
- scripts/migrate_content.php + migration_001 : ajout # titre dans les articles existants
- templates/maintenance.php : page HTTP 503 pendant une migration
- src/helpers.php : extractMarkdownTitle(), normalisation \r\n dans lineDiff()
- templates/wizard/step1.php : suppression champ titre, plan TOC dynamique
- public/assets/js/wizard.js : scope titleEl, scrollToCursor, buildToc, handlers externalisés

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 22:45:35 +02:00

152 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
function vd($var, ...$moreVars)
{
ob_start();
var_dump($var, ...$moreVars);
$output = ob_get_clean();
echo "<pre>$output</pre>";
}
function slugify(string $s): string
{
$map = ['à' => 'a','â' => 'a','ä' => 'a','é' => 'e','è' => 'e','ê' => 'e','ë' => 'e','î' => 'i','ï' => 'i','ô' => 'o','ö' => 'o','ù' => 'u','û' => 'u','ü' => 'u','ç' => 'c','æ' => 'ae','œ' => 'oe'];
$s = mb_strtolower($s);
$s = strtr($s, $map);
$s = (string)preg_replace('/[^a-z0-9]+/', '-', $s);
return trim($s, '-');
}
/** Extrait le titre depuis le premier titre Markdown `# ...` du contenu. */
function extractMarkdownTitle(string $content): string
{
foreach (explode("\n", str_replace("\r\n", "\n", $content)) as $line) {
if (preg_match('/^#\s+(.+)/', rtrim($line), $m)) {
return trim($m[1]);
}
}
return '';
}
/**
* Diff ligne-à-ligne via LCS. Retourne un tableau de [op, line] où
* op est '=' (inchangé), '-' (supprimé), '+' (ajouté).
*/
function lineDiff(string $old, string $new): array
{
$old = str_replace("\r\n", "\n", $old);
$new = str_replace("\r\n", "\n", $new);
$a = explode("\n", $old);
$b = explode("\n", $new);
$n = count($a);
$m = count($b);
if ($n * $m > 2_000_000) {
$diff = [['!', "Diff trop grand ({$n}×{$m} lignes) — affichage simplifié."]];
foreach ($a as $line) { $diff[] = ['-', $line]; }
foreach ($b as $line) { $diff[] = ['+', $line]; }
return $diff;
}
$dp = array_fill(0, $n + 1, array_fill(0, $m + 1, 0));
for ($i = $n - 1; $i >= 0; $i--) {
for ($j = $m - 1; $j >= 0; $j--) {
$dp[$i][$j] = $a[$i] === $b[$j]
? 1 + $dp[$i + 1][$j + 1]
: max($dp[$i + 1][$j], $dp[$i][$j + 1]);
}
}
$diff = [];
$i = 0;
$j = 0;
while ($i < $n || $j < $m) {
if ($i < $n && $j < $m && $a[$i] === $b[$j]) {
$diff[] = ['=', $a[$i]];
$i++;
$j++;
} elseif ($j < $m && ($i >= $n || $dp[$i][$j + 1] >= $dp[$i + 1][$j])) {
$diff[] = ['+', $b[$j++]];
} else {
$diff[] = ['-', $a[$i++]];
}
}
return $diff;
}
// 16 couleurs RGB de base — distribuées sur le spectre, visuellement distinctes
const COLOR_PALETTE_16 = [
[220, 38, 38], // rouge
[234, 88, 12], // orange
[217, 119, 6], // ambre
[161, 142, 14], // jaune-olive
[77, 124, 15], // citron
[22, 163, 74], // vert
[4, 120, 87], // émeraude
[15, 118, 110], // sarcelle
[8, 145, 178], // cyan
[3, 105, 161], // ciel
[37, 99, 235], // bleu
[79, 70, 229], // indigo
[109, 40, 217], // violet
[147, 51, 234], // pourpre
[192, 38, 211], // fuchsia
[219, 39, 119], // rose
];
/**
* Génère un dégradé CSS pour une catégorie.
* Avec $allCats, l'assignation est séquentielle (par ordre alpha) ;
* au-delà de 16, un décalage de teinte et d'angle différencie les palettes.
* Sans $allCats, fallback par hachage sur la palette.
*/
function coverGradient(string $seed, array $allCats = []): string
{
$key = strtolower(trim($seed));
if (!empty($allCats)) {
$keys = array_map(fn ($k) => strtolower(trim((string)$k)), array_keys($allCats));
$pos = array_search($key, $keys, true);
if ($pos !== false) {
$idx = (int) $pos;
$tier = (int) floor($idx / 16);
$ci = $idx % 16;
return _paletteGradient(COLOR_PALETTE_16[$ci], $tier);
}
}
// Hachage déterministe en l'absence de liste
$ci = abs(crc32($key)) % 16;
return _paletteGradient(COLOR_PALETTE_16[$ci], 0);
}
function _paletteGradient(array $rgb, int $tier): string
{
[$r, $g, $b] = $rgb;
// Tier 0 : dégradé standard clair → foncé, 135°
// Tier 1 : plus saturé, angle inversé, 315°
// Tier 2+ : plus sombre encore, 225°
$tintMix = match ($tier) {
0 => 0.65, 1 => 0.48, default => 0.35
};
$shadeK = match ($tier) {
0 => 0.35, 1 => 0.25, default => 0.18
};
$angle = match ($tier) {
0 => 135, 1 => 315, default => 225
};
$tr = (int) round($r * (1 - $tintMix) + 255 * $tintMix);
$tg = (int) round($g * (1 - $tintMix) + 255 * $tintMix);
$tb = (int) round($b * (1 - $tintMix) + 255 * $tintMix);
$sr = (int) round($r * $shadeK);
$sg = (int) round($g * $shadeK);
$sb = (int) round($b * $shadeK);
return "linear-gradient({$angle}deg,rgb($tr,$tg,$tb) 0%,rgb($sr,$sg,$sb) 100%)";
}