Rendre les notes en markdown, et en donner au projet
Une phase avait quatre lignes de texte brut ; un projet n'avait rien. Les deux
manques se tiennent : la frise ne dit pas qui pilote ni sur quel budget, et ce
contexte finissait dans les notes de la première phase venue, où il ne survit
pas à sa suppression.
Le bloc de notes est le même dans les deux panneaux et prend toute la hauteur
restante : les autres champs ont une taille dictée par leur contenu, une note
fait ce qu'on a à dire. Il s'ouvre sur l'aperçu quand la note existe, sur la
saisie quand elle est vide.
Les cases à cocher se cliquent dans l'aperçu — seul geste de l'aperçu qui touche
aux données. La bascule réécrit la seule ligne visée plutôt que de régénérer la
note, qui verrait sinon sa mise en forme normalisée. Inertes dans la vue par
mois, en lecture seule (décision 24).
La barre d'outils écrit par `document.execCommand('insertText')` et non en
affectant `value`, qui viderait la pile d'annulation du navigateur : `Ctrl+Z`
doit continuer de marcher. Le DOM est bâti nœud par nœud, sans un `innerHTML`.
Les infobulles aplatissent le markdown — `title` ne connaît que le texte —, et
celles d'un jalon et d'un libellé de projet les affichent désormais.
Le format passe en version 4 : le validateur reconstruit chaque projet champ par
champ, un binaire antérieur effacerait les notes de projet à la première
sauvegarde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -601,3 +601,111 @@ revenir des années avant ce qu'on regardait serait une punition pour avoir cons
|
||||
Deux conséquences dans le code, toutes deux de bon aloi : les noms de mois et `formaterDateLongue`
|
||||
sont remontés de `timeline.js` vers `model.js`, et les pastilles de tags dans `tags.js`. Deux vues
|
||||
sœurs les emploient, aucune n'a à importer l'autre pour écrire « septembre ».
|
||||
|
||||
## 25. Les notes sont en markdown, et le projet en a
|
||||
|
||||
Une phase avait des notes : un `textarea` de quatre lignes au bas du panneau, du texte brut. Un
|
||||
projet n'en avait aucune. Les deux manques se tiennent.
|
||||
|
||||
**Le projet en avait besoin.** La frise dit quand les choses arrivent ; elle ne dit pas qui pilote,
|
||||
sur quel budget, ni pourquoi le prestataire est engagé jusqu'en janvier. Ce contexte-là finissait
|
||||
dans les notes de la première phase venue, où il n'a rien à faire — il survit à la phase, il ne
|
||||
survit pas à sa suppression. Le champ est donc monté d'un cran, et le panneau d'un projet reçoit le
|
||||
même bloc que celui d'une phase : renommer un projet et renommer une phase sont la même opération à
|
||||
un niveau près (décision 21), y écrire une note aussi.
|
||||
|
||||
**Quatre lignes fixes étaient un plafond arbitraire.** Les autres champs du panneau ont une taille
|
||||
dictée par leur contenu — une date en fait dix caractères, un statut en fait quatre. Une note n'a
|
||||
aucune taille propre : elle fait ce qu'on a à dire. Elle prend donc toute la hauteur que les champs
|
||||
au-dessus lui laissent, avec un plancher sous lequel le corps du panneau défile plutôt que de
|
||||
l'écraser.
|
||||
|
||||
### Pourquoi un analyseur maison
|
||||
|
||||
Le markdown demandait une dépendance, ou du code. Une dépendance aurait été le premier
|
||||
`node_modules` de l'outil, contre la décision 10, pour une syntaxe dont on n'emploie ici qu'une
|
||||
poignée de formes. On a donc écrit `js/markdown.js` : un sous-ensemble, environ trois cents lignes,
|
||||
sans DOM ni réseau — ce qui le rend testable sous Node comme `model.js`, le rendu proprement dit
|
||||
vivant dans `notes.js`.
|
||||
|
||||
Le choix du sous-ensemble suit ce qu'on écrit dans une note de suivi : titres, emphase, code,
|
||||
listes, cases à cocher, liens, citations, séparateur. Pas de tableaux ni d'images — ils ne tiennent
|
||||
pas dans un volet latéral. Pas de HTML brut, jamais : le DOM est bâti nœud par nœud, sans un seul
|
||||
`innerHTML`. Un planning s'échange, on en ouvre un qu'un autre a écrit, et une note est du texte
|
||||
libre ; la seule façon sûre d'en afficher est de ne jamais laisser le navigateur l'interpréter comme
|
||||
du balisage. Les URL passent une liste blanche de schémas — `http`, `https`, `mailto` — et un lien
|
||||
refusé retombe sur son libellé plutôt que de disparaître.
|
||||
|
||||
Deux écarts au standard, assumés. **Un retour à la ligne en est un** : CommonMark recolle les lignes
|
||||
d'un paragraphe et exige deux espaces en fin de ligne pour un vrai retour, piège invisible dans un
|
||||
champ où l'on jette une petite liste à la volée. Et **ce qui n'est pas reconnu reste du texte** :
|
||||
`**gras` sans fermeture s'affiche tel quel, parce qu'une note à moitié écrite est l'état normal
|
||||
d'une note et que l'aperçu doit suivre la frappe sans à-coups.
|
||||
|
||||
### On lit d'abord, on écrit ensuite
|
||||
|
||||
Le bloc s'ouvre sur l'**aperçu** quand la note existe, sur la **saisie** quand elle est vide. C'est
|
||||
l'usage : on ouvre une phase bloquée pour relire pourquoi, une phase vierge pour y consigner
|
||||
quelque chose. Un crayon bascule d'un état à l'autre, et la barre d'outils ne paraît qu'en saisie —
|
||||
en aperçu elle ne commanderait rien, et sa place revient au texte.
|
||||
|
||||
Six boutons, pas plus : gras, italique, titre, liste, case à cocher, lien. Le markdown entier reste
|
||||
accessible au clavier puisque c'est du texte ; la barre n'est là que pour épargner la syntaxe des
|
||||
formes courantes. Une barre exhaustive prendrait deux rangées dans le volet et repousserait la note
|
||||
d'autant.
|
||||
|
||||
Les **cases à cocher se cliquent dans l'aperçu**, et c'est le seul geste de l'aperçu qui modifie les
|
||||
données. Une note de suivi contient des choses à faire ; les cocher en rouvrant la saisie pour
|
||||
transformer un `[ ]` en `[x]` serait une corvée absurde. La bascule réécrit **la seule ligne visée
|
||||
dans le texte d'origine** plutôt que de régénérer la note depuis l'arbre : régénérer normaliserait
|
||||
au passage les marqueurs, l'indentation et l'emphase de l'utilisateur, qui verrait sa mise en forme
|
||||
réécrite pour avoir coché une case.
|
||||
|
||||
Dans la **vue par mois**, en revanche, les cases sont inertes. La liste est en lecture seule
|
||||
(décision 24) et une case cliquable y serait la seule chose qu'on puisse modifier — une exception
|
||||
isolée dans une vue dont toute la promesse est qu'on n'y casse rien. On les voit cochées ou non, ce
|
||||
qui est justement ce qu'on vient y chercher quand on fait le point.
|
||||
|
||||
### `Ctrl+Z` doit continuer de marcher
|
||||
|
||||
Une barre d'outils écrit dans le champ à la place de l'utilisateur, et la façon évidente de le faire
|
||||
— affecter `champ.value` — **vide la pile d'annulation** du navigateur. `Ctrl+Z` cesse alors de
|
||||
remonter au-delà du bouton pressé, et la frappe qui précédait devient irrécupérable. C'est un prix
|
||||
qu'on ne peut pas faire payer à un champ de saisie : annuler est le réflexe le plus élémentaire
|
||||
qu'on ait devant du texte, et l'ancien champ de notes, lui, l'honorait sans qu'on ait rien à écrire.
|
||||
|
||||
Toutes les écritures passent donc par `document.execCommand('insertText')`, qui laisse le navigateur
|
||||
enregistrer l'opération comme si elle avait été tapée. L'API est marquée obsolète et reste sans
|
||||
remplaçant pour cet usage : les `InputEvent` synthétiques qui devaient lui succéder ne modifient
|
||||
rien, un navigateur ignorant les événements qu'il n'a pas produits. Tenir notre propre historique
|
||||
reviendrait à réimplémenter `Ctrl+Z`, `Ctrl+Y` et la fusion des frappes voisines — sans commune
|
||||
mesure avec le service rendu par un champ de notes. Si la commande échoue, on retombe sur
|
||||
l'affectation directe : on perd l'annulation, jamais la saisie.
|
||||
|
||||
Deux gardes vont avec. `execCommand` écrit **là où est le focus**, sans considération pour l'élément
|
||||
qu'on croit viser : on vérifie donc que le champ l'a bien pris, faute de quoi la commande irait
|
||||
insérer du markdown dans le champ « Nom ». Et une insertion vide n'en est pas une — c'est une
|
||||
suppression, que `insertText` ne traite pas partout, d'où le passage par `delete`.
|
||||
|
||||
Une seule écriture y échappe : cocher une case depuis l'aperçu, où le champ est masqué et ne peut
|
||||
pas prendre le focus. Le geste vide donc la pile — sans grande conséquence, puisqu'on n'était pas en
|
||||
train d'y taper.
|
||||
|
||||
### Les infobulles s'aplatissent
|
||||
|
||||
Les **infobulles** de la frise ne peuvent pas montrer de rendu : l'attribut `title` du
|
||||
navigateur ne connaît que le texte. Les notes y sont donc **aplaties** — marques retirées, et
|
||||
remplacées par un caractère qui porte le même sens là où il y en a un : `•` pour un tiret, `☐` et
|
||||
`☑` pour une case, `│` pour une citation. Sans cela, survoler une barre afficherait la source
|
||||
markdown, astérisques comprises, soit une note *moins* lisible qu'avant qu'on ne l'enrichisse. Deux
|
||||
infobulles taisaient d'ailleurs les notes et les disent maintenant : celle d'un jalon — c'est
|
||||
pourtant là qu'on consigne une décision — et celle du libellé d'un projet, seul endroit de la frise
|
||||
où ses notes à lui peuvent se lire, faute de barre qui lui appartienne.
|
||||
|
||||
### Le format passe en version 4
|
||||
|
||||
Ajouter un champ n'oblige à rien : une version 3 se relit sans encombre. Mais le validateur
|
||||
reconstruit chaque projet champ par champ et laisse tomber ce qu'il ne connaît pas. Sans
|
||||
incrémenter, un binaire antérieur ouvrirait un fichier plus riche que lui sans broncher et en
|
||||
effacerait toutes les notes de projet à la première sauvegarde. Le numéro ne sert qu'à cela : faire
|
||||
échouer bruyamment ce qui échouerait silencieusement.
|
||||
|
||||
Reference in New Issue
Block a user