Skip to content

Instantly share code, notes, and snippets.

@rahuldhole
Last active February 2, 2026 23:06
Show Gist options
  • Select an option

  • Save rahuldhole/35450c3fc2c5b63ec9ad9745d6bc918a to your computer and use it in GitHub Desktop.

Select an option

Save rahuldhole/35450c3fc2c5b63ec9ad9745d6bc918a to your computer and use it in GitHub Desktop.
<!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>
<!-- 1. Tailwind CSS for layout -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 2. GitHub Markdown CSS (Dark Mode) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-dark.min.css" />
<!-- 3. Highlight.js CSS (Tokyo Night Dark) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css" />
<!-- 4. MathJax Configuration (Must be before loading MathJax) -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
processEscapes: true
},
options: {
ignoreHtmlClass: 'tex2jax_ignore',
processHtmlClass: 'tex2jax_process'
}
};
</script>
<!-- Load MathJax -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
<!-- 5. Core Libraries (Pinned Versions for Stability) -->
<script src="https://cdn.jsdelivr.net/npm/marked@12.0.1/lib/marked.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.0/dist/mermaid.min.js"></script>
<style>
body {
background-color: #0d1117; /* GitHub Dark Dimmed */
min-height: 100vh;
}
.markdown-body {
background: transparent !important;
min-width: 200px;
max-width: 900px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
}
/* Mermaid Diagram Container */
.mermaid {
background: #161b22;
padding: 20px;
border-radius: 8px;
display: flex;
justify-content: center;
margin: 20px 0;
border: 1px solid #30363d;
overflow-x: auto;
}
/* Code Block Styling Fixes */
.markdown-body pre {
position: relative;
background-color: #1a1b26 !important; /* Tokyo Night bg */
border: 1px solid #30363d;
border-radius: 8px;
}
/* Copy Button */
.copy-btn {
position: absolute;
top: 8px;
right: 8px;
padding: 4px 8px;
font-size: 12px;
color: #8b949e;
background: #21262d;
border: 1px solid #30363d;
border-radius: 6px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s, background 0.2s;
}
.markdown-body pre:hover .copy-btn {
opacity: 1;
}
.copy-btn:hover {
background: #30363d;
color: white;
}
/* Loading Spinner */
.loader {
border: 4px solid #161b22;
border-top: 4px solid #58a6ff;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<main>
<div id="content" class="markdown-body">
<div class="py-20 text-center">
<div class="loader mb-4"></div>
<div class="text-gray-400">Loading document...</div>
</div>
</div>
</main>
<script>
// 1. Initialize Mermaid
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
securityLevel: 'loose',
});
// 2. Configure Marked Renderer (The Critical Fix)
const renderer = new marked.Renderer();
// Override the 'code' renderer
renderer.code = function(tokenOrCode, langStr) {
// HANDLE ARGUMENTS: Marked versions differ.
// Newer versions pass an object as the first arg ({text, lang}).
// Older versions pass strings (code, lang).
let code = "";
let lang = "";
if (typeof tokenOrCode === 'object' && tokenOrCode !== null) {
code = tokenOrCode.text || "";
lang = tokenOrCode.lang || "";
} else {
code = String(tokenOrCode || "");
lang = String(langStr || "");
}
// Handle Mermaid Blocks
if (lang === 'mermaid') {
return `<div class="mermaid">${code}</div>`;
}
// Handle Standard Code Blocks
// Check if language is valid for Highlight.js
const validLang = hljs.getLanguage(lang) ? lang : 'plaintext';
// We render raw code here. Syntax highlighting is applied in post-processing
// to avoid double-escaping issues.
return `
<pre><button class="copy-btn" onclick="copyCode(this)">Copy</button><code class="language-${validLang}">${code.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</code></pre>
`;
};
marked.setOptions({
renderer: renderer,
gfm: true,
breaks: true
});
// 3. Copy to Clipboard Functionality
window.copyCode = function(btn) {
const pre = btn.parentElement;
const code = pre.querySelector('code');
const text = code.innerText; // Get raw text
navigator.clipboard.writeText(text).then(() => {
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 2000);
});
};
// 4. Main Fetch & Render Logic
const mdUrl = "markdown-sample-fully-featured.md";
fetch(mdUrl)
.then(res => {
if (!res.ok) throw new Error("Network response was not ok");
return res.text();
})
.then(md => {
const contentEl = document.getElementById("content");
// A. Parse Markdown
contentEl.innerHTML = marked.parse(md);
// B. Apply Syntax Highlighting
contentEl.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// C. Render Mermaid Diagrams
mermaid.run({
nodes: contentEl.querySelectorAll('.mermaid')
});
// D. Render MathJax (Equations)
if (window.MathJax) {
MathJax.typesetPromise([contentEl]).catch((err) => console.log('MathJax error:', err));
}
})
.catch(err => {
console.error(err);
document.getElementById("content").innerHTML = `
<div class="border border-red-800 bg-red-900/20 p-6 rounded-lg text-center text-red-400">
<h2 class="text-xl font-bold mb-2">Failed to load content</h2>
<p>${err.message}</p>
</div>
`;
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment