/* 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));