Files
ObsiGate/docs/features/viewer-toolbar-highlight-avatars.md
T
bruno 943005328c
CI / lint (push) Successful in 2m5s
CI / security (push) Successful in 1m27s
CI / test (push) Successful in 4m10s
CI / build (push) Successful in 1m23s
CI / e2e (push) Successful in 13m55s
feat: barre d'outils de lecture epinglee, coloration syntaxique des fichiers de code et avatars predefinis (#115, #117, BUG-078)
2026-09-24 16:21:45 -04:00

5.5 KiB

#115 / BUG-078 / #117 — Barre d'outils épinglée, coloration syntaxique des fichiers de code & avatars prédéfinis

Statut : 🟢 livré (en attente vérification utilisateur) Impact : 🟡 (#115, BUG-078) · 🟢 (#117) Zone : frontend (frontend/js/viewer.js, frontend/js/themes.js, frontend/js/ui.js, frontend/js/config.js, frontend/index.html, frontend/style.css, frontend/popout.html, frontend/locales/fr.json, frontend/locales/en.json, frontend/icons/avatar/*)

Trois demandes traitées dans la même livraison, toutes côté frontend.

#115 — Barre d'outils de lecture toujours visible

Problème

La barre d'actions d'un document (pop-out, bookmark, Editer, Source, Copier, PDF, Export, Partager…) était rendue dans .file-header, un conteneur court placé en haut de la zone de lecture. Or un élément position: sticky reste borné par son parent : dès que .file-header sortait de l'écran au défilement d'un document long, la barre disparaissait.

Correctif

  • frontend/js/viewer.js et frontend/popout.html sortent la barre d'actions de .file-header et la placent dans un nouveau conteneur .file-toolbar, enfant direct de .content-area (le conteneur de défilement). La barre peut donc se coller au haut de la zone de lecture pour toute la hauteur du document.

  • frontend/style.css :

    .file-toolbar {
      position: sticky;
      top: 0;
      z-index: 30;
      margin: 0 0 16px;
      padding: 8px 0;
      background: var(--bg-primary);
      border-bottom: 1px solid var(--border);
    }
    

    Le fond est opaque pour qu'aucun texte ne transparaisse dessous.

  • body.reading-mode .file-toolbar { display: none } : la barre reste masquée en mode lecture, comme les autres actions.

BUG-078 — Coloration syntaxique des fichiers de code

Problème

Les fichiers .py, .sh, .ps1, .yml, .json… (et les blocs de code Markdown) s'affichaient en texte brut, sans couleurs. Le code était pourtant correctement généré côté backend (<pre><code class="language-python">…) et safeHighlight() appelait bien hljs.highlightElement().

La cause était ailleurs : les deux feuilles de style de highlight.js (#hljs-theme-dark et #hljs-theme-light, chargées depuis le CDN dans index.html) étaient basculées à partir de la clé de thème persistée (obsigate-theme = defaut-obsigate, …) :

darkSheet.disabled = theme !== "dark";    // "defaut-obsigate" !== "dark" → true
lightSheet.disabled = theme !== "light";  // "defaut-obsigate" !== "light" → true

Les deux feuilles finissaient donc désactivées, privant tous les tokens de leurs couleurs. Le résultat dépendait de l'ordre de deux initialisations concurrentes — UI.initTheme() (clé de thème) au démarrage et themes.initThemes() (mode) via Sync.init() — d'où un comportement non déterministe (parfois coloré, le plus souvent non).

Correctif

  • frontend/js/themes.js — applyTheme(themeKey, mode) bascule désormais les feuilles highlight.js selon le mode :

    var isDark = mode === 'dark';
    darkSheet.disabled = !isDark;
    lightSheet.disabled = isDark;
    

    (sepia et high-contrast réutilisent la palette claire.)

  • frontend/js/ui.js — initTheme() lit le mode persisté (obsigate-theme-mode) et applyTheme() résout un mode avant de fixer data-theme et de basculer les feuilles, au lieu de comparer la clé de thème à "dark"/"light".

Le basculement est ainsi déterministe dès le premier rendu et à chaque changement de mode.

#117 — Avatars prédéfinis dans le profil

Ce qui a été livré

  • Galerie de 12 avatars dans la section Profil (#cfg-profile), servis depuis frontend/icons/avatar/ (/static/icons/avatar/<fichier>.jpg) : Chat, chien, elephan, hibou, koala, lapin, lion, ours, penda, pingouin, raton, tigre.
  • Un clic charge l'image, la fait passer par le même pipeline que l'import (recadrage carré central, redimensionnement 256 px, export JPEG 0,85) et l'enregistre via PATCH /api/auth/me — aucune modification backend n'a été nécessaire, la validation data-URL PNG/JPEG/WebP existante (BUG-113) s'applique telle quelle.
  • L'avatar actif est surligné ; le choix est mémorisé dans localStorage (obsigate-avatar-preset) et purgé dès qu'on importe une photo personnalisée ou qu'on supprime l'avatar.
  • L'import d'une photo et la suppression restent disponibles.
  • i18n : nouvelle clé config.avatar_presets_label (FR/EN).

Tests

  • tests/frontend/unit.test.mjs — syntax highlight theme (BUG-078) : themes.applyTheme bascule les feuilles selon le mode et ui.js ne compare plus la clé au mode.
  • tests/frontend/toolbar-order.test.mjs — #115 : .file-toolbar dans viewer.js et popout.html, règle CSS position: sticky; top: 0, masquage en mode lecture.
  • tests/frontend/settings-order-avatar.test.mjs — #117 : 12 avatars présents dans #cfg-profile, fichiers d'images existants, pipeline config.js, règles CSS, clé i18n FR/EN.
  • Vérification Playwright (instance locale, auth désactivée) : coloration déterministe sur 5 chargements successifs d'un .py ; .file-toolbar dont le top ne bouge plus après défilement (épinglage effectif).

Limitations

  • L'avatar reste stocké en data-URL 256 px dans data/users.json (comportement #113 conservé).
  • Le mode high-contrast/sepia utilise le thème highlight.js clair (pas de palette dédiée).