fix go templating

This commit is contained in:
nahakubuilde
2025-08-25 09:44:14 +01:00
parent 17d8e8144b
commit 3e47f77ae9
12 changed files with 57 additions and 365 deletions

View File

@@ -19,7 +19,7 @@ func (h *Handlers) CreateNotePageHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -27,7 +27,7 @@ func (h *Handlers) CreateNotePageHandler(c *gin.Context) {
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "create", gin.H{
"app_name": h.config.AppName,
"folder_path": folderPath,
"notes_tree": notesTree,
@@ -105,7 +105,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
notePath := strings.TrimPrefix(c.Param("path"), "/")
if !strings.HasSuffix(notePath, ".md") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid note path",
"app_name": h.config.AppName,
"message": "Note path must end with .md",
@@ -115,7 +115,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
// Security check
if strings.Contains(notePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
@@ -125,7 +125,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
c.HTML(http.StatusForbidden, "error", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This note cannot be edited",
@@ -136,7 +136,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
fullPath := filepath.Join(h.config.NotesDir, notePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
c.HTML(http.StatusNotFound, "error", gin.H{
"error": "Note not found",
"app_name": h.config.AppName,
"message": "The requested note does not exist",
@@ -146,7 +146,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to read note",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -156,7 +156,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -170,7 +170,7 @@ func (h *Handlers) EditNotePageHandler(c *gin.Context) {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "edit", gin.H{
"app_name": h.config.AppName,
"title": title,
"content": string(content),

View File

@@ -38,7 +38,7 @@ func (h *Handlers) IndexHandler(c *gin.Context) {
folderContents, err := utils.GetFolderContents("", h.config)
if err != nil {
fmt.Printf("DEBUG: Error getting folder contents: %v\n", err)
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to read directory",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -51,7 +51,7 @@ func (h *Handlers) IndexHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
fmt.Printf("DEBUG: Error building tree structure: %v\n", err)
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -61,7 +61,7 @@ func (h *Handlers) IndexHandler(c *gin.Context) {
fmt.Printf("DEBUG: Tree structure built, app_name: %s\n", h.config.AppName)
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "folder", gin.H{
"app_name": h.config.AppName,
"folder_path": "",
"folder_contents": folderContents,
@@ -79,7 +79,7 @@ func (h *Handlers) FolderHandler(c *gin.Context) {
// Security check - prevent path traversal
if strings.Contains(folderPath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
@@ -89,7 +89,7 @@ func (h *Handlers) FolderHandler(c *gin.Context) {
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
c.HTML(http.StatusForbidden, "error", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This directory is not accessible",
@@ -99,7 +99,7 @@ func (h *Handlers) FolderHandler(c *gin.Context) {
folderContents, err := utils.GetFolderContents(folderPath, h.config)
if err != nil {
c.HTML(http.StatusNotFound, "base.html", gin.H{
c.HTML(http.StatusNotFound, "error", gin.H{
"error": "Folder not found",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -109,7 +109,7 @@ func (h *Handlers) FolderHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -117,7 +117,7 @@ func (h *Handlers) FolderHandler(c *gin.Context) {
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "folder", gin.H{
"app_name": h.config.AppName,
"folder_path": folderPath,
"folder_contents": folderContents,
@@ -134,7 +134,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
notePath := strings.TrimPrefix(c.Param("path"), "/")
if !strings.HasSuffix(notePath, ".md") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid note path",
"app_name": h.config.AppName,
"message": "Note path must end with .md",
@@ -144,7 +144,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
// Security check
if strings.Contains(notePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
@@ -154,7 +154,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
c.HTML(http.StatusForbidden, "error", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This note is not accessible",
@@ -165,7 +165,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
fullPath := filepath.Join(h.config.NotesDir, notePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
c.HTML(http.StatusNotFound, "error", gin.H{
"error": "Note not found",
"app_name": h.config.AppName,
"message": "The requested note does not exist",
@@ -175,7 +175,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to read note",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -185,7 +185,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
htmlContent, err := h.renderer.RenderMarkdown(string(content), notePath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to render markdown",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -195,7 +195,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -209,7 +209,7 @@ func (h *Handlers) NoteHandler(c *gin.Context) {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "note", gin.H{
"app_name": h.config.AppName,
"title": title,
"content": htmlContent,
@@ -314,7 +314,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
// Security check
if strings.Contains(filePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
c.HTML(http.StatusBadRequest, "error", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
@@ -325,7 +325,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
fullPath := filepath.Join(h.config.NotesDir, filePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
c.HTML(http.StatusNotFound, "error", gin.H{
"error": "File not found",
"app_name": h.config.AppName,
"message": "The requested file does not exist",
@@ -335,7 +335,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
// Check if file extension is allowed
if !models.IsAllowedFile(filePath, h.config.AllowedFileExtensions) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
c.HTML(http.StatusForbidden, "error", gin.H{
"error": "File type not allowed",
"app_name": h.config.AppName,
"message": "This file type cannot be viewed",
@@ -345,7 +345,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to read file",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -355,7 +355,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -368,7 +368,7 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "view_text", gin.H{
"app_name": h.config.AppName,
"file_name": filepath.Base(filePath),
"file_path": filePath,

View File

@@ -14,7 +14,7 @@ import (
func (h *Handlers) SettingsPageHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
c.HTML(http.StatusInternalServerError, "error", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
@@ -22,7 +22,7 @@ func (h *Handlers) SettingsPageHandler(c *gin.Context) {
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
c.HTML(http.StatusOK, "settings", gin.H{
"app_name": h.config.AppName,
"notes_tree": notesTree,
"active_path": []string{},

View File

@@ -1,3 +1,4 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
@@ -373,7 +374,7 @@
{{block "scripts" .}}{{end}}
</body>
</html>
{{end}}
<!-- Tree Node Template -->
{{define "tree_node"}}
<div class="tree-node">

View File

@@ -1,325 +0,0 @@
{{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}}

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "create"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="max-w-4xl mx-auto p-6">

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "edit"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="max-w-4xl mx-auto p-6">

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "error"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="flex items-center justify-center min-h-screen">

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "folder"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="p-6">

View File

@@ -1,3 +1,7 @@
{{define "note"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="max-w-4xl mx-auto p-6">
<!-- Note Header -->

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "settings"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="max-w-6xl mx-auto p-6">

View File

@@ -1,4 +1,6 @@
{{template "base.html" .}}
{{define "view_text"}}
{{template "base" .}}
{{end}}
{{define "content"}}
<div class="max-w-4xl mx-auto p-6">