// Reading Mode — official WebToApp sample.
//
// Demonstrates the full module surface in one place:
// - Reads multiple typed configItems via getConfig()
// - Heuristic article extraction (no external libs)
// - Heavy DOM restructuring with safe rollback
// - Registers a panel button via __WTA_MODULE_UI__ so users can summon it
// - Persists state across SPA navigations using sessionStorage
//
// The implementation is intentionally simple — under 200 lines — so it
// reads like a tutorial. For production use, wire in a real extraction
// library such as Mozilla's @mozilla/readability.
(function () {
var STATE_KEY = 'wta-reading-mode-active';
var ROOT_CLASS = 'wta-reading-mode-root';
var ARTICLE_ID = 'wta-reading-mode-article';
function getOpts() {
return {
theme: getConfig('theme', 'light'),
fontSize: getConfig('fontSize', 'medium'),
fontFamily: getConfig('fontFamily', 'serif'),
maxWidth: parseInt(getConfig('maxWidth', '720'), 10) || 720,
autoEnter: String(getConfig('autoEnter', 'false')) === 'true',
};
}
// ── content extraction ────────────────────────────────────────────
// Score every block element by text density × tag weight, pick the best.
// Not as accurate as Readability.js but good enough for the common case
// and trivial to read.
function findArticle() {
var candidates = document.querySelectorAll('article, main, [role=main], section, div');
var best = null;
var bestScore = 0;
var AD_PATTERNS = /ad|advert|promo|sidebar|footer|header|nav|menu|cookie|consent|popup|modal|social|share|comment|related|recommend/i;
for (var i = 0; i < candidates.length; i++) {
var el = candidates[i];
if (el === document.body || el === document.documentElement) continue;
var text = (el.textContent || '').trim();
var len = text.length;
if (len < 200) continue;
var paragraphs = el.querySelectorAll('p').length;
if (paragraphs < 2) continue;
var tagBoost = 1;
var name = el.tagName.toLowerCase();
if (name === 'article') tagBoost = 4;
else if (name === 'main') tagBoost = 3;
else if (el.getAttribute('role') === 'main') tagBoost = 3;
var links = el.querySelectorAll('a').length;
var linkDensity = links / Math.max(1, paragraphs);
var penalty = linkDensity > 3 ? 0.3 : 1;
var cls = (el.className || '') + ' ' + (el.id || '');
if (AD_PATTERNS.test(cls)) penalty *= 0.2;
var commLinks = el.querySelectorAll('a[href*="comment"], a[href*="share"], a[href*="social"]').length;
if (commLinks > 3) penalty *= 0.5;
var score = len * tagBoost * penalty;
if (score > bestScore) {
bestScore = score;
best = el;
}
}
return best;
}
function wordCount(el) {
return (el.textContent || '').split(/\s+/).filter(Boolean).length;
}
// ── enter / exit ──────────────────────────────────────────────────
function enter() {
if (document.documentElement.classList.contains(ROOT_CLASS)) return;
var article = findArticle();
if (!article) {
console.warn('[Reading Mode] no readable article detected');
return;
}
var opts = getOpts();
var html = article.innerHTML;
var title = (document.title || '').trim();
var words = (article.textContent || '').trim().split(/\s+/).filter(Boolean).length;
var readMin = Math.max(1, Math.round(words / 200));
var readLabel = readMin + ' min read';
var shell = document.createElement('div');
shell.id = ARTICLE_ID;
shell.dataset.theme = opts.theme;
shell.dataset.fontSize = String(opts.fontSize);
shell.dataset.fontFamily = opts.fontFamily;
shell.style.maxWidth = opts.maxWidth + 'px';
shell.innerHTML =
'' +
escapeHtml(title) +
'