How this editor works

Type Markdown in the left pane β€” the right pane renders a live GitHub-Flavored preview. Your document is autosaved to localStorage every 500 ms. Nothing leaves your browser. Export HTML or download a .md file to back up your work.

GFM features supported

FAQ

What is GitHub-Flavored Markdown (GFM)?

GFM is a Markdown dialect used on GitHub that adds tables, task lists, strikethrough, fenced code blocks with language hints, and autolinks to standard Markdown.

Is my Markdown document saved?

Your document is autosaved to your browser's localStorage every 500ms. Nothing is sent to any server. Clearing browser data will delete the saved content β€” use Export HTML or Download .md to keep a permanent copy.

How do I export my document as HTML?

Click the ⬇ HTML button in the toolbar. A self-contained .html file with inline CSS is downloaded to your device.

How do I export to PDF?

Click βŽ™ PDF. Your browser's Print dialog opens. Choose "Save as PDF" as the destination. Print CSS hides UI chrome and shows only the rendered preview.

Does this editor support tables and task lists?

Yes. The editor supports GFM tables, task lists (- [x] / - [ ]), fenced code blocks, strikethrough, autolinks, blockquotes, and all standard Markdown.

`; downloadFile(html, `${title.replace(/[^\w\s-]/g,'_')}.html`, 'text/html'); } // ─── EXPORT MD ───────────────────────────────────────────────── function exportMD() { const title = docNameInput.value || 'document'; downloadFile(editor.value, `${title.replace(/[^\w\s-]/g,'_')}.md`, 'text/markdown'); } // ─── COPY HTML ───────────────────────────────────────────────── function copyHTML() { const btn = document.getElementById('btn-copy-html'); navigator.clipboard.writeText(previewBody.innerHTML).then(() => { btn.textContent = 'βœ“ Copied!'; setTimeout(()=>{ btn.textContent='⧉ HTML'; }, 2000); }).catch(() => { const ta = document.createElement('textarea'); ta.value = previewBody.innerHTML; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); btn.textContent = 'βœ“ Copied!'; setTimeout(()=>{ btn.textContent='⧉ HTML'; }, 2000); }); } // ─── DOWNLOAD HELPER ─────────────────────────────────────────── function downloadFile(content, filename, mime) { const blob = new Blob([content], { type: mime }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); URL.revokeObjectURL(a.href); } // ─── DARK MODE ───────────────────────────────────────────────── function toggleDark() { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; const next = isDark ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); document.getElementById('btn-dark').textContent = next === 'dark' ? 'β˜€' : 'πŸŒ™'; try { localStorage.setItem('md_theme', next); } catch(e){} } function applyTheme() { try { const saved = localStorage.getItem('md_theme'); if (saved) { document.documentElement.setAttribute('data-theme', saved); document.getElementById('btn-dark').textContent = saved === 'dark' ? 'β˜€' : 'πŸŒ™'; return; } } catch(e){} if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.setAttribute('data-theme','dark'); document.getElementById('btn-dark').textContent = 'β˜€'; } } // ─── VIEW MODE ───────────────────────────────────────────────── function setView(mode) { viewMode = mode; editorPane.classList.toggle('hidden', mode === 'preview'); previewPane.classList.toggle('hidden', mode === 'editor'); resizeHandle.style.display = mode === 'split' ? '' : 'none'; document.getElementById('btn-view-editor').classList.toggle('active', mode === 'editor'); document.getElementById('btn-view-split').classList.toggle('active', mode === 'split'); document.getElementById('btn-view-preview').classList.toggle('active', mode === 'preview'); } // ─── MOBILE TAB SWITCH ───────────────────────────────────────── function switchMobileTab(tab) { const editTab = document.getElementById('tab-edit'); const previewTab = document.getElementById('tab-preview'); if (tab === 'edit') { editorPane.style.display = ''; previewPane.style.display = 'none'; editTab.classList.add('active'); previewTab.classList.remove('active'); editTab.setAttribute('aria-selected','true'); previewTab.setAttribute('aria-selected','false'); } else { editorPane.style.display = 'none'; previewPane.style.display = ''; editTab.classList.remove('active'); previewTab.classList.add('active'); editTab.setAttribute('aria-selected','false'); previewTab.setAttribute('aria-selected','true'); } } // ─── RESIZE PANES ────────────────────────────────────────────── let isResizing = false; resizeHandle.addEventListener('mousedown', (e) => { isResizing = true; resizeHandle.classList.add('dragging'); document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isResizing) return; const mainRect = document.getElementById('main').getBoundingClientRect(); const pct = Math.max(20, Math.min(80, (e.clientX - mainRect.left) / mainRect.width * 100)); editorPane.style.width = pct + '%'; }); document.addEventListener('mouseup', () => { if (!isResizing) return; isResizing = false; resizeHandle.classList.remove('dragging'); document.body.style.cursor = ''; document.body.style.userSelect = ''; }); // ─── IMAGE PASTE ─────────────────────────────────────────────── editor.addEventListener('paste', (e) => { const items = e.clipboardData && e.clipboardData.items; if (!items) return; for (const item of items) { if (item.type.startsWith('image/')) { e.preventDefault(); const reader = new FileReader(); reader.onload = (ev) => { const dataUri = ev.target.result; insertAtCursor(`![pasted-image](${dataUri})\n\n`); }; reader.readAsDataURL(item.getAsFile()); } } }); // ─── TABLE MODAL ─────────────────────────────────────────────── function openTableModal() { modalOverlay.classList.add('open'); document.getElementById('tbl-cols').focus(); } document.getElementById('btn-modal-cancel').onclick = () => modalOverlay.classList.remove('open'); document.getElementById('btn-modal-insert').onclick = () => { const cols = Math.max(1, Math.min(10, parseInt(document.getElementById('tbl-cols').value) || 3)); const rows = Math.max(1, Math.min(20, parseInt(document.getElementById('tbl-rows').value) || 3)); const header = '| ' + Array.from({length:cols}, (_,i)=>`Header ${i+1}`).join(' | ') + ' |'; const sep = '| ' + Array.from({length:cols}, ()=>'---').join(' | ') + ' |'; const row = '| ' + Array.from({length:cols}, ()=>'Cell').join(' | ') + ' |'; const tableStr = [header, sep, ...Array.from({length:rows}, ()=>row)].join('\n') + '\n'; insertAtCursor(tableStr); modalOverlay.classList.remove('open'); }; modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) modalOverlay.classList.remove('open'); }); // ─── URL INPUT MODAL (replaces prompt for link/image) ────────── let _urlModalCallback = null; const urlModalOverlay = document.getElementById('url-modal-overlay'); const urlModalTitle = document.getElementById('url-modal-title'); const urlModalInput = document.getElementById('url-modal-input'); function openUrlModal(title, callback) { _urlModalCallback = callback; urlModalTitle.textContent = title; urlModalInput.value = 'https://'; urlModalOverlay.style.display = 'flex'; urlModalInput.focus(); urlModalInput.select(); } function closeUrlModal() { urlModalOverlay.style.display = 'none'; _urlModalCallback = null; editor.focus(); } document.getElementById('btn-url-cancel').onclick = closeUrlModal; document.getElementById('btn-url-insert').onclick = () => { const url = urlModalInput.value.trim(); const cb = _urlModalCallback; closeUrlModal(); if (url && url !== 'https://' && cb) cb(url); }; urlModalInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); document.getElementById('btn-url-insert').click(); } if (e.key === 'Escape') { e.preventDefault(); closeUrlModal(); } }); urlModalOverlay.addEventListener('click', (e) => { if (e.target === urlModalOverlay) closeUrlModal(); }); // ─── CHEATSHEET DATA ─────────────────────────────────────────── const cheatsheetData = [ { section: 'Headings', items: [ { syntax: '# H1', desc: 'Heading 1' }, { syntax: '## H2', desc: 'Heading 2' }, { syntax: '### H3', desc: 'Heading 3' }, { syntax: '###### H6', desc: 'Heading 6' }, ]}, { section: 'Emphasis', items: [ { syntax: '**bold**', desc: 'Bold text' }, { syntax: '*italic*', desc: 'Italic text' }, { syntax: '***bold+italic***', desc: 'Bold and italic' }, { syntax: '~~strikethrough~~', desc: 'Strikethrough' }, ]}, { section: 'Lists', items: [ { syntax: '- item', desc: 'Unordered list' }, { syntax: '* item', desc: 'Unordered (alt)' }, { syntax: '1. item', desc: 'Ordered list' }, { syntax: ' - nested', desc: 'Nested list (2 spaces)' }, { syntax: '- [x] done', desc: 'Checked task' }, { syntax: '- [ ] todo', desc: 'Unchecked task' }, ]}, { section: 'Links & Images', items: [ { syntax: '[text](url)', desc: 'Hyperlink' }, { syntax: '[text](url "title")', desc: 'Link with title' }, { syntax: '![alt](url)', desc: 'Image' }, { syntax: '', desc: 'Autolink' }, ]}, { section: 'Code', items: [ { syntax: '`inline code`', desc: 'Inline code' }, { syntax: '```js\ncode\n```', desc: 'Fenced code block' }, { syntax: '```python', desc: 'With language hint' }, ]}, { section: 'Blockquotes', items: [ { syntax: '> quote', desc: 'Blockquote' }, { syntax: '>> nested', desc: 'Nested blockquote' }, ]}, { section: 'Tables', items: [ { syntax: '| H1 | H2 |\\n| --- | --- |\\n| A | B |', desc: 'GFM table' }, { syntax: '| :--- |', desc: 'Left aligned column' }, { syntax: '| ---: |', desc: 'Right aligned column' }, { syntax: '| :---: |', desc: 'Centered column' }, ]}, { section: 'Other', items: [ { syntax: '---', desc: 'Horizontal rule' }, { syntax: ' ', desc: 'Line break (2 trailing spaces)' }, { syntax: '\\*escaped\\*', desc: 'Escaped syntax char' }, ]}, ]; function buildCheatsheet() { const container = document.getElementById('cheatsheet-content'); let html = ''; for (const sec of cheatsheetData) { html += `

${escHtml(sec.section)}

`; for (const item of sec.items) { const displaySyntax = item.syntax.replace(/\\n/g,'↡'); html += `
${escHtml(displaySyntax)} ${escHtml(item.desc)}
`; } html += '
'; } container.innerHTML = html; container.addEventListener('click', (e) => { if (e.target.classList.contains('cs-copy')) { const syntax = e.target.dataset.syntax; navigator.clipboard.writeText(syntax).catch(()=>{}); e.target.textContent = 'βœ“'; setTimeout(()=>{ e.target.textContent='Copy'; }, 1200); } }); } // ─── INIT ────────────────────────────────────────────────────── const DEFAULT_MD = `# Welcome to the Markdown Editor Write **Markdown** here and see the *live preview* on the right. ## Features - πŸ“ GitHub-Flavored Markdown (GFM) - πŸ’Ύ Autosave to localStorage - πŸ“€ Export to HTML or download \`.md\` - πŸŒ™ Dark mode support - ↔ Drag to resize panes ## Example Table | Feature | Status | | :--- | :---: | | GFM tables | βœ… | | Task lists | βœ… | | Fenced code | βœ… | | Sync scroll | βœ… | ## Task List - [x] Live preview - [x] Syntax highlighting - [ ] Add your content here ## Code Example \`\`\`javascript function greet(name) { const msg = \`Hello, \${name}!\`; return msg; } \`\`\` --- > **Tip:** Use the toolbar buttons or keyboard shortcuts (Ctrl+B, Ctrl+I, Ctrl+K). > Press **?** for the cheatsheet. `; (function init() { applyTheme(); buildCheatsheet(); setView('split'); document.getElementById('footer-year').textContent = new Date().getFullYear(); const restored = loadSaved(); if (!restored) editor.value = DEFAULT_MD; autosaveIndicator.textContent = restored ? 'Restored' : 'Autosaved'; if (restored) autosaveIndicator.classList.add('saved'); render(); updateCursor(); editor.addEventListener('input', scheduleRender); editor.addEventListener('keyup', updateCursor); editor.addEventListener('click', updateCursor); docNameInput.addEventListener('input', scheduleSave); })();