|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8" /> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
|
<title>DocSite — Markdown Documentation</title> |
|
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
|
|
|
<script src="https://unpkg.com/heroicons@2.0.18/dist/outline.js"></script> |
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
|
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css"> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> |
|
|
<style> |
|
|
|
|
|
.scrollbar-thin::-webkit-scrollbar { width: 6px; } |
|
|
.scrollbar-thin::-webkit-scrollbar-thumb { background-color: rgba(100,100,100,0.4); border-radius: 3px; } |
|
|
</style> |
|
|
</head> |
|
|
<body class="h-screen flex overflow-hidden font-sans"> |
|
|
|
|
|
<aside id="sidebar" class="w-64 bg-gray-800 text-gray-200 p-4 flex flex-col transition-transform duration-300"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<h2 class="text-lg font-semibold">Pages</h2> |
|
|
<button id="newPageBtn" class="p-1 hover:bg-gray-700 rounded"><svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 4v16m8-8H4"/></svg></button> |
|
|
</div> |
|
|
<ul id="fileTree" class="flex-1 overflow-auto scrollbar-thin space-y-1"></ul> |
|
|
<button id="deletePageBtn" class="mt-4 px-3 py-2 bg-red-600 hover:bg-red-700 rounded-md">Delete Page</button> |
|
|
</aside> |
|
|
|
|
|
|
|
|
<main class="flex-1 flex flex-col"> |
|
|
|
|
|
<div id="toolbar" class="flex items-center bg-gray-100 p-2 border-b transition-transform duration-300"> |
|
|
<button id="toggleSidebarBtn" class="p-1 mr-2 hover:bg-gray-200 rounded"><svg class="w-5 h-5 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button> |
|
|
<button id="toggleEditorBtn" class="p-1 mr-2 hover:bg-gray-200 rounded"><svg class="w-5 h-5 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M8 4h8v16H8z"/></svg></button> |
|
|
<input id="pageTitle" class="flex-1 px-2 py-1 border rounded focus:outline-none mr-2" placeholder="Page Title..." /> |
|
|
<button id="saveBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded">Save</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex flex-1 overflow-hidden"> |
|
|
|
|
|
<div id="editorPane" class="relative w-1/2 transition-all duration-300"> |
|
|
<div id="editorToolbar" class="absolute top-2 left-2 flex space-x-1 z-10"> |
|
|
<button data-action="bold" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">B</button> |
|
|
<button data-action="italic" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">I</button> |
|
|
<button data-action="h1" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">H1</button> |
|
|
<button data-action="ul" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">• List</button> |
|
|
</div> |
|
|
<textarea id="editor" class="w-full h-full p-12 pt-10 text-sm font-mono outline-none border-r resize-none scrollbar-thin" placeholder="Write Markdown..."></textarea> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="previewPane" class="w-1/2 p-6 overflow-auto scrollbar-thin prose prose-indigo max-w-none transition-all duration-300"></div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
<script> |
|
|
|
|
|
let pages = JSON.parse(localStorage.getItem('docPages') || '{}'); |
|
|
let currentId = null; |
|
|
const AUTO_SAVE_INTERVAL = 5000; |
|
|
|
|
|
|
|
|
const sidebar = document.getElementById('sidebar'); |
|
|
const toolbarDiv = document.getElementById('toolbar'); |
|
|
const fileTree = document.getElementById('fileTree'); |
|
|
const newPageBtn = document.getElementById('newPageBtn'); |
|
|
const deletePageBtn = document.getElementById('deletePageBtn'); |
|
|
const saveBtn = document.getElementById('saveBtn'); |
|
|
const pageTitleInput = document.getElementById('pageTitle'); |
|
|
const editor = document.getElementById('editor'); |
|
|
const previewPane = document.getElementById('previewPane'); |
|
|
const toggleSidebarBtn = document.getElementById('toggleSidebarBtn'); |
|
|
const toggleEditorBtn = document.getElementById('toggleEditorBtn'); |
|
|
const editorToolbar = document.getElementById('editorToolbar'); |
|
|
const editorPane = document.getElementById('editorPane'); |
|
|
|
|
|
|
|
|
function renderTree() { |
|
|
fileTree.innerHTML = ''; |
|
|
for (let id of Object.keys(pages)) { |
|
|
const li = document.createElement('li'); |
|
|
li.textContent = pages[id].title; |
|
|
li.className = 'px-2 py-1 hover:bg-gray-700 rounded cursor-pointer ' + (id === currentId ? 'bg-gray-700' : ''); |
|
|
li.onclick = () => loadPage(id); |
|
|
fileTree.append(li); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function loadPage(id) { |
|
|
currentId = id; |
|
|
const { title, content } = pages[id]; |
|
|
pageTitleInput.value = title; |
|
|
editor.value = content; |
|
|
updatePreview(); |
|
|
renderTree(); |
|
|
} |
|
|
|
|
|
|
|
|
function updatePreview() { |
|
|
const html = marked.parse(editor.value || ''); |
|
|
previewPane.innerHTML = html; |
|
|
previewPane.querySelectorAll('pre code').forEach(block => hljs.highlightBlock(block)); |
|
|
} |
|
|
|
|
|
|
|
|
function savePage() { |
|
|
if (!currentId) return; |
|
|
pages[currentId] = { title: pageTitleInput.value || 'Untitled', content: editor.value }; |
|
|
localStorage.setItem('docPages', JSON.stringify(pages)); |
|
|
renderTree(); |
|
|
} |
|
|
|
|
|
|
|
|
newPageBtn.onclick = () => { |
|
|
const id = Date.now().toString(); |
|
|
pages[id] = { title: 'Untitled', content: '' }; |
|
|
savePage(); loadPage(id); |
|
|
}; |
|
|
|
|
|
|
|
|
deletePageBtn.onclick = () => { |
|
|
if (!currentId) return; |
|
|
delete pages[currentId]; |
|
|
localStorage.setItem('docPages', JSON.stringify(pages)); |
|
|
const ids = Object.keys(pages); |
|
|
if (ids.length) loadPage(ids[0]); |
|
|
else { currentId = null; editor.value = ''; pageTitleInput.value = ''; previewPane.innerHTML = ''; renderTree(); } |
|
|
}; |
|
|
|
|
|
saveBtn.onclick = () => { savePage(); alert('Saved!'); }; |
|
|
|
|
|
|
|
|
setInterval(savePage, AUTO_SAVE_INTERVAL); |
|
|
|
|
|
|
|
|
toggleSidebarBtn.onclick = () => { |
|
|
sidebar.classList.toggle('-translate-x-full'); |
|
|
toolbarDiv.classList.toggle('pl-0'); |
|
|
}; |
|
|
|
|
|
|
|
|
toggleEditorBtn.onclick = () => { |
|
|
const hidden = editorPane.classList.toggle('hidden'); |
|
|
if (hidden) previewPane.classList.replace('w-1/2', 'w-full'); |
|
|
else previewPane.classList.replace('w-full', 'w-1/2'); |
|
|
}; |
|
|
|
|
|
|
|
|
editorToolbar.onclick = e => { |
|
|
if (!e.target.dataset.action) return; |
|
|
const action = e.target.dataset.action; |
|
|
const start = editor.selectionStart; |
|
|
const end = editor.selectionEnd; |
|
|
let selected = editor.value.slice(start, end); |
|
|
let insert = ''; |
|
|
switch(action) { |
|
|
case 'bold': insert = `**${selected || 'bold'}**`; break; |
|
|
case 'italic': insert = `*${selected || 'italic'}*`; break; |
|
|
case 'h1': insert = `# ${selected || 'Heading'}`; break; |
|
|
case 'ul': insert = `- ${selected || 'List item'}`; break; |
|
|
} |
|
|
editor.setRangeText(insert, start, end, 'end'); |
|
|
updatePreview(); |
|
|
editor.focus(); |
|
|
}; |
|
|
|
|
|
editor.addEventListener('input', updatePreview); |
|
|
|
|
|
|
|
|
renderTree(); |
|
|
if (!currentId && Object.keys(pages).length) loadPage(Object.keys(pages)[0]); |
|
|
</script> |
|
|
</body> |
|
|
</html> |