Changement des ilink vers markdown pur
This commit is contained in:
@ -114,6 +114,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.handleFavorites(w, r)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(path, "/api/folder/") {
|
||||
h.handleFolderView(w, r)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
@ -278,15 +282,17 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Utiliser le template editor.html pour afficher la page d'accueil
|
||||
data := struct {
|
||||
Filename string
|
||||
Content string
|
||||
IsHome bool
|
||||
Backlinks []BacklinkInfo
|
||||
Filename string
|
||||
Content string
|
||||
IsHome bool
|
||||
Backlinks []BacklinkInfo
|
||||
Breadcrumb template.HTML
|
||||
}{
|
||||
Filename: "🏠 Accueil - Index",
|
||||
Content: content,
|
||||
IsHome: true,
|
||||
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
||||
Filename: "🏠 Accueil - Index",
|
||||
Content: content,
|
||||
IsHome: true,
|
||||
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
||||
Breadcrumb: h.generateBreadcrumb(""),
|
||||
}
|
||||
|
||||
err := h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||
@ -338,12 +344,19 @@ func (h *Handler) generateHomeMarkdown() string {
|
||||
// Section des notes récemment modifiées (après les favoris)
|
||||
h.generateRecentNotesSection(&sb)
|
||||
|
||||
// Titre de l'arborescence avec le nombre de notes
|
||||
sb.WriteString(fmt.Sprintf("## 📂 Toutes les notes (%d)\n\n", noteCount))
|
||||
// Section de toutes les notes avec accordéon
|
||||
sb.WriteString("<div class=\"home-section\">\n")
|
||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('all-notes')\">\n")
|
||||
sb.WriteString(fmt.Sprintf(" <h2 class=\"home-section-title\">📂 Toutes les notes (%d)</h2>\n", noteCount))
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-all-notes\">\n")
|
||||
|
||||
// Générer l'arborescence en Markdown
|
||||
h.generateMarkdownTree(&sb, tree, 0)
|
||||
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString("</div>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@ -355,18 +368,24 @@ func (h *Handler) generateTagsSection(sb *strings.Builder) {
|
||||
return
|
||||
}
|
||||
|
||||
sb.WriteString("## 🏷️ Tags\n\n")
|
||||
sb.WriteString("<div class=\"tags-cloud\">\n")
|
||||
sb.WriteString("<div class=\"home-section\">\n")
|
||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('tags')\">\n")
|
||||
sb.WriteString(" <h2 class=\"home-section-title\">🏷️ Tags</h2>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-tags\">\n")
|
||||
sb.WriteString(" <div class=\"tags-cloud\">\n")
|
||||
|
||||
for _, tc := range tags {
|
||||
// Créer un lien HTML discret et fonctionnel
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`<a href="#" class="tag-item" hx-get="/api/search?query=tag:%s" hx-target="#search-results" hx-swap="innerHTML"><kbd class="tag-badge">#%s</kbd> <mark class="tag-count">%d</mark></a>`,
|
||||
` <a href="#" class="tag-item" hx-get="/api/search?query=tag:%s" hx-target="#search-results" hx-swap="innerHTML"><kbd class="tag-badge">#%s</kbd> <mark class="tag-count">%d</mark></a>`,
|
||||
tc.Tag, tc.Tag, tc.Count,
|
||||
))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString("</div>\n\n")
|
||||
}
|
||||
|
||||
@ -377,36 +396,42 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
||||
return
|
||||
}
|
||||
|
||||
sb.WriteString("## ⭐ Favoris\n\n")
|
||||
sb.WriteString("<div class=\"note-tree favorites-tree\">\n")
|
||||
sb.WriteString("<div class=\"home-section\">\n")
|
||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('favorites')\">\n")
|
||||
sb.WriteString(" <h2 class=\"home-section-title\">⭐ Favoris</h2>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-favorites\">\n")
|
||||
sb.WriteString(" <div class=\"note-tree favorites-tree\">\n")
|
||||
|
||||
for _, fav := range favorites.Items {
|
||||
safeID := "fav-" + strings.ReplaceAll(strings.ReplaceAll(fav.Path, "/", "-"), "\\", "-")
|
||||
|
||||
|
||||
if fav.IsDir {
|
||||
// Dossier - avec accordéon
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", safeID))
|
||||
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", safeID))
|
||||
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
||||
|
||||
// Lister le contenu du dossier
|
||||
h.generateFavoriteFolderContent(sb, fav.Path, 2)
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
h.generateFavoriteFolderContent(sb, fav.Path, 3)
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
} else {
|
||||
// Fichier
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"file indent-level-1\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">", fav.Path))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"file indent-level-1\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">", fav.Path))
|
||||
sb.WriteString(fmt.Sprintf("📄 %s", fav.Title))
|
||||
sb.WriteString("</a>\n")
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString("</div>\n\n")
|
||||
}
|
||||
|
||||
@ -418,8 +443,12 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
return
|
||||
}
|
||||
|
||||
sb.WriteString("## 🕒 Récemment modifiés\n\n")
|
||||
sb.WriteString("<div class=\"recent-notes-container\">\n")
|
||||
sb.WriteString("<div class=\"home-section\">\n")
|
||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('recent')\">\n")
|
||||
sb.WriteString(" <h2 class=\"home-section-title\">🕒 Récemment modifiés</h2>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-recent\">\n")
|
||||
sb.WriteString(" <div class=\"recent-notes-container\">\n")
|
||||
|
||||
for _, doc := range recentDocs {
|
||||
// Extraire les premières lignes du corps (max 150 caractères)
|
||||
@ -434,13 +463,13 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
dateStr = doc.Date
|
||||
}
|
||||
|
||||
sb.WriteString(" <div class=\"recent-note-card\">\n")
|
||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">\n", doc.Path))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\">📅 %s</span>\n", dateStr))
|
||||
sb.WriteString(" <div class=\"recent-note-card\">\n")
|
||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">\n", doc.Path))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\">📅 %s</span>\n", dateStr))
|
||||
if len(doc.Tags) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
||||
for i, tag := range doc.Tags {
|
||||
if i > 0 {
|
||||
sb.WriteString(" ")
|
||||
@ -449,14 +478,16 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
}
|
||||
sb.WriteString("</span>\n")
|
||||
}
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
if preview != "" {
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-preview\">%s</div>\n", preview))
|
||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-preview\">%s</div>\n", preview))
|
||||
}
|
||||
sb.WriteString(" </a>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" </a>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
}
|
||||
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString("</div>\n\n")
|
||||
}
|
||||
|
||||
@ -788,15 +819,17 @@ func (h *Handler) handleGetNote(w http.ResponseWriter, r *http.Request, filename
|
||||
backlinkData := h.buildBacklinkData(backlinks)
|
||||
|
||||
data := struct {
|
||||
Filename string
|
||||
Content string
|
||||
IsHome bool
|
||||
Backlinks []BacklinkInfo
|
||||
Filename string
|
||||
Content string
|
||||
IsHome bool
|
||||
Backlinks []BacklinkInfo
|
||||
Breadcrumb template.HTML
|
||||
}{
|
||||
Filename: filename,
|
||||
Content: string(content),
|
||||
IsHome: false,
|
||||
Backlinks: backlinkData,
|
||||
Filename: filename,
|
||||
Content: string(content),
|
||||
IsHome: false,
|
||||
Backlinks: backlinkData,
|
||||
Breadcrumb: h.generateBreadcrumb(filename),
|
||||
}
|
||||
|
||||
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||
@ -1264,3 +1297,190 @@ func (h *Handler) buildBacklinkData(paths []string) []BacklinkInfo {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// handleFolderView affiche le contenu d'un dossier
|
||||
func (h *Handler) handleFolderView(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Si ce n'est pas une requête HTMX, rediriger vers la page principale
|
||||
if r.Header.Get("HX-Request") == "" {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Extraire le chemin du dossier depuis l'URL
|
||||
folderPath := strings.TrimPrefix(r.URL.Path, "/api/folder/")
|
||||
folderPath = strings.TrimPrefix(folderPath, "/")
|
||||
|
||||
// Sécurité : vérifier le chemin
|
||||
cleanPath := filepath.Clean(folderPath)
|
||||
if strings.HasPrefix(cleanPath, "..") || filepath.IsAbs(cleanPath) {
|
||||
http.Error(w, "Chemin invalide", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Construire le chemin absolu
|
||||
absPath := filepath.Join(h.notesDir, cleanPath)
|
||||
|
||||
// Vérifier que c'est bien un dossier
|
||||
info, err := os.Stat(absPath)
|
||||
if err != nil || !info.IsDir() {
|
||||
http.Error(w, "Dossier non trouvé", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Générer le contenu de la page
|
||||
content := h.generateFolderViewMarkdown(cleanPath)
|
||||
|
||||
// Utiliser le template editor.html
|
||||
data := struct {
|
||||
Filename string
|
||||
Content string
|
||||
IsHome bool
|
||||
Backlinks []BacklinkInfo
|
||||
Breadcrumb template.HTML
|
||||
}{
|
||||
Filename: cleanPath,
|
||||
Content: content,
|
||||
IsHome: true, // Pas d'édition pour une vue de dossier
|
||||
Backlinks: nil,
|
||||
Breadcrumb: h.generateBreadcrumb(cleanPath),
|
||||
}
|
||||
|
||||
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||
if err != nil {
|
||||
h.logger.Printf("Erreur d'exécution du template folder view: %v", err)
|
||||
http.Error(w, "Erreur interne", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// generateBreadcrumb génère un fil d'Ariane HTML cliquable
|
||||
func (h *Handler) generateBreadcrumb(path string) template.HTML {
|
||||
if path == "" {
|
||||
return template.HTML(`<strong>📁 Racine</strong>`)
|
||||
}
|
||||
|
||||
parts := strings.Split(filepath.ToSlash(path), "/")
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(`<span class="breadcrumb">`)
|
||||
|
||||
// Lien racine
|
||||
sb.WriteString(`<a href="#" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link">📁 Racine</a>`)
|
||||
|
||||
// Construire les liens pour chaque partie
|
||||
currentPath := ""
|
||||
for i, part := range parts {
|
||||
sb.WriteString(` <span class="breadcrumb-separator">›</span> `)
|
||||
|
||||
if currentPath == "" {
|
||||
currentPath = part
|
||||
} else {
|
||||
currentPath = currentPath + "/" + part
|
||||
}
|
||||
|
||||
// Le dernier élément (fichier) n'est pas cliquable
|
||||
if i == len(parts)-1 && strings.HasSuffix(part, ".md") {
|
||||
// C'est un fichier, pas cliquable
|
||||
displayName := strings.TrimSuffix(part, ".md")
|
||||
sb.WriteString(fmt.Sprintf(`<strong>%s</strong>`, displayName))
|
||||
} else {
|
||||
// C'est un dossier, cliquable
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`<a href="#" hx-get="/api/folder/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link">📂 %s</a>`,
|
||||
currentPath, part,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(`</span>`)
|
||||
return template.HTML(sb.String())
|
||||
}
|
||||
|
||||
// generateFolderViewMarkdown génère le contenu Markdown pour l'affichage d'un dossier
|
||||
func (h *Handler) generateFolderViewMarkdown(folderPath string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// En-tête
|
||||
if folderPath == "" {
|
||||
sb.WriteString("# 📁 Racine\n\n")
|
||||
} else {
|
||||
folderName := filepath.Base(folderPath)
|
||||
sb.WriteString(fmt.Sprintf("# 📂 %s\n\n", folderName))
|
||||
}
|
||||
|
||||
sb.WriteString("_Contenu du dossier_\n\n")
|
||||
|
||||
// Lister le contenu
|
||||
absPath := filepath.Join(h.notesDir, folderPath)
|
||||
entries, err := os.ReadDir(absPath)
|
||||
if err != nil {
|
||||
sb.WriteString("❌ Erreur lors de la lecture du dossier\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Séparer dossiers et fichiers
|
||||
var folders []os.DirEntry
|
||||
var files []os.DirEntry
|
||||
|
||||
for _, entry := range entries {
|
||||
// Ignorer les fichiers cachés
|
||||
if strings.HasPrefix(entry.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
folders = append(folders, entry)
|
||||
} else if strings.HasSuffix(entry.Name(), ".md") {
|
||||
files = append(files, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher les dossiers
|
||||
if len(folders) > 0 {
|
||||
sb.WriteString("## 📁 Dossiers\n\n")
|
||||
sb.WriteString("<div class=\"folder-list\">\n")
|
||||
for _, folder := range folders {
|
||||
subPath := filepath.Join(folderPath, folder.Name())
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`<div class="folder-item"><a href="#" hx-get="/api/folder/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true">📂 %s</a></div>`,
|
||||
filepath.ToSlash(subPath), folder.Name(),
|
||||
))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("</div>\n\n")
|
||||
}
|
||||
|
||||
// Afficher les fichiers
|
||||
if len(files) > 0 {
|
||||
sb.WriteString(fmt.Sprintf("## 📄 Notes (%d)\n\n", len(files)))
|
||||
sb.WriteString("<div class=\"file-list\">\n")
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(folderPath, file.Name())
|
||||
displayName := strings.TrimSuffix(file.Name(), ".md")
|
||||
|
||||
// Lire le titre du front matter si possible
|
||||
fullPath := filepath.Join(h.notesDir, filePath)
|
||||
fm, _, err := indexer.ExtractFrontMatterAndBody(fullPath)
|
||||
if err == nil && fm.Title != "" {
|
||||
displayName = fm.Title
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
`<div class="file-item"><a href="#" hx-get="/api/notes/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true">📄 %s</a></div>`,
|
||||
filepath.ToSlash(filePath), displayName,
|
||||
))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("</div>\n\n")
|
||||
}
|
||||
|
||||
if len(folders) == 0 && len(files) == 0 {
|
||||
sb.WriteString("_Ce dossier est vide_\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user