add local TailwindCSS, fix side bar folder view, add more function to Markdown editor and allow .markdown files
This commit is contained in:
@@ -5,30 +5,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.app_name}}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'obsidian': {
|
||||
'50': '#f8fafc',
|
||||
'100': '#f1f5f9',
|
||||
'200': '#e2e8f0',
|
||||
'300': '#cbd5e1',
|
||||
'400': '#94a3b8',
|
||||
'500': '#64748b',
|
||||
'600': '#475569',
|
||||
'700': '#334155',
|
||||
'800': '#1e293b',
|
||||
'900': '#0f172a',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="{{.prefix}}/static/tailwind.css">
|
||||
<link rel="stylesheet" href="{{.prefix}}/static/styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
|
||||
@@ -41,6 +19,8 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/sql.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js"></script>
|
||||
<!-- Mermaid for diagrams -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
|
||||
<style>
|
||||
/* Custom scrollbar for dark theme */
|
||||
@@ -66,6 +46,21 @@
|
||||
.prose-dark h1, .prose-dark h2, .prose-dark h3, .prose-dark h4, .prose-dark h5, .prose-dark h6 {
|
||||
color: white;
|
||||
}
|
||||
/* Headings sizing and spacing */
|
||||
.prose-dark h1 { font-size: 1.875rem; line-height: 2.25rem; margin-top: 1.5rem; margin-bottom: 0.75rem; }
|
||||
.prose-dark h2 { font-size: 1.5rem; line-height: 2rem; margin-top: 1.25rem; margin-bottom: 0.5rem; }
|
||||
.prose-dark h3 { font-size: 1.25rem; line-height: 1.75rem; margin-top: 1rem; margin-bottom: 0.5rem; }
|
||||
.prose-dark h4 { font-size: 1.125rem; line-height: 1.5rem; margin-top: 0.75rem; margin-bottom: 0.5rem; }
|
||||
.prose-dark p { margin: 0.75rem 0; }
|
||||
.prose-dark hr { border-color: #374151; margin: 1.25rem 0; }
|
||||
|
||||
/* Lists */
|
||||
.prose-dark ul { list-style-type: disc; padding-left: 1.5rem; margin: 0.75rem 0; }
|
||||
.prose-dark ol { list-style-type: decimal; padding-left: 1.5rem; margin: 0.75rem 0; }
|
||||
.prose-dark li { margin: 0.25rem 0; }
|
||||
.prose-dark li > ul { list-style-type: circle; }
|
||||
.prose-dark li > ol { list-style-type: lower-alpha; }
|
||||
|
||||
.prose-dark a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
@@ -81,7 +76,9 @@
|
||||
.prose-dark pre {
|
||||
background-color: #111827;
|
||||
border: 1px solid #374151;
|
||||
overflow: auto;
|
||||
}
|
||||
.prose-dark pre code { background: transparent; color: inherit; padding: 0; }
|
||||
.prose-dark blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
background-color: #1f2937;
|
||||
@@ -255,6 +252,20 @@
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Initialize Mermaid (dark theme) and run on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.mermaid) {
|
||||
try {
|
||||
window.mermaid.initialize({ startOnLoad: false, theme: 'dark' });
|
||||
const containers = document.querySelectorAll('.mermaid');
|
||||
if (containers.length) {
|
||||
window.mermaid.run({ querySelector: '.mermaid' });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-gray-300 min-h-screen">
|
||||
<div class="flex h-screen">
|
||||
@@ -264,14 +275,21 @@
|
||||
<!-- Header -->
|
||||
<div class="p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="{{url "/"}}" class="sidebar-title text-xl font-bold text-white hover:text-blue-300" title="Home">
|
||||
{{.app_name}}
|
||||
<a href="{{url "/"}}" class="sidebar-title text-xl font-bold text-white hover:text-blue-300" title="Home" aria-label="Home">
|
||||
<i class="fas fa-house"></i>
|
||||
<span class="sr-only">{{.app_name}}</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-2 items-center">
|
||||
<div class="sidebar-actions flex items-center space-x-3">
|
||||
<button id="open-search" class="text-gray-400 hover:text-white transition-colors" title="Search" aria-label="Search">
|
||||
<i class="fas fa-magnifying-glass"></i>
|
||||
</button>
|
||||
<button id="expand-all" class="text-gray-400 hover:text-white transition-colors" title="Expand all" aria-label="Expand all">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</button>
|
||||
<button id="collapse-all" class="text-gray-400 hover:text-white transition-colors" title="Collapse all" aria-label="Collapse all">
|
||||
<i class="fas fa-folder"></i>
|
||||
</button>
|
||||
{{if .Authenticated}}
|
||||
{{if .IsAdmin}}
|
||||
<a href="{{url "/editor/admin"}}" class="text-gray-400 hover:text-white transition-colors" title="Admin">
|
||||
@@ -404,7 +422,19 @@
|
||||
<!-- Scripts -->
|
||||
<script>
|
||||
// Initialize syntax highlighting
|
||||
hljs.highlightAll();
|
||||
// Avoid warnings and double-highlighting: skip elements already highlighted by Chroma
|
||||
if (window.hljs) {
|
||||
try {
|
||||
// Suppress unescaped HTML warnings from hljs
|
||||
window.hljs.configure({ ignoreUnescapedHTML: true });
|
||||
// Only highlight code blocks that are not inside a .chroma container
|
||||
document.querySelectorAll('pre code').forEach(function (el) {
|
||||
if (!el.closest('.chroma')) {
|
||||
window.hljs.highlightElement(el);
|
||||
}
|
||||
});
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Base URL prefix from server
|
||||
window.BASE = '{{base}}';
|
||||
@@ -418,18 +448,48 @@
|
||||
return p[0] === '/' ? (b + p) : (b + '/' + p);
|
||||
};
|
||||
|
||||
// Tree functionality
|
||||
// Tree functionality with persisted expanded state
|
||||
const LS_KEY_EXPANDED = 'tree:expanded';
|
||||
function getExpandedSet() {
|
||||
try {
|
||||
const raw = localStorage.getItem(LS_KEY_EXPANDED);
|
||||
const arr = raw ? JSON.parse(raw) : [];
|
||||
return new Set(Array.isArray(arr) ? arr : []);
|
||||
} catch { return new Set(); }
|
||||
}
|
||||
function saveExpandedSet(set) {
|
||||
try { localStorage.setItem(LS_KEY_EXPANDED, JSON.stringify(Array.from(set))); } catch {}
|
||||
}
|
||||
function setExpanded(toggleEl, expand) {
|
||||
const children = toggleEl.nextElementSibling;
|
||||
const chevron = toggleEl.querySelector('.tree-chevron');
|
||||
if (!children || !children.classList.contains('tree-children')) return;
|
||||
const isHidden = children.classList.contains('hidden');
|
||||
if (expand && isHidden) children.classList.remove('hidden');
|
||||
if (!expand && !isHidden) children.classList.add('hidden');
|
||||
if (chevron) chevron.classList.toggle('rotate-90', expand);
|
||||
}
|
||||
function applyExpandedState() {
|
||||
const expanded = getExpandedSet();
|
||||
document.querySelectorAll('.tree-toggle').forEach(t => {
|
||||
const path = t.getAttribute('data-path') || '';
|
||||
const shouldExpand = expanded.has(path);
|
||||
setExpanded(t, shouldExpand);
|
||||
});
|
||||
}
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.tree-toggle')) {
|
||||
const toggle = e.target.closest('.tree-toggle');
|
||||
const children = toggle.nextElementSibling;
|
||||
const chevron = toggle.querySelector('.tree-chevron');
|
||||
|
||||
if (children && children.classList.contains('tree-children')) {
|
||||
const expanded = getExpandedSet();
|
||||
const path = toggle.getAttribute('data-path') || '';
|
||||
const willExpand = children.classList.contains('hidden');
|
||||
children.classList.toggle('hidden');
|
||||
if (chevron) {
|
||||
chevron.classList.toggle('rotate-90');
|
||||
}
|
||||
if (chevron) chevron.classList.toggle('rotate-90');
|
||||
if (willExpand) expanded.add(path); else expanded.delete(path);
|
||||
saveExpandedSet(expanded);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -458,6 +518,11 @@
|
||||
if (chevron) {
|
||||
chevron.classList.add('rotate-90');
|
||||
}
|
||||
// persist this expanded state
|
||||
const expanded = getExpandedSet();
|
||||
const path = toggle.getAttribute('data-path') || '';
|
||||
expanded.add(path);
|
||||
saveExpandedSet(expanded);
|
||||
}
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
@@ -528,9 +593,28 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Expand active path in tree
|
||||
// Apply persisted expanded folders, then ensure active path is expanded
|
||||
applyExpandedState();
|
||||
expandActivePath();
|
||||
|
||||
// Wire expand/collapse all
|
||||
const expandAllBtn = document.getElementById('expand-all');
|
||||
const collapseAllBtn = document.getElementById('collapse-all');
|
||||
if (expandAllBtn) expandAllBtn.addEventListener('click', function() {
|
||||
const expanded = getExpandedSet();
|
||||
document.querySelectorAll('.tree-toggle').forEach(t => {
|
||||
const path = t.getAttribute('data-path') || '';
|
||||
setExpanded(t, true);
|
||||
expanded.add(path);
|
||||
});
|
||||
saveExpandedSet(expanded);
|
||||
});
|
||||
if (collapseAllBtn) collapseAllBtn.addEventListener('click', function() {
|
||||
const expanded = new Set();
|
||||
document.querySelectorAll('.tree-toggle').forEach(t => setExpanded(t, false));
|
||||
saveExpandedSet(expanded);
|
||||
});
|
||||
|
||||
// Sidebar tree fallback: if server didn't render any tree nodes (e.g., error pages), fetch and render via API
|
||||
(function ensureSidebarTree() {
|
||||
const container = document.getElementById('sidebar-tree');
|
||||
@@ -549,6 +633,7 @@
|
||||
container.appendChild(renderTreeNode(child));
|
||||
});
|
||||
// Re-apply expanded state and active path if any
|
||||
applyExpandedState();
|
||||
expandActivePath();
|
||||
})
|
||||
.catch(() => {/* ignore */});
|
||||
|
||||
Reference in New Issue
Block a user