let easyMDE;
const previewEl = document.getElementById('preview');
const hljsTheme = document.getElementById('hljs-theme');
const docTitlePill = document.getElementById('doc-title-pill');
marked.setOptions({
breaks: true,
highlight: (code, lang) => {
try { return hljs.highlight(code, {language: lang}).value; }
catch { return hljs.highlightAuto(code).value; }
}
});
function renderPreview(md) {
const html = marked.parse(md || '');
previewEl.innerHTML = `
${html}
`;
try { if (window.renderMathInElement) renderMathInElement(previewEl, { delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
]}); } catch {}
try {
if (window.mermaid && !window.__MERMAID_INIT__) {
window.__MERMAID_INIT__ = true;
mermaid.initialize({ startOnLoad: false, theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default' });
}
const blocks = previewEl.querySelectorAll('pre code.language-mermaid, code.language-mermaid');
blocks.forEach((node, i) => {
const src = node.textContent.trim();
const container = document.createElement('div');
container.className = 'mermaid';
container.textContent = src;
const pre = node.closest('pre');
if (pre) pre.replaceWith(container); else node.replaceWith(container);
});
if (window.mermaid && previewEl.querySelector('.mermaid')) mermaid.run({ querySelector: '.mermaid' });
} catch {}
}
function getMarkdown() { return easyMDE ? easyMDE.value() : ''; }
function setupEditor() {
easyMDE = new EasyMDE({
element: document.getElementById('editor'),
spellChecker: false,
autosave: { enabled: true, uniqueId: 'md-editor-autosave', delay: 1000 },
previewRender: (plainText) => marked.parse(plainText),
toolbar: [
{ name: 'open', action: () => document.getElementById('btn-import-md').click(), className: 'fa fa-folder-open', title: 'Open Markdown file' },
'undo', 'redo', '|',
'bold', 'italic', 'heading-1', 'heading-2', 'heading-3', 'table', '|',
{ name: 'indent', action: (ed) => ed.codemirror.execCommand('indentMore'), className: 'fa fa-indent', title: 'Indent' },
{ name: 'outdent', action: (ed) => ed.codemirror.execCommand('indentLess'), className: 'fa fa-outdent', title: 'Outdent' },
'|', 'unordered-list', 'ordered-list', '|', 'quote', 'code', 'horizontal-rule', '|', 'link', 'image', '|',
'preview', 'side-by-side', 'fullscreen', '|',
{ name: 'clear', action: () => { easyMDE.value(''); renderPreview(''); }, className: 'fa fa-eraser', title: 'Clear' }
],
});
easyMDE.codemirror.on('change', () => renderPreview(getMarkdown()));
const initial = getMarkdown();
if (!initial || initial.trim() === '') {
const sample = `# Welcome to Markdown Editor!\n\nThis page works like **StackEdit**. Edit on the left, preview on the right.\nIf you want to learn Markdown quickly, read and modify this document.\n\n# Files\n\nYour content is autosaved in your browser and works **offline**.\n\n## Create files and folders\nUse the Samples menu for starters or begin typing.\n\n## Switch to another file\nYou can paste any Markdown content here and it will render instantly.\n\n## Export a file\nUse the Export menu to save as Markdown, raw HTML, styled HTML, or PDF.\n\n# Synchronization\n\nThis demo does not connect to cloud storage, but you can copy/paste to any service.\n\n# Markdown extensions\n\n## Code blocks\n\n\`\`\`python\nprint('Hello, Markdown!')\n\`\`\`\n\n## Tables\n\n| Feature | Status |\n|---|---|\n| Autosave | ✅ |\n| Dark Mode | ✅ |\n| Export | ✅ |\n\n## Blockquotes\n\n> Tips: Use the toolbar to format text, add lists, tables, and more.\n\n## Mermaid diagrams\n\n\`\`\`mermaid\ngraph LR\nA[Write] --> B[Preview]\nB --> C{Export}\nC -->|PDF| D[Share]\nC -->|HTML| E[Publish]\n\`\`\`\n\n---\n\nHappy writing!`;
easyMDE.value(sample);
}
renderPreview(getMarkdown());
}
function setupSplit() {
if (window.Split) {
Split(['#leftPane', '#rightPane'], { sizes: [50, 50], minSize: 280, gutterSize: 8, cursor: 'col-resize' });
}
}
function pickFile(inputEl, cb) {
inputEl.onchange = (e) => { const f = e.target.files[0]; if (f) cb(f); inputEl.value = ''; };
inputEl.click();
}
function readFileText(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.readAsText(file);
});
}
function bindActions() {
// Theme toggle
const themeBtn = document.getElementById('btn-theme');
const setDark = (on) => {
document.documentElement.classList.toggle('light', !on);
document.documentElement.classList.toggle('dark', !!on);
if (hljsTheme) {
hljsTheme.href = on
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css'
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css';
}
try { localStorage.setItem('md-theme-dark', on ? '1' : '0'); } catch {}
};
try {
const saved = localStorage.getItem('md-theme-dark');
const initialDark = saved === null ? false : saved === '1';
setDark(initialDark);
const icon = themeBtn.querySelector('i');
if (icon) icon.className = initialDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
} catch { setDark(false); }
themeBtn.addEventListener('click', () => {
const isDark = document.documentElement.classList.contains('dark');
const nowDark = !isDark;
setDark(nowDark);
const icon = themeBtn.querySelector('i');
if (icon) icon.className = nowDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
});
document.getElementById('btn-import-md').addEventListener('click', () => {
pickFile(document.getElementById('file-md'), async (file) => {
const text = await readFileText(file);
easyMDE.value(text);
renderPreview(text);
});
});
document.getElementById('btn-import-html').addEventListener('click', () => {
pickFile(document.getElementById('file-html'), async (file) => {
const text = await readFileText(file);
easyMDE.value(text);
renderPreview(text);
});
});
const samplesBtn = document.getElementById('btn-samples');
const samplesModal = new bootstrap.Modal(document.getElementById('samplesModal'));
samplesBtn.addEventListener('click', () => samplesModal.show());
const samples = {
cheatsheet: `# Markdown Cheat Sheet\n\n## Headings\n# H1\n## H2\n### H3\n\n## Emphasis\n*italic* **bold** ~~strike~~ \`code\`\n\n## Lists\n- unordered\n- list\n - nested\n1. ordered\n2. list\n\n## Links & Images\n[OpenAI](https://openai.com) \\n\n\n## Blockquote\n> A wise quote.\n\n## Table\n| Name | Role |\n|---|---|\n| Alice | Developer |\n| Bob | Designer |\n\n## Task List\n- [x] design\n- [ ] implement\n- [ ] test\n\n## Code Block\n\`\`\`python\nfrom math import sqrt\nprint(sqrt(9))\n\`\`\`\n\n---\n\n### Horizontal rule above`,
email: `**Subject:** Request to Schedule Project Kickoff Meeting\n\nDear [Recipient Name],\n\nI hope you're doing well. I'd like to schedule a 30‑minute kickoff meeting to align on [Project/Topic].\n\n**Proposed agenda:**\n- Objectives and success criteria\n- Scope, timeline, and roles\n- Risks and dependencies\n- Next steps\n\n**Suggested times (IST):**\n- [Option 1, e.g., Tue 3:00–3:30 PM]\n- [Option 2, e.g., Wed 11:00–11:30 AM]\n\nIf these don't work, please share alternative slots and your preferred meeting platform.\n\nThanks in advance, and I look forward to our discussion.\n\nBest regards,\n\n[Your Full Name]\n[Your Role], [Your Company]\nPhone: [Your Phone]\nEmail: you@example.com\n\n---`,
readme: `# Project Title\n\n[](LICENSE) \n\nA short description of your project.\n\n## Features\n- Awesome feature\n- Fast and reliable\n\n## Installation\n\`\`\`bash\npip install -r requirements.txt\npython app.py\n\`\`\`\n\n## Usage\nDescribe how to use it.\n\n## License\nMIT © You`,
github_readme_pro: `# Awesome App\n\n[](https://github.com/OWNER/REPO/actions) [](LICENSE)\n\n> One‑liner that explains what this project does.\n\n## Table of Contents\n- [Demo](#demo)\n- [Stack](#stack)\n- [Install](#install)\n- [Project Structure](#project-structure)\n- [CLI](#cli)\n- [Docs](#docs)\n\n## Demo\n\n\n## Stack\n| Layer | Tech |\n|---|---|\n| Frontend | React / Vite |\n| Backend | FastAPI |\n| DB | PostgreSQL |\n| Infra | Docker / GH Actions |\n\n## Install\n\`\`\`bash\n git clone https://github.com/OWNER/REPO && cd REPO\n cp .env.example .env\n docker compose up -d --build\n\`\`\`\n\n## Project Structure\n\`\`\`text\nREPO/\n ├─ apps/\n │ ├─ api/\n │ └─ web/\n ├─ packages/\n │ ├─ ui/\n │ └─ config/\n └─ tools/\n └─ scripts/\n\`\`\`\n\n## Docs\n### Data Flow (Mermaid)\n\`\`\`mermaid\ngraph TD\nA[Browser] --> B(Edge API)\nB --> C{Auth}\nC -->|pass| D[Service] --> E[(DB)]\nC -->|fail| F[401]\n\`\`\`\n\n## License\nMIT`,
math_notes: `# KaTeX Quick Examples\n\nInline: $E=mc^2$, $\\int_0^1 x^2\\,dx = \\tfrac{1}{3}$.\n\nBlock:\n\n$$\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$$\n\nEuler integral for the gamma function:\n\n$$\\Gamma(z)=\\int_0^\\infty t^{z-1}e^{-t}\\,dt$$\n\n> Tip: See more syntax in the MathJax guide.`,
api_docs: `# HTTP API\n\n> Version 1\n\n## Authentication\nUse a Bearer token in the Authorization header.\n\n## Endpoints\n### GET /v1/users\nReturns a paginated list of users.\n\n\`\`\`http\nGET /v1/users?page=1&pageSize=20\nAuthorization: Bearer \n\`\`\`\n\nResponse:\n\`\`\`json\n{\n "items": [{"id": 1, "name": "Ada"}],\n "nextPage": 2\n}\n\`\`\`\n\n### Errors\n| Code | Meaning |\n|---|---|\n| 400 | Bad request |\n| 401 | Unauthorized |\n| 404 | Not found |`,
meeting_notes: `# Meeting Notes\n\n**When**: 2025-08-28 14:00–14:45 IST \n**Where**: Zoom \n**Attendees**: Alex, Priya, Sam\n\n## Agenda\n1. Status updates\n2. Release checklist\n3. Risks\n\n## Decisions\n- Ship v1.2 on Monday\n- Freeze new features until post‑release\n\n## Action Items\n- [ ] Alex: finalize docs\n- [ ] Priya: run load tests\n- [ ] Sam: prepare rollout plan\n\n## Timeline\n\`\`\`mermaid\ngantt\n title Release plan\n dateFormat YYYY-MM-DD\n section Prep\n Docs :a1, 2025-08-28, 2d\n Load tests :a2, 2025-08-28, 2d\n section Ship\n Release :milestone, 2025-08-30, 1d\n\`\`\``
};
document.querySelectorAll('[data-sample]').forEach(btn => {
btn.addEventListener('click', () => {
const key = btn.getAttribute('data-sample');
easyMDE.value(samples[key] || '');
renderPreview(getMarkdown());
samplesModal.hide();
});
});
document.querySelectorAll('[data-sample-append]').forEach(btn => {
btn.addEventListener('click', () => {
const key = btn.getAttribute('data-sample-append');
const text = samples[key] || '';
easyMDE.value((getMarkdown() + '\n\n' + text).trim());
renderPreview(getMarkdown());
samplesModal.hide();
});
});
document.getElementById('btn-export-md').addEventListener('click', () => {
const content = easyMDE.value();
const filename = document.getElementById('doc-title-pill').textContent || 'document';
const blob = new Blob([content], { type: 'text/markdown' });
saveAs(blob, `${filename}.md`);
});
document.getElementById('btn-export-html').addEventListener('click', () => {
const content = easyMDE.value();
const html = marked.parse(content);
const filename = document.getElementById('doc-title-pill').textContent || 'document';
const blob = new Blob([html], { type: 'text/html' });
saveAs(blob, `${filename}.html`);
});
document.getElementById('btn-export-html-styled').addEventListener('click', () => {
const content = easyMDE.value();
const html = marked.parse(content);
const styledHtml = `
${document.getElementById('doc-title-pill').textContent || 'Document'}
${html}
`;
const filename = document.getElementById('doc-title-pill').textContent || 'document';
const blob = new Blob([styledHtml], { type: 'text/html' });
saveAs(blob, `${filename}-styled.html`);
});
document.getElementById('btn-export-pdf').addEventListener('click', () => {
const pdfOptionsModal = new bootstrap.Modal(document.getElementById('pdfOptionsModal'));
pdfOptionsModal.show();
});
document.getElementById('btn-generate-pdf').addEventListener('click', () => {
const orientation = document.querySelector('input[name="orientation"]:checked').value;
const paperSize = document.getElementById('paperSize').value;
const pdfOptionsModal = bootstrap.Modal.getInstance(document.getElementById('pdfOptionsModal'));
pdfOptionsModal.hide();
generatePDF(orientation, paperSize);
});
function generatePDF(orientation, paperSize, fullPage) {
const content = easyMDE.value();
const html = marked.parse(content);
const pageBreakStyles = fullPage ? '' : `
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
page-break-after: avoid;
}
.markdown-body table {
page-break-inside: avoid;
}
.markdown-body pre {
page-break-inside: avoid;
}
.markdown-body hr {
page-break-after: avoid;
}`;
const styledHtml = `
${document.getElementById('doc-title-pill').textContent || 'Document'}
${html}
`;
const filename = document.getElementById('doc-title-pill').textContent || 'document';
if (fullPage) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = styledHtml;
tempDiv.style.position = 'absolute';
tempDiv.style.left = '-9999px';
tempDiv.style.top = '0';
tempDiv.style.width = '800px';
tempDiv.style.background = '#ffffff';
tempDiv.style.padding = '20px';
tempDiv.style.overflow = 'visible';
tempDiv.style.fontSize = '14px';
tempDiv.style.lineHeight = '1.6';
document.body.appendChild(tempDiv);
setTimeout(() => {
const contentHeight = Math.max(tempDiv.scrollHeight, tempDiv.offsetHeight);
console.log('Content height:', contentHeight);
document.body.removeChild(tempDiv);
const opt = {
margin: [0, 0, 0, 0],
filename: `${filename}-fullpage.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 1,
useCORS: true,
letterRendering: true,
backgroundColor: '#ffffff',
width: 800,
height: contentHeight,
scrollY: 0,
scrollX: 0,
allowTaint: true,
foreignObjectRendering: true
},
jsPDF: {
unit: 'px',
format: [800, contentHeight + 40],
orientation: 'portrait',
compress: true
}
};
html2pdf().from(styledHtml).set(opt).save();
}, 300);
} else {
const opt = {
margin: [10, 10, 10, 10],
filename: `${filename}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 2,
useCORS: true,
letterRendering: true,
backgroundColor: '#ffffff'
},
jsPDF: {
unit: 'mm',
format: paperSize,
orientation: orientation
}
};
html2pdf().from(styledHtml).set(opt).save();
}
}
const readBtn = document.getElementById('btn-readmode');
if (readBtn) {
let readMode = false;
readBtn.addEventListener('click', () => {
readMode = !readMode;
document.body.classList.toggle('read-mode', readMode);
const icon = readBtn.querySelector('i');
if (icon) icon.className = readMode ? 'fa-solid fa-book' : 'fa-solid fa-book-open';
readBtn.textContent = readMode ? ' Exit read mode' : ' Read mode';
readBtn.prepend(icon);
});
}
if (docTitlePill) {
const savedTitle = localStorage.getItem('md-doc-title') || 'Welcome file';
docTitlePill.textContent = savedTitle;
docTitlePill.addEventListener('input', () => {
const v = (docTitlePill.textContent || '').trim() || 'Untitled';
localStorage.setItem('md-doc-title', v);
});
}
}
window.addEventListener('DOMContentLoaded', () => {
const ensureCss = (href) => { if (!document.querySelector(`link[href="${href}"]`)) { const l=document.createElement('link'); l.rel='stylesheet'; l.href=href; document.head.appendChild(l);} };
const ensureScript = (src, cb) => { if (document.querySelector(`script[src="${src}"]`)) { cb && cb(); return; } const s=document.createElement('script'); s.src=src; s.defer=true; s.onload=() => cb && cb(); document.body.appendChild(s); };
ensureCss('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css');
ensureScript('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js', () => {
ensureScript('https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js', () => {
try { renderPreview(getMarkdown()); } catch {}
});
});
ensureScript('https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js', () => { try { renderPreview(getMarkdown()); } catch {} });
setupEditor();
setupSplit();
bindActions();
const aboutBtn = document.getElementById('btn-about');
if (aboutBtn) {
const aboutEl = document.getElementById('aboutModal');
const aboutModal = new bootstrap.Modal(aboutEl);
aboutBtn.addEventListener('click', () => aboutModal.show());
aboutEl.addEventListener('hidden.bs.modal', () => {
document.body.classList.remove('modal-open');
document.body.style.removeProperty('padding-right');
document.querySelectorAll('.modal-backdrop').forEach(b => b.remove());
document.documentElement.style.overflow = '';
document.body.style.overflow = '';
});
}
});