feat: repli des articles en mode lecture avec bouton Lire plus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8b334767d
commit
dcacf1acb1
5 files changed
+872
No files matched your search
@@ -0,0 +1,101 @@
|
||||
/* Read More — FreshRSS extension.
|
||||
* Collapses long articles in reader view behind a "Read more" button.
|
||||
* Collapsing uses max-height (never display:none) so FreshRSS's
|
||||
* mark-as-read-on-scroll keeps measuring a non-zero article height. */
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
const THRESHOLD_RATIO = 1.5;
|
||||
const STATE = { collapsed: 'collapsed', short: 'short', expanded: 'expanded' };
|
||||
const defaultMeasure = (el) => el.scrollHeight;
|
||||
|
||||
function shouldCollapse(contentHeight, collapsedHeight) {
|
||||
return contentHeight >= collapsedHeight * THRESHOLD_RATIO;
|
||||
}
|
||||
|
||||
function expand(flux, text, button) {
|
||||
text.classList.remove('read-more-collapsed');
|
||||
text.style.removeProperty('--read-more-height');
|
||||
button.remove();
|
||||
flux.dataset.readMore = STATE.expanded;
|
||||
}
|
||||
|
||||
function collapse(flux, text, opts) {
|
||||
const doc = flux.ownerDocument;
|
||||
text.classList.add('read-more-collapsed');
|
||||
text.style.setProperty('--read-more-height', opts.collapsedHeight + 'px');
|
||||
const button = doc.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'btn read-more-btn';
|
||||
button.textContent = opts.label;
|
||||
button.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
expand(flux, text, button);
|
||||
});
|
||||
text.after(button);
|
||||
flux.dataset.readMore = STATE.collapsed;
|
||||
}
|
||||
|
||||
function processEntry(flux, opts) {
|
||||
const state = flux.dataset.readMore;
|
||||
if (state !== undefined && state !== STATE.short) {
|
||||
return;
|
||||
}
|
||||
const text = flux.querySelector('.text');
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const measure = opts.measure || defaultMeasure;
|
||||
if (shouldCollapse(measure(text), opts.collapsedHeight)) {
|
||||
collapse(flux, text, opts);
|
||||
} else {
|
||||
flux.dataset.readMore = STATE.short;
|
||||
}
|
||||
}
|
||||
|
||||
function processEntries(rootNode, opts) {
|
||||
rootNode.querySelectorAll('.flux').forEach((flux) => processEntry(flux, opts));
|
||||
}
|
||||
|
||||
function init(win, measure) {
|
||||
const context = win.context;
|
||||
if (!context || context.current_view !== 'reader') {
|
||||
return;
|
||||
}
|
||||
const conf = (context.extensions && context.extensions.read_more) || {};
|
||||
const opts = {
|
||||
collapsedHeight: Number(conf.collapsed_height) || 250,
|
||||
label: conf.label || 'Read more',
|
||||
measure: measure,
|
||||
};
|
||||
const doc = win.document;
|
||||
processEntries(doc, opts);
|
||||
doc.body.addEventListener('freshrss:load-more', () => processEntries(doc, opts));
|
||||
// Late-loading images can make a "short" article long enough to collapse.
|
||||
doc.addEventListener('load', function (ev) {
|
||||
const target = ev.target;
|
||||
if (!target || target.tagName !== 'IMG' || !target.closest) {
|
||||
return;
|
||||
}
|
||||
const flux = target.closest('.flux[data-read-more="short"]');
|
||||
if (flux) {
|
||||
processEntry(flux, opts);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
const api = { THRESHOLD_RATIO, shouldCollapse, processEntries, init };
|
||||
root.ReadMore = api;
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = api;
|
||||
}
|
||||
|
||||
if (root.document && root.document.addEventListener) {
|
||||
if (typeof root.context !== 'undefined' && typeof root.context.extensions !== 'undefined') {
|
||||
init(root);
|
||||
} else {
|
||||
root.document.addEventListener('freshrss:globalContextLoaded', () => init(root), false);
|
||||
}
|
||||
}
|
||||
}(typeof window !== 'undefined' ? window : globalThis));
|
||||
Reference in new issue
Block a user