6.8 KiB
#69 - Éditeur mobile natif — Interface tactile optimisée
Statut : ✅ Terminé — 100 % implémenté + 24 tests frontend (2026-09-12) Effort : 2-3 jours (réalisé) | Impact : 🟢 Références : Roadmap · Changelog
-
Fichiers clés :
frontend/js/mobile-editor.js— module complet (helpers purs + câblage UI)frontend/style.css— barre flottante, poignée de redimensionnement, mode lecture, bottom sheetfrontend/index.html— section « Édition mobile » du guide intégré + lien de navigationfrontend/locales/{fr,en}.json— clésmobile_editor.*ethelp.mobile_editor_*frontend/js/app.js— appelinitMobileEditor()au démarragetests/frontend/mobile-editor.test.mjs— 24 tests JSDOM (helpers + câblage DOM).gitea/workflows/ci.yml— test ajouté au joblint
-
Description : sur téléphone et tablette, l'édition et la lecture s'adaptent au tactile : barre d'outils Markdown flottante, bouton « Coller » persistant, zoom par pincement, hauteur ajustable, gestes de balayage (liens entrants / table des matières) et mode lecture plein écran avec navigation d'un fichier à l'autre.
Architecture
mobile-editor.js
├── Helpers purs (testables) clampZoom · detectSwipe · formatChange · insertText
│ applyFormatToView · applyFormatToTextarea
└── Câblage UI (initMobileEditor)
├── Barre d'outils flottante #me-toolbar (gras/italique/code/liste/lien + A−/A+)
├── Presse-papiers #me-paste (Clipboard API + repli iOS)
├── Zoom pincement touchstart/move (2 doigts → taille de police)
├── Hauteur ajustable #me-resize-handle (poignée, persistée)
├── Bottom sheet #me-sheet (liens entrants / table des matières)
├── Raccourcis swipe #content-area (gauche → backlinks, droite → TOC)
└── Mode lecture #me-reading-btn (plein écran + navigation fichiers)
1. Barre d'outils mobile flottante
Injectée dans #editor-modal et affichée uniquement sous 768px lorsque l'éditeur est actif
(.editor-modal.active .me-toolbar). Chaque bouton applique une transformation Markdown à la
sélection courante de CodeMirror (ou du textarea de secours) :
| Bouton | Action | Transformation |
|---|---|---|
| B | bold |
**texte** |
| I | italic |
*texte* |
<> |
code |
`texte` |
| • | list |
préfixe - sur chaque ligne sélectionnée |
| 🔗 | link |
[texte](url) avec le curseur placé sur url |
- Toggle : ré-appuyer sur un bouton alors que la sélection porte déjà les marqueurs
retire ces marqueurs (ex.
**hi**sélectionné →hi). - Sans sélection : les marqueurs sont insérés et le curseur est placé à l'intérieur.
- Les helpers
formatChange()(calcul de la transaction) etapplyFormatToView()/applyFormatToTextarea()(application) sont purs côté calcul → testables sans CodeMirror.
2. Presse-papiers — bouton « Coller » persistant
iOS Safari n'autorise navigator.clipboard.readText() que sur geste utilisateur explicite et peut
le refuser. Le bouton 📋 :
- tente
navigator.clipboard.readText(); - en cas de refus ou d'absence, affiche un message d'aide (
mobile_editor.paste_hint) invitant à appuyer longuement dans le champ puis à choisir « Coller » ; - en cas de succès, insère le texte au niveau du curseur (CodeMirror ou textarea).
3. Adaptation CodeMirror
- Zoom par pincement : deux doigts sur
#editor-bodycalculent le ratio de distance et ajustent la taille de police du.cm-scroller(bornée10–28px). BoutonsA−/A+pour les utilisateurs sans geste tactile. - Hauteur ajustable : une poignée (
#me-resize-handle) entre le corps de l'éditeur et le pied permet d'étirer/réduire la zone d'édition (flex: 0 0 <px>), bornée à140px. - Les deux valeurs sont persistées dans
localStorage(obsigate-editor-font-size,obsigate-editor-height) et réappliquées à chaque ouverture / changement de fichier via unMutationObserver.
4. Raccourcis swipe (lecture)
Sur #content-area, un touchstart/touchend unique détecte un geste horizontal
(detectSwipe, seuil 60px, dominance horizontale requise) :
- Balayage gauche → bottom sheet des liens entrants (fetch
/api/file/{vault}/backlinks, clic → ouverture dans un onglet) ; - Balayage droite → bottom sheet de la table des matières (
state.headingsCache, clic →scrollIntoView).
5. Mode lecture plein écran
Le bouton flottant 📖 (#me-reading-btn) bascule body.reading-mode :
- masque l'en-tête, la sidebar, la barre latérale droite, la barre d'outils mobile et les actions de fichier ; centre le contenu en colonne de lecture ;
- demande l'API Fullscreen (repli silencieux si non supportée, ex. iOS) ; la sortie du plein écran (Échap) resynchronise la classe ;
- en mode lecture, le balayage horizontal navigue vers le fichier suivant/précédent du
même dossier (
/api/vault/{vault}/files?dir=…&recursive=false, tri alphabétique, bouclage borné avec message si extrémité atteinte) ; - l'état est mémorisé (
obsigate-reading-mode) et restauré au rechargement sur mobile.
Visibilité conditionnelle (BUG-016). Le bouton 📖 n'est affiché que si un fichier est
ouvert (state.currentPath) et que le panneau assistant est fermé : sinon il passait
au-dessus du panneau mobile plein écran et masquait le bouton d'envoi. updateReadingButtonVisibility()
est appelé à l'init, à chaque mutation de #content-area (MutationObserver) et sur les événements
bookslm:opened / bookslm:closed.
Tests
tests/frontend/mobile-editor.test.mjs (JSDOM, 24 tests) couvre :
FORMAT_ACTIONS,clampZoom,detectSwipe(seuil, dominance verticale, gauche/droite) ;formatChangepour bold/italic/code/liste/lien, avec et sans sélection, toggle et ligne unique ;insertText,applyFormatToTextarea,applyEditorFontSize(clamp + persistance) ;- le câblage DOM : construction de
#me-toolbar, idempotence, contrôles du mode lecture, basculetoggleReadingMode/isReadingMode, et visibilité conditionnelle de#me-reading-btn(fichier ouvert + assistant fermé).
Les chemins réseau (backlinks, fichiers adjacents) et les gestes réels restent à valider en E2E navigateur / test manuel mobile.
Notes
- Le module n'importe
ui.js,auth.jsetviewer.jsque dynamiquement dans les fonctions concernées, afin de rester testable en JSDOM sans charger la pile applicative complète. - Aucune modification backend : les endpoints réutilisés (
/backlinks,/vault/{vault}/files) existaient déjà.