first commit
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Flask Blog{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github-dark.min.css">
|
||||
<link type="text/css" href="{{ url_for('static', filename='css/app_custom_css.css' ) }}" rel="stylesheet">
|
||||
<script>
|
||||
// Apply sidebar width and minimized state before page paint to prevent flicker
|
||||
(function() {
|
||||
try {
|
||||
var savedWidth = localStorage.getItem('sidebar-width');
|
||||
var minimized = localStorage.getItem('sidebar-minimized') === 'true';
|
||||
var style = document.createElement('style');
|
||||
var css = '';
|
||||
if (minimized) {
|
||||
css += '#sidebar { width: 48px !important; min-width: 48px !important; max-width: 48px !important; overflow-x: hidden; padding-left: 0.2rem; padding-right: 0.2rem; transition: none !important; }';
|
||||
} else if (savedWidth) {
|
||||
css += '#sidebar { width: ' + savedWidth + ' !important; transition: none !important; }';
|
||||
}
|
||||
if (css) {
|
||||
style.innerHTML = css;
|
||||
style.setAttribute('id', 'sidebar-initial-style');
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body style="overflow: hidden;">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">Flask Blog</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{# <li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('create_note') }}">Create Note</a>
|
||||
</li> #}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid" style="height: calc(100vh - 56px);">
|
||||
<div class="row" style="flex-wrap:nowrap; height: 100%;">
|
||||
<aside id="sidebar" class="sidebar" style="height: 100%; overflow-y: auto;">
|
||||
<div class="d-flex align-items-center gap-2 mb-3" style="margin-top: 0.2em;">
|
||||
<a href="{{ url_for('create_note') }}" class="btn btn-primary btn-sm px-2 py-1" title="New Note" style="font-size:1.1em;"><i class="fa fa-plus"></i></a>
|
||||
<button id="open-search-modal" class="btn btn-outline-secondary btn-sm px-2 py-1" title="Search" style="font-size:1.1em;"><i class="fa fa-search"></i></button>
|
||||
<button id="toggle-all-dirs" class="btn btn-outline-secondary btn-sm px-2 py-1" title="Expand/Collapse All" style="font-size:1.1em;"><i class="fa fa-folder"></i></button>
|
||||
<button class="sidebar-toggle btn btn-outline-secondary btn-sm px-2 py-1" title="Toggle Sidebar" style="font-size:1.1em;margin-left:auto;"><i class="fa fa-angle-double-left"></i></button>
|
||||
</div>
|
||||
<div class="resize-handle" style="position:absolute; right:0; top:0; bottom:0; width:4px; cursor:ew-resize;"></div>
|
||||
<ul class="nav flex-column file-tree">
|
||||
{% if notes_tree and notes_tree|length > 0 %}
|
||||
{% macro render_tree(tree, current_path='', level=0) %}
|
||||
{% for node in tree %}
|
||||
{% if node.type == 'dir' %}
|
||||
{% set dir_path = current_path + '/' + node.name if current_path else node.name %}
|
||||
<li class="nav-item file-tree-dir">
|
||||
<span class="file-tree-toggle" tabindex="0" data-path="{{ dir_path }}" style="position: relative; display: block; padding: 6px 0;">
|
||||
<div style="padding-left: {{ level * 16 }}px;">
|
||||
<!-- Tree line indicators -->
|
||||
{% if level > 0 %}
|
||||
<span style="position: absolute; left: {{ (level - 1) * 16 + 7 }}px; top: 0; bottom: 0; border-left: 1px dotted rgba(255,255,255,0.1);"></span>
|
||||
<span style="position: absolute; left: {{ (level - 1) * 16 + 7 }}px; width: 9px; top: 50%; border-top: 1px dotted rgba(255,255,255,0.1);"></span>
|
||||
{% endif %}
|
||||
<!-- Folder icon and name -->
|
||||
<i class="fa {% if active_path and dir_path in active_path %}fa-folder-open text-info{% else %}fa-folder text-warning{% endif %}" style="margin-right: 5px; width: 16px; text-align: center;"></i>
|
||||
<span style="margin-left: 2px; opacity: 0.9;">{{ node.name }}</span>
|
||||
</div>
|
||||
</span>
|
||||
<ul class="nav flex-column file-tree-children" style="display: {% if active_path and dir_path in active_path %}block{% else %}none{% endif %};">
|
||||
{{ render_tree(node.children, dir_path, level + 1) }}
|
||||
</ul>
|
||||
</li>
|
||||
{% elif node.type == 'file' and node.name.endswith('.md') %}
|
||||
<li class="nav-item file-tree-file">
|
||||
<a class="nav-link {% if node.path == current_note %}active{% endif %}"
|
||||
href="{{ url_for('note', note_path=node.path) }}"
|
||||
style="position: relative; display: block; padding: 4px 0;">
|
||||
<div style="padding-left: {{ level * 16 }}px;">
|
||||
<!-- Tree line indicators -->
|
||||
{% if level > 0 %}
|
||||
<span style="position: absolute; left: {{ (level - 1) * 16 + 7 }}px; top: 0; bottom: 0; border-left: 1px dotted rgba(255,255,255,0.1);"></span>
|
||||
<span style="position: absolute; left: {{ (level - 1) * 16 + 7 }}px; width: 9px; top: 50%; border-top: 1px dotted rgba(255,255,255,0.1);"></span>
|
||||
{% endif %}
|
||||
<!-- File icon and name -->
|
||||
<i class="fa fa-file-text-o" style="margin-right: 5px; width: 16px; text-align: center; opacity: 0.7;"></i>
|
||||
<span style="opacity: 0.85;">{{ node.display_name if node.display_name else node.name[:-3] }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{{ render_tree(notes_tree) }}
|
||||
{% else %}
|
||||
<li class="nav-item text-muted">No notes found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</aside>
|
||||
<main class="main-content col py-4" style="height: 100%; overflow-y: auto;">
|
||||
<!-- Compact Breadcrumbs inside main content -->
|
||||
<nav aria-label="breadcrumb" style="margin-bottom: 0.7em;">
|
||||
<ol class="breadcrumb p-0 m-0 bg-transparent" style="font-size: 0.97em;">
|
||||
{% for crumb in breadcrumbs %}
|
||||
<li class="breadcrumb-item {% if loop.last %}active{% endif %}" {% if loop.last %}aria-current="page"{% endif %} style="padding-right: 0;">
|
||||
{% if crumb.url %}
|
||||
<a href="{{ crumb.url }}" class="text-info text-decoration-none" style="opacity: 0.9; transition: opacity 0.2s, color 0.2s;" onmouseover="this.style.opacity='1'; this.style.color='#17a2b8';" onmouseout="this.style.opacity='0.9'; this.style.color='';">{{ crumb.name }}</a>
|
||||
{% else %}
|
||||
<span class="text-light" style="opacity: 0.95;">{{ crumb.name }}</span>
|
||||
{% endif %}
|
||||
{% if not loop.last %}
|
||||
<span class="text-light" style="opacity: 0.6; margin: 0 0.3em;">></span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-info">{{ messages[0] }}</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="text-center mt-3 mb-2 text-muted" style="position: absolute; width: 100%; bottom: 0; left: 0;">
|
||||
© 2025 Flask Blog
|
||||
</footer>
|
||||
|
||||
<!-- Move Bootstrap JS above custom scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Move main JS to end of body -->
|
||||
<script>
|
||||
// Highlight code blocks in EasyMDE preview
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Sidebar width and minimize persistence
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return; // Prevent errors if sidebar is missing
|
||||
const toggleBtn = sidebar.querySelector('.sidebar-toggle');
|
||||
const resizeHandle = sidebar.querySelector('.resize-handle');
|
||||
const savedWidth = localStorage.getItem('sidebar-width');
|
||||
const minimized = localStorage.getItem('sidebar-minimized') === 'true';
|
||||
// Remove initial style tag if present
|
||||
const initialStyle = document.getElementById('sidebar-initial-style');
|
||||
if (initialStyle) initialStyle.remove();
|
||||
// Set initial state
|
||||
if (savedWidth && !minimized) sidebar.style.width = savedWidth;
|
||||
if (minimized) {
|
||||
sidebar.classList.add('minimized');
|
||||
sidebar.style.width = '48px';
|
||||
sidebar.style.minWidth = '48px';
|
||||
sidebar.style.maxWidth = '48px';
|
||||
sidebar.style.paddingLeft = '0.2rem';
|
||||
sidebar.style.paddingRight = '0.2rem';
|
||||
toggleBtn.innerHTML = '<i class="fa fa-angle-double-right"></i>';
|
||||
} else {
|
||||
sidebar.classList.remove('minimized');
|
||||
sidebar.style.minWidth = '';
|
||||
sidebar.style.maxWidth = '';
|
||||
sidebar.style.paddingLeft = '';
|
||||
sidebar.style.paddingRight = '';
|
||||
toggleBtn.innerHTML = '<i class="fa fa-angle-double-left"></i>';
|
||||
}
|
||||
toggleBtn.addEventListener('click', function() {
|
||||
const isMin = sidebar.classList.toggle('minimized');
|
||||
localStorage.setItem('sidebar-minimized', isMin);
|
||||
if (isMin) {
|
||||
sidebar.style.width = '48px';
|
||||
sidebar.style.minWidth = '48px';
|
||||
sidebar.style.maxWidth = '48px';
|
||||
sidebar.style.paddingLeft = '0.2rem';
|
||||
sidebar.style.paddingRight = '0.2rem';
|
||||
toggleBtn.innerHTML = '<i class="fa fa-angle-double-right"></i>';
|
||||
} else {
|
||||
const width = localStorage.getItem('sidebar-width') || '220px';
|
||||
sidebar.style.width = width;
|
||||
sidebar.style.minWidth = '';
|
||||
sidebar.style.maxWidth = '';
|
||||
sidebar.style.paddingLeft = '';
|
||||
sidebar.style.paddingRight = '';
|
||||
toggleBtn.innerHTML = '<i class="fa fa-angle-double-left"></i>';
|
||||
}
|
||||
});
|
||||
// Sidebar resizing with handle
|
||||
let isResizing = false;
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
resizeHandle.addEventListener('mousedown', function(e) {
|
||||
if (!sidebar.classList.contains('minimized')) {
|
||||
isResizing = true;
|
||||
startX = e.clientX;
|
||||
startWidth = sidebar.offsetWidth;
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (!isResizing) return;
|
||||
let newWidth = startWidth + (e.clientX - startX);
|
||||
newWidth = Math.max(48, Math.min(350, newWidth));
|
||||
sidebar.style.width = newWidth + 'px';
|
||||
});
|
||||
document.addEventListener('mouseup', function() {
|
||||
if (isResizing) {
|
||||
isResizing = false;
|
||||
document.body.style.cursor = '';
|
||||
if (!sidebar.classList.contains('minimized')) {
|
||||
localStorage.setItem('sidebar-width', sidebar.style.width);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Highlight.js for EasyMDE preview using MutationObserver
|
||||
const observer = new MutationObserver(function() {
|
||||
document.querySelectorAll('.editor-preview pre code, .editor-preview-active pre code').forEach(function(block) {
|
||||
if (!block.classList.contains('hljs')) {
|
||||
hljs.highlightElement(block);
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(document.body, { subtree: true, childList: true });
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// File tree expand/collapse with state persistence
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toggleAllBtn = document.getElementById('toggle-all-dirs');
|
||||
let isAllExpanded = false;
|
||||
|
||||
// Load expanded folders from localStorage
|
||||
let expandedFolders = new Set(JSON.parse(localStorage.getItem('expanded-folders') || '[]'));
|
||||
|
||||
// Initialize folders state
|
||||
document.querySelectorAll('.file-tree-toggle').forEach(function(toggle) {
|
||||
const path = toggle.getAttribute('data-path');
|
||||
const parent = toggle.parentElement;
|
||||
const children = parent.querySelector('.file-tree-children');
|
||||
const icon = toggle.querySelector('i');
|
||||
|
||||
if (expandedFolders.has(path)) {
|
||||
children.style.display = 'block';
|
||||
icon.className = 'fa fa-folder-open text-info';
|
||||
}
|
||||
});
|
||||
|
||||
// Individual folder toggle with state persistence
|
||||
document.querySelectorAll('.file-tree-toggle').forEach(function(toggle) {
|
||||
toggle.addEventListener('click', function() {
|
||||
const path = this.getAttribute('data-path');
|
||||
const parent = toggle.parentElement;
|
||||
const children = parent.querySelector('.file-tree-children');
|
||||
const icon = toggle.querySelector('i');
|
||||
|
||||
if (children) {
|
||||
const isOpen = children.style.display === 'block';
|
||||
children.style.display = isOpen ? 'none' : 'block';
|
||||
icon.className = isOpen ? 'fa fa-folder text-warning' : 'fa fa-folder-open text-info';
|
||||
|
||||
// Update localStorage
|
||||
if (isOpen) {
|
||||
expandedFolders.delete(path);
|
||||
} else {
|
||||
expandedFolders.add(path);
|
||||
}
|
||||
localStorage.setItem('expanded-folders', JSON.stringify([...expandedFolders]));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expand/collapse all button with state persistence
|
||||
toggleAllBtn.addEventListener('click', function() {
|
||||
isAllExpanded = !isAllExpanded;
|
||||
const icon = toggleAllBtn.querySelector('i');
|
||||
icon.className = isAllExpanded ? 'fa fa-folder-open' : 'fa fa-folder';
|
||||
|
||||
document.querySelectorAll('.file-tree-toggle').forEach(function(toggle) {
|
||||
const path = toggle.getAttribute('data-path');
|
||||
const children = toggle.parentElement.querySelector('.file-tree-children');
|
||||
const folderIcon = toggle.querySelector('i');
|
||||
|
||||
if (children) {
|
||||
children.style.display = isAllExpanded ? 'block' : 'none';
|
||||
folderIcon.className = isAllExpanded ? 'fa fa-folder-open text-info' : 'fa fa-folder text-warning';
|
||||
|
||||
if (isAllExpanded) {
|
||||
expandedFolders.add(path);
|
||||
} else {
|
||||
expandedFolders.delete(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update localStorage
|
||||
localStorage.setItem('expanded-folders', JSON.stringify([...expandedFolders]));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% include '_search_modal.html' %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user