feat: add syntax highlighting and code block styling with theme support

This commit is contained in:
2025-10-21 08:34:46 -04:00
parent 062d743481
commit d788c9d267
75 changed files with 4783 additions and 830 deletions
+239
View File
@@ -0,0 +1,239 @@
# Système de Théming ObsiViewer - Documentation
## Vue d'ensemble
Système de théming double dimension : **7 thèmes × 2 modes = 14 combinaisons**
- **Modes** : Light, Dark
- **Thèmes** : Light (default), Dark (default), Obsidian, Nord, Notion, GitHub, Discord, Monokai
## Architecture
### 1. Tokens CSS (`src/styles/themes.css`)
Tous les thèmes définissent un ensemble cohérent de tokens CSS :
```css
/* Backgrounds */
--bg, --bg-main, --bg-muted
--card, --card-bg, --elevated
--sidebar-bg, --surface-1, --surface-2
/* Text */
--fg, --text-main, --text-muted, --muted
/* Borders */
--border
/* Brand & Accents */
--primary, --brand, --brand-700, --brand-800
--secondary, --accent
/* Status */
--success, --warning, --danger, --info
/* UI Elements */
--chip-bg, --link, --link-hover, --ring
/* Editor (CodeMirror 6) */
--editor-bg, --editor-fg, --editor-selection
--editor-gutter-bg, --editor-gutter-fg, --editor-cursor
/* Shadows */
--shadow-color, --scrollbar-thumb
```
### 2. Configuration Tailwind (`tailwind.config.js`)
Tous les tokens sont mappés dans Tailwind :
```javascript
colors: {
bg: 'var(--bg)',
'bg-main': 'var(--bg-main)',
fg: 'var(--fg)',
surface1: 'var(--surface-1)',
primary: 'var(--primary)',
// ... etc
}
```
### 3. ThemeService (`src/app/core/services/theme.service.ts`)
Service central qui gère :
- Mode : `'system' | 'light' | 'dark'`
- Thème : `'light' | 'dark' | 'obsidian' | 'nord' | 'notion' | 'github' | 'discord' | 'monokai'`
- Application sur `<html>` : classe `.dark` + attribut `data-theme`
- Persistance dans localStorage
- Observable `onPrefs$` pour réactivité
### 4. CodeMirror 6 (`src/app/core/codemirror-themes.ts`)
Fonction `cm6ThemeFor(themeId, mode)` qui retourne les extensions CM6 appropriées pour chaque combinaison thème/mode.
### 5. Anti-FOUC (`index.html`)
Script inline qui lit les préférences et applique le thème avant le chargement d'Angular :
```javascript
(function () {
const prefs = JSON.parse(localStorage.getItem('obsiviewer.preferences.v1') || '{}');
const isDark = prefs.mode === 'dark' || (prefs.mode === 'system' && prefersDark);
html.classList.toggle('dark', isDark);
html.setAttribute('data-theme', prefs.theme || 'light');
})();
```
## Matrice des Thèmes
### Light (Default)
- **Light mode** : Blanc pur, texte gris foncé, accents bleus
- **Dark mode** : N/A (thème light uniquement)
### Dark (Default)
- **Light mode** : N/A (thème dark uniquement)
- **Dark mode** : Fond bleu-gris foncé, texte clair, accents bleus clairs
### Obsidian
- **Light mode** : Beige chaud (#fafaf8), texte anthracite, accents terre
- **Dark mode** : Gris très foncé (#1e1e1e), texte clair, accents beiges
### Nord
- **Light mode** : Blanc neigeux (#eceff4), texte bleu-gris, palette nordique pastel
- **Dark mode** : Bleu-gris arctique (#2e3440), texte givré, palette nordique
### Notion
- **Light mode** : Blanc pur, texte charbon, minimaliste
- **Dark mode** : Noir profond (#171717), texte ivoire, contraste élevé
### GitHub
- **Light mode** : Blanc, texte GitHub (#24292f), palette GitHub
- **Dark mode** : Dimmed dark (#0d1117), palette GitHub dark
### Discord
- **Light mode** : Gris très clair (#f6f7f9), texte foncé, violet Discord
- **Dark mode** : Gris Discord (#2b2d31), texte clair, violet Discord
### Monokai
- **Light mode** : Crème (#fbfbf7), texte foncé, accents verts/oranges atténués
- **Dark mode** : Vert-gris foncé (#272822), texte clair, palette Monokai classique
## Utilisation dans les Composants
### Classes Tailwind recommandées
```html
<!-- Backgrounds -->
<div class="bg-card">Carte</div>
<div class="bg-surface1">Surface niveau 1</div>
<div class="bg-surface2">Surface niveau 2</div>
<!-- Text -->
<p class="text-main">Texte principal</p>
<p class="text-muted">Texte secondaire</p>
<!-- Borders -->
<div class="border border-border">Bordure</div>
<!-- Interactive -->
<button class="bg-primary text-white hover:bg-brand-700">
Action
</button>
<!-- Links -->
<a class="text-link hover:text-link-hover">Lien</a>
```
### ⚠️ À ÉVITER
```html
<!-- ❌ Couleurs hardcodées -->
<div class="bg-slate-100 text-gray-900">...</div>
<div class="dark:bg-gray-800">...</div>
<!-- ✅ Utiliser les tokens -->
<div class="bg-surface1 text-main">...</div>
```
## Tests
### Checklist de validation
Pour chaque combinaison (14 au total) :
- [ ] **Sidebar** : Fond, hover, items actifs, badges
- [ ] **Topbar** : Barre de recherche, focus, placeholder
- [ ] **Liste des notes** : Items, hover, selected, path muted
- [ ] **Page détail** : Titres, liens, listes, blockquotes, code, tables
- [ ] **Mode édition** : Inputs, buttons, toolbars, CodeMirror
- [ ] **Panneaux** : TOC, propriétés, tags
- [ ] **Modales** : Fond, bordure, overlay
- [ ] **Menus contextuels** : Dropdown, tooltips
- [ ] **Toasts** : Fond, bordure, barre de progression
- [ ] **Tables** : Headers, zebra, hover
- [ ] **Chips/Tags** : Fond, texte, bordure, hover, selected
- [ ] **Scrollbars** : Thumb, track
- [ ] **Page Parameters** : Contrôles mode/thème
- [ ] **Mobile** : Bottom nav, sheets, drawers
### Contraste
Tous les thèmes doivent respecter WCAG AA :
- Texte normal : ratio ≥ 4.5:1
- Texte large : ratio ≥ 3:1
## Refactoring effectué
### Automatisé (script `refactor-colors.mjs`)
- ✅ 66 fichiers mis à jour automatiquement
- ✅ Remplacement de 232+ occurrences de couleurs hardcodées
- ✅ Gestion des variantes : hover, focus, dark
### Restant (109 occurrences)
Cas complexes nécessitant révision manuelle :
- Composants avec logique conditionnelle
- Styles inline dynamiques
- Cas spécifiques métier
## Commandes utiles
```bash
# Lancer le refactoring automatique
node scripts/refactor-colors.mjs
# Rechercher les couleurs hardcodées restantes
grep -r "bg-slate-\|bg-gray-\|text-slate-\|text-gray-" src/
# Lancer l'application en mode dev
npm run dev
# Tests E2E
npm run test:e2e
```
## Prochaines étapes
1. ✅ Créer themes.css avec 14 variantes
2. ✅ Configurer Tailwind
3. ✅ Mettre à jour codemirror-themes.ts
4. ✅ Refactoring automatisé (68% des couleurs)
5. 🔄 Refactoring manuel des 109 occurrences restantes
6. ⏳ Tests matrice complète
7. ⏳ Validation contrastes
8. ⏳ Documentation utilisateur
## Notes techniques
- **Persistance** : `localStorage` clé `obsiviewer.preferences.v1`
- **Réactivité** : Observable `ThemeService.onPrefs$`
- **SSR-safe** : Vérifications `typeof window !== 'undefined'`
- **Performance** : Transitions CSS 150ms
- **Accessibilité** : `color-scheme` CSS pour scrollbars natives
## Ressources
- [Tailwind CSS Variables](https://tailwindcss.com/docs/customizing-colors#using-css-variables)
- [CodeMirror 6 Theming](https://codemirror.net/docs/ref/#view.EditorView^theme)
- [WCAG Contrast Guidelines](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html)
+95
View File
@@ -0,0 +1,95 @@
# UI Theming and CM6 Highlight
## Theme Variables
- **Global tokens** (in `src/styles.css`):
- `--color-accent` main accent color (per-theme override via `[data-theme]`).
- `--cm-hl-bg` computed background for inline highlights (theme dependent).
- `--cm-hl-br` border radius for highlights (default 3px).
- **Button tokens**:
- `--btn-radius`, `--btn-padding-y`, `--btn-padding-x`, `--btn-font`, `--btn-shadow`, `--btn-ring`, `--btn-speed`.
- `--btn-bg`, `--btn-fg`, `--btn-bg-hover`, `--btn-bg-active`, `--btn-outline`.
### Defaults
```css
:root {
--color-accent: #3b82f6;
--cm-hl-bg: color-mix(in srgb, var(--color-accent) 28%, transparent);
--cm-hl-br: 3px;
}
[data-theme="dark"], .dark {
--cm-hl-bg: color-mix(in srgb, var(--color-accent) 22%, transparent);
}
[data-theme="nimbus"] { --color-accent: #7c3aed; }
[data-theme="emerald"] { --color-accent: #10b981; }
```
## CodeMirror 6 Highlight Extensions
Files in `src/app/shared/editor/extensions/`:
- `highlight-occurrences.extension.ts` – inline dynamic occurrences via regex.
- `ranged-highlights.extension.ts` – ranged highlights via `StateEffect`/`StateField`.
- `markdown-theme-highlight.extension.ts` – composes ranged + Markdown `HighlightStyle` and facet-driven CSS variable.
### CSS class
- `.cm-md-highlight { background: var(--cm-hl-bg); border-radius: var(--cm-hl-br, 3px); transition: background var(--btn-speed) ease; }`
## Angular Integration
Service: `EditorHighlightService` (`src/app/shared/editor/editor-highlight.service.ts`)
- `extensions: Extension[]` – base markdown theme highlight extensions to add at editor creation.
- `occurrencesExtension(pattern)` – build occurrences extension.
- `applyOccurrences(view, compartment, pattern)` – reconfigure a `Compartment` with occurrences.
- `setRanges(view, ranges)` / `clearRanges(view)` – ranged highlights via effects.
- `facet()` – access the facet to optionally override `--cm-hl-bg` per-view.
Example inside editor component:
```ts
// compartments
occurrencesCompartment = new Compartment();
highlightFacetCompartment = new Compartment();
// in EditorState.create extensions
...highlightService.extensions,
occurrencesCompartment.of([]),
highlightFacetCompartment.of([]),
// API usage
highlightOccurrences("TODO");
setHighlights([{ from: 10, to: 24 }]);
clearHighlights();
// optional color override
view.dispatch({ effects: highlightFacetCompartment.reconfigure([ highlightService.facet().of('color-mix(in srgb, var(--color-accent) 40%, transparent)') ])});
```
Live theme changes are applied by reconfiguring the theme compartment and calling `EditorView.requestMeasure(...)` to refresh decorations that rely on CSS variables.
## Button Utilities
Classes in `src/styles.css`:
- Base: `btn`
- Variants: `btn-solid`, `btn-outline`, `btn-ghost`
- Sizes: `btn-sm`, `btn-md`, `btn-lg`
Example:
```html
<button class="btn btn-solid btn-sm">Save</button>
<button class="btn btn-outline btn-sm">Cancel</button>
<button class="btn btn-ghost btn-sm" aria-label="Close">✕</button>
```
## Accessibility
- Buttons use focus ring via `--btn-outline`.
- Ensure icon-only buttons have `aria-label`.
## Performance Notes
- Occurrence highlights compute decorations only over visible ranges; cost is O(visibleText) per viewport update.
- Ranged highlights use a `StateField` and `tr.changes.mapPos` to keep ranges aligned after edits; updates are incremental.
- For large docs (≥5k lines), prefer ranged highlights for bulk operations; occurrences are fine for quick regex patterns like `TODO`.