feat: 初始化 kenaito-notebook 桌面记事本项目

This commit is contained in:
2026-06-13 21:08:00 +08:00
commit 5fafcd28e3
55 changed files with 9722 additions and 0 deletions
+247
View File
@@ -0,0 +1,247 @@
package services
import (
"archive/zip"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"time"
"github.com/yuin/goldmark"
)
type ExportService struct{}
func NewExportService() *ExportService {
return &ExportService{}
}
func (es *ExportService) ExportHTML(title, content, outputPath string) error {
var htmlContent strings.Builder
if err := goldmark.Convert([]byte(content), &htmlContent); err != nil {
return err
}
htmlTemplate := `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
max-width: 900px;
margin: 0 auto;
padding: 20px;
color: #333;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
h1 { border-bottom: 2px solid #eee; padding-bottom: 10px; }
code {
background-color: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-family: "Consolas", "Monaco", monospace;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 4px;
padding: 12px;
overflow-x: auto;
}
pre code {
background: none;
padding: 0;
}
blockquote {
border-left: 4px solid #ddd;
margin: 0;
padding: 0 20px;
color: #666;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px 12px;
text-align: left;
}
th {
background-color: #f5f5f5;
}
img {
max-width: 100%;
height: auto;
}
a {
color: #0066cc;
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>
{{.Content}}
<hr>
<p style="color: #999; font-size: 12px;">
导出于 {{.ExportTime}} | Kenaito Notebook
</p>
</body>
</html>`
tmpl, err := template.New("html").Parse(htmlTemplate)
if err != nil {
return err
}
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
return tmpl.Execute(file, map[string]string{
"Title": title,
"Content": htmlContent.String(),
"ExportTime": fmt.Sprintf("%s", time.Now().Format("2006-01-02 15:04:05")),
})
}
func (es *ExportService) ExportMarkdown(title, content, outputPath string) error {
fullContent := fmt.Sprintf("# %s\n\n%s", title, content)
return os.WriteFile(outputPath, []byte(fullContent), 0644)
}
func (es *ExportService) ExportFolderMarkdown(folderTitle string, notes []NoteInfo, outputPath string) error {
var content strings.Builder
content.WriteString(fmt.Sprintf("# %s\n\n", folderTitle))
for _, note := range notes {
content.WriteString(fmt.Sprintf("## %s\n\n", note.Title))
content.WriteString(note.Content)
content.WriteString("\n\n---\n\n")
}
return os.WriteFile(outputPath, []byte(content.String()), 0644)
}
func (es *ExportService) ExportAllMarkdown(notes []NoteInfo, outputPath string) error {
var content strings.Builder
content.WriteString("# 所有笔记\n\n")
for _, note := range notes {
content.WriteString(fmt.Sprintf("## %s\n\n", note.Title))
content.WriteString(note.Content)
content.WriteString("\n\n---\n\n")
}
return os.WriteFile(outputPath, []byte(content.String()), 0644)
}
func (es *ExportService) ExportFolderZip(folderTitle string, notes []*NoteInfo, outputPath string) error {
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
w := zip.NewWriter(file)
defer w.Close()
rootFolder := es.GetSafeFilename(folderTitle) + "/"
_, err = w.Create(rootFolder)
if err != nil {
return err
}
es.addNotesToZip(w, notes, rootFolder)
return nil
}
func (es *ExportService) ExportAllZip(notes []*NoteInfo, outputPath string) error {
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
w := zip.NewWriter(file)
defer w.Close()
rootFolder := "所有笔记/"
_, err = w.Create(rootFolder)
if err != nil {
return err
}
es.addNotesToZip(w, notes, rootFolder)
return nil
}
func (es *ExportService) addNotesToZip(w *zip.Writer, notes []*NoteInfo, parentPath string) {
for _, note := range notes {
safeName := es.GetSafeFilename(note.Title)
if note.IsFolder {
folderPath := parentPath + safeName + "/"
_, _ = w.Create(folderPath)
if note.Children != nil {
es.addNotesToZip(w, note.Children, folderPath)
}
} else {
filePath := parentPath + safeName + ".md"
fullContent := fmt.Sprintf("# %s\n\n%s", note.Title, note.Content)
f, err := w.Create(filePath)
if err != nil {
continue
}
_, _ = f.Write([]byte(fullContent))
}
}
}
type NoteInfo struct {
Title string
Content string
IsFolder bool
Children []*NoteInfo
}
func (es *ExportService) GetDefaultExportPath(title, ext string) string {
homeDir, _ := os.UserHomeDir()
exportDir := filepath.Join(homeDir, "Desktop")
safeName := es.GetSafeFilename(title)
return filepath.Join(exportDir, fmt.Sprintf("%s%s", safeName, ext))
}
func (es *ExportService) GetSafeFilename(title string) string {
safeName := strings.ReplaceAll(title, "/", "-")
safeName = strings.ReplaceAll(safeName, "\\", "-")
safeName = strings.ReplaceAll(safeName, ":", "-")
safeName = strings.ReplaceAll(safeName, "*", "")
safeName = strings.ReplaceAll(safeName, "?", "")
safeName = strings.ReplaceAll(safeName, "\"", "")
safeName = strings.ReplaceAll(safeName, "<", "")
safeName = strings.ReplaceAll(safeName, ">", "")
safeName = strings.ReplaceAll(safeName, "|", "")
if strings.TrimSpace(safeName) == "" {
safeName = "未命名笔记"
}
return safeName
}