diff --git a/internal/handlers/editor.go b/internal/handlers/editor.go index 4f30a03..f15292b 100644 --- a/internal/handlers/editor.go +++ b/internal/handlers/editor.go @@ -67,10 +67,28 @@ func (h *Handlers) CreateNoteHandler(c *gin.Context) { return } - // Ensure title ends with .md - if !strings.HasSuffix(title, ".md") { - title += ".md" - } + // Determine extension logic + ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(title)), ".") + if ext == "" { + // No extension provided: default to markdown + title += ".md" + ext = "md" + } else { + // Has extension: allow if md or in allowed file extensions + allowed := ext == "md" + if !allowed { + for _, a := range h.config.AllowedFileExtensions { + if strings.EqualFold(a, ext) { + allowed = true + break + } + } + } + if !allowed { + c.JSON(http.StatusBadRequest, gin.H{"error": "File extension not allowed"}) + return + } + } // Create full path var notePath string @@ -101,12 +119,18 @@ func (h *Handlers) CreateNoteHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "Note created successfully", - "note_path": notePath, - "redirect": "/note/" + notePath, - }) + // Redirect based on extension + redirect := "/note/" + notePath + if strings.ToLower(ext) != "md" { + redirect = "/view_text/" + notePath + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Note created successfully", + "note_path": notePath, + "redirect": redirect, + }) } func (h *Handlers) EditNotePageHandler(c *gin.Context) { diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index eacfbf5..5a4b347 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -24,6 +24,137 @@ type Handlers struct { renderer *markdown.Renderer } +// EditTextPageHandler renders an editor for allowed text files (json, html, xml, yaml, etc.) +func (h *Handlers) EditTextPageHandler(c *gin.Context) { + filePath := strings.TrimPrefix(c.Param("path"), "/") + + // Security check + if strings.Contains(filePath, "..") { + c.HTML(http.StatusBadRequest, "error", gin.H{ + "error": "Invalid path", + "app_name": h.config.AppName, + "message": "Path traversal is not allowed", + "ContentTemplate": "error_content", + "ScriptsTemplate": "error_scripts", + "Page": "error", + }) + return + } + + fullPath := filepath.Join(h.config.NotesDir, filePath) + + // Ensure file exists + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + c.HTML(http.StatusNotFound, "error", gin.H{ + "error": "File not found", + "app_name": h.config.AppName, + "message": "The requested file does not exist", + "ContentTemplate": "error_content", + "ScriptsTemplate": "error_scripts", + "Page": "error", + }) + return + } + + // Only allow editing of configured text file types (not markdown here) + ext := filepath.Ext(fullPath) + ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions) + if ftype != models.FileTypeText { + c.HTML(http.StatusForbidden, "error", gin.H{ + "error": "Editing not allowed", + "app_name": h.config.AppName, + "message": "This file type cannot be edited here", + "ContentTemplate": "error_content", + "ScriptsTemplate": "error_scripts", + "Page": "error", + }) + return + } + + // Load content + data, err := os.ReadFile(fullPath) + if err != nil { + c.HTML(http.StatusInternalServerError, "error", gin.H{ + "error": "Failed to read file", + "app_name": h.config.AppName, + "message": err.Error(), + "ContentTemplate": "error_content", + "ScriptsTemplate": "error_scripts", + "Page": "error", + }) + return + } + + // Build notes tree + notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config) + if err != nil { + c.HTML(http.StatusInternalServerError, "error", gin.H{ + "error": "Failed to build notes tree", + "app_name": h.config.AppName, + "message": err.Error(), + "ContentTemplate": "error_content", + "ScriptsTemplate": "error_scripts", + "Page": "error", + }) + return + } + + folderPath := filepath.Dir(filePath) + if folderPath == "." { + folderPath = "" + } + + c.HTML(http.StatusOK, "edit_text", gin.H{ + "app_name": h.config.AppName, + "title": filepath.Base(filePath), + "content": string(data), + "file_path": filePath, + "file_ext": strings.TrimPrefix(strings.ToLower(ext), "."), + "folder_path": folderPath, + "notes_tree": notesTree, + "active_path": utils.GetActivePath(folderPath), + "breadcrumbs": utils.GenerateBreadcrumbs(folderPath), + "ContentTemplate": "edit_text_content", + "ScriptsTemplate": "edit_text_scripts", + "Page": "edit_text", + }) +} + +// PostEditTextHandler saves changes to an allowed text file +func (h *Handlers) PostEditTextHandler(c *gin.Context) { + filePath := strings.TrimPrefix(c.Param("path"), "/") + + if strings.Contains(filePath, "..") { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"}) + return + } + + fullPath := filepath.Join(h.config.NotesDir, filePath) + + // Enforce allowed file type + ext := filepath.Ext(fullPath) + ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions) + if ftype != models.FileTypeText { + c.JSON(http.StatusForbidden, gin.H{"error": "This file type cannot be edited"}) + return + } + + content := c.PostForm("content") + + // Ensure parent directory exists + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create parent directory"}) + return + } + + if err := os.WriteFile(fullPath, []byte(content), 0o644); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"}) + return + } + + c.JSON(http.StatusOK, gin.H{"success": true, "redirect": "/view_text/" + filePath}) +} + func New(cfg *config.Config, store *sessions.CookieStore) *Handlers { return &Handlers{ config: cfg, @@ -431,11 +562,18 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) { folderPath = "" } + // Determine extension and whether file is editable as text + ext := filepath.Ext(filePath) + ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions) + isEditable := ftype == models.FileTypeText + c.HTML(http.StatusOK, "view_text", gin.H{ "app_name": h.config.AppName, "file_name": filepath.Base(filePath), "file_path": filePath, "content": string(content), + "file_ext": strings.TrimPrefix(strings.ToLower(ext), "."), + "is_editable": isEditable, "folder_path": folderPath, "notes_tree": notesTree, "active_path": utils.GetActivePath(folderPath), diff --git a/internal/server/server.go b/internal/server/server.go index a6456c5..66e1d94 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -74,6 +74,8 @@ func (s *Server) setupRoutes() { s.router.GET("/serve_stored_image/:filename", h.ServeStoredImageHandler) s.router.GET("/download/*path", h.DownloadHandler) s.router.GET("/view_text/*path", h.ViewTextHandler) + s.router.GET("/edit_text/*path", h.EditTextPageHandler) + s.router.POST("/edit_text/*path", h.PostEditTextHandler) // Upload routes s.router.POST("/upload", h.UploadHandler) diff --git a/web/templates/base.html b/web/templates/base.html index de6e33a..a7b7d5a 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -324,6 +324,8 @@ {{template "note_content" .}} {{else if eq .Page "view_text"}} {{template "view_text_content" .}} + {{else if eq .Page "edit_text"}} + {{template "edit_text_content" .}} {{else if eq .Page "create"}} {{template "create_content" .}} {{else if eq .Page "edit"}} @@ -585,6 +587,8 @@ {{template "note_scripts" .}} {{else if eq .Page "view_text"}} {{template "view_text_scripts" .}} + {{else if eq .Page "edit_text"}} + {{template "edit_text_scripts" .}} {{else if eq .Page "create"}} {{template "create_scripts" .}} {{else if eq .Page "edit"}} diff --git a/web/templates/edit_text.html b/web/templates/edit_text.html new file mode 100644 index 0000000..7586a45 --- /dev/null +++ b/web/templates/edit_text.html @@ -0,0 +1,195 @@ +{{define "edit_text"}} + {{template "base" .}} +{{end}} + +{{define "edit_text_content"}} +
+ +
+
+

Edit: {{.title}}

+
+ + + +
+
+ {{if .folder_path}} +

+ + {{.folder_path}} +

+ {{end}} +
+ + +
+
+
+
+ +
Ctrl+S to save
+
+ +
+
+
+
+
+{{end}} + +{{define "edit_text_scripts"}} + + + + + + + + + + + +{{end}} diff --git a/web/templates/folder.html b/web/templates/folder.html index 51ea79d..195f9da 100644 --- a/web/templates/folder.html +++ b/web/templates/folder.html @@ -74,6 +74,11 @@ {{end}} + {{if eq .Type "text"}} + + + + {{end}} {{if eq .Type "image"}} diff --git a/web/templates/view_text.html b/web/templates/view_text.html index ad77bd9..973cab8 100644 --- a/web/templates/view_text.html +++ b/web/templates/view_text.html @@ -9,6 +9,11 @@

{{.file_name}}

+ {{if .is_editable}} + + Edit + + {{end}} Download