Commit avant changement d'agent vers devstral
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/mathieu/personotes/internal/i18n"
|
||||
"github.com/mathieu/personotes/internal/indexer"
|
||||
)
|
||||
|
||||
@ -39,15 +41,17 @@ type Handler struct {
|
||||
idx *indexer.Indexer
|
||||
templates *template.Template
|
||||
logger *log.Logger
|
||||
i18n *i18n.Translator
|
||||
}
|
||||
|
||||
// NewHandler construit un handler unifié pour l'API.
|
||||
func NewHandler(notesDir string, idx *indexer.Indexer, tpl *template.Template, logger *log.Logger) *Handler {
|
||||
func NewHandler(notesDir string, idx *indexer.Indexer, tpl *template.Template, logger *log.Logger, translator *i18n.Translator) *Handler {
|
||||
return &Handler{
|
||||
notesDir: notesDir,
|
||||
idx: idx,
|
||||
templates: tpl,
|
||||
logger: logger,
|
||||
i18n: translator,
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +59,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
h.logger.Printf("%s %s", r.Method, path)
|
||||
|
||||
// I18n endpoint - serve translation files
|
||||
if strings.HasPrefix(path, "/api/i18n/") {
|
||||
h.handleI18n(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// REST API v1 endpoints
|
||||
if strings.HasPrefix(path, "/api/v1/notes") {
|
||||
h.handleRESTNotes(w, r)
|
||||
@ -278,7 +288,7 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Générer le contenu Markdown avec la liste de toutes les notes
|
||||
content := h.generateHomeMarkdown()
|
||||
content := h.generateHomeMarkdown(r)
|
||||
|
||||
// Utiliser le template editor.html pour afficher la page d'accueil
|
||||
data := struct {
|
||||
@ -317,12 +327,12 @@ func (h *Handler) handleAbout(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// generateHomeMarkdown génère le contenu Markdown de la page d'accueil
|
||||
func (h *Handler) generateHomeMarkdown() string {
|
||||
func (h *Handler) generateHomeMarkdown(r *http.Request) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// En-tête
|
||||
sb.WriteString("# 📚 Index\n\n")
|
||||
sb.WriteString("_Mise à jour automatique • " + time.Now().Format("02/01/2006 à 15:04") + "_\n\n")
|
||||
sb.WriteString("_" + h.t(r, "home.autoUpdate") + " • " + time.Now().Format("02/01/2006 à 15:04") + "_\n\n")
|
||||
|
||||
// Construire l'arborescence
|
||||
tree, err := h.buildFileTree()
|
||||
@ -339,15 +349,15 @@ func (h *Handler) generateHomeMarkdown() string {
|
||||
h.generateTagsSection(&sb)
|
||||
|
||||
// Section des favoris (après les tags)
|
||||
h.generateFavoritesSection(&sb)
|
||||
h.generateFavoritesSection(&sb, r)
|
||||
|
||||
// Section des notes récemment modifiées (après les favoris)
|
||||
h.generateRecentNotesSection(&sb)
|
||||
h.generateRecentNotesSection(&sb, r)
|
||||
|
||||
// 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(fmt.Sprintf(" <h2 class=\"home-section-title\">📂 %s (%d)</h2>\n", h.t(r, "home.allNotes"), noteCount))
|
||||
sb.WriteString(" </div>\n")
|
||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-all-notes\">\n")
|
||||
|
||||
@ -390,7 +400,7 @@ func (h *Handler) generateTagsSection(sb *strings.Builder) {
|
||||
}
|
||||
|
||||
// generateFavoritesSection génère la section des favoris avec arborescence dépliable
|
||||
func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
||||
func (h *Handler) generateFavoritesSection(sb *strings.Builder, r *http.Request) {
|
||||
favorites, err := h.loadFavorites()
|
||||
if err != nil || len(favorites.Items) == 0 {
|
||||
return
|
||||
@ -398,7 +408,7 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
||||
|
||||
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(" <h2 class=\"home-section-title\">⭐ " + h.t(r, "favorites.title") + "</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")
|
||||
@ -423,7 +433,7 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
||||
} 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(" <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\">", fav.Path))
|
||||
sb.WriteString(fmt.Sprintf("📄 %s", fav.Title))
|
||||
sb.WriteString("</a>\n")
|
||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||
@ -436,7 +446,7 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
||||
}
|
||||
|
||||
// generateRecentNotesSection génère la section des notes récemment modifiées
|
||||
func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
func (h *Handler) generateRecentNotesSection(sb *strings.Builder, r *http.Request) {
|
||||
recentDocs := h.idx.GetRecentDocuments(5)
|
||||
|
||||
if len(recentDocs) == 0 {
|
||||
@ -445,7 +455,7 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
|
||||
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(" <h2 class=\"home-section-title\">🕒 " + h.t(r, "home.recentlyModified") + "</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")
|
||||
@ -464,7 +474,7 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
||||
}
|
||||
|
||||
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(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\">\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))
|
||||
@ -527,7 +537,7 @@ func (h *Handler) generateFavoriteFolderContent(sb *strings.Builder, folderPath
|
||||
// Fichier markdown
|
||||
displayName := strings.TrimSuffix(name, ".md")
|
||||
sb.WriteString(fmt.Sprintf("%s<div class=\"file %s\">\n", indent, indentClass))
|
||||
sb.WriteString(fmt.Sprintf("%s <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">", indent, relativePath))
|
||||
sb.WriteString(fmt.Sprintf("%s <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\">", indent, relativePath))
|
||||
sb.WriteString(fmt.Sprintf("📄 %s", displayName))
|
||||
sb.WriteString("</a>\n")
|
||||
sb.WriteString(fmt.Sprintf("%s</div>\n", indent))
|
||||
@ -1484,3 +1494,60 @@ func (h *Handler) generateFolderViewMarkdown(folderPath string) string {
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// getLanguage extrait la langue préférée depuis les cookies ou Accept-Language header
|
||||
func (h *Handler) getLanguage(r *http.Request) string {
|
||||
// 1. Vérifier le cookie
|
||||
if cookie, err := r.Cookie("language"); err == nil && cookie.Value != "" {
|
||||
return cookie.Value
|
||||
}
|
||||
|
||||
// 2. Vérifier l'en-tête Accept-Language
|
||||
acceptLang := r.Header.Get("Accept-Language")
|
||||
if acceptLang != "" {
|
||||
// Parse simple: prendre le premier code de langue
|
||||
parts := strings.Split(acceptLang, ",")
|
||||
if len(parts) > 0 {
|
||||
lang := strings.Split(parts[0], ";")[0]
|
||||
lang = strings.Split(lang, "-")[0] // "fr-FR" -> "fr"
|
||||
return strings.TrimSpace(lang)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Par défaut: anglais
|
||||
return "en"
|
||||
}
|
||||
|
||||
// t est un helper pour traduire une clé dans la langue de la requête
|
||||
func (h *Handler) t(r *http.Request, key string, args ...map[string]string) string {
|
||||
lang := h.getLanguage(r)
|
||||
return h.i18n.T(lang, key, args...)
|
||||
}
|
||||
|
||||
// handleI18n sert les fichiers de traduction JSON pour le frontend
|
||||
func (h *Handler) handleI18n(w http.ResponseWriter, r *http.Request) {
|
||||
// Extraire le code de langue depuis l'URL: /api/i18n/en ou /api/i18n/fr
|
||||
lang := strings.TrimPrefix(r.URL.Path, "/api/i18n/")
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
|
||||
// Récupérer les traductions pour cette langue
|
||||
translations, ok := h.i18n.GetTranslations(lang)
|
||||
if !ok {
|
||||
// Fallback vers l'anglais si la langue n'existe pas
|
||||
translations, ok = h.i18n.GetTranslations("en")
|
||||
if !ok {
|
||||
http.Error(w, "translations not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Retourner le JSON
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(translations); err != nil {
|
||||
h.logger.Printf("error encoding translations: %v", err)
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user