This commit is contained in:
nahakubuilde
2025-08-25 08:48:52 +01:00
commit bfa0eaf68a
26 changed files with 4388 additions and 0 deletions

259
internal/handlers/editor.go Normal file
View File

@@ -0,0 +1,259 @@
package handlers
import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"gobsidian/internal/utils"
)
func (h *Handlers) CreateNotePageHandler(c *gin.Context) {
folderPath := c.Query("folder")
if folderPath == "" {
folderPath = ""
}
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"folder_path": folderPath,
"notes_tree": notesTree,
"active_path": utils.GetActivePath(folderPath),
"current_note": nil,
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
})
}
func (h *Handlers) CreateNoteHandler(c *gin.Context) {
folderPath := strings.TrimSpace(c.PostForm("folder_path"))
title := strings.TrimSpace(c.PostForm("title"))
content := c.PostForm("content")
if title == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Title is required"})
return
}
// Security check
if strings.Contains(folderPath, "..") || strings.Contains(title, "..") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path or title"})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) {
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot create notes in this directory"})
return
}
// Ensure title ends with .md
if !strings.HasSuffix(title, ".md") {
title += ".md"
}
// Create full path
var notePath string
if folderPath == "" {
notePath = title
} else {
notePath = filepath.Join(folderPath, title)
}
fullPath := filepath.Join(h.config.NotesDir, notePath)
// Check if file already exists
if _, err := os.Stat(fullPath); !os.IsNotExist(err) {
c.JSON(http.StatusConflict, gin.H{"error": "A note with this title already exists"})
return
}
// Ensure directory exists
dir := filepath.Dir(fullPath)
if err := utils.EnsureDir(dir); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create directory"})
return
}
// Write file
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create note"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Note created successfully",
"note_path": notePath,
"redirect": "/note/" + notePath,
})
}
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{
"error": "Invalid note path",
"app_name": h.config.AppName,
"message": "Note path must end with .md",
})
return
}
// Security check
if strings.Contains(notePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This note cannot be edited",
})
return
}
fullPath := filepath.Join(h.config.NotesDir, notePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
"error": "Note not found",
"app_name": h.config.AppName,
"message": "The requested note does not exist",
})
return
}
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to read note",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
title := strings.TrimSuffix(filepath.Base(notePath), ".md")
folderPath := filepath.Dir(notePath)
if folderPath == "." {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"title": title,
"content": string(content),
"note_path": notePath,
"folder_path": folderPath,
"notes_tree": notesTree,
"active_path": utils.GetActivePath(folderPath),
"current_note": notePath,
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
})
}
func (h *Handlers) EditNoteHandler(c *gin.Context) {
notePath := strings.TrimPrefix(c.Param("path"), "/")
content := c.PostForm("content")
if !strings.HasSuffix(notePath, ".md") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid note path"})
return
}
// Security check
if strings.Contains(notePath, "..") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) {
c.JSON(http.StatusForbidden, gin.H{"error": "This note cannot be edited"})
return
}
fullPath := filepath.Join(h.config.NotesDir, notePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "Note not found"})
return
}
// Write updated content
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save note"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Note saved successfully",
"redirect": "/note/" + notePath,
})
}
func (h *Handlers) DeleteHandler(c *gin.Context) {
filePath := strings.TrimPrefix(c.Param("path"), "/")
// Security check
if strings.Contains(filePath, "..") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(filePath, h.config.NotesDirSkip) {
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete files in this directory"})
return
}
fullPath := filepath.Join(h.config.NotesDir, filePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
return
}
// Delete file or directory
if err := os.RemoveAll(fullPath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete file"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "File deleted successfully",
})
}

View File

@@ -0,0 +1,483 @@
package handlers
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"github.com/gorilla/sessions"
"github.com/h2non/filetype"
"gobsidian/internal/config"
"gobsidian/internal/markdown"
"gobsidian/internal/models"
"gobsidian/internal/utils"
)
type Handlers struct {
config *config.Config
store *sessions.CookieStore
renderer *markdown.Renderer
}
func New(cfg *config.Config, store *sessions.CookieStore) *Handlers {
return &Handlers{
config: cfg,
store: store,
renderer: markdown.NewRenderer(cfg),
}
}
func (h *Handlers) IndexHandler(c *gin.Context) {
fmt.Printf("DEBUG: IndexHandler called\n")
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{
"error": "Failed to read directory",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
fmt.Printf("DEBUG: Found %d folder contents\n", len(folderContents))
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{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
fmt.Printf("DEBUG: Tree structure built, app_name: %s\n", h.config.AppName)
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"folder_path": "",
"folder_contents": folderContents,
"notes_tree": notesTree,
"active_path": []string{},
"current_note": nil,
"breadcrumbs": utils.GenerateBreadcrumbs(""),
"allowed_image_extensions": h.config.AllowedImageExtensions,
"allowed_file_extensions": h.config.AllowedFileExtensions,
})
}
func (h *Handlers) FolderHandler(c *gin.Context) {
folderPath := strings.TrimPrefix(c.Param("path"), "/")
// Security check - prevent path traversal
if strings.Contains(folderPath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(folderPath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This directory is not accessible",
})
return
}
folderContents, err := utils.GetFolderContents(folderPath, h.config)
if err != nil {
c.HTML(http.StatusNotFound, "base.html", gin.H{
"error": "Folder not found",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"folder_path": folderPath,
"folder_contents": folderContents,
"notes_tree": notesTree,
"active_path": utils.GetActivePath(folderPath),
"current_note": nil,
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
"allowed_image_extensions": h.config.AllowedImageExtensions,
"allowed_file_extensions": h.config.AllowedFileExtensions,
})
}
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{
"error": "Invalid note path",
"app_name": h.config.AppName,
"message": "Note path must end with .md",
})
return
}
// Security check
if strings.Contains(notePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
})
return
}
// Check if path is in skipped directories
if utils.IsPathInSkippedDirs(notePath, h.config.NotesDirSkip) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
"error": "Access denied",
"app_name": h.config.AppName,
"message": "This note is not accessible",
})
return
}
fullPath := filepath.Join(h.config.NotesDir, notePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
"error": "Note not found",
"app_name": h.config.AppName,
"message": "The requested note does not exist",
})
return
}
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to read note",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
htmlContent, err := h.renderer.RenderMarkdown(string(content), notePath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to render markdown",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
title := strings.TrimSuffix(filepath.Base(notePath), ".md")
folderPath := filepath.Dir(notePath)
if folderPath == "." {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"title": title,
"content": htmlContent,
"note_path": notePath,
"folder_path": folderPath,
"notes_tree": notesTree,
"active_path": utils.GetActivePath(folderPath),
"current_note": notePath,
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
})
}
func (h *Handlers) ServeAttachedImageHandler(c *gin.Context) {
imagePath := strings.TrimPrefix(c.Param("path"), "/")
// Security check
if strings.Contains(imagePath, "..") {
c.AbortWithStatus(http.StatusBadRequest)
return
}
var fullPath string
switch h.config.ImageStorageMode {
case 1: // Root directory
fullPath = filepath.Join(h.config.NotesDir, imagePath)
case 3: // Same as note directory
fullPath = filepath.Join(h.config.NotesDir, imagePath)
case 4: // Subfolder of note directory
fullPath = filepath.Join(h.config.NotesDir, imagePath)
default:
c.AbortWithStatus(http.StatusNotFound)
return
}
// Check if file exists and is an image
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.AbortWithStatus(http.StatusNotFound)
return
}
if !models.IsImageFile(filepath.Base(imagePath), h.config.AllowedImageExtensions) {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.File(fullPath)
}
func (h *Handlers) ServeStoredImageHandler(c *gin.Context) {
filename := c.Param("filename")
// Security check
if strings.Contains(filename, "..") || strings.Contains(filename, "/") {
c.AbortWithStatus(http.StatusBadRequest)
return
}
if h.config.ImageStorageMode != 2 {
c.AbortWithStatus(http.StatusNotFound)
return
}
fullPath := filepath.Join(h.config.ImageStoragePath, filename)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.AbortWithStatus(http.StatusNotFound)
return
}
if !models.IsImageFile(filename, h.config.AllowedImageExtensions) {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.File(fullPath)
}
func (h *Handlers) DownloadHandler(c *gin.Context) {
filePath := strings.TrimPrefix(c.Param("path"), "/")
// Security check
if strings.Contains(filePath, "..") {
c.AbortWithStatus(http.StatusBadRequest)
return
}
fullPath := filepath.Join(h.config.NotesDir, filePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.AbortWithStatus(http.StatusNotFound)
return
}
filename := filepath.Base(filePath)
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
c.File(fullPath)
}
func (h *Handlers) ViewTextHandler(c *gin.Context) {
filePath := strings.TrimPrefix(c.Param("path"), "/")
// Security check
if strings.Contains(filePath, "..") {
c.HTML(http.StatusBadRequest, "base.html", gin.H{
"error": "Invalid path",
"app_name": h.config.AppName,
"message": "Path traversal is not allowed",
})
return
}
fullPath := filepath.Join(h.config.NotesDir, filePath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
c.HTML(http.StatusNotFound, "base.html", gin.H{
"error": "File not found",
"app_name": h.config.AppName,
"message": "The requested file does not exist",
})
return
}
// Check if file extension is allowed
if !models.IsAllowedFile(filePath, h.config.AllowedFileExtensions) {
c.HTML(http.StatusForbidden, "base.html", gin.H{
"error": "File type not allowed",
"app_name": h.config.AppName,
"message": "This file type cannot be viewed",
})
return
}
content, err := os.ReadFile(fullPath)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to read file",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.HTML(http.StatusInternalServerError, "base.html", gin.H{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
folderPath := filepath.Dir(filePath)
if folderPath == "." {
folderPath = ""
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"file_name": filepath.Base(filePath),
"file_path": filePath,
"content": string(content),
"folder_path": folderPath,
"notes_tree": notesTree,
"active_path": utils.GetActivePath(folderPath),
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
})
}
func (h *Handlers) UploadHandler(c *gin.Context) {
// Parse multipart form
if err := c.Request.ParseMultipartForm(h.config.MaxContentLength); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "File too large or invalid form data"})
return
}
// Get the upload path
uploadPath := c.PostForm("path")
if uploadPath == "" {
uploadPath = ""
}
// Security check
if strings.Contains(uploadPath, "..") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid upload path"})
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded"})
return
}
defer file.Close()
// Validate file type
buffer := make([]byte, 512)
if _, err := file.Read(buffer); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read file"})
return
}
file.Seek(0, 0) // Reset file pointer
kind, _ := filetype.Match(buffer)
if kind == filetype.Unknown {
// Allow text files and other allowed extensions
if !models.IsAllowedFile(header.Filename, append(h.config.AllowedImageExtensions, h.config.AllowedFileExtensions...)) {
c.JSON(http.StatusBadRequest, gin.H{"error": "File type not allowed"})
return
}
} else {
// Check if detected type is allowed
isImageType := strings.HasPrefix(kind.MIME.Value, "image/")
if !isImageType && !models.IsAllowedFile(header.Filename, h.config.AllowedFileExtensions) {
c.JSON(http.StatusBadRequest, gin.H{"error": "File type not allowed"})
return
}
}
// Determine upload directory
var uploadDir string
isImage := models.IsImageFile(header.Filename, h.config.AllowedImageExtensions)
if isImage {
// For images, use the configured storage mode
storageInfo := utils.GetImageStorageInfo(uploadPath, h.config)
uploadDir = storageInfo.StorageDir
} else {
// For other files, upload to the current folder
uploadDir = filepath.Join(h.config.NotesDir, uploadPath)
}
// Ensure upload directory exists
if err := utils.EnsureDir(uploadDir); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"})
return
}
// Create destination file
destPath := filepath.Join(uploadDir, header.Filename)
dest, err := os.Create(destPath)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create destination file"})
return
}
defer dest.Close()
// Copy file content
if _, err := io.Copy(dest, file); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "File uploaded successfully",
"filename": header.Filename,
"size": header.Size,
})
}
func (h *Handlers) TreeAPIHandler(c *gin.Context) {
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, notesTree)
}

View File

@@ -0,0 +1,171 @@
package handlers
import (
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"gobsidian/internal/utils"
)
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{
"error": "Failed to build tree structure",
"app_name": h.config.AppName,
"message": err.Error(),
})
return
}
c.HTML(http.StatusOK, "base.html", gin.H{
"app_name": h.config.AppName,
"notes_tree": notesTree,
"active_path": []string{},
"current_note": nil,
"breadcrumbs": []gin.H{
{"name": "/", "url": "/"},
{"name": "Settings", "url": ""},
},
})
}
func (h *Handlers) GetImageStorageSettingsHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"mode": h.config.ImageStorageMode,
"path": h.config.ImageStoragePath,
"subfolder": h.config.ImageSubfolderName,
})
}
func (h *Handlers) PostImageStorageSettingsHandler(c *gin.Context) {
modeStr := c.PostForm("storage_mode")
path := strings.TrimSpace(c.PostForm("storage_path"))
subfolder := strings.TrimSpace(c.PostForm("subfolder_name"))
mode, err := strconv.Atoi(modeStr)
if err != nil || mode < 1 || mode > 4 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid storage mode"})
return
}
// Validate path for mode 2
if mode == 2 && path == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Storage path is required for mode 2"})
return
}
// Validate subfolder name for mode 4
if mode == 4 && subfolder == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Subfolder name is required for mode 4"})
return
}
// Save settings
if err := h.config.SaveSetting("MD_NOTES_APP", "IMAGE_STORAGE_MODE", modeStr); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save storage mode"})
return
}
if mode == 2 {
if err := h.config.SaveSetting("MD_NOTES_APP", "IMAGE_STORAGE_PATH", path); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save storage path"})
return
}
}
if mode == 4 {
if err := h.config.SaveSetting("MD_NOTES_APP", "IMAGE_SUBFOLDER_NAME", subfolder); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save subfolder name"})
return
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Image storage settings updated successfully",
"reload_required": true,
})
}
func (h *Handlers) GetNotesDirSettingsHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"notes_dir": h.config.NotesDir,
})
}
func (h *Handlers) PostNotesDirSettingsHandler(c *gin.Context) {
newDir := strings.TrimSpace(c.PostForm("notes_dir"))
if newDir == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Notes directory is required"})
return
}
// Convert to absolute path
if !filepath.IsAbs(newDir) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Please provide an absolute path"})
return
}
// Ensure directory exists
if err := utils.EnsureDir(newDir); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create directory: " + err.Error()})
return
}
// Save setting
if err := h.config.SaveSetting("MD_NOTES_APP", "NOTES_DIR", newDir); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save notes directory"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Notes directory updated successfully",
"reload_required": true,
})
}
func (h *Handlers) GetFileExtensionsSettingsHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"allowed_image_extensions": strings.Join(h.config.AllowedImageExtensions, ", "),
"allowed_file_extensions": strings.Join(h.config.AllowedFileExtensions, ", "),
"images_hide": h.config.ImagesHide,
})
}
func (h *Handlers) PostFileExtensionsSettingsHandler(c *gin.Context) {
imageExtensions := strings.TrimSpace(c.PostForm("allowed_image_extensions"))
fileExtensions := strings.TrimSpace(c.PostForm("allowed_file_extensions"))
imagesHide := c.PostForm("images_hide") == "true"
// Save settings
if err := h.config.SaveSetting("MD_NOTES_APP", "ALLOWED_IMAGE_EXTENSIONS", imageExtensions); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image extensions"})
return
}
if err := h.config.SaveSetting("MD_NOTES_APP", "ALLOWED_FILE_EXTENSIONS", fileExtensions); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file extensions"})
return
}
imagesHideStr := "false"
if imagesHide {
imagesHideStr = "true"
}
if err := h.config.SaveSetting("MD_NOTES_APP", "IMAGES_HIDE", imagesHideStr); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save images hide setting"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "File extension settings updated successfully",
"reload_required": true,
})
}