Files
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

9.1 KiB
Raw Permalink Blame History

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).