文章可读性提取器 · 提取正文 · 切换主题 · 导出多种格式
等标签的 HTML 内容', urlLabel: '文章 URL', urlPlaceholder: 'https://example.com/article', fetchUrlBtn: '提取', urlNote: '注意:纯前端无法跨域,建议使用模式1粘贴内容', extractBtn: '提取文章', clearBtn: '清空', placeholderTitle: '粘贴文章内容后点击「提取文章」', placeholderDesc: '支持 HTML 或纯文本格式', themeTitle: '阅读主题', themeDark: '深色', themeLight: '浅色', themeWarm: '暖色', themeEye: '护眼', sizeTitle: '字体大小', sizeSm: '小', sizeMd: '中', sizeLg: '大', sizeXl: '特大', statsTitle: '文章统计', statWords: '字数', statReadTime: '阅读时间', statParagraphs: '段落数', exportTitle: '导出', exportMarkdown: 'Markdown', exportText: '纯文本', exportPdf: 'PDF (打印)', copyContent: '复制文章内容', wordCount: '字', readTimeMinute: '分钟', toastPasteFirst: '请粘贴文章内容', toastExtracted: '文章已提取', toastNoCrossOrigin: '纯前端无法跨域获取 URL 内容,请使用「粘贴内容」模式', toastExtractFirst: '请先提取文章', toastExportedMarkdown: '已导出 Markdown', toastExportedText: '已导出纯文本', toastCopied: '文章内容已复制' }, en: { pageTitle: 'Readability - Article Readability Extractor | Online Tool', metaDescription: 'Free online article readability extraction tool. Paste article content or enter URL, extract title/author/body, switch reading themes and font sizes, export to Markdown/PlainText/PDF.', ogTitle: 'Readability - Article Readability Extractor', ogDescription: 'Free online article readability extraction tool with multiple themes and font sizes, export to Markdown/PlainText/PDF.', headerTitle: 'Readability', headerSubtitle: 'Article Readability Extractor · Extract Content · Switch Themes · Export Formats', inputModePaste: 'Paste Content', inputModeUrl: 'Enter URL', pasteLabel: 'Paste article content (HTML or plain text)', contentInputPlaceholder: 'Paste article content HTML or plain text here...\n\nTip: You can directly paste HTML containing
tags', urlLabel: 'Article URL', urlPlaceholder: 'https://example.com/article', fetchUrlBtn: 'Fetch', urlNote: 'Note: Pure frontend cannot cross-origin. Recommend Paste mode.', extractBtn: 'Extract', clearBtn: 'Clear', placeholderTitle: 'Paste article content then click "Extract"', placeholderDesc: 'Supports HTML or plain text', themeTitle: 'Reading Theme', themeDark: 'Dark', themeLight: 'Light', themeWarm: 'Warm', themeEye: 'Eye-care', sizeTitle: 'Font Size', sizeSm: 'Small', sizeMd: 'Medium', sizeLg: 'Large', sizeXl: 'Extra Large', statsTitle: 'Article Stats', statWords: 'Words', statReadTime: 'Read Time', statParagraphs: 'Paragraphs', exportTitle: 'Export', exportMarkdown: 'Markdown', exportText: 'Plain Text', exportPdf: 'PDF (Print)', copyContent: 'Copy Content', wordCount: 'characters', readTimeMinute: 'min', toastPasteFirst: 'Please paste article content', toastExtracted: 'Article extracted', toastNoCrossOrigin: 'Cannot fetch URL cross-origin. Use Paste mode.', toastExtractFirst: 'Please extract an article first', toastExportedMarkdown: 'Exported as Markdown', toastExportedText: 'Exported as plain text', toastCopied: 'Content copied' } }; function t(key) { const pack = i18n[currentLang]; return (pack && pack[key]) || (i18n['zh'][key]) || key; } // Global error handler window.onerror = function(msg, url, line, col, err) { console.error('[Global Error]', msg, 'at', url, line, col); }; window.addEventListener('unhandledrejection', function(e) { console.error('[Unhandled Promise Rejection]', e.reason); }); // Apply i18n to HTML elements function applyI18n() { // title const titleEl = document.querySelector('title[data-i18n]'); if(titleEl) document.title = t(titleEl.getAttribute('data-i18n')); // meta with data-i18n-content document.querySelectorAll('meta[data-i18n-content]').forEach(function(el){ const key = el.getAttribute('data-i18n-content'); if(key) el.setAttribute('content', t(key)); }); // elements with data-i18n document.querySelectorAll('[data-i18n]').forEach(function(el){ if(el.tagName === 'TITLE' || el.tagName === 'META') return; const key = el.getAttribute('data-i18n'); if(key) el.textContent = t(key); }); // placeholders document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){ const key = el.getAttribute('data-i18n-placeholder'); if(key) el.setAttribute('placeholder', t(key)); }); } // Override showToast window.showToast = function(msg) { const el = document.getElementById('toast'); if(!el) return; el.textContent = msg; el.classList.add('show'); clearTimeout(el._timer); el._timer = setTimeout(function(){ el.classList.remove('show'); }, 2400); }; // Override extractArticle window.extractArticle = function() { const raw = document.getElementById('contentInput').value.trim(); if (!raw) { showToast(t('toastPasteFirst')); return; } let title = ''; let author = ''; let contentHtml = ''; let contentText = raw; const parser = new DOMParser(); const doc = parser.parseFromString(raw, 'text/html'); const hasHtml = doc.body && doc.body.children.length > 0 && !raw.startsWith(' '); if (hasHtml && doc.body.innerHTML !== raw) { const h1 = doc.querySelector('h1'); if (h1) title = h1.textContent.trim(); else { const h2 = doc.querySelector('h2'); if (h2) title = h2.textContent.trim(); } const metaAuthor = doc.querySelector('meta[name="author"]'); if (metaAuthor) author = metaAuthor.content; if (!author) { const byline = doc.querySelector('.byline, .author, [rel="author"]'); if (byline) author = byline.textContent.trim(); } if (!author) { const timeEl = doc.querySelector('time'); if (timeEl) author = timeEl.textContent.trim(); } contentHtml = doc.body.innerHTML; contentText = doc.body.textContent.trim(); } else { const lines = raw.split('\n').filter(function(l){ return l.trim(); }); if (lines.length > 0) { title = lines[0].trim(); if (title.length > 80) title = ''; } contentText = raw; contentHtml = raw.split('\n').filter(function(l){ return l.trim(); }).map(function(l){ return '
' + escHtml(l) + '
'; }).join('\n'); } if (!title) title = '无标题文章'; contentText = contentText.replace(/\s+/g, ' ').trim(); window.currentArticle = { title: title, author: author, content: contentText, html: contentHtml }; renderArticle(); updateStats(); }; // Override renderArticle window.renderArticle = function() { const ra = document.getElementById('readingArea'); if(!ra) return; const article = window.currentArticle; const authorLine = article.author ? ' · ' + escHtml(article.author) : ''; ra.innerHTML = '' + t('placeholderTitle') + '
' + t('placeholderDesc') + '