init
This commit is contained in:
405
web/templates/base.html
Normal file
405
web/templates/base.html
Normal file
@@ -0,0 +1,405 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}{{.app_name}}{{end}}</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="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>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/markdown.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/yaml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/xml.min.js"></script>
|
||||
<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>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
|
||||
<style>
|
||||
/* Custom scrollbar for dark theme */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e293b;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #475569;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #64748b;
|
||||
}
|
||||
|
||||
/* Content markdown styles */
|
||||
.prose-dark {
|
||||
color: #d1d5db;
|
||||
}
|
||||
.prose-dark h1, .prose-dark h2, .prose-dark h3, .prose-dark h4, .prose-dark h5, .prose-dark h6 {
|
||||
color: white;
|
||||
}
|
||||
.prose-dark a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
.prose-dark a:hover {
|
||||
color: #93c5fd;
|
||||
}
|
||||
.prose-dark code {
|
||||
background-color: #1f2937;
|
||||
color: #10b981;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.prose-dark pre {
|
||||
background-color: #111827;
|
||||
border: 1px solid #374151;
|
||||
}
|
||||
.prose-dark blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
background-color: #1f2937;
|
||||
padding-left: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
.prose-dark table {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #4b5563;
|
||||
}
|
||||
.prose-dark th, .prose-dark td {
|
||||
border: 1px solid #4b5563;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.prose-dark th {
|
||||
background-color: #1f2937;
|
||||
font-weight: 600;
|
||||
}
|
||||
.prose-dark img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar-item:hover {
|
||||
background-color: #374151;
|
||||
}
|
||||
.sidebar-item.active {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Custom button styles */
|
||||
.btn-primary {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
.btn-secondary {
|
||||
background-color: #4b5563;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: #374151;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: #dc2626;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #b91c1c;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #1f2937;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
max-width: 32rem;
|
||||
width: 100%;
|
||||
margin: 1rem;
|
||||
max-height: 24rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Editor styles */
|
||||
.editor-textarea {
|
||||
width: 100%;
|
||||
min-height: 24rem;
|
||||
background-color: #1f2937;
|
||||
color: #d1d5db;
|
||||
border: 1px solid #4b5563;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
resize: vertical;
|
||||
}
|
||||
.editor-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
/* Form input styles */
|
||||
.form-input, .form-textarea {
|
||||
width: 100%;
|
||||
background-color: #374151;
|
||||
border: 1px solid #4b5563;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: white;
|
||||
}
|
||||
.form-input:focus, .form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-gray-300 min-h-screen">
|
||||
<div class="flex h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div id="sidebar" class="w-80 bg-slate-800 border-r border-gray-700 flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-bold text-white">{{.app_name}}</h1>
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/settings" class="text-gray-400 hover:text-white transition-colors" title="Settings">
|
||||
<i class="fas fa-cog"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="px-4 py-4">
|
||||
<a href="/create" class="btn-primary text-sm w-full text-center">
|
||||
<i class="fas fa-plus mr-2"></i>New Note
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- File Tree -->
|
||||
<div class="flex-1 overflow-y-auto px-4 pb-4">
|
||||
{{if .notes_tree}}
|
||||
{{template "tree_node" dict "node" .notes_tree "active_path" .active_path "current_note" .current_note}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Breadcrumbs -->
|
||||
{{if .breadcrumbs}}
|
||||
<div class="bg-slate-800 border-b border-gray-700 px-6 py-3">
|
||||
<nav class="flex items-center space-x-2 text-sm">
|
||||
{{range $i, $crumb := .breadcrumbs}}
|
||||
{{if $i}}<i class="fas fa-chevron-right text-gray-500 text-xs"></i>{{end}}
|
||||
{{if $crumb.URL}}
|
||||
<a href="{{$crumb.URL}}" class="text-blue-400 hover:text-blue-300 transition-colors">{{$crumb.Name}}</a>
|
||||
{{else}}
|
||||
<span class="text-gray-300">{{$crumb.Name}}</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</nav>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
{{block "content" .}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script>
|
||||
// Initialize syntax highlighting
|
||||
hljs.highlightAll();
|
||||
|
||||
// Tree functionality
|
||||
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')) {
|
||||
children.classList.toggle('hidden');
|
||||
if (chevron) {
|
||||
chevron.classList.toggle('rotate-90');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-expand active path
|
||||
function expandActivePath() {
|
||||
const activeItem = document.querySelector('.sidebar-item.active');
|
||||
if (activeItem) {
|
||||
let parent = activeItem.parentElement;
|
||||
while (parent) {
|
||||
if (parent.classList.contains('tree-children')) {
|
||||
parent.classList.remove('hidden');
|
||||
const toggle = parent.previousElementSibling;
|
||||
if (toggle && toggle.classList.contains('tree-toggle')) {
|
||||
const chevron = toggle.querySelector('.tree-chevron');
|
||||
if (chevron) {
|
||||
chevron.classList.add('rotate-90');
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notification system
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed top-4 right-4 z-50 px-6 py-3 rounded-lg shadow-lg max-w-sm`;
|
||||
|
||||
if (type === 'success') {
|
||||
notification.className += ' bg-green-600 text-white';
|
||||
} else if (type === 'error') {
|
||||
notification.className += ' bg-red-600 text-white';
|
||||
} else {
|
||||
notification.className += ' bg-blue-600 text-white';
|
||||
}
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="flex items-center justify-between">
|
||||
<span>${message}</span>
|
||||
<button class="ml-4 text-white hover:text-gray-300" onclick="this.closest('div').remove()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.remove();
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
expandActivePath();
|
||||
});
|
||||
</script>
|
||||
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Tree Node Template -->
|
||||
{{define "tree_node"}}
|
||||
<div class="tree-node">
|
||||
{{if .node.Children}}
|
||||
<div class="tree-toggle flex items-center py-1 hover:bg-gray-700 rounded px-2 cursor-pointer" data-path="{{.node.Path}}">
|
||||
<i class="fas fa-chevron-right transform transition-transform duration-200 mr-2 text-xs tree-chevron"></i>
|
||||
<span class="mr-2">📁</span>
|
||||
<span class="flex-1">{{.node.Name}}</span>
|
||||
</div>
|
||||
<div class="tree-children ml-4 hidden">
|
||||
{{range .node.Children}}
|
||||
{{template "tree_node" dict "node" . "active_path" $.active_path "current_note" $.current_note}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{if eq .node.Type "md"}}
|
||||
<a href="/note/{{.node.Path}}" class="sidebar-item {{if eq .current_note .node.Path}}active{{end}}">
|
||||
<span class="mr-2">📝</span>
|
||||
<span>{{.node.Name}}</span>
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/view_text/{{.node.Path}}" class="sidebar-item">
|
||||
<span class="mr-2">📄</span>
|
||||
<span>{{.node.Name}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
325
web/templates/base_new.html
Normal file
325
web/templates/base_new.html
Normal file
@@ -0,0 +1,325 @@
|
||||
{{define "base.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}{{.app_name}}{{end}}</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="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/font-awesome/6.4.0/js/all.min.js"></script>
|
||||
<style>
|
||||
/* Custom scrollbar for dark theme */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e293b;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #475569;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #64748b;
|
||||
}
|
||||
|
||||
/* Content markdown styles */
|
||||
.prose-dark {
|
||||
color: #d1d5db;
|
||||
}
|
||||
.prose-dark h1, .prose-dark h2, .prose-dark h3, .prose-dark h4, .prose-dark h5, .prose-dark h6 {
|
||||
color: white;
|
||||
}
|
||||
.prose-dark a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
.prose-dark a:hover {
|
||||
color: #93c5fd;
|
||||
}
|
||||
.prose-dark code {
|
||||
background-color: #1f2937;
|
||||
color: #10b981;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.prose-dark pre {
|
||||
background-color: #111827;
|
||||
border: 1px solid #374151;
|
||||
}
|
||||
.prose-dark blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
background-color: #1f2937;
|
||||
padding-left: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Custom button styles */
|
||||
.btn-primary {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
.btn-secondary {
|
||||
background-color: #4b5563;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: #374151;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: #dc2626;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #b91c1c;
|
||||
}
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar-item:hover {
|
||||
background-color: #374151;
|
||||
}
|
||||
.sidebar-item.active {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #1f2937;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
max-width: 32rem;
|
||||
width: 100%;
|
||||
margin: 1rem;
|
||||
max-height: 24rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Editor styles */
|
||||
.editor-textarea {
|
||||
width: 100%;
|
||||
min-height: 24rem;
|
||||
background-color: #1f2937;
|
||||
color: #d1d5db;
|
||||
border: 1px solid #4b5563;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
resize: vertical;
|
||||
}
|
||||
.editor-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
/* Form input styles */
|
||||
.form-input, .form-textarea {
|
||||
width: 100%;
|
||||
background-color: #374151;
|
||||
border: 1px solid #4b5563;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: white;
|
||||
}
|
||||
.form-input:focus, .form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-gray-300 min-h-screen">
|
||||
<div class="flex h-screen">
|
||||
<!-- Sidebar -->
|
||||
<div id="sidebar" class="w-80 bg-slate-800 border-r border-gray-700 flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-bold text-white">{{.app_name}}</h1>
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/settings" class="text-gray-400 hover:text-white transition-colors" title="Settings">
|
||||
<i class="fas fa-cog"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="p-4">
|
||||
<input type="text" id="search-input" placeholder="Search notes..."
|
||||
class="form-input text-sm">
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="px-4 pb-4">
|
||||
<a href="/create" class="btn-primary text-sm w-full text-center block">
|
||||
<i class="fas fa-plus mr-2"></i>New Note
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- File Tree -->
|
||||
<div class="flex-1 overflow-y-auto px-4 pb-4">
|
||||
{{if .notes_tree}}
|
||||
{{template "tree_node" dict "node" .notes_tree "active_path" .active_path "current_note" .current_note}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Breadcrumbs -->
|
||||
{{if .breadcrumbs}}
|
||||
<div class="bg-slate-800 border-b border-gray-700 px-6 py-3">
|
||||
<nav class="flex items-center space-x-2 text-sm">
|
||||
{{range $i, $crumb := .breadcrumbs}}
|
||||
{{if $i}}<i class="fas fa-chevron-right text-gray-500 text-xs"></i>{{end}}
|
||||
{{if $crumb.URL}}
|
||||
<a href="{{$crumb.URL}}" class="text-blue-400 hover:text-blue-300 transition-colors">{{$crumb.Name}}</a>
|
||||
{{else}}
|
||||
<span class="text-gray-300">{{$crumb.Name}}</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</nav>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
{{block "content" .}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script>
|
||||
// Initialize syntax highlighting
|
||||
hljs.highlightAll();
|
||||
|
||||
// Search functionality
|
||||
const searchInput = document.getElementById('search-input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
// TODO: Implement search functionality
|
||||
});
|
||||
}
|
||||
|
||||
// Tree functionality
|
||||
document.querySelectorAll('.tree-toggle').forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
const children = this.nextElementSibling;
|
||||
if (children && children.classList.contains('tree-children')) {
|
||||
children.classList.toggle('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Notification system
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
// Simple notification for now
|
||||
alert(message);
|
||||
}
|
||||
</script>
|
||||
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
<!-- Tree Node Template -->
|
||||
{{define "tree_node"}}
|
||||
<div class="tree-node">
|
||||
{{if .node.Children}}
|
||||
<div class="tree-toggle flex items-center py-1 hover:bg-gray-700 rounded px-2 cursor-pointer" data-path="{{.node.Path}}">
|
||||
<i class="fas fa-chevron-right transform transition-transform duration-200 mr-2 text-xs"></i>
|
||||
<span class="mr-2">📁</span>
|
||||
<span class="flex-1">{{.node.Name}}</span>
|
||||
</div>
|
||||
<div class="tree-children ml-4 hidden">
|
||||
{{range .node.Children}}
|
||||
{{template "tree_node" dict "node" . "active_path" $.active_path "current_note" $.current_note}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{if eq .node.Type "md"}}
|
||||
<a href="/note/{{.node.Path}}" class="sidebar-item {{if eq .current_note .node.Path}}active{{end}}">
|
||||
<span class="mr-2">📝</span>
|
||||
<span>{{.node.Name}}</span>
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/view_text/{{.node.Path}}" class="sidebar-item">
|
||||
<span class="mr-2">📄</span>
|
||||
<span>{{.node.Name}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
145
web/templates/create.html
Normal file
145
web/templates/create.html
Normal file
@@ -0,0 +1,145 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-white mb-4">Create New Note</h1>
|
||||
{{if .folder_path}}
|
||||
<p class="text-gray-400">
|
||||
<i class="fas fa-folder mr-2"></i>
|
||||
Creating in: <span class="text-blue-400">{{.folder_path}}</span>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Create Form -->
|
||||
<form id="create-form" class="space-y-6">
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<!-- Title Input -->
|
||||
<div class="mb-6">
|
||||
<label for="title" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Note Title
|
||||
</label>
|
||||
<input type="text" id="title" name="title" required
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="Enter note title (e.g., My New Note)">
|
||||
<p class="text-xs text-gray-500 mt-1">The .md extension will be added automatically</p>
|
||||
</div>
|
||||
|
||||
<!-- Content Editor -->
|
||||
<div class="mb-6">
|
||||
<label for="content" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Content
|
||||
</label>
|
||||
<textarea id="content" name="content" rows="20"
|
||||
class="editor-textarea"
|
||||
placeholder="# Your Note Title
|
||||
|
||||
Start writing your note here using Markdown syntax...
|
||||
|
||||
## Examples
|
||||
|
||||
- **Bold text**
|
||||
- *Italic text*
|
||||
- [Links](https://example.com)
|
||||
- 
|
||||
- `Code snippets`
|
||||
|
||||
```javascript
|
||||
// Code blocks
|
||||
console.log('Hello, World!');
|
||||
```
|
||||
|
||||
> Blockquotes
|
||||
|
||||
| Tables | Work | Too |
|
||||
|--------|------|-----|
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="{{if .folder_path}}/folder/{{.folder_path}}{{else}}/{{end}}" class="btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-2"></i>Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save mr-2"></i>Create Note
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
const createForm = document.getElementById('create-form');
|
||||
const titleInput = document.getElementById('title');
|
||||
const contentTextarea = document.getElementById('content');
|
||||
|
||||
createForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const title = titleInput.value.trim();
|
||||
const content = contentTextarea.value;
|
||||
const folderPath = '{{.folder_path}}';
|
||||
|
||||
if (!title) {
|
||||
showNotification('Please enter a note title', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('content', content);
|
||||
formData.append('folder_path', folderPath);
|
||||
|
||||
fetch('/create', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('Note created successfully', 'success');
|
||||
if (data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to create note');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Error: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-resize textarea
|
||||
contentTextarea.addEventListener('input', function() {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
contentTextarea.addEventListener('keydown', function(e) {
|
||||
// Ctrl+S or Cmd+S to save
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||
e.preventDefault();
|
||||
createForm.dispatchEvent(new Event('submit'));
|
||||
}
|
||||
|
||||
// Tab for indentation
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = this.selectionStart;
|
||||
const end = this.selectionEnd;
|
||||
const value = this.value;
|
||||
|
||||
this.value = value.substring(0, start) + '\t' + value.substring(end);
|
||||
this.selectionStart = this.selectionEnd = start + 1;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
190
web/templates/edit.html
Normal file
190
web/templates/edit.html
Normal file
@@ -0,0 +1,190 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold text-white">Edit: {{.title}}</h1>
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="/note/{{.note_path}}" class="btn-secondary">
|
||||
<i class="fas fa-eye mr-2"></i>Preview
|
||||
</a>
|
||||
<button type="submit" form="edit-form" class="btn-primary">
|
||||
<i class="fas fa-save mr-2"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .folder_path}}
|
||||
<p class="text-gray-400">
|
||||
<i class="fas fa-folder mr-2"></i>
|
||||
<a href="/folder/{{.folder_path}}" class="text-blue-400 hover:text-blue-300">{{.folder_path}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Edit Form -->
|
||||
<form id="edit-form" class="space-y-6">
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<!-- Content Editor -->
|
||||
<div class="mb-6">
|
||||
<label for="content" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Content
|
||||
</label>
|
||||
<textarea id="content" name="content" rows="25"
|
||||
class="editor-textarea">{{.content}}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Editor Toolbar -->
|
||||
<div class="flex items-center justify-between border-t border-gray-700 pt-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertMarkdown('**', '**')" title="Bold">
|
||||
<i class="fas fa-bold"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertMarkdown('*', '*')" title="Italic">
|
||||
<i class="fas fa-italic"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertMarkdown('`', '`')" title="Code">
|
||||
<i class="fas fa-code"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertMarkdown('[', '](url)')" title="Link">
|
||||
<i class="fas fa-link"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertMarkdown('')" title="Image">
|
||||
<i class="fas fa-image"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertHeading()" title="Heading">
|
||||
<i class="fas fa-heading"></i>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary text-sm" onclick="insertList()" title="List">
|
||||
<i class="fas fa-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
Press Ctrl+S to save
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
const editForm = document.getElementById('edit-form');
|
||||
const contentTextarea = document.getElementById('content');
|
||||
|
||||
editForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const content = contentTextarea.value;
|
||||
const notePath = '{{.note_path}}';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('content', content);
|
||||
|
||||
fetch('/edit/' + notePath, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('Note saved successfully', 'success');
|
||||
if (data.redirect) {
|
||||
setTimeout(() => {
|
||||
window.location.href = data.redirect;
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to save note');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Error: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-resize textarea
|
||||
function autoResize() {
|
||||
contentTextarea.style.height = 'auto';
|
||||
contentTextarea.style.height = (contentTextarea.scrollHeight) + 'px';
|
||||
}
|
||||
|
||||
contentTextarea.addEventListener('input', autoResize);
|
||||
|
||||
// Initial resize
|
||||
autoResize();
|
||||
|
||||
// Keyboard shortcuts
|
||||
contentTextarea.addEventListener('keydown', function(e) {
|
||||
// Ctrl+S or Cmd+S to save
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||
e.preventDefault();
|
||||
editForm.dispatchEvent(new Event('submit'));
|
||||
}
|
||||
|
||||
// Tab for indentation
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = this.selectionStart;
|
||||
const end = this.selectionEnd;
|
||||
const value = this.value;
|
||||
|
||||
this.value = value.substring(0, start) + '\t' + value.substring(end);
|
||||
this.selectionStart = this.selectionEnd = start + 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Markdown insertion functions
|
||||
function insertMarkdown(before, after) {
|
||||
const start = contentTextarea.selectionStart;
|
||||
const end = contentTextarea.selectionEnd;
|
||||
const text = contentTextarea.value;
|
||||
const selectedText = text.substring(start, end);
|
||||
|
||||
const newText = text.substring(0, start) + before + selectedText + after + text.substring(end);
|
||||
contentTextarea.value = newText;
|
||||
|
||||
// Position cursor
|
||||
const newPos = start + before.length + selectedText.length;
|
||||
contentTextarea.focus();
|
||||
contentTextarea.setSelectionRange(newPos, newPos);
|
||||
|
||||
autoResize();
|
||||
}
|
||||
|
||||
function insertHeading() {
|
||||
const start = contentTextarea.selectionStart;
|
||||
const text = contentTextarea.value;
|
||||
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
||||
const lineText = text.substring(lineStart, start);
|
||||
|
||||
if (lineText.startsWith('# ')) {
|
||||
return; // Already a heading
|
||||
}
|
||||
|
||||
const newText = text.substring(0, lineStart) + '# ' + text.substring(lineStart);
|
||||
contentTextarea.value = newText;
|
||||
contentTextarea.focus();
|
||||
contentTextarea.setSelectionRange(start + 2, start + 2);
|
||||
|
||||
autoResize();
|
||||
}
|
||||
|
||||
function insertList() {
|
||||
const start = contentTextarea.selectionStart;
|
||||
const text = contentTextarea.value;
|
||||
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
||||
|
||||
const newText = text.substring(0, lineStart) + '- ' + text.substring(lineStart);
|
||||
contentTextarea.value = newText;
|
||||
contentTextarea.focus();
|
||||
contentTextarea.setSelectionRange(start + 2, start + 2);
|
||||
|
||||
autoResize();
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
38
web/templates/error.html
Normal file
38
web/templates/error.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="max-w-md w-full mx-4">
|
||||
<div class="bg-gray-800 rounded-lg p-8 text-center">
|
||||
<div class="mb-6">
|
||||
<i class="fas fa-exclamation-triangle text-6xl text-red-500 mb-4"></i>
|
||||
<h1 class="text-2xl font-bold text-white mb-2">Error</h1>
|
||||
{{if .error}}
|
||||
<h2 class="text-lg text-gray-300 mb-4">{{.error}}</h2>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .message}}
|
||||
<div class="bg-gray-700 rounded-lg p-4 mb-6">
|
||||
<p class="text-gray-300 text-sm">{{.message}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="space-y-3">
|
||||
<a href="/" class="block btn-primary">
|
||||
<i class="fas fa-home mr-2"></i>Go Home
|
||||
</a>
|
||||
<button onclick="history.back()" class="block btn-secondary w-full">
|
||||
<i class="fas fa-arrow-left mr-2"></i>Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "error_scripts"}}
|
||||
<script>
|
||||
// No additional scripts needed for error page
|
||||
</script>
|
||||
{{end}}
|
||||
256
web/templates/folder.html
Normal file
256
web/templates/folder.html
Normal file
@@ -0,0 +1,256 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="p-6">
|
||||
<!-- Header with upload button -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white mb-2">
|
||||
{{if .folder_path}}
|
||||
{{.folder_path}}
|
||||
{{else}}
|
||||
Welcome to {{.app_name}}
|
||||
{{end}}
|
||||
</h1>
|
||||
<p class="text-gray-400">
|
||||
{{if .folder_contents}}
|
||||
{{len .folder_contents}} items
|
||||
{{else}}
|
||||
No items found
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<button id="upload-btn" class="btn-primary">
|
||||
<i class="fas fa-upload mr-2"></i>Upload File
|
||||
</button>
|
||||
<a href="/create?folder={{.folder_path}}" class="btn-secondary">
|
||||
<i class="fas fa-plus mr-2"></i>New Note
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Area (hidden by default) -->
|
||||
<div id="upload-area" class="upload-area mb-6 hidden">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-cloud-upload text-4xl text-gray-500 mb-4"></i>
|
||||
<p class="text-gray-400 mb-2">Drag and drop files here or click to select</p>
|
||||
<input type="file" id="file-input" multiple class="hidden">
|
||||
<button id="select-files" class="btn-secondary">Select Files</button>
|
||||
</div>
|
||||
<div id="upload-progress" class="mt-4 hidden">
|
||||
<div class="w-full bg-gray-700 rounded-full h-2">
|
||||
<div id="progress-bar" class="bg-blue-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
<p id="upload-status" class="text-sm text-gray-400 mt-2"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Grid -->
|
||||
<div class="grid gap-4">
|
||||
{{if .folder_contents}}
|
||||
{{range .folder_contents}}
|
||||
<div class="bg-gray-800 rounded-lg p-4 hover:bg-gray-700 transition-colors duration-200 cursor-pointer item-card"
|
||||
data-path="{{.Path}}" data-type="{{.Type}}">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="text-2xl">{{fileTypeIcon .Type}}</span>
|
||||
<div>
|
||||
<h3 class="font-medium text-white">{{.DisplayName}}</h3>
|
||||
<p class="text-sm text-gray-400">
|
||||
{{if eq .Type "dir"}}
|
||||
Folder
|
||||
{{else}}
|
||||
{{formatSize .Size}} • {{formatTime .ModTime}}
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
{{if eq .Type "md"}}
|
||||
<a href="/edit/{{.Path}}" class="text-blue-400 hover:text-blue-300 p-2" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
{{end}}
|
||||
{{if ne .Type "dir"}}
|
||||
<a href="/download/{{.Path}}" class="text-green-400 hover:text-green-300 p-2" title="Download">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
{{end}}
|
||||
<button class="text-red-400 hover:text-red-300 p-2 delete-btn" data-path="{{.Path}}" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-folder-open text-6xl text-gray-600 mb-4"></i>
|
||||
<p class="text-xl text-gray-400 mb-2">This folder is empty</p>
|
||||
<p class="text-gray-500">Create a new note or upload files to get started</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div id="delete-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<h3 class="text-lg font-medium text-white mb-4">Confirm Delete</h3>
|
||||
<p class="text-gray-300 mb-6">Are you sure you want to delete this item? This action cannot be undone.</p>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button id="cancel-delete" class="btn-secondary">Cancel</button>
|
||||
<button id="confirm-delete" class="btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
let uploadArea = document.getElementById('upload-area');
|
||||
let fileInput = document.getElementById('file-input');
|
||||
let uploadBtn = document.getElementById('upload-btn');
|
||||
let selectFilesBtn = document.getElementById('select-files');
|
||||
let uploadProgress = document.getElementById('upload-progress');
|
||||
let progressBar = document.getElementById('progress-bar');
|
||||
let uploadStatus = document.getElementById('upload-status');
|
||||
let deleteModal = document.getElementById('delete-modal');
|
||||
let deleteTarget = null;
|
||||
|
||||
// Toggle upload area
|
||||
uploadBtn.addEventListener('click', function() {
|
||||
uploadArea.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// File selection
|
||||
selectFilesBtn.addEventListener('click', function() {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', function() {
|
||||
if (this.files.length > 0) {
|
||||
uploadFiles(this.files);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag and drop
|
||||
uploadArea.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
this.classList.add('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', function(e) {
|
||||
e.preventDefault();
|
||||
this.classList.remove('dragover');
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
this.classList.remove('dragover');
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
uploadFiles(e.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
// Upload files function
|
||||
function uploadFiles(files) {
|
||||
uploadProgress.classList.remove('hidden');
|
||||
progressBar.style.width = '0%';
|
||||
uploadStatus.textContent = 'Preparing upload...';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('path', '{{.folder_path}}');
|
||||
|
||||
for (let file of files) {
|
||||
formData.append('file', file);
|
||||
}
|
||||
|
||||
fetch('/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
progressBar.style.width = '100%';
|
||||
uploadStatus.textContent = 'Upload complete!';
|
||||
showNotification('Files uploaded successfully', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error(data.error || 'Upload failed');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
uploadStatus.textContent = 'Upload failed: ' + error.message;
|
||||
showNotification('Upload failed: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Item click handlers
|
||||
document.addEventListener('click', function(e) {
|
||||
const itemCard = e.target.closest('.item-card');
|
||||
if (itemCard && !e.target.closest('a') && !e.target.closest('button')) {
|
||||
const path = itemCard.dataset.path;
|
||||
const type = itemCard.dataset.type;
|
||||
|
||||
if (type === 'dir') {
|
||||
window.location.href = '/folder/' + path;
|
||||
} else if (type === 'md') {
|
||||
window.location.href = '/note/' + path;
|
||||
} else if (type === 'image') {
|
||||
window.open('/serve_attached_image/' + path, '_blank');
|
||||
} else {
|
||||
window.location.href = '/view_text/' + path;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Delete functionality
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.delete-btn')) {
|
||||
e.stopPropagation();
|
||||
deleteTarget = e.target.closest('.delete-btn').dataset.path;
|
||||
deleteModal.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('cancel-delete').addEventListener('click', function() {
|
||||
deleteModal.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
});
|
||||
|
||||
document.getElementById('confirm-delete').addEventListener('click', function() {
|
||||
if (deleteTarget) {
|
||||
fetch('/delete/' + deleteTarget, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('Item deleted successfully', 'success');
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw new Error(data.error || 'Delete failed');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Delete failed: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
deleteModal.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
});
|
||||
|
||||
// Close modal when clicking outside
|
||||
deleteModal.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
this.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
93
web/templates/note.html
Normal file
93
web/templates/note.html
Normal file
@@ -0,0 +1,93 @@
|
||||
{{define "content"}}
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<!-- Note Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold text-white">{{.title}}</h1>
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="/edit/{{.note_path}}" class="btn-primary">
|
||||
<i class="fas fa-edit mr-2"></i>Edit
|
||||
</a>
|
||||
<a href="/download/{{.note_path}}" class="btn-secondary">
|
||||
<i class="fas fa-download mr-2"></i>Download
|
||||
</a>
|
||||
<button class="btn-danger delete-note-btn" data-path="{{.note_path}}">
|
||||
<i class="fas fa-trash mr-2"></i>Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .folder_path}}
|
||||
<p class="text-gray-400">
|
||||
<i class="fas fa-folder mr-2"></i>
|
||||
<a href="/folder/{{.folder_path}}" class="text-blue-400 hover:text-blue-300">{{.folder_path}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Note Content -->
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<div class="prose prose-dark max-w-none">
|
||||
{{.content | safeHTML}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div id="delete-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<h3 class="text-lg font-medium text-white mb-4">Confirm Delete</h3>
|
||||
<p class="text-gray-300 mb-6">Are you sure you want to delete this note? This action cannot be undone.</p>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button id="cancel-delete" class="btn-secondary">Cancel</button>
|
||||
<button id="confirm-delete" class="btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const deleteBtn = document.querySelector('.delete-note-btn');
|
||||
const modal = document.getElementById('delete-modal');
|
||||
const cancelBtn = document.getElementById('cancel-delete');
|
||||
const confirmBtn = document.getElementById('confirm-delete');
|
||||
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener('click', function() {
|
||||
modal.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', function() {
|
||||
modal.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmBtn) {
|
||||
confirmBtn.addEventListener('click', function() {
|
||||
const path = deleteBtn.dataset.path;
|
||||
fetch(`/delete/${path}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
alert('Error deleting note');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error deleting note');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Re-highlight code blocks that might have been added dynamically
|
||||
hljs.highlightAll();
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
289
web/templates/settings.html
Normal file
289
web/templates/settings.html
Normal file
@@ -0,0 +1,289 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="max-w-6xl mx-auto p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Settings</h1>
|
||||
<p class="text-gray-400">Configure your {{.app_name}} instance</p>
|
||||
</div>
|
||||
|
||||
<!-- Settings Sections -->
|
||||
<div class="space-y-8">
|
||||
<!-- Image Storage Settings -->
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">
|
||||
<i class="fas fa-images mr-2"></i>Image Storage
|
||||
</h2>
|
||||
<p class="text-gray-400 mb-6">Configure how images are stored and referenced in your notes</p>
|
||||
|
||||
<form id="image-storage-form" class="space-y-6">
|
||||
<!-- Storage Mode -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-3">Storage Mode</label>
|
||||
<div class="space-y-3">
|
||||
<label class="flex items-start space-x-3">
|
||||
<input type="radio" name="storage_mode" value="1" class="mt-1 text-blue-600">
|
||||
<div>
|
||||
<div class="text-white font-medium">Root Directory</div>
|
||||
<div class="text-sm text-gray-400">Store images directly in the notes root directory</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start space-x-3">
|
||||
<input type="radio" name="storage_mode" value="2" class="mt-1 text-blue-600">
|
||||
<div>
|
||||
<div class="text-white font-medium">Specific Folder</div>
|
||||
<div class="text-sm text-gray-400">Store all images in a specific folder</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start space-x-3">
|
||||
<input type="radio" name="storage_mode" value="3" class="mt-1 text-blue-600">
|
||||
<div>
|
||||
<div class="text-white font-medium">Same as Note</div>
|
||||
<div class="text-sm text-gray-400">Store images in the same directory as the note</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start space-x-3">
|
||||
<input type="radio" name="storage_mode" value="4" class="mt-1 text-blue-600">
|
||||
<div>
|
||||
<div class="text-white font-medium">Subfolder of Note</div>
|
||||
<div class="text-sm text-gray-400">Store images in a subfolder within the note's directory</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Storage Path (for mode 2) -->
|
||||
<div id="storage-path-section" class="hidden">
|
||||
<label for="storage_path" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Storage Path
|
||||
</label>
|
||||
<input type="text" id="storage_path" name="storage_path"
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="e.g., images">
|
||||
</div>
|
||||
|
||||
<!-- Subfolder Name (for mode 4) -->
|
||||
<div id="subfolder-section" class="hidden">
|
||||
<label for="subfolder_name" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Subfolder Name
|
||||
</label>
|
||||
<input type="text" id="subfolder_name" name="subfolder_name"
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="e.g., attached">
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save mr-2"></i>Save Image Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Notes Directory Settings -->
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">
|
||||
<i class="fas fa-folder mr-2"></i>Notes Directory
|
||||
</h2>
|
||||
<p class="text-gray-400 mb-6">Set the root directory where your notes are stored</p>
|
||||
|
||||
<form id="notes-dir-form" class="space-y-6">
|
||||
<div>
|
||||
<label for="notes_dir" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Notes Directory Path
|
||||
</label>
|
||||
<input type="text" id="notes_dir" name="notes_dir"
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="/path/to/your/notes">
|
||||
<p class="text-xs text-gray-500 mt-1">Provide the absolute path to your notes directory</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save mr-2"></i>Save Directory Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- File Extensions Settings -->
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">
|
||||
<i class="fas fa-file mr-2"></i>File Extensions
|
||||
</h2>
|
||||
<p class="text-gray-400 mb-6">Configure which file types are allowed and visible</p>
|
||||
|
||||
<form id="file-extensions-form" class="space-y-6">
|
||||
<div>
|
||||
<label for="allowed_image_extensions" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Allowed Image Extensions
|
||||
</label>
|
||||
<input type="text" id="allowed_image_extensions" name="allowed_image_extensions"
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="jpg, jpeg, png, webp, gif">
|
||||
<p class="text-xs text-gray-500 mt-1">Comma-separated list of image file extensions</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="allowed_file_extensions" class="block text-sm font-medium text-gray-300 mb-2">
|
||||
Allowed File Extensions
|
||||
</label>
|
||||
<input type="text" id="allowed_file_extensions" name="allowed_file_extensions"
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="txt, pdf, html, json, yaml, yml, conf, csv">
|
||||
<p class="text-xs text-gray-500 mt-1">Comma-separated list of viewable file extensions</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="images_hide" name="images_hide"
|
||||
class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
||||
<label for="images_hide" class="ml-2 text-sm text-gray-300">
|
||||
Hide images from main folder view
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save mr-2"></i>Save Extension Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
// Load current settings
|
||||
function loadSettings() {
|
||||
// Load image storage settings
|
||||
fetch('/settings/image_storage')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector(`input[name="storage_mode"][value="${data.mode}"]`).checked = true;
|
||||
document.getElementById('storage_path').value = data.path || '';
|
||||
document.getElementById('subfolder_name').value = data.subfolder || '';
|
||||
toggleStorageOptions();
|
||||
})
|
||||
.catch(error => console.error('Error loading image storage settings:', error));
|
||||
|
||||
// Load notes directory settings
|
||||
fetch('/settings/notes_dir')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('notes_dir').value = data.notes_dir || '';
|
||||
})
|
||||
.catch(error => console.error('Error loading notes directory settings:', error));
|
||||
|
||||
// Load file extensions settings
|
||||
fetch('/settings/file_extensions')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('allowed_image_extensions').value = data.allowed_image_extensions || '';
|
||||
document.getElementById('allowed_file_extensions').value = data.allowed_file_extensions || '';
|
||||
document.getElementById('images_hide').checked = data.images_hide || false;
|
||||
})
|
||||
.catch(error => console.error('Error loading file extensions settings:', error));
|
||||
}
|
||||
|
||||
// Toggle storage mode options
|
||||
function toggleStorageOptions() {
|
||||
const mode = document.querySelector('input[name="storage_mode"]:checked')?.value;
|
||||
const pathSection = document.getElementById('storage-path-section');
|
||||
const subfolderSection = document.getElementById('subfolder-section');
|
||||
|
||||
pathSection.classList.toggle('hidden', mode !== '2');
|
||||
subfolderSection.classList.toggle('hidden', mode !== '4');
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.name === 'storage_mode') {
|
||||
toggleStorageOptions();
|
||||
}
|
||||
});
|
||||
|
||||
// Image storage form
|
||||
document.getElementById('image-storage-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
fetch('/settings/image_storage', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('Image storage settings saved successfully', 'success');
|
||||
if (data.reload_required) {
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to save settings');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Error: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// Notes directory form
|
||||
document.getElementById('notes-dir-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
fetch('/settings/notes_dir', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('Notes directory settings saved successfully', 'success');
|
||||
if (data.reload_required) {
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to save settings');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Error: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// File extensions form
|
||||
document.getElementById('file-extensions-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
fetch('/settings/file_extensions', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('File extension settings saved successfully', 'success');
|
||||
if (data.reload_required) {
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to save settings');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Error: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// Load settings on page load
|
||||
document.addEventListener('DOMContentLoaded', loadSettings);
|
||||
</script>
|
||||
{{end}}
|
||||
18
web/templates/test.html
Normal file
18
web/templates/test.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test Page Works!</h1>
|
||||
<p>App Name: {{.app_name}}</p>
|
||||
<p>Folder Contents Count: {{len .folder_contents}}</p>
|
||||
{{if .folder_contents}}
|
||||
<ul>
|
||||
{{range .folder_contents}}
|
||||
<li>{{.DisplayName}} ({{.Type}})</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
103
web/templates/view_text.html
Normal file
103
web/templates/view_text.html
Normal file
@@ -0,0 +1,103 @@
|
||||
{{template "base.html" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold text-white">{{.file_name}}</h1>
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="/download/{{.file_path}}" class="btn-secondary">
|
||||
<i class="fas fa-download mr-2"></i>Download
|
||||
</a>
|
||||
<button class="btn-danger delete-file-btn" data-path="{{.file_path}}">
|
||||
<i class="fas fa-trash mr-2"></i>Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .folder_path}}
|
||||
<p class="text-gray-400">
|
||||
<i class="fas fa-folder mr-2"></i>
|
||||
<a href="/folder/{{.folder_path}}" class="text-blue-400 hover:text-blue-300">{{.folder_path}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- File Content -->
|
||||
<div class="bg-gray-800 rounded-lg p-6">
|
||||
<pre class="text-sm text-gray-300 whitespace-pre-wrap overflow-x-auto"><code>{{.content}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div id="delete-modal" class="modal-overlay hidden">
|
||||
<div class="modal-content">
|
||||
<h3 class="text-lg font-medium text-white mb-4">Confirm Delete</h3>
|
||||
<p class="text-gray-300 mb-6">Are you sure you want to delete this file? This action cannot be undone.</p>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button id="cancel-delete" class="btn-secondary">Cancel</button>
|
||||
<button id="confirm-delete" class="btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
let deleteModal = document.getElementById('delete-modal');
|
||||
let deleteTarget = null;
|
||||
|
||||
// Delete functionality
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.delete-file-btn')) {
|
||||
deleteTarget = e.target.closest('.delete-file-btn').dataset.path;
|
||||
deleteModal.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('cancel-delete').addEventListener('click', function() {
|
||||
deleteModal.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
});
|
||||
|
||||
document.getElementById('confirm-delete').addEventListener('click', function() {
|
||||
if (deleteTarget) {
|
||||
fetch('/delete/' + deleteTarget, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('File deleted successfully', 'success');
|
||||
// Redirect to folder or root
|
||||
const folderPath = '{{.folder_path}}';
|
||||
if (folderPath) {
|
||||
window.location.href = '/folder/' + folderPath;
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Delete failed');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('Delete failed: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
deleteModal.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
});
|
||||
|
||||
// Close modal when clicking outside
|
||||
deleteModal.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
this.classList.add('hidden');
|
||||
deleteTarget = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight code if possible
|
||||
hljs.highlightAll();
|
||||
</script>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user