fix new file path and error page
This commit is contained in:
@@ -55,40 +55,62 @@ func (h *Handlers) CreateNoteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize slashes: treat backslashes as folder separators
|
||||||
|
folderPath = strings.ReplaceAll(folderPath, "\\", "/")
|
||||||
|
title = strings.ReplaceAll(title, "\\", "/")
|
||||||
|
|
||||||
|
// Merge any subfolder segments included in title into folderPath
|
||||||
|
if strings.Contains(title, "/") {
|
||||||
|
dirPart := filepath.Dir(title)
|
||||||
|
base := filepath.Base(title)
|
||||||
|
if dirPart != "." && dirPart != "" {
|
||||||
|
if folderPath == "" {
|
||||||
|
folderPath = dirPart
|
||||||
|
} else {
|
||||||
|
folderPath = filepath.Join(folderPath, dirPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title = base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip any leading separators that might imply absolute path
|
||||||
|
folderPath = strings.TrimPrefix(folderPath, "/")
|
||||||
|
title = strings.TrimPrefix(title, "/")
|
||||||
|
|
||||||
// Security check
|
// Security check
|
||||||
if strings.Contains(folderPath, "..") || strings.Contains(title, "..") {
|
if strings.Contains(folderPath, "..") || strings.Contains(title, "..") {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path or title"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path or title"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if path is in skipped directories
|
// Check if path is in skipped directories (after merging title path)
|
||||||
if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) {
|
if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot create notes in this directory"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot create notes in this directory"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine extension logic
|
// Determine extension logic
|
||||||
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(title)), ".")
|
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(title)), ".")
|
||||||
if ext == "" {
|
if ext == "" {
|
||||||
// No extension provided: default to markdown
|
// No extension provided: default to markdown
|
||||||
title += ".md"
|
title += ".md"
|
||||||
ext = "md"
|
ext = "md"
|
||||||
} else {
|
} else {
|
||||||
// Has extension: allow if md or in allowed file extensions
|
// Has extension: allow if md or in allowed file extensions
|
||||||
allowed := ext == "md"
|
allowed := ext == "md"
|
||||||
if !allowed {
|
if !allowed {
|
||||||
for _, a := range h.config.AllowedFileExtensions {
|
for _, a := range h.config.AllowedFileExtensions {
|
||||||
if strings.EqualFold(a, ext) {
|
if strings.EqualFold(a, ext) {
|
||||||
allowed = true
|
allowed = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !allowed {
|
if !allowed {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "File extension not allowed"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "File extension not allowed"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create full path
|
// Create full path
|
||||||
var notePath string
|
var notePath string
|
||||||
|
|||||||
@@ -288,7 +288,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- File Tree -->
|
<!-- File Tree -->
|
||||||
<div class="sidebar-content flex-1 overflow-y-auto px-4 pb-4">
|
<div id="sidebar-tree" class="sidebar-content flex-1 overflow-y-auto px-4 pb-4">
|
||||||
{{if .notes_tree}}
|
{{if .notes_tree}}
|
||||||
{{/* Render only children of the root to hide root folder label */}}
|
{{/* Render only children of the root to hide root folder label */}}
|
||||||
{{range .notes_tree.Children}}
|
{{range .notes_tree.Children}}
|
||||||
@@ -467,6 +467,69 @@
|
|||||||
// Expand active path in tree
|
// Expand active path in tree
|
||||||
expandActivePath();
|
expandActivePath();
|
||||||
|
|
||||||
|
// Sidebar tree fallback: if server didn't render any tree nodes (e.g., error pages), fetch and render via API
|
||||||
|
(function ensureSidebarTree() {
|
||||||
|
const container = document.getElementById('sidebar-tree');
|
||||||
|
if (!container) return;
|
||||||
|
const hasTree = container.querySelector('.tree-node, .sidebar-item');
|
||||||
|
if (hasTree) return; // already populated
|
||||||
|
|
||||||
|
// Fetch tree
|
||||||
|
fetch('/api/tree')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data || !Array.isArray(data.children)) return;
|
||||||
|
// Clear container
|
||||||
|
container.innerHTML = '';
|
||||||
|
data.children.forEach(child => {
|
||||||
|
container.appendChild(renderTreeNode(child));
|
||||||
|
});
|
||||||
|
// Re-apply expanded state and active path if any
|
||||||
|
expandActivePath();
|
||||||
|
})
|
||||||
|
.catch(() => {/* ignore */});
|
||||||
|
|
||||||
|
function renderTreeNode(node) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'tree-node';
|
||||||
|
if (node.children && node.children.length) {
|
||||||
|
const toggle = document.createElement('div');
|
||||||
|
toggle.className = 'tree-toggle flex items-center py-1 hover:bg-gray-700 rounded px-2 cursor-pointer';
|
||||||
|
toggle.setAttribute('data-path', node.path || '');
|
||||||
|
toggle.innerHTML = '<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">${escapeHTML(node.name || '')}</span>`;
|
||||||
|
const children = document.createElement('div');
|
||||||
|
children.className = 'tree-children ml-4 hidden';
|
||||||
|
(node.children || []).forEach(ch => {
|
||||||
|
children.appendChild(renderTreeNode(ch));
|
||||||
|
});
|
||||||
|
wrapper.appendChild(toggle);
|
||||||
|
wrapper.appendChild(children);
|
||||||
|
} else {
|
||||||
|
let href = '/view_text/' + (node.path || '');
|
||||||
|
let icon = '📄';
|
||||||
|
if ((node.type || '').toLowerCase() === 'md') {
|
||||||
|
href = '/note/' + (node.path || '');
|
||||||
|
icon = '📝';
|
||||||
|
} else if ((node.type || '').toLowerCase() === 'image') {
|
||||||
|
href = '/serve_attached_image/' + (node.path || '');
|
||||||
|
icon = '🖼️';
|
||||||
|
}
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = href;
|
||||||
|
a.className = 'sidebar-item';
|
||||||
|
a.innerHTML = `<span class="mr-2">${icon}</span><span>${escapeHTML(node.name || '')}</span>`;
|
||||||
|
if ((node.type || '').toLowerCase() === 'image') a.target = '_blank';
|
||||||
|
wrapper.appendChild(a);
|
||||||
|
}
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
function escapeHTML(str) {
|
||||||
|
return String(str).replace(/[&<>"']/g, s => ({'&':'&','<':'<','>':'>','"':'"','\'':'''}[s]));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Search modal wiring
|
// Search modal wiring
|
||||||
const searchModal = document.getElementById('search-modal');
|
const searchModal = document.getElementById('search-modal');
|
||||||
const openSearchBtn = document.getElementById('open-search');
|
const openSearchBtn = document.getElementById('open-search');
|
||||||
|
|||||||
Reference in New Issue
Block a user