Files
freshrss-read-more/docs/superpowers/specs/2026-11-06-read-more-design.md
T
Vincent BourdonandClaude Opus 5.5 66d997cc6e fix: init robuste avant <body>, pas de repli tardif au-dessus de l'écran, README
- écoute freshrss:load-more sur document (script async possiblement exécuté
  avant le parsing de <body>)
- ne replie jamais après coup un article qui commence au-dessus de l'écran :
  la page sauterait et des articles non vus seraient marqués lus
- README : l'image officielle n'a pas git, installation depuis l'hôte

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 19:15:54 +01:00

166 lines
9.1 KiB
Markdown
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.
# FreshRSS « Read More » — conception
Date : 2026-11-06
Dépôt : https://git.siteop.biz/vincentb99/freshrss-read-more.git
## Intention
En **mode lecture** de FreshRSS, les articles s'affichent en entier les uns sous les autres.
Pour des newsletters de veille lues sur smartphone, juger si un sujet intéresse demande
seulement **le titre et un paragraphe** ; quand il n'intéresse pas, il faut aujourd'hui
scroller tout l'article pour atteindre le suivant.
L'extension replie chaque article à une hauteur réduite avec un bouton **« Lire plus »**
qui déroule l'article complet sur place. Un article non intéressant se traverse en un
geste de scroll.
**Contrainte centrale :** le passage automatique en « lu » au scroll de FreshRSS doit
continuer de fonctionner. C'est tout l'intérêt de l'extension : on scrolle, ce qu'on a
dépassé est lu.
## Objectifs
- Réduire le scroll nécessaire pour parcourir une liste d'articles en mode lecture.
- Permettre de lire l'article complet en un tap (« Lire plus »).
- Préserver strictement le marquage « lu » au scroll.
## Existant
Aucune extension équivalente trouvée (2026-11-06) dans la liste officielle
(FreshRSS/Extensions). La plus proche, *Quick Collapse*, bascule tout le flux entre titres
seuls et articles complets, uniquement en vue normale — pas de repli partiel ni de mode lecture.
## Non-objectifs (v1)
- Pas d'effet sur la vue normale ni la vue globale.
- Pas de réglage par flux / catégorie.
- Pas de bouton « Réduire » après dépliage.
- Pas de mémorisation de l'état déplié entre deux chargements de page.
## Anti-objectifs (échec même si « ça marche »)
- Un article replié qui n'est plus marqué « lu » au scroll.
- Un clic sur « Lire plus » qui fait sauter la page, recharge, ou change l'état lu/non lu.
- Des articles chargés dynamiquement (scroll infini) qui ne sont pas repliés.
- Un bouton « Lire plus » sur un article qui tient déjà presque entièrement.
## Faits vérifiés dans le code FreshRSS (master au 2026-11-06)
**Version cible : FreshRSS 1.30.4** (production). Comparaison 1.30.4 ↔ master : aucune
différence sur `onScroll()`, les événements `freshrss:load-more` / `globalContextLoaded`,
le hook `JsVars`, le markup du mode lecture ni le chargement des extensions. Les faits
ci-dessous valent donc pour 1.30.4.
- **Marquage lu au scroll** — `p/scripts/main.js`, `onScroll()` : un `.not_read:not(.keep_unread)`
est marqué lu quand `div.offsetHeight > 0` et que son bord bas
(`offsetParent.offsetTop + offsetTop + offsetHeight`) passe au-dessus du haut de la zone
scrollée (−5 px). La hauteur **réelle affichée** est utilisée : replier l'article le fait
passer en lu plus tôt, ce qui est le comportement souhaité.
⚠️ Un article de hauteur 0 (`display:none`) n'est jamais marqué : ne jamais masquer
l'article entier.
Le scroll est suivi sur `document.scrollingElement` (sauf vue globale : `#panel`), avec
throttle 500 ms.
- Ce comportement dépend de l'option utilisateur FreshRSS « marquer comme lu au défilement »
(`context.auto_mark_scroll`) — l'extension ne la modifie pas.
- **Markup mode lecture** — `app/views/index/reader.phtml` : chaque article est un
`div.flux` (classes `not_read`, `keep_unread`…) contenant
`article.flux_content > div.content > … > div.text` (`app/views/helpers/index/article.phtml`).
- **Vue courante côté JS** — `context.current_view === 'reader'`.
- **Chargement de nouveaux articles** — événement `freshrss:load-more` dispatché sur
`document.body` (bubbles) à chaque lot chargé.
- **Passage de config au JS** — hook `Minz_HookType::JsVars` (clé `read_more.collapsed_height`, `read_more.label`) ; les données retournées
apparaissent dans `context.extensions`.
- **Découverte des extensions** — tout dossier de `extensions/` (`THIRDPARTY_EXTENSIONS_PATH`)
contenant un `metadata.json` valide ; pas de préfixe de nom imposé. En Docker :
`/var/www/FreshRSS/extensions` (volume `freshrss_extensions` dans la doc officielle).
## Comportement décidé
| Sujet | Décision | Origine |
|---|---|---|
| Vue concernée | Mode lecture uniquement | utilisateur |
| Écrans | Même repli sur mobile et desktop | utilisateur |
| Critère de repli | Hauteur fixe en px (pas « N paragraphes » : logos/bannières de newsletter en tête) | proposé, validé |
| Hauteur par défaut | 250 px, réglable dans la config de l'extension | proposé, validé |
| Seuil | Ne pas replier si la hauteur du texte < 1,5 × la hauteur repliée | proposé, validé |
| Indicateur | Dégradé en bas de la zone repliée + bouton « Lire plus » dessous | proposé, validé |
| Dépliage | Sur place, sans rechargement, sans saut de scroll | proposé, validé |
| Après dépliage | Reste déplié ; pas de « Réduire » | proposé, validé |
| État lu | Le bouton ne touche pas à l'état lu ; seul le scroll natif marque | proposé, validé |
| Portée | Tous les flux | proposé, validé |
| Articles chargés au scroll | Repliés aussi | contrainte technique |
| Langue | FR + EN | proposé, validé |
## Architecture
Extension de type `user` (activable par utilisateur), sans base de données ni build.
```
metadata.json name "Read More", entrypoint "ReadMore", type "user"
extension.php class ReadMoreExtension extends Minz_Extension
init(): registerTranslates(), appendScript/appendStyle (static/)
registerHook(JsVars) -> ['read_more' => ['collapsed_height' => …, 'label' => …]]
handleConfigureAction(): valide et enregistre `collapsed_height` (int, bornes raisonnables)
configure.phtml formulaire : hauteur repliée (px)
i18n/fr/ext.php, i18n/en/ext.php libellés (« Lire plus », config)
static/read-more.js logique côté client
static/read-more.css styles (max-height, overflow hidden, dégradé, bouton)
README.md, LICENSE
```
Le **dépôt git est directement le dossier de l'extension** (cloné dans `extensions/`).
### Logique JS (principe)
1. Ne rien faire si `context.current_view !== 'reader'`. Attendre que `context` soit prêt
(événement `freshrss:globalContextLoaded` ou équivalent selon l'ordre de chargement).
2. Pour chaque `.flux` non encore traité : sur son `.text`, si `scrollHeight ≥ 1,5 × hauteur`,
ajouter une classe de repli (CSS `max-height` + `overflow:hidden` + dégradé) et insérer
un bouton « Lire plus » après le `.text`. Marquer le `.flux` comme traité.
3. Clic sur le bouton : retirer la classe de repli, supprimer le bouton ; `preventDefault`
et `stopPropagation` pour éviter que FreshRSS interprète le clic (ouverture/marquage).
4. Rejouer l'étape 2 sur `freshrss:load-more`.
5. Les images chargées après coup font grandir le contenu : un article d'abord jugé court
peut dépasser le seuil — réévaluer (ex. événements `load` des images ou
`ResizeObserver`), au choix du développeur.
## Étapes pour créer et utiliser l'extension
1. **Développement** : écrire les fichiers ci-dessus dans ce dépôt.
2. **Test local** : lancer un FreshRSS Docker de test avec le dépôt monté en volume :
`docker run -d -p 8080:80 -v "$PWD":/var/www/FreshRSS/extensions/freshrss-read-more freshrss/freshrss:1.30.4`
(image épinglée sur la version de production),
ajouter quelques flux (dont une newsletter longue), activer « marquer comme lu au
défilement », passer en mode lecture.
3. **Activation** : Configuration → Extensions → « Read More » → activer ; régler la hauteur
via l'icône engrenage.
4. **Vérification** (voir critères ci-dessous) sur desktop et en mode mobile du navigateur,
puis sur un vrai smartphone.
5. **Publication** : `git push` vers `git.siteop.biz/vincentb99/freshrss-read-more`.
6. **Installation sur le serveur** : dans le volume extensions du conteneur de production,
`git clone https://git.siteop.biz/vincentb99/freshrss-read-more.git`
(ou `docker exec` dans `/var/www/FreshRSS/extensions`), puis activer dans l'interface.
Mises à jour : `git pull` dans ce dossier, puis rechargement du navigateur.
## Critères d'acceptation
- En mode lecture, un article long est replié à la hauteur configurée, avec dégradé et
bouton « Lire plus ».
- Un article court (< 1,5 × hauteur) n'est pas replié et n'a pas de bouton.
- Scroller au-delà d'un article replié non lu le marque « lu » (option FreshRSS activée).
- Scroller au-delà d'un article déplié le marque « lu » une fois son bas dépassé.
- « Lire plus » déplie sur place, sans saut de scroll, sans changer l'état lu.
- Les articles chargés par le scroll infini sont repliés.
- Vue normale et vue globale : aucun changement.
- Changer la hauteur dans la config change le repli après rechargement.
- Désactiver l'extension rétablit l'affichage d'origine.
## Laissé au développeur
- Indiquer dans le README la version minimale testée (1.30.4).
- Bornes de validation de la hauteur (ex. 100–2000 px).
- Mécanisme exact de réévaluation après chargement des images.
- Style précis du bouton (s'appuyer sur les classes `btn` de FreshRSS pour suivre le thème).
- Méthode d'injection de la config dans le CSS (variable CSS posée par le JS recommandée).