init
This commit is contained in:
282
internal/utils/utils.go
Normal file
282
internal/utils/utils.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gobsidian/internal/config"
|
||||
"gobsidian/internal/models"
|
||||
)
|
||||
|
||||
func BuildTreeStructure(rootDir string, hiddenDirs []string, cfg *config.Config) (*models.TreeNode, error) {
|
||||
root := &models.TreeNode{
|
||||
Name: filepath.Base(rootDir),
|
||||
Path: "",
|
||||
Type: models.FileTypeDirectory,
|
||||
}
|
||||
|
||||
err := buildTreeRecursive(rootDir, root, hiddenDirs, cfg)
|
||||
return root, err
|
||||
}
|
||||
|
||||
func buildTreeRecursive(currentPath string, node *models.TreeNode, hiddenDirs []string, cfg *config.Config) error {
|
||||
entries, err := os.ReadDir(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort entries: directories first, then files
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
if entries[i].IsDir() != entries[j].IsDir() {
|
||||
return entries[i].IsDir()
|
||||
}
|
||||
return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
|
||||
})
|
||||
|
||||
for _, entry := range entries {
|
||||
// Skip hidden files
|
||||
if strings.HasPrefix(entry.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip hidden directories
|
||||
if entry.IsDir() && contains(hiddenDirs, entry.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
childPath := filepath.Join(currentPath, entry.Name())
|
||||
relativePath := getRelativePath(childPath, cfg.NotesDir)
|
||||
|
||||
child := &models.TreeNode{
|
||||
Name: entry.Name(),
|
||||
Path: relativePath,
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
child.Type = models.FileTypeDirectory
|
||||
// Recursively build children
|
||||
if err := buildTreeRecursive(childPath, child, hiddenDirs, cfg); err != nil {
|
||||
continue // Skip directories that can't be read
|
||||
}
|
||||
} else {
|
||||
child.Type = models.GetFileType(filepath.Ext(entry.Name()), cfg.AllowedImageExtensions, cfg.AllowedFileExtensions)
|
||||
// Only include markdown files and allowed file types in the tree
|
||||
if child.Type != models.FileTypeMarkdown && child.Type != models.FileTypeText {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetFolderContents(folderPath string, cfg *config.Config) ([]models.FileInfo, error) {
|
||||
fullPath := filepath.Join(cfg.NotesDir, folderPath)
|
||||
|
||||
entries, err := os.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contents []models.FileInfo
|
||||
|
||||
// Sort entries: directories first, then files
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
if entries[i].IsDir() != entries[j].IsDir() {
|
||||
return entries[i].IsDir()
|
||||
}
|
||||
return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
|
||||
})
|
||||
|
||||
for _, entry := range entries {
|
||||
// Skip hidden files
|
||||
if strings.HasPrefix(entry.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
relativePath := filepath.Join(folderPath, entry.Name())
|
||||
if folderPath == "" {
|
||||
relativePath = entry.Name()
|
||||
}
|
||||
|
||||
fileInfo := models.FileInfo{
|
||||
Name: entry.Name(),
|
||||
Path: relativePath,
|
||||
Size: info.Size(),
|
||||
ModTime: info.ModTime(),
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
fileInfo.Type = models.FileTypeDirectory
|
||||
fileInfo.DisplayName = entry.Name()
|
||||
} else {
|
||||
fileInfo.Type = models.GetFileType(filepath.Ext(entry.Name()), cfg.AllowedImageExtensions, cfg.AllowedFileExtensions)
|
||||
|
||||
// Set display name based on file type
|
||||
if fileInfo.Type == models.FileTypeMarkdown {
|
||||
fileInfo.DisplayName = strings.TrimSuffix(entry.Name(), ".md")
|
||||
} else {
|
||||
fileInfo.DisplayName = entry.Name()
|
||||
}
|
||||
|
||||
// Skip images if they should be hidden
|
||||
if cfg.ImagesHide && fileInfo.Type == models.FileTypeImage {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip files that are not allowed
|
||||
if fileInfo.Type == models.FileTypeOther {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
contents = append(contents, fileInfo)
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func GenerateBreadcrumbs(path string) []models.Breadcrumb {
|
||||
var breadcrumbs []models.Breadcrumb
|
||||
|
||||
// Add root
|
||||
breadcrumbs = append(breadcrumbs, models.Breadcrumb{
|
||||
Name: "/",
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
if path == "" {
|
||||
return breadcrumbs
|
||||
}
|
||||
|
||||
parts := strings.Split(path, "/")
|
||||
currentPath := ""
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentPath == "" {
|
||||
currentPath = part
|
||||
} else {
|
||||
currentPath = filepath.Join(currentPath, part)
|
||||
}
|
||||
|
||||
breadcrumbs = append(breadcrumbs, models.Breadcrumb{
|
||||
Name: part,
|
||||
URL: "/folder/" + currentPath,
|
||||
})
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
|
||||
func GetImageStorageInfo(notePath string, cfg *config.Config) models.ImageStorageInfo {
|
||||
var storageDir, markdownPath string
|
||||
|
||||
switch cfg.ImageStorageMode {
|
||||
case 1: // Store directly in NOTES_DIR
|
||||
storageDir = cfg.NotesDir
|
||||
markdownPath = ""
|
||||
|
||||
case 2: // Store in specific folder
|
||||
storageDir = cfg.ImageStoragePath
|
||||
markdownPath = ""
|
||||
|
||||
case 3: // Store in same directory as note
|
||||
if notePath != "" {
|
||||
storageDir = filepath.Join(cfg.NotesDir, filepath.Dir(notePath))
|
||||
} else {
|
||||
storageDir = cfg.NotesDir
|
||||
}
|
||||
markdownPath = ""
|
||||
|
||||
case 4: // Store in subfolder of note's directory
|
||||
if notePath != "" {
|
||||
storageDir = filepath.Join(cfg.NotesDir, filepath.Dir(notePath), cfg.ImageSubfolderName)
|
||||
} else {
|
||||
storageDir = filepath.Join(cfg.NotesDir, cfg.ImageSubfolderName)
|
||||
}
|
||||
markdownPath = cfg.ImageSubfolderName
|
||||
|
||||
default:
|
||||
storageDir = cfg.NotesDir
|
||||
markdownPath = ""
|
||||
}
|
||||
|
||||
return models.ImageStorageInfo{
|
||||
StorageDir: storageDir,
|
||||
MarkdownPath: markdownPath,
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureDir(dirPath string) error {
|
||||
return os.MkdirAll(dirPath, 0755)
|
||||
}
|
||||
|
||||
func IsPathInSkippedDirs(path string, skippedDirs []string) bool {
|
||||
parts := strings.Split(filepath.Clean(path), string(filepath.Separator))
|
||||
|
||||
for _, part := range parts {
|
||||
if contains(skippedDirs, part) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetActivePath(currentPath string) []string {
|
||||
if currentPath == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return strings.Split(strings.Trim(currentPath, "/"), "/")
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if strings.TrimSpace(s) == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getRelativePath(fullPath, basePath string) string {
|
||||
rel, err := filepath.Rel(basePath, fullPath)
|
||||
if err != nil {
|
||||
return fullPath
|
||||
}
|
||||
return filepath.ToSlash(rel)
|
||||
}
|
||||
|
||||
func FormatFileSize(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
func FormatTime(t time.Time) string {
|
||||
return t.Format("2006-01-02 15:04")
|
||||
}
|
||||
Reference in New Issue
Block a user