wa-dev-tools/public/md.html
treamz 6f42dd0c89 Fix broken container: remove extra </div> on placeholder, md, parser
- Duplicate badge removal script left orphan closing divs
- Content was outside max-w container, stretched to 100%
- Fixed: placeholder, md, parser now properly contained
2026-03-22 10:58:53 +03:00

255 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Markdown Viewer — WA Dev Tools</title>
<script src="/vendor/tailwind.js"></script>
<script>window.WA_TOOL_ID = 'md';</script>
<script src="/shared.js?v=3"></script>
<link href="/vendor/fonts.css" rel="stylesheet">
<link href="/shared.css" rel="stylesheet">
<script src="/vendor/marked.min.js"></script>
<script src="/vendor/highlight.min.js"></script>
<link id="hljsTheme" href="/vendor/hljs-github-dark.css" rel="stylesheet">
<style>
/* Dropzone */
.md-dropzone { border: 2px dashed var(--surface-600); transition: all .3s; }
.md-dropzone.active { border-color: #0054e6; background: rgba(0,84,230,.05); }
/* Markdown rendered output */
.md-output {
color: var(--text-primary);
line-height: 1.75;
font-size: 15px;
}
.md-output h1 { font-size: 2em; font-weight: 800; margin: 1.2em 0 .6em; padding-bottom: .3em; border-bottom: 1px solid var(--surface-600); }
.md-output h2 { font-size: 1.5em; font-weight: 700; margin: 1em 0 .5em; padding-bottom: .25em; border-bottom: 1px solid var(--surface-600); }
.md-output h3 { font-size: 1.25em; font-weight: 600; margin: .9em 0 .4em; }
.md-output h4 { font-size: 1.1em; font-weight: 600; margin: .8em 0 .3em; }
.md-output p { margin: .6em 0; }
.md-output ul, .md-output ol { margin: .6em 0; padding-left: 1.8em; }
.md-output li { margin: .25em 0; }
.md-output ul li { list-style: disc; }
.md-output ol li { list-style: decimal; }
.md-output a { color: #0054e6; text-decoration: underline; text-underline-offset: 2px; }
.md-output 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; }
.md-output code { font-family: 'JetBrains Mono', monospace; font-size: .88em; padding: .15em .4em; border-radius: 4px; background: var(--surface-700); }
.md-output pre { margin: .8em 0; border-radius: 10px; overflow-x: auto; }
.md-output pre code { display: block; padding: 1em 1.2em; background: var(--surface-700); font-size: .85em; line-height: 1.6; border-radius: 10px; }
.md-output table { border-collapse: collapse; width: 100%; margin: .8em 0; font-size: .92em; }
.md-output th, .md-output td { border: 1px solid var(--surface-600); padding: .5em .8em; text-align: left; }
.md-output th { background: var(--surface-700); font-weight: 600; }
.md-output hr { border: none; border-top: 1px solid var(--surface-600); margin: 1.5em 0; }
.md-output img { max-width: 100%; border-radius: 8px; margin: .8em 0; }
/* Tabs */
.tab-btn { transition: all .2s; }
.tab-btn.active { color: #0054e6; border-color: #0054e6; }
</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-4xl mx-auto">
<!-- Header -->
<div class="page-header" style="text-align:center;">
<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>
MD · GitHub Flavored
</div>
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight dark:text-white text-gray-900">
Просмотр <span class="text-accent">Markdown</span>
</h1>
<p class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">Рендеринг Markdown с подсветкой синтаксиса и экспортом</p>
</div>
<!-- Input Card -->
<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 fade-up-delay shadow-sm dark:shadow-none">
<!-- Tabs -->
<div class="flex gap-2 mb-5 border-b dark:border-surface-600 border-gray-200 pb-3">
<button class="tab-btn active text-sm font-medium px-3 py-1.5 border-b-2 border-transparent dark:text-gray-400 text-gray-500" data-tab="file">
Файл
</button>
<button class="tab-btn text-sm font-medium px-3 py-1.5 border-b-2 border-transparent dark:text-gray-400 text-gray-500" data-tab="text">
Текст
</button>
</div>
<!-- File tab -->
<div id="tabFile" class="tab-content">
<div id="dropzone" class="md-dropzone rounded-xl p-8 sm:p-10 text-center cursor-pointer group">
<svg class="mx-auto w-10 h-10 dark:text-gray-500 text-gray-400 group-hover:text-accent transition-colors mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
<p class="text-sm dark:text-gray-400 text-gray-500">
Перетащите <span class="text-accent font-medium">.md</span> файл сюда или <span class="text-accent font-medium cursor-pointer">выберите</span>
</p>
<p id="fileName" class="mt-2 text-xs font-mono dark:text-gray-600 text-gray-400 hidden"></p>
<input type="file" accept=".md,.markdown,.txt" id="fileInput" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" style="position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer">
</div>
</div>
<!-- Text tab -->
<div id="tabText" class="tab-content hidden">
<textarea id="mdInput" class="w-full h-48 px-4 py-3 rounded-xl text-sm font-mono dark:bg-surface-700 bg-gray-50 border dark:border-surface-600 border-gray-200 dark:text-gray-200 text-gray-700 focus:outline-none focus:border-accent/50 resize-y transition-colors" placeholder="Вставьте Markdown текст..."></textarea>
<button id="renderBtn" class="mt-3 px-5 py-2.5 rounded-xl text-sm font-semibold bg-accent text-white hover:bg-accent-bright transition-all active:scale-[0.98]">
Рендер
</button>
</div>
</div>
<!-- Output -->
<div id="outputCard" 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">
<!-- Toolbar -->
<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="downloadHtmlBtn" 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">
Скачать HTML
</button>
<button id="copyHtmlBtn" class="px-3 py-1.5 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="px-3 py-1.5 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>
</div>
<!-- Rendered markdown -->
<div id="mdOutput" class="md-output"></div>
</div>
</div>
</div>
</div><!-- /main-content -->
<script>
function applyHljsTheme() {
const isDark = document.documentElement.classList.contains('dark');
document.getElementById('hljsTheme').href = isDark ? '/vendor/hljs-github-dark.css' : '/vendor/hljs-github.css';
}
applyHljsTheme();
// Listen for theme changes from shared.js
document.addEventListener('themeChanged', applyHljsTheme);
// Also observe class changes on html element for theme toggle
const observer = new MutationObserver(applyHljsTheme);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
// Tabs
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tab = btn.dataset.tab;
document.getElementById('tabFile').classList.toggle('hidden', tab !== 'file');
document.getElementById('tabText').classList.toggle('hidden', tab !== 'text');
});
});
// Marked config
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
},
breaks: true,
gfm: true,
});
function renderMarkdown(md) {
const output = document.getElementById('mdOutput');
const card = document.getElementById('outputCard');
output.innerHTML = marked.parse(md);
card.classList.remove('hidden');
// Re-highlight any code blocks
output.querySelectorAll('pre code').forEach(block => {
hljs.highlightElement(block);
});
}
// File drop
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
// Make dropzone relative for absolute file input
dropzone.style.position = 'relative';
dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('active'); });
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('active'));
dropzone.addEventListener('drop', e => {
e.preventDefault();
dropzone.classList.remove('active');
const file = e.dataTransfer.files[0];
if (file) readFile(file);
});
fileInput.addEventListener('change', e => {
if (e.target.files[0]) readFile(e.target.files[0]);
});
function readFile(file) {
const nameEl = document.getElementById('fileName');
nameEl.textContent = file.name;
nameEl.classList.remove('hidden');
const reader = new FileReader();
reader.onload = e => renderMarkdown(e.target.result);
reader.readAsText(file);
}
// Text render
document.getElementById('renderBtn').addEventListener('click', () => {
const text = document.getElementById('mdInput').value.trim();
if (text) renderMarkdown(text);
});
// Also render on Ctrl+Enter in textarea
document.getElementById('mdInput').addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
document.getElementById('renderBtn').click();
}
});
// Copy HTML
document.getElementById('copyHtmlBtn').addEventListener('click', () => {
const html = document.getElementById('mdOutput').innerHTML;
navigator.clipboard.writeText(html).then(() => {
const btn = document.getElementById('copyHtmlBtn');
const orig = btn.textContent;
btn.textContent = 'Скопировано!';
btn.classList.add('text-accent');
setTimeout(() => { btn.textContent = orig; btn.classList.remove('text-accent'); }, 1500);
});
});
// Download HTML
document.getElementById('downloadHtmlBtn').addEventListener('click', () => {
const body = document.getElementById('mdOutput').innerHTML;
const fullHtml = '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>Markdown</title>\n<style>\nbody{font-family:sans-serif;max-width:800px;margin:40px auto;padding:0 20px;line-height:1.7;color:#333}\npre{background:#f5f5f5;padding:1em;border-radius:6px;overflow-x:auto}\ncode{font-family:monospace;font-size:.9em;background:#f0f0f0;padding:2px 5px;border-radius:3px}\npre code{background:none;padding:0}\ntable{border-collapse:collapse;width:100%}\nth,td{border:1px solid #ddd;padding:8px;text-align:left}\nth{background:#f5f5f5}\nblockquote{border-left:3px solid #0054e6;padding:.5em 1em;margin:1em 0;color:#666;background:#f9f9f9}\nimg{max-width:100%}\n</style>\n</head>\n<body>\n' + body + '\n</body>\n</html>';
const blob = new Blob([fullHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'markdown.html';
a.click();
URL.revokeObjectURL(url);
});
// Clear
document.getElementById('clearBtn').addEventListener('click', () => {
document.getElementById('mdOutput').innerHTML = '';
document.getElementById('outputCard').classList.add('hidden');
document.getElementById('mdInput').value = '';
document.getElementById('fileName').classList.add('hidden');
fileInput.value = '';
});
</script>
</body>
</html>