945 lines
28 KiB
HTML
945 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Markdown Viewer</title>
|
|
<!-- Marked.js for Markdown parsing -->
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<!-- Highlight.js for syntax highlighting -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
<!-- DOMPurify for XSS protection -->
|
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
color: #333;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 20px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
padding: 30px;
|
|
text-align: center;
|
|
color: white;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2.5em;
|
|
margin-bottom: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.header p {
|
|
font-size: 1.1em;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.content {
|
|
display: grid;
|
|
grid-template-columns: 400px 1fr;
|
|
gap: 30px;
|
|
padding: 30px;
|
|
}
|
|
|
|
.panel {
|
|
background: #f8f9fa;
|
|
border-radius: 15px;
|
|
padding: 25px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.panel h2 {
|
|
color: #667eea;
|
|
margin-bottom: 20px;
|
|
font-size: 1.5em;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.tab-btn {
|
|
flex: 1;
|
|
padding: 12px 20px;
|
|
border: none;
|
|
background: white;
|
|
color: #667eea;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.tab-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.tab-btn.active {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.upload-area {
|
|
border: 3px dashed #667eea;
|
|
border-radius: 15px;
|
|
padding: 40px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
background: white;
|
|
}
|
|
|
|
.upload-area:hover {
|
|
border-color: #764ba2;
|
|
background: #f0f4ff;
|
|
}
|
|
|
|
.upload-area.dragover {
|
|
border-color: #764ba2;
|
|
background: #e8ecff;
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
.upload-icon {
|
|
font-size: 3em;
|
|
margin-bottom: 15px;
|
|
color: #667eea;
|
|
}
|
|
|
|
.upload-area p {
|
|
color: #666;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
#fileInput {
|
|
display: none;
|
|
}
|
|
|
|
textarea {
|
|
width: 100%;
|
|
min-height: 300px;
|
|
padding: 15px;
|
|
border: 2px solid #e0e0e0;
|
|
border-radius: 10px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.95em;
|
|
resize: vertical;
|
|
transition: border-color 0.3s ease;
|
|
background: white;
|
|
}
|
|
|
|
textarea:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
padding: 15px;
|
|
margin-top: 15px;
|
|
border: none;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 10px;
|
|
font-size: 1.1em;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
|
}
|
|
|
|
.btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.options-group {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background: white;
|
|
border-radius: 10px;
|
|
border: 2px solid #e0e0e0;
|
|
}
|
|
|
|
.options-group h3 {
|
|
font-size: 1em;
|
|
color: #667eea;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.checkbox-group label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
font-size: 0.95em;
|
|
}
|
|
|
|
.checkbox-group input[type="checkbox"] {
|
|
cursor: pointer;
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
#markdownOutput {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
min-height: 500px;
|
|
overflow-y: auto;
|
|
max-height: calc(100vh - 250px);
|
|
position: relative;
|
|
}
|
|
|
|
.export-controls {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
z-index: 100;
|
|
}
|
|
|
|
.export-btn {
|
|
min-width: 100px;
|
|
padding: 10px 15px;
|
|
border: none;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 10px;
|
|
font-size: 0.9em;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.export-btn:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
|
}
|
|
|
|
.export-btn.success {
|
|
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
|
}
|
|
|
|
.placeholder {
|
|
text-align: center;
|
|
color: #999;
|
|
font-size: 1.2em;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 400px;
|
|
}
|
|
|
|
.placeholder-icon {
|
|
font-size: 4em;
|
|
margin-bottom: 15px;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.error {
|
|
background: #fee;
|
|
border: 2px solid #fcc;
|
|
color: #c33;
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.success {
|
|
background: #efe;
|
|
border: 2px solid #cfc;
|
|
color: #3c3;
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
/* Markdown Styling */
|
|
#markdownOutput.rendered {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
line-height: 1.6;
|
|
color: #24292e;
|
|
}
|
|
|
|
#markdownOutput.rendered h1 {
|
|
font-size: 2em;
|
|
border-bottom: 2px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#markdownOutput.rendered h2 {
|
|
font-size: 1.5em;
|
|
border-bottom: 1px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#markdownOutput.rendered h3 {
|
|
font-size: 1.25em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#markdownOutput.rendered h4,
|
|
#markdownOutput.rendered h5,
|
|
#markdownOutput.rendered h6 {
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#markdownOutput.rendered p {
|
|
margin-top: 0;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
#markdownOutput.rendered ul,
|
|
#markdownOutput.rendered ol {
|
|
padding-left: 2em;
|
|
margin-top: 0;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
#markdownOutput.rendered li {
|
|
margin-bottom: 0.25em;
|
|
}
|
|
|
|
#markdownOutput.rendered blockquote {
|
|
padding: 0 1em;
|
|
color: #6a737d;
|
|
border-left: 0.25em solid #dfe2e5;
|
|
margin: 0 0 16px 0;
|
|
}
|
|
|
|
#markdownOutput.rendered code {
|
|
padding: 0.2em 0.4em;
|
|
margin: 0;
|
|
font-size: 85%;
|
|
background-color: rgba(27, 31, 35, 0.05);
|
|
border-radius: 6px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
}
|
|
|
|
#markdownOutput.rendered pre {
|
|
padding: 16px;
|
|
overflow: auto;
|
|
font-size: 85%;
|
|
line-height: 1.45;
|
|
background-color: #f6f8fa;
|
|
border-radius: 6px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
#markdownOutput.rendered pre code {
|
|
display: block;
|
|
padding: 0;
|
|
margin: 0;
|
|
overflow: visible;
|
|
line-height: inherit;
|
|
word-wrap: normal;
|
|
background-color: transparent;
|
|
border: 0;
|
|
}
|
|
|
|
#markdownOutput.rendered table {
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
margin-bottom: 16px;
|
|
width: 100%;
|
|
}
|
|
|
|
#markdownOutput.rendered table th,
|
|
#markdownOutput.rendered table td {
|
|
padding: 6px 13px;
|
|
border: 1px solid #dfe2e5;
|
|
}
|
|
|
|
#markdownOutput.rendered table th {
|
|
font-weight: 600;
|
|
background-color: #f6f8fa;
|
|
}
|
|
|
|
#markdownOutput.rendered table tr {
|
|
background-color: #fff;
|
|
border-top: 1px solid #c6cbd1;
|
|
}
|
|
|
|
#markdownOutput.rendered table tr:nth-child(2n) {
|
|
background-color: #f6f8fa;
|
|
}
|
|
|
|
#markdownOutput.rendered a {
|
|
color: #0366d6;
|
|
text-decoration: none;
|
|
}
|
|
|
|
#markdownOutput.rendered a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
#markdownOutput.rendered img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 8px;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
#markdownOutput.rendered hr {
|
|
height: 0.25em;
|
|
padding: 0;
|
|
margin: 24px 0;
|
|
background-color: #e1e4e8;
|
|
border: 0;
|
|
}
|
|
|
|
/* Dark theme for code blocks */
|
|
#markdownOutput.rendered pre code.hljs {
|
|
background: #2d2d2d;
|
|
color: #f8f8f2;
|
|
padding: 16px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
@media print {
|
|
body {
|
|
background: white;
|
|
padding: 0;
|
|
}
|
|
|
|
.container {
|
|
box-shadow: none;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.content {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.panel:first-child,
|
|
.export-controls {
|
|
display: none !important;
|
|
}
|
|
|
|
#markdownOutput {
|
|
max-height: none;
|
|
overflow: visible;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 968px) {
|
|
.content {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.export-controls {
|
|
position: relative;
|
|
top: 0;
|
|
right: 0;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 20px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>📝 Markdown Viewer</h1>
|
|
<p>Upload a file or paste your Markdown to see beautiful rendered output</p>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="panel">
|
|
<h2>📄 Input</h2>
|
|
|
|
<div class="tabs">
|
|
<button class="tab-btn active" onclick="switchTab('paste')">Paste Markdown</button>
|
|
<button class="tab-btn" onclick="switchTab('upload')">Upload File</button>
|
|
</div>
|
|
|
|
<div id="pasteTab" class="tab-content active">
|
|
<textarea id="markdownCode" placeholder="Paste your Markdown content here...
|
|
|
|
Example:
|
|
# Hello World
|
|
|
|
This is **bold** and this is *italic*.
|
|
|
|
- Item 1
|
|
- Item 2
|
|
- Item 3
|
|
|
|
```javascript
|
|
console.log('Hello, World!');
|
|
```
|
|
"></textarea>
|
|
<button class="btn" onclick="renderMarkdown()">🎨 Render Markdown</button>
|
|
</div>
|
|
|
|
<div id="uploadTab" class="tab-content">
|
|
<div class="upload-area" id="uploadArea" onclick="document.getElementById('fileInput').click()">
|
|
<div class="upload-icon">📁</div>
|
|
<p><strong>Click to upload</strong> or drag and drop</p>
|
|
<p style="font-size: 0.9em; margin-top: 10px; opacity: 0.7;">Supports .md, .markdown, .txt files</p>
|
|
</div>
|
|
<input type="file" id="fileInput" accept=".md,.markdown,.txt" onchange="handleFile(event)">
|
|
</div>
|
|
|
|
<div class="options-group">
|
|
<h3>⚙️ Options</h3>
|
|
<div class="checkbox-group">
|
|
<label>
|
|
<input type="checkbox" id="enableGFM" checked>
|
|
GitHub Flavored Markdown (GFM)
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" id="enableBreaks" checked>
|
|
Line breaks
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" id="enableTables" checked>
|
|
Tables support
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" id="enableSanitize" checked>
|
|
Sanitize HTML (Security)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="message"></div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>👁️ Preview</h2>
|
|
<div id="markdownOutput">
|
|
<div class="placeholder">
|
|
<div class="placeholder-icon">📄</div>
|
|
<p>Your rendered Markdown will appear here</p>
|
|
</div>
|
|
<div class="export-controls" id="exportControls">
|
|
<button class="export-btn success" onclick="exportAsHTML()" title="Export as HTML">💾 HTML</button>
|
|
<button class="export-btn" onclick="exportAsPDF()" title="Print as PDF">🖨️ PDF</button>
|
|
<button class="export-btn" onclick="copyToClipboard()" title="Copy HTML to Clipboard">📋 Copy</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentTab = 'paste';
|
|
|
|
// Configure marked with options
|
|
function getMarkedOptions() {
|
|
return {
|
|
gfm: document.getElementById('enableGFM').checked,
|
|
breaks: document.getElementById('enableBreaks').checked,
|
|
tables: document.getElementById('enableTables').checked,
|
|
highlight: function(code, lang) {
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
try {
|
|
return hljs.highlight(code, { language: lang }).value;
|
|
} catch (err) {
|
|
console.error('Highlight error:', err);
|
|
}
|
|
}
|
|
return hljs.highlightAuto(code).value;
|
|
}
|
|
};
|
|
}
|
|
|
|
function switchTab(tab) {
|
|
currentTab = tab;
|
|
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
|
|
if (tab === 'paste') {
|
|
document.querySelector('.tab-btn:first-child').classList.add('active');
|
|
document.getElementById('pasteTab').classList.add('active');
|
|
} else {
|
|
document.querySelector('.tab-btn:last-child').classList.add('active');
|
|
document.getElementById('uploadTab').classList.add('active');
|
|
}
|
|
}
|
|
|
|
function showMessage(message, type) {
|
|
const messageDiv = document.getElementById('message');
|
|
messageDiv.innerHTML = `<div class="${type}">${message}</div>`;
|
|
setTimeout(() => {
|
|
messageDiv.innerHTML = '';
|
|
}, 5000);
|
|
}
|
|
|
|
function renderMarkdown() {
|
|
const code = document.getElementById('markdownCode').value;
|
|
const output = document.getElementById('markdownOutput');
|
|
const exportControls = document.getElementById('exportControls');
|
|
|
|
if (!code.trim()) {
|
|
showMessage('Please provide Markdown content', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Configure marked with current options
|
|
marked.setOptions(getMarkedOptions());
|
|
|
|
// Parse markdown
|
|
let html = marked.parse(code);
|
|
|
|
// Sanitize if enabled
|
|
if (document.getElementById('enableSanitize').checked) {
|
|
html = DOMPurify.sanitize(html, {
|
|
ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'strong', 'em', 'u', 's', 'ul', 'ol', 'li', 'a', 'img', 'code', 'pre', 'blockquote', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'div', 'span'],
|
|
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'style', 'target', 'rel']
|
|
});
|
|
}
|
|
|
|
// Render to output
|
|
output.innerHTML = html;
|
|
output.classList.add('rendered');
|
|
|
|
// Highlight code blocks
|
|
output.querySelectorAll('pre code').forEach((block) => {
|
|
hljs.highlightElement(block);
|
|
});
|
|
|
|
// Show export controls
|
|
const controlsDiv = document.createElement('div');
|
|
controlsDiv.className = 'export-controls';
|
|
controlsDiv.id = 'exportControls';
|
|
controlsDiv.style.display = 'flex';
|
|
controlsDiv.innerHTML = `
|
|
<button class="export-btn success" onclick="exportAsHTML()" title="Export as HTML">💾 HTML</button>
|
|
<button class="export-btn" onclick="exportAsPDF()" title="Print as PDF">🖨️ PDF</button>
|
|
<button class="export-btn" onclick="copyToClipboard()" title="Copy HTML to Clipboard">📋 Copy</button>
|
|
`;
|
|
|
|
// Remove old controls if exist
|
|
const oldControls = output.querySelector('.export-controls');
|
|
if (oldControls) {
|
|
oldControls.remove();
|
|
}
|
|
|
|
output.appendChild(controlsDiv);
|
|
|
|
showMessage('✅ Markdown rendered successfully!', 'success');
|
|
} catch (error) {
|
|
output.innerHTML = '<div class="placeholder"><div class="placeholder-icon">❌</div><p>Failed to render Markdown</p></div>';
|
|
showMessage('❌ Error: ' + error.message, 'error');
|
|
console.error('Render error:', error);
|
|
}
|
|
}
|
|
|
|
function handleFile(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
const content = e.target.result;
|
|
document.getElementById('markdownCode').value = content;
|
|
renderMarkdown();
|
|
showMessage('✅ File loaded: ' + file.name, 'success');
|
|
};
|
|
reader.onerror = function() {
|
|
showMessage('❌ Error reading file', 'error');
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function exportAsHTML() {
|
|
const output = document.getElementById('markdownOutput');
|
|
const html = output.innerHTML;
|
|
|
|
if (!html || html.includes('placeholder')) {
|
|
showMessage('❌ No content to export', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create complete HTML document
|
|
const fullHTML = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Markdown Export</title>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
line-height: 1.6;
|
|
color: #24292e;
|
|
max-width: 980px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
${getMarkdownStyles()}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
${html}
|
|
</body>
|
|
</html>`;
|
|
|
|
// Create blob and download
|
|
const blob = new Blob([fullHTML], { type: 'text/html;charset=utf-8' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const link = document.createElement('a');
|
|
link.download = `markdown-export-${Date.now()}.html`;
|
|
link.href = url;
|
|
link.click();
|
|
|
|
// Cleanup
|
|
URL.revokeObjectURL(url);
|
|
|
|
showMessage('✅ HTML exported successfully!', 'success');
|
|
} catch (error) {
|
|
showMessage('❌ Error: ' + error.message, 'error');
|
|
console.error('Export error:', error);
|
|
}
|
|
}
|
|
|
|
function exportAsPDF() {
|
|
showMessage('📄 Opening print dialog... Choose "Save as PDF" from the printer options', 'success');
|
|
setTimeout(() => {
|
|
window.print();
|
|
}, 500);
|
|
}
|
|
|
|
function copyToClipboard() {
|
|
const output = document.getElementById('markdownOutput');
|
|
const html = output.innerHTML;
|
|
|
|
if (!html || html.includes('placeholder')) {
|
|
showMessage('❌ No content to copy', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Copy to clipboard
|
|
navigator.clipboard.writeText(html).then(() => {
|
|
showMessage('✅ HTML copied to clipboard!', 'success');
|
|
}).catch(err => {
|
|
// Fallback for older browsers
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = html;
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(textArea);
|
|
showMessage('✅ HTML copied to clipboard!', 'success');
|
|
});
|
|
} catch (error) {
|
|
showMessage('❌ Error: ' + error.message, 'error');
|
|
console.error('Copy error:', error);
|
|
}
|
|
}
|
|
|
|
function getMarkdownStyles() {
|
|
return `
|
|
h1 {
|
|
font-size: 2em;
|
|
border-bottom: 2px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
h2 {
|
|
font-size: 1.5em;
|
|
border-bottom: 1px solid #eaecef;
|
|
padding-bottom: 0.3em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
h3 {
|
|
font-size: 1.25em;
|
|
margin-top: 24px;
|
|
margin-bottom: 16px;
|
|
font-weight: 600;
|
|
}
|
|
p {
|
|
margin-bottom: 16px;
|
|
}
|
|
ul, ol {
|
|
padding-left: 2em;
|
|
margin-bottom: 16px;
|
|
}
|
|
blockquote {
|
|
padding: 0 1em;
|
|
color: #6a737d;
|
|
border-left: 0.25em solid #dfe2e5;
|
|
margin: 0 0 16px 0;
|
|
}
|
|
code {
|
|
padding: 0.2em 0.4em;
|
|
margin: 0;
|
|
font-size: 85%;
|
|
background-color: rgba(27, 31, 35, 0.05);
|
|
border-radius: 6px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
}
|
|
pre {
|
|
padding: 16px;
|
|
overflow: auto;
|
|
font-size: 85%;
|
|
line-height: 1.45;
|
|
background-color: #2d2d2d;
|
|
border-radius: 6px;
|
|
margin-bottom: 16px;
|
|
}
|
|
pre code {
|
|
display: block;
|
|
padding: 0;
|
|
background-color: transparent;
|
|
color: #f8f8f2;
|
|
}
|
|
table {
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
margin-bottom: 16px;
|
|
width: 100%;
|
|
}
|
|
table th, table td {
|
|
padding: 6px 13px;
|
|
border: 1px solid #dfe2e5;
|
|
}
|
|
table th {
|
|
font-weight: 600;
|
|
background-color: #f6f8fa;
|
|
}
|
|
a {
|
|
color: #0366d6;
|
|
text-decoration: none;
|
|
}
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 8px;
|
|
margin: 16px 0;
|
|
}
|
|
hr {
|
|
height: 0.25em;
|
|
padding: 0;
|
|
margin: 24px 0;
|
|
background-color: #e1e4e8;
|
|
border: 0;
|
|
}
|
|
`;
|
|
}
|
|
|
|
// Drag and drop support
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.add('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('dragleave', () => {
|
|
uploadArea.classList.remove('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.remove('dragover');
|
|
|
|
const file = e.dataTransfer.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(event) {
|
|
const content = event.target.result;
|
|
document.getElementById('markdownCode').value = content;
|
|
renderMarkdown();
|
|
showMessage('✅ File loaded: ' + file.name, 'success');
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
});
|
|
|
|
// Keyboard shortcut
|
|
document.getElementById('markdownCode').addEventListener('keydown', function(e) {
|
|
if (e.ctrlKey && e.key === 'Enter') {
|
|
renderMarkdown();
|
|
}
|
|
});
|
|
|
|
// Auto-render on option change
|
|
document.querySelectorAll('.checkbox-group input').forEach(checkbox => {
|
|
checkbox.addEventListener('change', () => {
|
|
const code = document.getElementById('markdownCode').value;
|
|
if (code.trim()) {
|
|
renderMarkdown();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|