first commit

This commit is contained in:
nahakubuilde
2025-06-21 23:21:18 +01:00
commit e3e775a693
9 changed files with 940 additions and 0 deletions
+309
View File
@@ -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;">&gt;</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;">
&copy; 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>