From 4cf2ab3b08456d40feb1e3afa1b855801e31feaa Mon Sep 17 00:00:00 2001 From: Vincent Bourdon Date: Fri, 6 Nov 2026 18:45:53 +0100 Subject: [PATCH] docs: design de l'extension Read More Co-Authored-By: Claude Opus 5.5 --- .../specs/2026-11-06-read-more-design.md | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/superpowers/specs/2026-11-06-read-more-design.md diff --git a/docs/superpowers/specs/2026-11-06-read-more-design.md b/docs/superpowers/specs/2026-11-06-read-more-design.md new file mode 100644 index 0000000..28e89ac --- /dev/null +++ b/docs/superpowers/specs/2026-11-06-read-more-design.md @@ -0,0 +1,165 @@ +# 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` ; 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' => ['height' => …, libellés]] + 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).