Skip to content

Instantly share code, notes, and snippets.

@thimslugga
Last active October 30, 2025 21:18
Show Gist options
  • Save thimslugga/2fc75b11c9d9776b71fc3743e875bc68 to your computer and use it in GitHub Desktop.
Save thimslugga/2fc75b11c9d9776b71fc3743e875bc68 to your computer and use it in GitHub Desktop.
Simple wiki that renders markdown notes
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WikiMD</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
display: flex;
height: 100vh;
overflow: hidden;
background: #f5f5f5;
}
/* Sidebar */
#sidebar {
width: 280px;
background: #2c3e50;
color: #ecf0f1;
display: flex;
flex-direction: column;
border-right: 1px solid #34495e;
}
#sidebar-header {
padding: 20px;
background: #34495e;
border-bottom: 1px solid #2c3e50;
}
#sidebar-header h1 {
font-size: 20px;
margin-bottom: 10px;
}
#search-box {
width: 100%;
padding: 8px 12px;
border: none;
border-radius: 4px;
background: #2c3e50;
color: #ecf0f1;
font-size: 14px;
}
#search-box::placeholder {
color: #95a5a6;
}
#file-list {
flex: 1;
overflow-y: auto;
padding: 10px 0;
}
.file-item {
padding: 12px 20px;
cursor: pointer;
transition: background 0.2s;
border-left: 3px solid transparent;
font-size: 14px;
}
.file-item:hover {
background: #34495e;
}
.file-item.active {
background: #34495e;
border-left-color: #3498db;
}
.file-item-name {
font-weight: 500;
}
.file-item-path {
font-size: 11px;
color: #95a5a6;
margin-top: 2px;
}
/* Main content */
#main-content {
flex: 1;
display: flex;
flex-direction: column;
background: white;
}
#toolbar {
padding: 15px 30px;
background: white;
border-bottom: 1px solid #e1e4e8;
display: flex;
justify-content: space-between;
align-items: center;
}
#current-file {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: #3498db;
color: white;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.btn:hover {
background: #2980b9;
}
.btn-secondary {
background: #95a5a6;
}
.btn-secondary:hover {
background: #7f8c8d;
}
#content-area {
flex: 1;
overflow-y: auto;
padding: 40px;
}
/* Markdown styles */
#rendered-content {
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
color: #333;
}
#rendered-content h1 {
font-size: 2em;
margin: 0.67em 0;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 0.3em;
}
#rendered-content h2 {
font-size: 1.5em;
margin: 0.75em 0;
border-bottom: 1px solid #e1e4e8;
padding-bottom: 0.3em;
}
#rendered-content h3 {
font-size: 1.25em;
margin: 1em 0;
}
#rendered-content p {
margin: 1em 0;
}
#rendered-content code {
background: #f6f8fa;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
#rendered-content pre {
background: #f6f8fa;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
margin: 1em 0;
}
#rendered-content pre code {
background: none;
padding: 0;
}
#rendered-content ul, #rendered-content ol {
margin: 1em 0;
padding-left: 2em;
}
#rendered-content li {
margin: 0.5em 0;
}
#rendered-content blockquote {
border-left: 4px solid #dfe2e5;
padding-left: 1em;
color: #6a737d;
margin: 1em 0;
}
#rendered-content a {
color: #3498db;
text-decoration: none;
}
#rendered-content a:hover {
text-decoration: underline;
}
#rendered-content table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
#rendered-content th, #rendered-content td {
border: 1px solid #dfe2e5;
padding: 8px 12px;
text-align: left;
}
#rendered-content th {
background: #f6f8fa;
font-weight: 600;
}
#rendered-content img {
max-width: 100%;
height: auto;
}
/* Welcome screen */
#welcome-screen {
text-align: center;
color: #7f8c8d;
}
#welcome-screen h2 {
font-size: 24px;
margin-bottom: 20px;
}
#file-input-wrapper {
margin-top: 20px;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div id="sidebar">
<div id="sidebar-header">
<h1>📚 Markdown Wiki</h1>
<input type="text" id="search-box" placeholder="Search notes...">
</div>
<div id="file-list"></div>
</div>
<!-- Main content -->
<div id="main-content">
<div id="toolbar">
<div id="current-file">Welcome</div>
<div>
<button class="btn" onclick="loadFiles()">Load Notes</button>
<button class="btn btn-secondary" onclick="clearAll()">Clear All</button>
</div>
</div>
<div id="content-area">
<div id="welcome-screen">
<h2>WikiMD</h2>
<p>Load your markdown files to get started</p>
<div id="file-input-wrapper">
<input type="file" id="file-input" multiple accept=".md,.markdown,.txt" style="display:none">
<button class="btn" onclick="document.getElementById('file-input').click()">
Choose Files
</button>
</div>
</div>
<div id="rendered-content"></div>
</div>
</div>
<script>
// Store loaded files
let notes = [];
let currentNote = null;
// Initialize marked
marked.setOptions({
breaks: true,
gfm: true
});
// File input handler
document.getElementById('file-input').addEventListener('change', async (e) => {
const files = Array.from(e.target.files);
await loadFilesFromInput(files);
});
async function loadFilesFromInput(files) {
notes = [];
for (const file of files) {
const content = await file.text();
notes.push({
name: file.name,
path: file.webkitRelativePath || file.name,
content: content
});
}
renderFileList();
if (notes.length > 0) {
document.getElementById('welcome-screen').style.display = 'none';
selectNote(notes[0]);
}
}
function renderFileList() {
const fileList = document.getElementById('file-list');
const searchTerm = document.getElementById('search-box').value.toLowerCase();
const filteredNotes = notes.filter(note =>
note.name.toLowerCase().includes(searchTerm) ||
note.content.toLowerCase().includes(searchTerm)
);
fileList.innerHTML = filteredNotes.map((note, index) => `
<div class="file-item ${currentNote === note ? 'active' : ''}"
onclick="selectNote(notes[${notes.indexOf(note)}])">
<div class="file-item-name">${note.name}</div>
<div class="file-item-path">${note.path}</div>
</div>
`).join('');
}
function selectNote(note) {
currentNote = note;
document.getElementById('current-file').textContent = note.name;
const renderedContent = document.getElementById('rendered-content');
renderedContent.innerHTML = marked.parse(note.content);
renderedContent.style.display = 'block';
renderFileList();
}
function loadFiles() {
document.getElementById('file-input').click();
}
function clearAll() {
notes = [];
currentNote = null;
document.getElementById('file-list').innerHTML = '';
document.getElementById('rendered-content').innerHTML = '';
document.getElementById('rendered-content').style.display = 'none';
document.getElementById('welcome-screen').style.display = 'block';
document.getElementById('current-file').textContent = 'Welcome';
}
// Search functionality
document.getElementById('search-box').addEventListener('input', () => {
renderFileList();
});
// Handle wiki-style links [[Note Name]]
document.addEventListener('click', (e) => {
if (e.target.tagName === 'A') {
const href = e.target.getAttribute('href');
if (href && href.startsWith('[[') && href.endsWith(']]')) {
e.preventDefault();
const noteName = href.slice(2, -2);
const note = notes.find(n =>
n.name === noteName ||
n.name === noteName + '.md'
);
if (note) {
selectNote(note);
}
}
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K for search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('search-box').focus();
}
// Arrow keys for navigation
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
const currentIndex = notes.indexOf(currentNote);
if (currentIndex !== -1) {
e.preventDefault();
const newIndex = e.key === 'ArrowDown'
? Math.min(currentIndex + 1, notes.length - 1)
: Math.max(currentIndex - 1, 0);
selectNote(notes[newIndex]);
}
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment