|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Portable Offline Markdown Viewer</title> |
|
|
<style> |
|
|
body, html { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
height: 100%; |
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.container { |
|
|
display: flex; |
|
|
height: 100vh; |
|
|
width: 100vw; |
|
|
} |
|
|
|
|
|
.pane { |
|
|
flex-grow: 1; |
|
|
flex-basis: 0; |
|
|
overflow: auto; |
|
|
padding: 15px; |
|
|
box-sizing: border-box; |
|
|
height: 100%; |
|
|
} |
|
|
|
|
|
#input-pane { |
|
|
background-color: #fdfdfd; |
|
|
border-right: 1px solid #e0e0e0; |
|
|
} |
|
|
|
|
|
#markdown-input { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
border: 1px solid #d1d5da; |
|
|
box-sizing: border-box; |
|
|
padding: 10px; |
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; |
|
|
font-size: 14px; |
|
|
line-height: 1.5; |
|
|
resize: none; |
|
|
background-color: #fff; |
|
|
color: #24292e; |
|
|
} |
|
|
#markdown-input:focus { |
|
|
outline: none; |
|
|
border-color: #0366d6; |
|
|
box-shadow: 0 0 0 2px rgba(3, 102, 214, 0.3); |
|
|
} |
|
|
|
|
|
|
|
|
#output-pane { |
|
|
background-color: #ffffff; |
|
|
padding-left: 20px; |
|
|
} |
|
|
|
|
|
#html-output { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
color: #24292e; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
|
|
|
#html-output h1, #html-output h2, #html-output h3, #html-output h4, #html-output h5, #html-output h6 { |
|
|
margin-top: 24px; |
|
|
margin-bottom: 16px; |
|
|
font-weight: 600; |
|
|
line-height: 1.25; |
|
|
border-bottom: 1px solid #eaecef; |
|
|
padding-bottom: 0.3em; |
|
|
} |
|
|
#html-output h1 { font-size: 2em; } |
|
|
#html-output h2 { font-size: 1.5em; } |
|
|
#html-output h3 { font-size: 1.25em; } |
|
|
#html-output p { margin-top: 0; margin-bottom: 16px; } |
|
|
#html-output ul, #html-output ol { margin-top: 0; margin-bottom: 16px; padding-left: 2em; } |
|
|
#html-output li > p { margin-bottom: 0.2em; } |
|
|
#html-output blockquote { |
|
|
margin: 0 0 16px 0; |
|
|
padding: 0 1em; |
|
|
color: #6a737d; |
|
|
border-left: 0.25em solid #dfe2e5; |
|
|
} |
|
|
#html-output code { |
|
|
padding: 0.2em 0.4em; |
|
|
margin: 0; |
|
|
font-size: 85%; |
|
|
background-color: rgba(27,31,35,0.05); |
|
|
border-radius: 3px; |
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; |
|
|
} |
|
|
#html-output pre { |
|
|
padding: 16px; |
|
|
overflow: auto; |
|
|
font-size: 85%; |
|
|
line-height: 1.45; |
|
|
background-color: #f6f8fa; |
|
|
border-radius: 3px; |
|
|
margin-bottom: 16px; |
|
|
word-wrap: normal; |
|
|
} |
|
|
#html-output pre code { |
|
|
padding: 0; |
|
|
margin: 0; |
|
|
font-size: 100%; |
|
|
background-color: transparent; |
|
|
border-radius: 0; |
|
|
display: inline; |
|
|
max-width: auto; |
|
|
overflow: visible; |
|
|
line-height: inherit; |
|
|
word-wrap: normal; |
|
|
} |
|
|
#html-output table { |
|
|
border-collapse: collapse; |
|
|
margin-top: 0; |
|
|
margin-bottom: 16px; |
|
|
display: block; |
|
|
width: max-content; |
|
|
max-width: 100%; |
|
|
overflow: auto; |
|
|
} |
|
|
#html-output th, #html-output td { |
|
|
padding: 6px 13px; |
|
|
border: 1px solid #dfe2e5; |
|
|
} |
|
|
#html-output th { font-weight: 600; } |
|
|
#html-output hr { |
|
|
height: 0.25em; |
|
|
padding: 0; |
|
|
margin: 24px 0; |
|
|
background-color: #e1e4e8; |
|
|
border: 0; |
|
|
} |
|
|
#html-output img { max-width: 100%; box-sizing: content-box; background-color: #fff; } |
|
|
|
|
|
|
|
|
.resizer { |
|
|
width: 10px; |
|
|
background-color: #e0e0e0; |
|
|
cursor: col-resize; |
|
|
flex-shrink: 0; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
position: relative; |
|
|
z-index: 10; |
|
|
} |
|
|
.resizer:hover { |
|
|
background-color: #c0c0c0; |
|
|
} |
|
|
.resizer::before { |
|
|
content: '⋮'; |
|
|
color: #666; |
|
|
font-size: 14px; |
|
|
line-height: 0; |
|
|
} |
|
|
</style> |
|
|
|
|
|
<script src="marked.min.js"></script> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<div class="pane" id="input-pane"> |
|
|
<textarea id="markdown-input" placeholder="Paste or type Markdown here..."></textarea> |
|
|
</div> |
|
|
<div class="resizer" id="dragMe"></div> |
|
|
<div class="pane" id="output-pane"> |
|
|
<div id="html-output"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const markdownInput = document.getElementById('markdown-input'); |
|
|
const htmlOutput = document.getElementById('html-output'); |
|
|
const resizer = document.getElementById('dragMe'); |
|
|
const leftPane = document.getElementById('input-pane'); |
|
|
const rightPane = document.getElementById('output-pane'); |
|
|
const container = document.querySelector('.container'); |
|
|
|
|
|
|
|
|
function renderMarkdown() { |
|
|
const mdText = markdownInput.value; |
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
htmlOutput.innerHTML = marked.parse(mdText, { gfm: true, breaks: true, pedantic: false }); |
|
|
} catch (e) { |
|
|
|
|
|
try { |
|
|
htmlOutput.innerHTML = marked(mdText, { gfm: true, breaks: true, pedantic: false }); |
|
|
} catch (innerErr) { |
|
|
htmlOutput.innerHTML = "<p style='color:red;'>Error rendering Markdown. Check browser console.</p>"; |
|
|
console.error("Markdown rendering error:", innerErr); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
markdownInput.addEventListener('input', renderMarkdown); |
|
|
|
|
|
|
|
|
let isResizing = false; |
|
|
|
|
|
resizer.addEventListener('mousedown', (e) => { |
|
|
e.preventDefault(); |
|
|
isResizing = true; |
|
|
document.body.style.userSelect = 'none'; |
|
|
|
|
|
|
|
|
leftPane.style.flexGrow = '0'; |
|
|
rightPane.style.flexGrow = '0'; |
|
|
|
|
|
document.addEventListener('mousemove', handleMouseMove); |
|
|
document.addEventListener('mouseup', stopResize); |
|
|
}); |
|
|
|
|
|
function handleMouseMove(e) { |
|
|
if (!isResizing) return; |
|
|
|
|
|
const containerRect = container.getBoundingClientRect(); |
|
|
let leftWidth = e.clientX - containerRect.left; |
|
|
|
|
|
|
|
|
|
|
|
const minPaneWidth = 30; |
|
|
const resizerWidth = resizer.offsetWidth; |
|
|
|
|
|
|
|
|
if (leftWidth < minPaneWidth) { |
|
|
leftWidth = minPaneWidth; |
|
|
} |
|
|
if (leftWidth > containerRect.width - resizerWidth - minPaneWidth) { |
|
|
leftWidth = containerRect.width - resizerWidth - minPaneWidth; |
|
|
} |
|
|
|
|
|
const rightWidth = containerRect.width - leftWidth - resizerWidth; |
|
|
|
|
|
leftPane.style.flexBasis = `${leftWidth}px`; |
|
|
rightPane.style.flexBasis = `${rightWidth}px`; |
|
|
} |
|
|
|
|
|
function stopResize() { |
|
|
isResizing = false; |
|
|
document.body.style.userSelect = ''; |
|
|
document.removeEventListener('mousemove', handleMouseMove); |
|
|
document.removeEventListener('mouseup', stopResize); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
markdownInput.value = `# Welcome to Your Offline Markdown Viewer! |
|
|
|
|
|
## How to Use |
|
|
1. **Paste or type** your Markdown text into this left pane. |
|
|
2. The **rendered HTML** will appear live in the right pane. |
|
|
3. **Drag the vertical bar** in the middle to resize the panes. You can shrink one pane almost completely to focus on the other. |
|
|
|
|
|
## Features |
|
|
* **Live Preview:** Updates as you type. |
|
|
* **GitHub Flavored Markdown (GFM):** Includes support for tables, fenced code blocks, etc. |
|
|
* **Line Breaks:** Single newlines are rendered as line breaks (\`<br>\`). |
|
|
* **Portable & Offline:** Just this HTML file and \`marked.min.js\` in the same folder. No installation needed! |
|
|
|
|
|
--- |
|
|
|
|
|
### Example Markdown: |
|
|
|
|
|
\`\`\`javascript |
|
|
// Code block example |
|
|
function greet(name) { |
|
|
return \`Hello, \${name}!\`; |
|
|
} |
|
|
console.log(greet("Markdown User")); |
|
|
\`\`\` |
|
|
|
|
|
- Item 1 |
|
|
- Item 2 |
|
|
- Sub-item A |
|
|
- Sub-item B |
|
|
|
|
|
> This is a blockquote. It's useful for highlighting text. |
|
|
|
|
|
**Bold text**, *italic text*, and \`inline code\`. |
|
|
|
|
|
A horizontal rule: |
|
|
*** |
|
|
|
|
|
A table: |
|
|
|
|
|
| Header 1 | Header 2 | Header 3 | |
|
|
|----------|----------|----------| |
|
|
| Cell 1A | Cell 2A | Cell 3A | |
|
|
| Cell 1B | Cell 2B | Cell 3B | |
|
|
`; |
|
|
renderMarkdown(); |
|
|
|
|
|
</script> |
|
|
</body> |
|
|
</html> |