| <!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> |