- Extract shared CSS (sidebar, theme vars, layout) into shared.css - Extract shared JS (tailwind config, sidebar injection, theme toggle) into shared.js - Remove ~1200 lines of duplicated code across 13 HTML pages - Standardize all titles to format: Tool Name — WA Dev Tools - Standardize page headers (h1 + description) across all tools - Standardize container padding (tool-container class) - Sidebar now injected via JS — single source of truth - Use CSS variables for accent colors everywhere (no more hardcoded #0054e6)
414 lines
22 KiB
HTML
414 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru" class="dark">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Парсер статей — WA Dev Tools</title>
|
|
<script src="/vendor/tailwind.js"></script>
|
|
<script>window.WA_TOOL_ID = 'parser';</script>
|
|
<script src="/shared.js"></script>
|
|
<link href="/vendor/fonts.css" rel="stylesheet">
|
|
<link href="/shared.css" rel="stylesheet">
|
|
<style>
|
|
@keyframes pulse-border { 0%, 100% { border-color: rgba(0, 84, 230, 0.2); } 50% { border-color: rgba(0, 84, 230, 0.5); } }
|
|
.processing { animation: pulse-border 1.5s ease-in-out infinite; }
|
|
|
|
.article-content { color: var(--text-primary); line-height: 1.75; font-size: 15px; }
|
|
.article-content h1 { font-size: 1.8em; font-weight: 800; margin: 1em 0 .5em; }
|
|
.article-content h2 { font-size: 1.4em; font-weight: 700; margin: .9em 0 .4em; }
|
|
.article-content h3 { font-size: 1.2em; font-weight: 600; margin: .8em 0 .3em; }
|
|
.article-content p { margin: .6em 0; }
|
|
.article-content ul, .article-content ol { margin: .6em 0; padding-left: 1.8em; }
|
|
.article-content li { margin: .25em 0; }
|
|
.article-content ul li { list-style: disc; }
|
|
.article-content ol li { list-style: decimal; }
|
|
.article-content a { color: #0054e6; text-decoration: underline; text-underline-offset: 2px; }
|
|
.article-content blockquote { border-left: 3px solid #0054e6; padding: .5em 1em; margin: .8em 0; color: var(--text-secondary); background: rgba(0,84,230,.03); border-radius: 0 8px 8px 0; }
|
|
.article-content code { font-family: 'JetBrains Mono', monospace; font-size: .88em; padding: .15em .4em; border-radius: 4px; background: var(--surface-700); }
|
|
.article-content pre { margin: .8em 0; border-radius: 10px; overflow-x: auto; }
|
|
.article-content pre code { display: block; padding: 1em 1.2em; background: var(--surface-700); }
|
|
.article-content img { max-width: 100%; border-radius: 8px; margin: .8em 0; }
|
|
.article-content table { border-collapse: collapse; width: 100%; margin: .8em 0; }
|
|
.article-content th, .article-content td { border: 1px solid var(--surface-600); padding: .5em .8em; text-align: left; }
|
|
.article-content th { background: var(--surface-700); font-weight: 600; }
|
|
|
|
.mode-btn { transition: all .2s; }
|
|
.mode-btn.active { color: #0054e6; background: rgba(0,84,230,.08); border-color: rgba(0,84,230,.3); }
|
|
</style>
|
|
</head>
|
|
<body class="noise dark:text-gray-200 text-gray-700 font-sans antialiased">
|
|
|
|
<div class="main-content">
|
|
<div class="tool-container relative z-10 max-w-3xl mx-auto">
|
|
|
|
<!-- Header -->
|
|
<div class="text-center mb-8 fade-up">
|
|
<div class="inline-flex items-center gap-2 mb-3 px-3 py-1 rounded-full border dark:border-surface-600 border-gray-300 dark:bg-surface-800/60 bg-white/60 text-xs font-mono dark:text-gray-400 text-gray-500 tracking-wide">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
|
|
URL Article Parser
|
|
</div>
|
|
<div class="page-header">
|
|
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight dark:text-white text-gray-900">
|
|
Парсер <span class="text-accent">статей</span>
|
|
</h1>
|
|
<p class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">извлечение контента из любой веб-страницы</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Input Card -->
|
|
<div id="inputCard" class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm fade-up-delay shadow-sm dark:shadow-none">
|
|
<div class="flex gap-2">
|
|
<input type="url" id="urlInput" placeholder="https://example.com/article"
|
|
class="flex-1 px-4 py-3 dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 rounded-xl text-sm font-mono focus:outline-none focus:border-accent/50 transition-colors dark:text-white text-gray-800"
|
|
>
|
|
<button id="parseBtn"
|
|
class="px-6 py-3 rounded-xl font-semibold text-sm tracking-wide transition-all duration-300
|
|
bg-accent text-white border border-accent cursor-pointer
|
|
hover:bg-accent-bright hover:shadow-lg hover:shadow-accent/20
|
|
active:scale-[0.98] whitespace-nowrap">
|
|
Парсить
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Mode buttons -->
|
|
<div class="flex gap-2 mt-4">
|
|
<button class="mode-btn active flex-1 px-3 py-2 rounded-lg text-xs font-medium border dark:border-surface-600 border-gray-200 dark:text-gray-400 text-gray-500 transition-colors" data-mode="full">
|
|
Полный
|
|
</button>
|
|
<button class="mode-btn flex-1 px-3 py-2 rounded-lg text-xs font-medium border dark:border-surface-600 border-gray-200 dark:text-gray-400 text-gray-500 transition-colors" data-mode="metadata">
|
|
Метаданные
|
|
</button>
|
|
<button class="mode-btn flex-1 px-3 py-2 rounded-lg text-xs font-medium border dark:border-surface-600 border-gray-200 dark:text-gray-400 text-gray-500 transition-colors" data-mode="text">
|
|
Текст
|
|
</button>
|
|
<button class="mode-btn flex-1 px-3 py-2 rounded-lg text-xs font-medium border dark:border-surface-600 border-gray-200 dark:text-gray-400 text-gray-500 transition-colors" data-mode="preview">
|
|
Превью
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div id="errorBlock" class="hidden mt-5 bg-danger/10 border border-danger/20 rounded-xl px-5 py-4 text-sm text-danger font-medium fade-up"></div>
|
|
|
|
<!-- Loading -->
|
|
<div id="loadingBlock" class="hidden mt-5">
|
|
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-8 backdrop-blur-sm shadow-sm dark:shadow-none text-center processing">
|
|
<div class="inline-flex items-center gap-3">
|
|
<svg class="animate-spin w-5 h-5 text-accent" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/></svg>
|
|
<span class="text-sm dark:text-gray-400 text-gray-500 font-mono">Загружаю и парсю страницу...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Result: Full article -->
|
|
<div id="resultFull" class="hidden mt-5 fade-up space-y-4">
|
|
<!-- Preview card -->
|
|
<div id="previewCard" class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl overflow-hidden backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<img id="leadImage" class="w-full h-48 object-cover hidden" alt="">
|
|
<div class="p-5 sm:p-7">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span id="siteName" class="text-xs font-mono dark:text-gray-500 text-gray-400"></span>
|
|
<span id="articleDate" class="text-xs font-mono dark:text-gray-500 text-gray-400"></span>
|
|
</div>
|
|
<h2 id="articleTitle" class="text-xl font-bold dark:text-white text-gray-900 mb-2"></h2>
|
|
<p id="articleAuthor" class="text-sm dark:text-gray-500 text-gray-400 hidden"></p>
|
|
<p id="articleExcerpt" class="text-sm dark:text-gray-400 text-gray-500 mt-2 leading-relaxed"></p>
|
|
<div class="flex items-center gap-3 mt-3">
|
|
<span id="wordCount" class="text-xs font-mono dark:text-gray-600 text-gray-400"></span>
|
|
<span id="articleLang" class="text-xs font-mono dark:text-gray-600 text-gray-400"></span>
|
|
<span id="imageCount" class="text-xs font-mono dark:text-gray-600 text-gray-400"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action buttons -->
|
|
<div class="flex gap-2 flex-wrap">
|
|
<button id="copyJsonBtn" class="px-4 py-2 rounded-lg text-xs font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors border border-accent/20">
|
|
Скопировать JSON
|
|
</button>
|
|
<button id="copyTextBtn" class="px-4 py-2 rounded-lg text-xs font-medium dark:bg-surface-700 bg-gray-100 dark:text-gray-300 text-gray-600 hover:text-accent transition-colors border dark:border-surface-600 border-gray-200">
|
|
Скопировать текст
|
|
</button>
|
|
<button id="copyHtmlBtn" class="px-4 py-2 rounded-lg text-xs font-medium dark:bg-surface-700 bg-gray-100 dark:text-gray-300 text-gray-600 hover:text-accent transition-colors border dark:border-surface-600 border-gray-200">
|
|
Скопировать HTML
|
|
</button>
|
|
<button id="clearBtn" class="ml-auto px-4 py-2 rounded-lg text-xs font-medium dark:bg-surface-700 bg-gray-100 dark:text-gray-300 text-gray-600 hover:text-danger transition-colors border dark:border-surface-600 border-gray-200">
|
|
Очистить
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Article content -->
|
|
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<div class="flex items-center justify-between mb-4 pb-3 border-b dark:border-surface-600 border-gray-200">
|
|
<span class="text-xs font-mono dark:text-gray-500 text-gray-400 uppercase tracking-wider">Контент</span>
|
|
<div class="flex gap-2">
|
|
<button id="viewHtmlBtn" class="mode-btn active px-3 py-1 rounded text-xs font-medium dark:text-gray-400 text-gray-500 border dark:border-surface-600 border-gray-200" data-view="html">HTML</button>
|
|
<button id="viewTextBtn" class="mode-btn px-3 py-1 rounded text-xs font-medium dark:text-gray-400 text-gray-500 border dark:border-surface-600 border-gray-200" data-view="text">Text</button>
|
|
<button id="viewJsonBtn" class="mode-btn px-3 py-1 rounded text-xs font-medium dark:text-gray-400 text-gray-500 border dark:border-surface-600 border-gray-200" data-view="json">JSON</button>
|
|
</div>
|
|
</div>
|
|
<div id="contentHtml" class="article-content"></div>
|
|
<pre id="contentText" class="hidden whitespace-pre-wrap text-sm font-mono dark:text-gray-300 text-gray-600 leading-relaxed max-h-[600px] overflow-y-auto"></pre>
|
|
<pre id="contentJson" class="hidden whitespace-pre-wrap text-xs font-mono dark:text-gray-300 text-gray-600 leading-relaxed max-h-[600px] overflow-y-auto dark:bg-surface-700 bg-gray-50 p-4 rounded-xl"></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Result: Metadata only -->
|
|
<div id="resultMetadata" class="hidden mt-5 fade-up">
|
|
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<div class="flex items-center justify-between mb-4 pb-3 border-b dark:border-surface-600 border-gray-200">
|
|
<span class="text-xs font-mono dark:text-gray-500 text-gray-400 uppercase tracking-wider">Метаданные</span>
|
|
<button id="copyMetaJsonBtn" class="px-3 py-1.5 rounded-lg text-xs font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors border border-accent/20">
|
|
Скопировать
|
|
</button>
|
|
</div>
|
|
<pre id="metadataJson" class="whitespace-pre-wrap text-xs font-mono dark:text-gray-300 text-gray-600 leading-relaxed dark:bg-surface-700 bg-gray-50 p-4 rounded-xl"></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Result: Text only -->
|
|
<div id="resultText" class="hidden mt-5 fade-up">
|
|
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<div class="flex items-center justify-between mb-4 pb-3 border-b dark:border-surface-600 border-gray-200">
|
|
<span class="text-xs font-mono dark:text-gray-500 text-gray-400 uppercase tracking-wider">Текст</span>
|
|
<button id="copyPlainTextBtn" class="px-3 py-1.5 rounded-lg text-xs font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors border border-accent/20">
|
|
Скопировать
|
|
</button>
|
|
</div>
|
|
<pre id="plainText" class="whitespace-pre-wrap text-sm dark:text-gray-300 text-gray-600 leading-relaxed max-h-[600px] overflow-y-auto"></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Result: Preview -->
|
|
<div id="resultPreview" class="hidden mt-5 fade-up">
|
|
<div id="previewLinkCard" class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl overflow-hidden backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<img id="previewImg" class="w-full h-52 object-cover hidden" alt="">
|
|
<div class="p-5 sm:p-7">
|
|
<p id="previewSite" class="text-xs font-mono dark:text-gray-500 text-gray-400 mb-1"></p>
|
|
<h3 id="previewTitle" class="text-lg font-bold dark:text-white text-gray-900 mb-2"></h3>
|
|
<p id="previewDesc" class="text-sm dark:text-gray-400 text-gray-500 leading-relaxed"></p>
|
|
</div>
|
|
</div>
|
|
<button id="copyPreviewJsonBtn" class="mt-3 px-4 py-2 rounded-lg text-xs font-medium bg-accent/10 text-accent hover:bg-accent/20 transition-colors border border-accent/20">
|
|
Скопировать JSON
|
|
</button>
|
|
</div>
|
|
|
|
<!-- API Reference -->
|
|
<div class="mt-8 fade-up">
|
|
<div class="dark:bg-surface-800/80 bg-white/85 border dark:border-surface-600 border-gray-200 rounded-2xl p-5 sm:p-7 backdrop-blur-sm shadow-sm dark:shadow-none">
|
|
<h3 class="text-sm font-semibold dark:text-white text-gray-800 mb-3">API</h3>
|
|
<div class="space-y-2 text-xs font-mono dark:text-gray-400 text-gray-500 leading-relaxed">
|
|
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/parse?url=URL</code> — полный парсинг (title, content, images, links)</p>
|
|
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/metadata?url=URL</code> — только метаданные</p>
|
|
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/text?url=URL</code> — только plain text</p>
|
|
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/preview?url=URL</code> — превью-карточка (title + image + description)</p>
|
|
<p><code class="dark:bg-surface-700 bg-gray-100 px-2 py-0.5 rounded">/health</code> — проверка доступности</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div><!-- /main-content -->
|
|
|
|
<script>
|
|
// State
|
|
let currentMode = 'full';
|
|
let lastResult = null;
|
|
let lastMetadata = null;
|
|
let lastText = null;
|
|
let lastPreview = null;
|
|
|
|
const $ = id => document.getElementById(id);
|
|
|
|
// Mode buttons
|
|
document.querySelectorAll('.mode-btn[data-mode]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.mode-btn[data-mode]').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
currentMode = btn.dataset.mode;
|
|
});
|
|
});
|
|
|
|
// View toggle (HTML/Text/JSON)
|
|
document.querySelectorAll('.mode-btn[data-view]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.mode-btn[data-view]').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
const view = btn.dataset.view;
|
|
$('contentHtml').classList.toggle('hidden', view !== 'html');
|
|
$('contentText').classList.toggle('hidden', view !== 'text');
|
|
$('contentJson').classList.toggle('hidden', view !== 'json');
|
|
});
|
|
});
|
|
|
|
// Parse
|
|
$('parseBtn').addEventListener('click', doParse);
|
|
$('urlInput').addEventListener('keydown', e => { if (e.key === 'Enter') doParse(); });
|
|
|
|
async function doParse() {
|
|
const url = $('urlInput').value.trim();
|
|
if (!url) return;
|
|
|
|
hideAll();
|
|
$('loadingBlock').classList.remove('hidden');
|
|
|
|
try {
|
|
if (currentMode === 'full') {
|
|
const res = await fetch('/parse?url=' + encodeURIComponent(url));
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || 'Ошибка');
|
|
lastResult = data;
|
|
showFullResult(data);
|
|
} else if (currentMode === 'metadata') {
|
|
const res = await fetch('/metadata?url=' + encodeURIComponent(url));
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || 'Ошибка');
|
|
lastMetadata = data;
|
|
showMetadata(data);
|
|
} else if (currentMode === 'text') {
|
|
const res = await fetch('/text?url=' + encodeURIComponent(url));
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.error || 'Ошибка');
|
|
}
|
|
const text = await res.text();
|
|
lastText = text;
|
|
showText(text);
|
|
} else if (currentMode === 'preview') {
|
|
const res = await fetch('/preview?url=' + encodeURIComponent(url));
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || 'Ошибка');
|
|
lastPreview = data;
|
|
showPreview(data);
|
|
}
|
|
} catch (err) {
|
|
$('errorBlock').textContent = err.message;
|
|
$('errorBlock').classList.remove('hidden');
|
|
} finally {
|
|
$('loadingBlock').classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
function hideAll() {
|
|
$('errorBlock').classList.add('hidden');
|
|
$('resultFull').classList.add('hidden');
|
|
$('resultMetadata').classList.add('hidden');
|
|
$('resultText').classList.add('hidden');
|
|
$('resultPreview').classList.add('hidden');
|
|
}
|
|
|
|
function showFullResult(data) {
|
|
// Lead image
|
|
if (data.lead_image) {
|
|
$('leadImage').src = data.lead_image;
|
|
$('leadImage').classList.remove('hidden');
|
|
} else {
|
|
$('leadImage').classList.add('hidden');
|
|
}
|
|
|
|
$('articleTitle').textContent = data.title || 'Без заголовка';
|
|
$('siteName').textContent = data.site || '';
|
|
$('articleDate').textContent = data.date_published ? new Date(data.date_published).toLocaleDateString('ru') : '';
|
|
|
|
if (data.author) {
|
|
$('articleAuthor').textContent = data.author;
|
|
$('articleAuthor').classList.remove('hidden');
|
|
} else {
|
|
$('articleAuthor').classList.add('hidden');
|
|
}
|
|
|
|
$('articleExcerpt').textContent = data.excerpt || '';
|
|
$('wordCount').textContent = data.word_count ? data.word_count + ' слов' : '';
|
|
$('articleLang').textContent = data.lang ? 'lang: ' + data.lang : '';
|
|
$('imageCount').textContent = data.images ? data.images.length + ' изобр.' : '';
|
|
|
|
// Content views
|
|
$('contentHtml').innerHTML = data.content_html || '<p class="dark:text-gray-500 text-gray-400">Контент не извлечён</p>';
|
|
$('contentText').textContent = data.content_text || '';
|
|
$('contentJson').textContent = JSON.stringify(data, null, 2);
|
|
|
|
// Reset view to HTML
|
|
document.querySelectorAll('.mode-btn[data-view]').forEach(b => b.classList.remove('active'));
|
|
$('viewHtmlBtn').classList.add('active');
|
|
$('contentHtml').classList.remove('hidden');
|
|
$('contentText').classList.add('hidden');
|
|
$('contentJson').classList.add('hidden');
|
|
|
|
$('resultFull').classList.remove('hidden');
|
|
}
|
|
|
|
function showMetadata(data) {
|
|
$('metadataJson').textContent = JSON.stringify(data, null, 2);
|
|
$('resultMetadata').classList.remove('hidden');
|
|
}
|
|
|
|
function showText(text) {
|
|
$('plainText').textContent = text;
|
|
$('resultText').classList.remove('hidden');
|
|
}
|
|
|
|
function showPreview(data) {
|
|
if (data.image) {
|
|
$('previewImg').src = data.image;
|
|
$('previewImg').classList.remove('hidden');
|
|
} else {
|
|
$('previewImg').classList.add('hidden');
|
|
}
|
|
$('previewSite').textContent = data.site || '';
|
|
$('previewTitle').textContent = data.title || '';
|
|
$('previewDesc').textContent = data.description || '';
|
|
$('resultPreview').classList.remove('hidden');
|
|
}
|
|
|
|
// Copy helpers
|
|
function copyToClipboard(text, btn) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
const orig = btn.textContent;
|
|
btn.textContent = 'Скопировано!';
|
|
btn.classList.add('text-accent');
|
|
setTimeout(() => { btn.textContent = orig; btn.classList.remove('text-accent'); }, 1500);
|
|
});
|
|
}
|
|
|
|
$('copyJsonBtn').addEventListener('click', () => {
|
|
if (lastResult) copyToClipboard(JSON.stringify(lastResult, null, 2), $('copyJsonBtn'));
|
|
});
|
|
|
|
$('copyTextBtn').addEventListener('click', () => {
|
|
if (lastResult) copyToClipboard(lastResult.content_text || '', $('copyTextBtn'));
|
|
});
|
|
|
|
$('copyHtmlBtn').addEventListener('click', () => {
|
|
if (lastResult) copyToClipboard(lastResult.content_html || '', $('copyHtmlBtn'));
|
|
});
|
|
|
|
$('copyMetaJsonBtn').addEventListener('click', () => {
|
|
if (lastMetadata) copyToClipboard(JSON.stringify(lastMetadata, null, 2), $('copyMetaJsonBtn'));
|
|
});
|
|
|
|
$('copyPlainTextBtn').addEventListener('click', () => {
|
|
if (lastText) copyToClipboard(lastText, $('copyPlainTextBtn'));
|
|
});
|
|
|
|
$('copyPreviewJsonBtn').addEventListener('click', () => {
|
|
if (lastPreview) copyToClipboard(JSON.stringify(lastPreview, null, 2), $('copyPreviewJsonBtn'));
|
|
});
|
|
|
|
// Clear
|
|
$('clearBtn').addEventListener('click', () => {
|
|
hideAll();
|
|
lastResult = null;
|
|
lastMetadata = null;
|
|
lastText = null;
|
|
lastPreview = null;
|
|
$('urlInput').value = '';
|
|
$('urlInput').focus();
|
|
});
|
|
|
|
// Auto-focus
|
|
$('urlInput').focus();
|
|
</script>
|
|
</body>
|
|
</html>
|