Add logo and rename

This commit is contained in:
2025-11-12 17:16:13 +01:00
parent 584a4a0acd
commit f903e28728
49 changed files with 628 additions and 192 deletions

4
API.md
View File

@ -1,4 +1,4 @@
# Project Notes REST API Documentation
# PersoNotes REST API Documentation
Version: **v1**
Base URL: `http://localhost:8080/api/v1`
@ -20,7 +20,7 @@ Base URL: `http://localhost:8080/api/v1`
## Vue d'ensemble
L'API REST de Project Notes permet de gérer vos notes Markdown via HTTP. Elle supporte :
L'API REST de PersoNotes permet de gérer vos notes Markdown via HTTP. Elle supporte :
- **Listage** : Récupérer la liste de toutes les notes avec métadonnées
- **Lecture** : Télécharger une note en JSON ou Markdown brut

View File

@ -1,6 +1,6 @@
# Architecture Overview
Project Notes is a web-based Markdown note-taking application built with a hybrid architecture combining Go backend, HTMX for interactions, and modern JavaScript for UI enhancements.
PersoNotes is a web-based Markdown note-taking application built with a hybrid architecture combining Go backend, HTMX for interactions, and modern JavaScript for UI enhancements.
## Design Philosophy
@ -68,7 +68,7 @@ User → Browser
│ ├─ Inject file tree
│ └─ Return HTML
├─ Load static/dist/project-notes-frontend.es.js (Vite bundle)
├─ Load static/dist/personotes-frontend.es.js (Vite bundle)
│ │
│ ├─ Initialize FileTree (file-tree.js)
│ ├─ Initialize Search (search.js)
@ -237,8 +237,8 @@ frontend/src/
↓ (Vite build)
static/dist/
├── project-notes-frontend.es.js (1.0 MB - ES modules)
└── project-notes-frontend.umd.js (679 KB - UMD)
├── personotes-frontend.es.js (1.0 MB - ES modules)
└── personotes-frontend.umd.js (679 KB - UMD)
↓ (Loaded by browser)
@ -472,7 +472,7 @@ HTTP Request
```nginx
location / {
auth_basic "Project Notes";
auth_basic "PersoNotes";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080;
}

View File

@ -1,6 +1,6 @@
# Changelog
All notable changes to Project Notes will be documented in this file.
All notable changes to PersoNotes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@ -46,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Graceful fallback if package not installed
- **About Page**
- New "About Project Notes" page accessible from sidebar
- New "About PersoNotes" page accessible from sidebar
- Features overview with keyboard shortcuts reference
- Visual guide to all shortcuts with `<kbd>` styling
- Accessible via button next to settings

View File

@ -316,8 +316,8 @@ npm run build # Compile frontend modules to static/dist/
```
Output files (loaded by templates):
- `static/dist/project-notes-frontend.es.js` (ES module)
- `static/dist/project-notes-frontend.umd.js` (UMD format)
- `static/dist/personotes-frontend.es.js` (ES module)
- `static/dist/personotes-frontend.umd.js` (UMD format)
Frontend dependencies (from `frontend/package.json`):
- `@codemirror/basic-setup` (^0.20.0) - Base editor functionality
@ -388,8 +388,8 @@ The frontend uses Vite (`frontend/vite.config.js`) for bundling JavaScript modul
1. Vite reads all source files from `frontend/src/`
2. Resolves npm dependencies (@codemirror packages)
3. Bundles everything into two formats:
- ES module (`project-notes-frontend.es.js`) - 1.0 MB
- UMD (`project-notes-frontend.umd.js`) - 679 KB
- ES module (`personotes-frontend.es.js`) - 1.0 MB
- UMD (`personotes-frontend.umd.js`) - 679 KB
4. Outputs to `static/dist/` where Go server can serve them
5. Templates load the ES module version via `<script type="module">`
@ -737,13 +737,13 @@ Loaded in `templates/index.html`:
### Build Output
The Vite build process produces:
- `static/dist/project-notes-frontend.es.js` - ES module format (1.0 MB, includes all CodeMirror 6 dependencies)
- `static/dist/project-notes-frontend.umd.js` - UMD format (679 KB, legacy compatibility)
- `static/dist/personotes-frontend.es.js` - ES module format (1.0 MB, includes all CodeMirror 6 dependencies)
- `static/dist/personotes-frontend.umd.js` - UMD format (679 KB, legacy compatibility)
## Project Structure
```
project-notes/
personotes/
├── cmd/
│ └── server/
│ └── main.go # Server entry point
@ -776,8 +776,8 @@ project-notes/
│ └── vite.config.js # Vite build configuration
├── static/
│ ├── dist/ # Compiled frontend (generated)
│ │ ├── project-notes-frontend.es.js
│ │ └── project-notes-frontend.umd.js
│ │ ├── personotes-frontend.es.js
│ │ └── personotes-frontend.umd.js
│ └── theme.css # Material Darker theme
├── templates/
│ ├── index.html # Main page layout

View File

@ -2,7 +2,7 @@
## ✅ Fonctionnalité Implémentée
Un système complet de gestion de thèmes a été ajouté à Project Notes, permettant aux utilisateurs de personnaliser l'apparence de l'application.
Un système complet de gestion de thèmes a été ajouté à PersoNotes, permettant aux utilisateurs de personnaliser l'apparence de l'application.
## 📁 Fichiers Créés

View File

@ -1,4 +1,4 @@
# Project Notes
# PersoNotes
A lightweight, web-based Markdown note-taking application with a Go backend and a minimalist frontend built with htmx.
@ -13,7 +13,7 @@ A lightweight, web-based Markdown note-taking application with a Go backend and
- 🛠️ Super Easy to build
- 🚀 Powerful REST API
![Project Notes Interface](image.png)
![PersoNotes Interface](image.png)
## Features
@ -56,8 +56,8 @@ A lightweight, web-based Markdown note-taking application with a Go backend and
1. **Clone the repository:**
```bash
git clone https://github.com/mathieu/project-notes.git
cd project-notes
git clone https://github.com/mathieu/personotes.git
cd personotes
```
2. **Download Go modules:**
```bash

View File

@ -13,9 +13,9 @@ import (
"syscall"
"time"
"github.com/mathieu/project-notes/internal/api"
"github.com/mathieu/project-notes/internal/indexer"
"github.com/mathieu/project-notes/internal/watcher"
"github.com/mathieu/personotes/internal/api"
"github.com/mathieu/personotes/internal/indexer"
"github.com/mathieu/personotes/internal/watcher"
)
func main() {

View File

@ -2,7 +2,7 @@
## Hybrid Architecture
Project Notes uses a **hybrid architecture** that combines the best of multiple paradigms:
PersoNotes uses a **hybrid architecture** that combines the best of multiple paradigms:
### Core Components
@ -240,7 +240,7 @@ Internet → Reverse Proxy (nginx/Caddy)
Basic Auth / OAuth
Project Notes (Go)
PersoNotes (Go)
Filesystem (notes/)
```
@ -248,7 +248,7 @@ Internet → Reverse Proxy (nginx/Caddy)
Example nginx config:
```nginx
location / {
auth_basic "Project Notes";
auth_basic "PersoNotes";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080;
}

View File

@ -1,6 +1,6 @@
# Daily Notes - Documentation
Les **Daily Notes** sont des notes quotidiennes permettant une prise de notes rapide et organisée par date. Cette fonctionnalité s'intègre parfaitement dans Project Notes avec un calendrier interactif et des raccourcis clavier.
Les **Daily Notes** sont des notes quotidiennes permettant une prise de notes rapide et organisée par date. Cette fonctionnalité s'intègre parfaitement dans PersoNotes avec un calendrier interactif et des raccourcis clavier.
## 🎯 Fonctionnalités
@ -385,4 +385,4 @@ if ((event.ctrlKey || event.metaKey) && event.key === 'j') {
**Version** : 1.0.0
**Date** : 2025-01-11
**Auteur** : Project Notes Team
**Auteur** : PersoNotes Team

View File

@ -69,7 +69,7 @@ Le serveur démarre sur `http://localhost:8080`
cp server /usr/local/bin/project-notes
# Créer un utilisateur dédié
pw useradd -n notes -c "Project Notes" -d /var/notes -s /usr/sbin/nologin
pw useradd -n notes -c "PersoNotes" -d /var/notes -s /usr/sbin/nologin
# Créer le dossier de notes
mkdir -p /var/notes/notes

View File

@ -1,6 +1,6 @@
# ⌨️ Raccourcis Clavier - Project Notes
# ⌨️ Raccourcis Clavier - PersoNotes
Cette documentation liste tous les raccourcis clavier disponibles dans l'application Project Notes.
Cette documentation liste tous les raccourcis clavier disponibles dans l'application PersoNotes.
## 📋 Liste des Raccourcis

View File

@ -1,6 +1,6 @@
# Documentation
Ce dossier contient la documentation détaillée des fonctionnalités de Project Notes.
Ce dossier contient la documentation détaillée des fonctionnalités de PersoNotes.
## Guides Disponibles

View File

@ -2,7 +2,7 @@
## Vue d'ensemble
L'application Project Notes dispose d'un système de thèmes complet permettant aux utilisateurs de personnaliser l'apparence de l'interface. Six thèmes sombres professionnels sont disponibles par défaut.
L'application PersoNotes dispose d'un système de thèmes complet permettant aux utilisateurs de personnaliser l'apparence de l'interface. Six thèmes sombres professionnels sont disponibles par défaut.
## Thèmes Disponibles

View File

@ -105,7 +105,7 @@ Everforest est un thème inspiré de la nature avec une palette de couleurs vert
## Installation
Les deux thèmes sont maintenant disponibles par défaut dans Project Notes !
Les deux thèmes sont maintenant disponibles par défaut dans PersoNotes !
### Activation

View File

@ -1,6 +1,6 @@
# Usage Guide
Complete guide for using Project Notes - from creating your first note to advanced features.
Complete guide for using PersoNotes - from creating your first note to advanced features.
## Table of Contents
@ -19,14 +19,14 @@ Complete guide for using Project Notes - from creating your first note to advanc
## Quick Start
The fastest way to get started with Project Notes:
The fastest way to get started with PersoNotes:
1. **Open the application** at `http://localhost:8080`
2. **Press `Ctrl/Cmd+D`** to create today's daily note
3. **Start writing** - the editor saves automatically with `Ctrl/Cmd+S`
4. **Press `Ctrl/Cmd+K`** to search your notes anytime
That's it! You're now using Project Notes.
That's it! You're now using PersoNotes.
---
@ -158,7 +158,7 @@ Your Markdown content...
## Searching Notes
Project Notes includes a powerful search system with two interfaces.
PersoNotes includes a powerful search system with two interfaces.
### Quick Search Modal (Recommended)

View File

@ -16,7 +16,8 @@ function initDailyNotesShortcut() {
if (typeof htmx !== 'undefined') {
htmx.ajax('GET', '/api/daily/today', {
target: '#editor-container',
swap: 'innerHTML'
swap: 'innerHTML',
pushUrl: true
});
}
}

View File

@ -10,6 +10,12 @@ class FileTree {
}
init() {
this.setupEventListeners();
console.log('FileTree initialized with event delegation');
}
setupEventListeners() {
// Utiliser la délégation d'événements sur le conteneur de la sidebar
// Cela évite de perdre les listeners après les swaps htmx
const sidebar = document.getElementById('sidebar');
@ -18,8 +24,13 @@ class FileTree {
return;
}
// Event listener délégué pour les clics sur les folder-headers
sidebar.addEventListener('click', (e) => {
// Supprimer les anciens listeners s'ils existent
if (this.clickHandler) {
sidebar.removeEventListener('click', this.clickHandler);
}
// Créer et stocker le handler pour pouvoir le supprimer plus tard
this.clickHandler = (e) => {
// Ignorer les clics sur les checkboxes
if (e.target.classList.contains('selection-checkbox')) {
return;
@ -41,12 +52,13 @@ class FileTree {
// Ne pas bloquer la propagation pour les fichiers
return;
}
});
};
// Attacher le handler
sidebar.addEventListener('click', this.clickHandler);
// Event listeners délégués pour le drag & drop
this.setupDelegatedDragAndDrop(sidebar);
console.log('FileTree initialized with event delegation');
}
toggleFolder(header) {
@ -69,8 +81,17 @@ class FileTree {
}
setupDelegatedDragAndDrop(sidebar) {
// Supprimer les anciens handlers s'ils existent
if (this.dragStartHandler) {
sidebar.removeEventListener('dragstart', this.dragStartHandler);
sidebar.removeEventListener('dragend', this.dragEndHandler);
sidebar.removeEventListener('dragover', this.dragOverHandler);
sidebar.removeEventListener('dragleave', this.dragLeaveHandler);
sidebar.removeEventListener('drop', this.dropHandler);
}
// Drag start - délégué pour fichiers et dossiers
sidebar.addEventListener('dragstart', (e) => {
this.dragStartHandler = (e) => {
const fileItem = e.target.closest('.file-item');
const folderHeader = e.target.closest('.folder-header');
@ -79,41 +100,48 @@ class FileTree {
} else if (folderHeader && folderHeader.draggable) {
this.handleDragStart(e, 'folder', folderHeader);
}
});
};
// Drag end - délégué
sidebar.addEventListener('dragend', (e) => {
this.dragEndHandler = (e) => {
const fileItem = e.target.closest('.file-item');
const folderHeader = e.target.closest('.folder-header');
if (fileItem || folderHeader) {
this.handleDragEnd(e);
}
});
};
// Drag over - délégué sur les folder-headers
sidebar.addEventListener('dragover', (e) => {
this.dragOverHandler = (e) => {
const folderHeader = e.target.closest('.folder-header');
if (folderHeader) {
this.handleDragOver(e, folderHeader);
}
});
};
// Drag leave - délégué
sidebar.addEventListener('dragleave', (e) => {
this.dragLeaveHandler = (e) => {
const folderHeader = e.target.closest('.folder-header');
if (folderHeader) {
this.handleDragLeave(e, folderHeader);
}
});
};
// Drop - délégué
sidebar.addEventListener('drop', (e) => {
this.dropHandler = (e) => {
const folderHeader = e.target.closest('.folder-header');
if (folderHeader) {
this.handleDrop(e, folderHeader);
}
});
};
// Attacher les handlers
sidebar.addEventListener('dragstart', this.dragStartHandler);
sidebar.addEventListener('dragend', this.dragEndHandler);
sidebar.addEventListener('dragover', this.dragOverHandler);
sidebar.addEventListener('dragleave', this.dragLeaveHandler);
sidebar.addEventListener('drop', this.dropHandler);
// Rendre les dossiers draggables (sauf racine)
this.updateDraggableAttributes();
@ -124,6 +152,7 @@ class FileTree {
// Vérifier si le swap concerne le file-tree
const target = event.detail?.target;
if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) {
console.log('FileTree: afterSwap detected, updating attributes...');
this.updateDraggableAttributes();
}
});
@ -131,10 +160,22 @@ class FileTree {
// Écouter aussi les swaps out-of-band (oob) qui mettent à jour le file-tree
document.body.addEventListener('htmx:oobAfterSwap', (event) => {
const target = event.detail?.target;
// Ignorer les swaps de statut (auto-save-status, save-status)
if (target && target.id === 'file-tree') {
console.log('FileTree: oobAfterSwap detected, updating attributes...');
this.updateDraggableAttributes();
}
});
// Écouter les restaurations d'historique (bouton retour du navigateur)
document.body.addEventListener('htmx:historyRestore', () => {
console.log('FileTree: History restored, re-initializing event listeners...');
// Réinitialiser complètement les event listeners après restauration de l'historique
setTimeout(() => {
this.setupEventListeners();
this.updateDraggableAttributes();
}, 50);
});
}
updateDraggableAttributes() {
@ -415,7 +456,8 @@ window.handleNewNote = function(event) {
if (typeof htmx !== 'undefined') {
htmx.ajax('GET', `/api/notes/${encodeURIComponent(noteName)}`, {
target: '#editor-container',
swap: 'innerHTML'
swap: 'innerHTML',
pushUrl: true
});
}
}

View File

@ -0,0 +1,193 @@
/**
* SidebarSections - Gère les sections rétractables de la sidebar
* Permet de replier/déplier les favoris et le répertoire de notes
*/
class SidebarSections {
constructor() {
this.sections = {
favorites: { key: 'sidebar-favorites-expanded', defaultState: true },
notes: { key: 'sidebar-notes-expanded', defaultState: true }
};
this.init();
}
init() {
console.log('SidebarSections: Initialisation...');
// Restaurer l'état sauvegardé au démarrage
this.restoreStates();
// Écouter les événements HTMX pour réattacher les handlers après les swaps
document.body.addEventListener('htmx:afterSwap', (event) => {
const targetId = event.detail?.target?.id;
if (targetId === 'favorites-list') {
console.log('Favoris rechargés, restauration de l\'état...');
setTimeout(() => this.restoreSectionState('favorites'), 50);
}
if (targetId === 'file-tree') {
console.log('File-tree rechargé, restauration de l\'état...');
setTimeout(() => this.restoreSectionState('notes'), 50);
}
});
document.body.addEventListener('htmx:oobAfterSwap', (event) => {
const targetId = event.detail?.target?.id;
// Ne restaurer l'état que pour les swaps du file-tree complet
// Les swaps de statut (auto-save-status) ne doivent pas déclencher la restauration
if (targetId === 'file-tree') {
console.log('File-tree rechargé (oob), restauration de l\'état...');
setTimeout(() => this.restoreSectionState('notes'), 50);
}
});
// Écouter les restaurations d'historique (bouton retour du navigateur)
document.body.addEventListener('htmx:historyRestore', () => {
console.log('SidebarSections: History restored, restoring section states...');
// Restaurer les états des sections après restauration de l'historique
setTimeout(() => {
this.restoreSectionState('favorites');
this.restoreSectionState('notes');
}, 100);
});
console.log('SidebarSections: Initialisé');
}
/**
* Récupère l'état sauvegardé d'une section
*/
getSectionState(sectionName) {
const section = this.sections[sectionName];
if (!section) return true;
const saved = localStorage.getItem(section.key);
return saved !== null ? saved === 'true' : section.defaultState;
}
/**
* Sauvegarde l'état d'une section
*/
setSectionState(sectionName, isExpanded) {
const section = this.sections[sectionName];
if (!section) return;
localStorage.setItem(section.key, isExpanded.toString());
console.log(`État sauvegardé: ${sectionName} = ${isExpanded}`);
}
/**
* Toggle une section (ouvert/fermé)
*/
toggleSection(sectionName, headerElement) {
if (!headerElement) {
console.error(`Header element not found for section: ${sectionName}`);
return;
}
const contentElement = headerElement.nextElementSibling;
const toggleIcon = headerElement.querySelector('.section-toggle');
if (!contentElement) {
console.error(`Content element not found for section: ${sectionName}`);
return;
}
const isCurrentlyExpanded = contentElement.style.display !== 'none';
const newState = !isCurrentlyExpanded;
if (newState) {
// Ouvrir
contentElement.style.display = 'block';
if (toggleIcon) {
toggleIcon.classList.add('expanded');
}
} else {
// Fermer
contentElement.style.display = 'none';
if (toggleIcon) {
toggleIcon.classList.remove('expanded');
}
}
this.setSectionState(sectionName, newState);
console.log(`Section ${sectionName} ${newState ? 'ouverte' : 'fermée'}`);
}
/**
* Restaure l'état d'une section depuis localStorage
*/
restoreSectionState(sectionName) {
const isExpanded = this.getSectionState(sectionName);
const header = document.querySelector(`[data-section="${sectionName}"]`);
if (!header) {
console.warn(`Header not found for section: ${sectionName}`);
return;
}
const content = header.nextElementSibling;
const toggleIcon = header.querySelector('.section-toggle');
if (!content) {
console.warn(`Content not found for section: ${sectionName}`);
return;
}
if (isExpanded) {
content.style.display = 'block';
if (toggleIcon) {
toggleIcon.classList.add('expanded');
}
} else {
content.style.display = 'none';
if (toggleIcon) {
toggleIcon.classList.remove('expanded');
}
}
console.log(`État restauré: ${sectionName} = ${isExpanded ? 'ouvert' : 'fermé'}`);
}
/**
* Restaure tous les états au démarrage
*/
restoreStates() {
// Attendre que le DOM soit complètement chargé et que HTMX ait fini de charger les contenus
// Délai augmenté pour correspondre aux délais des triggers HTMX (250ms + marge)
setTimeout(() => {
this.restoreSectionState('favorites');
this.restoreSectionState('notes');
}, 400);
}
}
/**
* Fonction globale pour toggle une section (appelée depuis le HTML)
*/
window.toggleSidebarSection = function(sectionName, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
const headerElement = event?.currentTarget || document.querySelector(`[data-section="${sectionName}"]`);
if (window.sidebarSections && headerElement) {
window.sidebarSections.toggleSection(sectionName, headerElement);
}
};
/**
* Initialisation automatique
*/
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.sidebarSections = new SidebarSections();
});
} else {
// DOM déjà chargé
window.sidebarSections = new SidebarSections();
}

View File

@ -13,8 +13,8 @@ export default defineConfig({
build: {
lib: {
entry: 'src/main.js', // This will be our main entry point
name: 'ProjectNotesFrontend',
fileName: (format) => `project-notes-frontend.${format}.js`
name: 'PersoNotesFrontend',
fileName: (format) => `personotes-frontend.${format}.js`
},
outDir: '../static/dist', // Output to a new 'dist' folder inside the existing 'static' directory
emptyOutDir: true,

View File

@ -138,8 +138,8 @@ frontend/
## Build
\`npm run build\` génère:
- \`project-notes-frontend.es.js\` (ES modules)
- \`project-notes-frontend.umd.js\` (UMD)
- \`personotes-frontend.es.js\` (ES modules)
- \`personotes-frontend.umd.js\` (UMD)
## Watch Mode
@ -737,7 +737,7 @@ Not aligned with minimalist approach."
# Quelques notes à la racine
create_note "welcome.md" "Welcome" '"default"' \
"# Welcome to Project Notes
"# Welcome to PersoNotes
This is your personal note-taking app.

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/mathieu/project-notes
module github.com/mathieu/personotes
go 1.22

View File

@ -212,6 +212,12 @@ func (h *Handler) handleDailyCalendar(w http.ResponseWriter, r *http.Request, ye
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
}
// Parser année et mois
year, err := strconv.Atoi(yearStr)
if err != nil || year < 1900 || year > 2100 {
@ -353,6 +359,12 @@ func (h *Handler) handleDailyRecent(w http.ResponseWriter, r *http.Request) {
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
}
// Chercher les daily notes des 14 derniers jours (au cas où certaines manquent)
recentNotes := make([]*DailyNoteInfo, 0, 7)

View File

@ -87,38 +87,45 @@ func (h *Handler) handleFavorites(w http.ResponseWriter, r *http.Request) {
// handleGetFavorites retourne la liste des favoris (HTML)
func (h *Handler) handleGetFavorites(w http.ResponseWriter, r *http.Request) {
// Pas de redirection ici car cet endpoint est utilisé par HTMX ET par fetch()
// depuis le JavaScript pour mettre à jour la liste après ajout/suppression
h.renderFavoritesList(w)
}
// renderFavoritesList rend le template des favoris (méthode interne)
func (h *Handler) renderFavoritesList(w http.ResponseWriter) {
favorites, err := h.loadFavorites()
if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err)
// En cas d'erreur, retourner une liste vide plutôt qu'une erreur 500
favorites = &FavoritesData{Items: []Favorite{}}
}
// Enrichir avec les informations des fichiers
enrichedFavorites := []map[string]interface{}{}
for _, fav := range favorites.Items {
absPath := filepath.Join(h.notesDir, fav.Path)
// Vérifier si le fichier/dossier existe toujours
if _, err := os.Stat(absPath); os.IsNotExist(err) {
continue // Skip les favoris qui n'existent plus
}
item := map[string]interface{}{
"Path": fav.Path,
"IsDir": fav.IsDir,
"Title": fav.Title,
"Icon": getIcon(fav.IsDir, fav.Path),
}
enrichedFavorites = append(enrichedFavorites, item)
}
data := map[string]interface{}{
"Favorites": enrichedFavorites,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := h.templates.ExecuteTemplate(w, "favorites.html", data); err != nil {
h.logger.Printf("Erreur template favoris: %v", err)
@ -192,24 +199,34 @@ func (h *Handler) handleAddFavorite(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return
}
// Retourner la liste mise à jour
h.handleGetFavorites(w, r)
h.renderFavoritesList(w)
}
// handleRemoveFavorite retire un élément des favoris
func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
// Pour DELETE, il faut lire le body manuellement
body, _ := io.ReadAll(r.Body)
r.Body.Close()
values, _ := url.ParseQuery(string(body))
r.Form = values
// Pour DELETE, il faut toujours lire le body manuellement
// car ParseForm() ne lit pas le body pour les méthodes DELETE
body, err := io.ReadAll(r.Body)
if err != nil {
h.logger.Printf("Erreur lecture body: %v", err)
http.Error(w, "Erreur lecture requête", http.StatusBadRequest)
return
}
path := r.FormValue("path")
r.Body.Close()
values, err := url.ParseQuery(string(body))
if err != nil {
h.logger.Printf("Erreur parsing query: %v", err)
http.Error(w, "Erreur parsing requête", http.StatusBadRequest)
return
}
path := values.Get("path")
if path == "" {
h.logger.Printf("Chemin requis manquant dans la requête")
http.Error(w, "Chemin requis", http.StatusBadRequest)
return
}
@ -243,15 +260,15 @@ func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) {
}
favorites.Items = newItems
if err := h.saveFavorites(favorites); err != nil {
h.logger.Printf("Erreur sauvegarde favoris: %v", err)
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return
}
// Retourner la liste mise à jour
h.handleGetFavorites(w, r)
h.renderFavoritesList(w)
}
// handleReorderFavorites réorganise l'ordre des favoris

View File

@ -16,7 +16,7 @@ import (
yaml "gopkg.in/yaml.v3"
"github.com/mathieu/project-notes/internal/indexer"
"github.com/mathieu/personotes/internal/indexer"
)
// TreeNode représente un nœud dans l'arborescence des fichiers
@ -235,6 +235,12 @@ func (h *Handler) handleFileTree(w http.ResponseWriter, r *http.Request) {
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
}
tree, err := h.buildFileTree()
if err != nil {
h.logger.Printf("erreur lors de la construction de l arborescence: %v", err)
@ -261,18 +267,26 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
return
}
// Si ce n'est pas une requête HTMX (ex: accès direct via URL), rediriger vers la page principale
if r.Header.Get("HX-Request") == "" {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// Générer le contenu Markdown avec la liste de toutes les notes
content := h.generateHomeMarkdown()
// Utiliser le template editor.html pour afficher la page d'accueil
data := struct {
Filename string
Content string
IsHome bool
Filename string
Content string
IsHome bool
Backlinks []BacklinkInfo
}{
Filename: "🏠 Accueil - Index",
Content: content,
IsHome: true,
Filename: "🏠 Accueil - Index",
Content: content,
IsHome: true,
Backlinks: nil, // Pas de backlinks pour la page d'accueil
}
err := h.templates.ExecuteTemplate(w, "editor.html", data)
@ -577,13 +591,15 @@ func (h *Handler) createAndRenderNote(w http.ResponseWriter, r *http.Request, fi
initialContent := "---\n" + string(fmBytes) + "---\n\n# " + newFM.Title + "\n\nCommencez à écrire votre note ici..."
data := struct {
Filename string
Content string
IsHome bool
Filename string
Content string
IsHome bool
Backlinks []BacklinkInfo
}{
Filename: filename,
Content: initialContent,
IsHome: false,
Filename: filename,
Content: initialContent,
IsHome: false,
Backlinks: nil, // Pas de backlinks pour une nouvelle note
}
err = h.templates.ExecuteTemplate(w, "editor.html", data)
@ -599,6 +615,11 @@ func (h *Handler) handleSearch(w http.ResponseWriter, r *http.Request) {
return
}
// Pas de redirection ici car cet endpoint est utilisé par:
// 1. La sidebar de recherche (HTMX)
// 2. La modale de recherche Ctrl+K (fetch)
// 3. Le link inserter pour créer des backlinks (fetch)
query := strings.TrimSpace(r.URL.Query().Get("query"))
if query == "" {
query = strings.TrimSpace(r.URL.Query().Get("tag"))
@ -673,6 +694,13 @@ func (h *Handler) handleDeleteNote(w http.ResponseWriter, r *http.Request, filen
}
func (h *Handler) handleGetNote(w http.ResponseWriter, r *http.Request, filename string) {
// Si ce n'est pas une requête HTMX (ex: refresh navigateur), rediriger vers la page principale
// Cela évite d'afficher un fragment HTML sans CSS lors d'un Ctrl+F5
if r.Header.Get("HX-Request") == "" {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fullPath := filepath.Join(h.notesDir, filename)
content, err := os.ReadFile(fullPath)
if err != nil {
@ -800,8 +828,12 @@ func (h *Handler) handlePostNote(w http.ResponseWriter, r *http.Request, filenam
}
}()
// Repondre a htmx pour vider l'editeur et rafraichir l'arborescence
h.renderFileTreeOOB(w)
// Pour les notes existantes, ne pas recharger le file-tree (évite de fermer les dossiers ouverts)
// Le file-tree sera rechargé uniquement lors de la création de nouveaux fichiers/dossiers
if isNewFile {
// Nouvelle note : mettre à jour le file-tree pour l'afficher
h.renderFileTreeOOB(w)
}
// Répondre avec les statuts de sauvegarde OOB
nowStr := time.Now().Format("15:04:05")

View File

@ -11,7 +11,7 @@ import (
"strings"
"testing"
"github.com/mathieu/project-notes/internal/indexer"
"github.com/mathieu/personotes/internal/indexer"
)
func newTestHandler(t *testing.T, notesDir string) *Handler {

View File

@ -12,7 +12,7 @@ import (
yaml "gopkg.in/yaml.v3"
"github.com/mathieu/project-notes/internal/indexer"
"github.com/mathieu/personotes/internal/indexer"
)
// REST API Structures

View File

@ -11,7 +11,7 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/mathieu/project-notes/internal/indexer"
"github.com/mathieu/personotes/internal/indexer"
)
// Watcher observe les modifications dans le repertoire des notes et relance l indexation au besoin.

View File

@ -7,96 +7,68 @@
"added_at": "2025-11-11T13:55:41.091354066+01:00",
"order": 0
},
{
"path": "documentation/old-ideas.md",
"is_dir": false,
"title": "old-ideas",
"added_at": "2025-11-11T13:55:46.034104752+01:00",
"order": 1
},
{
"path": "documentation/bienvenue.md",
"is_dir": false,
"title": "bienvenue",
"added_at": "2025-11-11T13:55:46.95626865+01:00",
"order": 2
},
{
"path": "research/ai",
"is_dir": true,
"title": "ai",
"added_at": "2025-11-11T13:55:49.371541279+01:00",
"order": 3
},
{
"path": "research/design/typography.md",
"is_dir": false,
"title": "typography",
"added_at": "2025-11-11T13:55:51.238574069+01:00",
"order": 4
"order": 1
},
{
"path": "research/design/ui-inspiration.md",
"is_dir": false,
"title": "ui-inspiration",
"added_at": "2025-11-11T14:20:49.985321698+01:00",
"order": 5
},
{
"path": "research/tech/go-performance.md",
"is_dir": false,
"title": "go-performance",
"added_at": "2025-11-11T14:20:53.861619294+01:00",
"order": 6
"order": 2
},
{
"path": "research/tech/websockets.md",
"is_dir": false,
"title": "websockets",
"added_at": "2025-11-11T14:20:55.347335695+01:00",
"order": 7
"order": 3
},
{
"path": "tasks/backlog.md",
"is_dir": false,
"title": "backlog",
"added_at": "2025-11-11T14:20:57.762787363+01:00",
"order": 8
"order": 4
},
{
"path": "ideas/client-feedback.md",
"is_dir": false,
"title": "client-feedback",
"added_at": "2025-11-11T14:22:16.497953232+01:00",
"order": 9
"order": 5
},
{
"path": "ideas/collaboration.md",
"is_dir": false,
"title": "collaboration",
"added_at": "2025-11-11T14:22:18.012032002+01:00",
"order": 10
"order": 6
},
{
"path": "ideas/mobile-app.md",
"is_dir": false,
"title": "mobile-app",
"added_at": "2025-11-11T14:22:19.048311608+01:00",
"order": 11
"order": 7
},
{
"path": "meetings/2025",
"is_dir": true,
"title": "2025",
"added_at": "2025-11-11T14:22:21.531283601+01:00",
"order": 12
"order": 8
},
{
"path": "meetings/outscale.md",
"is_dir": false,
"title": "outscale",
"added_at": "2025-11-11T14:22:22.519332518+01:00",
"order": 13
"order": 9
}
]
}

28
notes/daily/2025/11/12.md Normal file
View File

@ -0,0 +1,28 @@
---
title: Daily Note - 2025-11-12
date: 12-11-2025
last_modified: 12-11-2025:10:37
tags:
- daily
---
# 📅 Mercredi 12 novembre 2025
## 🎯 Objectifs du jour
-
## 📝 Notes
- C'est la note du jour
Elle est cool
/i
## ✅ Accompli
-
## 💭 Réflexions
-
## 🔗 Liens
-

View File

@ -1,5 +1,5 @@
---
title: Bienvenue dans Project Notes
title: Bienvenue dans PersoNotes
date: 08-11-2025
last_modified: 09-11-2025:01:13
tags:
@ -17,7 +17,7 @@ C'est mon application de prise de note
## J'espére qu'elle va bien marcher
# Bienvenue dans Project Notes
# Bienvenue dans PersoNotes
Bienvenue dans votre application de prise de notes en Markdown ! Cette page vous explique comment utiliser l'application et le format front matter.

View File

@ -1,8 +1,10 @@
---
title: "Sprint Planning January"
date: "10-11-2025"
last_modified: "10-11-2025:19:21"
tags: ["meeting", "planning"]
title: Sprint Planning January
date: 10-11-2025
last_modified: 12-11-2025:10:26
tags:
- meeting
- planning
---
# Sprint Planning - Janvier 2025
@ -26,3 +28,6 @@ tags: ["meeting", "planning"]
- Complexité du drag & drop de dossiers
- Tests E2E à mettre en place
/il

View File

@ -1,7 +1,7 @@
---
title: 2025 Learning Goals
date: 10-11-2025
last_modified: 11-11-2025:17:15
last_modified: 12-11-2025:10:28
tags:
- personal
- learning
@ -28,5 +28,3 @@ tags:
2. The Pragmatic Programmer
3. Clean Architecture
/

View File

@ -1,7 +1,7 @@
---
title: API Design
date: 10-11-2025
last_modified: 11-11-2025:15:23
last_modified: 12-11-2025:10:32
tags:
- projet
- backend
@ -29,3 +29,6 @@ Pour l'instant, pas d'authentification. À implémenter avec JWT.
<!-- -->
## This is a test

View File

@ -1,8 +1,11 @@
---
title: "CodeMirror Integration"
date: "10-11-2025"
last_modified: "10-11-2025:19:21"
tags: ["projet", "frontend", "editor"]
title: CodeMirror Integration
date: 10-11-2025
last_modified: 12-11-2025:09:37
tags:
- projet
- frontend
- editor
---
# CodeMirror 6 Integration
@ -22,6 +25,7 @@ Système de commandes rapides avec `/`:
- /table - Tableau
- /code - Bloc de code
## Auto-save
Déclenché après 2 secondes d'inactivité.

View File

@ -23,8 +23,8 @@ frontend/
## Build
`npm run build` génère:
- `project-notes-frontend.es.js` (ES modules)
- `project-notes-frontend.umd.js` (UMD)
- `personotes-frontend.es.js` (ES modules)
- `personotes-frontend.umd.js` (UMD)
## Watch Mode

View File

@ -11,4 +11,4 @@ ddddddddlffdfdddddddddddddd
[texte](url)
<a href="#" onclick="return false;" hx-get="/api/notes/documentation/bienvenue.md" hx-target="#editor-container" hx-swap="innerHTML">Bienvenue dans Project Notes</a>
<a href="#" onclick="return false;" hx-get="/api/notes/documentation/bienvenue.md" hx-target="#editor-container" hx-swap="innerHTML">Bienvenue dans PersoNotes</a>

View File

@ -5,7 +5,7 @@ last_modified: "10-11-2025:19:21"
tags: ["default"]
---
# Welcome to Project Notes
# Welcome to PersoNotes
This is your personal note-taking app.

BIN
personotes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

76
static/images/logo.svg Normal file
View File

@ -0,0 +1,76 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<style>
.dog-face { fill: #E8DCC8; }
.dog-fur-dark { fill: #8B7355; }
.dog-ear-inner { fill: #D4A574; }
.dog-eye { fill: #2C1810; }
.dog-nose { fill: #1A0F0A; }
.dog-mouth { fill: none; stroke: #2C1810; stroke-width: 1; stroke-linecap: round; }
.pencil-body { fill: #F4C430; }
.pencil-tip { fill: #2C1810; }
.pencil-eraser { fill: #FF6B9D; }
.pencil-band { fill: #C0C0C0; }
.highlight { fill: white; opacity: 0.6; }
</style>
</defs>
<!-- Oreille gauche -->
<path class="dog-fur-dark" d="M 18 15 L 12 5 L 22 8 Z"/>
<path class="dog-ear-inner" d="M 18 15 L 14 8 L 20 10 Z"/>
<!-- Oreille droite -->
<path class="dog-fur-dark" d="M 46 15 L 52 5 L 42 8 Z"/>
<path class="dog-ear-inner" d="M 46 15 L 50 8 L 44 10 Z"/>
<!-- Tache de fourrure sombre sur le front -->
<ellipse class="dog-fur-dark" cx="32" cy="20" rx="12" ry="8"/>
<!-- Visage principal -->
<circle class="dog-face" cx="32" cy="32" r="20"/>
<!-- Taches de fourrure claires sur les joues -->
<ellipse class="dog-face" cx="22" cy="35" rx="8" ry="10" opacity="0.8"/>
<ellipse class="dog-face" cx="42" cy="35" rx="8" ry="10" opacity="0.8"/>
<!-- Yeux -->
<ellipse class="dog-eye" cx="26" cy="28" rx="3" ry="4"/>
<ellipse class="dog-eye" cx="38" cy="28" rx="3" ry="4"/>
<!-- Reflets dans les yeux pour le rendre plus vivant -->
<circle class="highlight" cx="27" cy="27" r="1.5"/>
<circle class="highlight" cx="39" cy="27" r="1.5"/>
<!-- Museau -->
<ellipse class="dog-face" cx="32" cy="38" rx="10" ry="8"/>
<!-- Nez -->
<ellipse class="dog-nose" cx="32" cy="36" rx="3" ry="2.5"/>
<!-- Crayon dans la bouche -->
<!-- Corps du crayon (partie hexagonale) -->
<g transform="translate(32, 42) rotate(0)">
<!-- Partie principale du crayon -->
<rect class="pencil-body" x="-15" y="-2" width="30" height="4" rx="0.5"/>
<!-- Bandes décoratives -->
<rect class="pencil-band" x="-15" y="-2" width="2" height="4"/>
<rect class="pencil-band" x="13" y="-2" width="2" height="4"/>
<!-- Pointe du crayon (gauche) -->
<path class="pencil-body" d="M -15 -2 L -19 0 L -15 2 Z"/>
<path class="pencil-tip" d="M -19 -0.5 L -22 0 L -19 0.5 Z"/>
<!-- Gomme (droite) -->
<rect class="pencil-eraser" x="15" y="-2" width="3" height="4" rx="0.5"/>
<!-- Reflet sur le crayon -->
<rect class="highlight" x="-12" y="-1.5" width="20" height="1" rx="0.5" opacity="0.3"/>
</g>
<!-- Petites moustaches -->
<line class="dog-mouth" x1="18" y1="36" x2="12" y2="35"/>
<line class="dog-mouth" x1="18" y1="38" x2="12" y2="39"/>
<line class="dog-mouth" x1="46" y1="36" x2="52" y2="35"/>
<line class="dog-mouth" x1="46" y1="38" x2="52" y2="39"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,5 +1,5 @@
/*
* Project Notes - Material Darker Theme
* PersoNotes - Material Darker Theme
* Inspired by Material Design with dark palette
*/
@ -219,6 +219,38 @@ aside h2,
margin: 0 0 var(--spacing-sm) 0;
}
/* Sections rétractables de la sidebar */
.sidebar-section-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0;
user-select: none;
transition: opacity var(--transition-fast);
}
.sidebar-section-header:hover {
opacity: 0.8;
}
.section-toggle {
font-size: 0.75rem;
color: var(--text-secondary);
display: inline-block;
transition: transform var(--transition-fast);
width: 1rem;
text-align: center;
}
.section-toggle.expanded {
transform: rotate(90deg);
}
.sidebar-section-content {
overflow: hidden;
transition: opacity var(--transition-fast);
}
aside hr {
border: none;
border-top: 1px solid var(--border-primary);

View File

@ -1,5 +1,5 @@
/*
* Project Notes - Multi-Theme System
* PersoNotes - Multi-Theme System
* Supports: Material Dark (default), Monokai Dark, Dracula, One Dark, Solarized Dark, Nord
*/

View File

@ -1,7 +1,7 @@
<div id="about-content" style="padding: 3rem; max-width: 900px; margin: 0 auto;">
<div style="text-align: center; margin-bottom: 3rem;">
<h1 style="font-size: 2.5rem; color: #c792ea; margin-bottom: 1rem;">
📝 About Project Notes
📝 About PersoNotes
</h1>
<p style="font-size: 1.2rem; color: var(--text-secondary); margin-bottom: 2rem;">
Un gestionnaire de notes Markdown moderne et puissant

View File

@ -36,6 +36,7 @@
hx-get="/api/daily/{{.Date.Format "2006-01-02"}}"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
{{end}}
title="{{if .HasNote}}Note du {{.Date.Format "02/01/2006"}}{{else}}{{.Date.Format "02/01/2006"}} - Pas de note{{end}}">
<span class="calendar-day-number">{{.Day}}</span>
@ -49,6 +50,7 @@
hx-get="/api/daily/today"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
title="Ouvrir la note du jour">
📅 Aujourd'hui
</button>

View File

@ -6,6 +6,7 @@
hx-get="/api/daily/{{.Date.Format "2006-01-02"}}"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
title="Note du {{.Date.Format "02/01/2006"}}">
<span class="daily-recent-icon">📄</span>
<div class="daily-recent-content">

View File

@ -10,7 +10,7 @@
{{end}}
</label>
{{if .IsHome}}
<button type="button" class="toggle-preview-btn" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" title="Actualiser la page d'accueil">
<button type="button" class="toggle-preview-btn" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" title="Actualiser la page d'accueil">
🔄 Actualiser
</button>
{{else}}
@ -32,7 +32,7 @@
<ul class="backlinks-list">
{{range .Backlinks}}
<li class="backlink-item">
<a href="#" onclick="return false;" hx-get="/api/notes/{{.Path}}" hx-target="#editor-container" hx-swap="innerHTML" class="backlink-link">
<a href="#" onclick="return false;" hx-get="/api/notes/{{.Path}}" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="backlink-link">
📄 {{.Title}}
</a>
</li>

View File

@ -16,7 +16,8 @@
data-path="{{.Path}}"
hx-get="/api/notes/{{.Path}}"
hx-target="#editor-container"
hx-swap="innerHTML">
hx-swap="innerHTML"
hx-push-url="true">
<span class="favorite-icon"></span>
<span class="favorite-file-icon">{{.Icon}}</span>
<span class="favorite-name">{{.Title}}</span>

View File

@ -1,21 +1,25 @@
<!-- Indicateur de racine (non cliquable) -->
<div class="root-indicator">
<!-- Indicateur de racine (maintenant cliquable et rétractable) -->
<div class="sidebar-section-header" data-section="notes" onclick="toggleSidebarSection('notes', event)" style="cursor: pointer;">
<span class="section-toggle expanded"></span>
<span class="folder-icon">🏠</span>
<span class="folder-name">Racine</span>
<span class="root-hint">(notes/)</span>
</div>
<hr style="border: none; border-top: 1px solid var(--border-primary); margin: 0.75rem 0;">
<!-- Contenu rétractable du file-tree -->
<div class="sidebar-section-content" id="notes-content" style="display: block;">
<hr style="border: none; border-top: 1px solid var(--border-primary); margin: 0.75rem 0;">
{{if .Tree}}
{{if .Tree.Children}}
{{template "tree-node" .Tree}}
{{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}}
{{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}}
{{if .Tree}}
{{if .Tree.Children}}
{{template "tree-node" .Tree}}
{{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}}
{{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}}
</div>
{{define "tree-node"}}
{{range .Children}}
@ -43,6 +47,7 @@
hx-get="/api/notes/{{.Path}}"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
draggable="true">
📄 {{.Name}}
</a>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Notes</title>
<title>PersoNotes</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
@ -21,15 +21,19 @@
<script src="/frontend/src/font-manager.js"></script>
<script src="/frontend/src/vim-mode-manager.js"></script>
<script src="/frontend/src/favorites.js"></script>
<script src="/frontend/src/sidebar-sections.js"></script>
<script src="/frontend/src/keyboard-shortcuts.js"></script>
<script type="module" src="/static/dist/project-notes-frontend.es.js"></script>
<script type="module" src="/static/dist/personotes-frontend.es.js"></script>
</head>
<body>
<header>
<button id="toggle-sidebar-btn" title="Afficher/Masquer la barre latérale (Ctrl/Cmd+B)" style="background: none; border: none; padding: 0; margin-right: 1rem; cursor: pointer; color: var(--text-primary); display: flex; align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
</button>
<h1>📝 Project Notes</h1>
<div style="display: flex; align-items: center; gap: 0.75rem;">
<img src="/static/images/logo.svg" alt="Logo" style="width: 40px; height: 40px;">
<h1 style="margin: 0;">PersoNotes</h1>
</div>
<input
type="search"
name="query"
@ -44,6 +48,7 @@
hx-get="/api/home"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
style="white-space: nowrap;"
title="Retour à la page d'accueil (Ctrl/Cmd+H)">
🏠 Accueil
@ -52,6 +57,7 @@
hx-get="/api/daily/today"
hx-target="#editor-container"
hx-swap="innerHTML"
hx-push-url="true"
style="white-space: nowrap;"
title="Note du jour (Ctrl/Cmd+D)">
📅 Note du jour
@ -478,13 +484,18 @@
<hr>
<section>
<h2 class="sidebar-section-title">⭐ Favoris</h2>
<div id="favorites-list"
hx-get="/api/favorites"
hx-trigger="load"
hx-swap="innerHTML">
<!-- Les favoris apparaîtront ici -->
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center;">Chargement...</p>
<div class="sidebar-section-header" data-section="favorites" onclick="toggleSidebarSection('favorites', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
<span class="section-toggle expanded"></span>
<h2 class="sidebar-section-title" style="margin: 0; flex: 1;">⭐ Favoris</h2>
</div>
<div class="sidebar-section-content" id="favorites-content" style="display: block;">
<div id="favorites-list"
hx-get="/api/favorites"
hx-trigger="load once delay:100ms"
hx-swap="innerHTML">
<!-- Les favoris apparaîtront ici -->
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center;">Chargement...</p>
</div>
</div>
</section>
@ -494,7 +505,7 @@
<h2 class="sidebar-section-title">📅 Daily Notes</h2>
<div id="daily-calendar-container"
hx-get="/api/daily/calendar/{{.Now.Format "2006/01"}}"
hx-trigger="load"
hx-trigger="load once delay:150ms"
hx-swap="innerHTML">
<!-- Le calendrier apparaîtra ici -->
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center;">Chargement...</p>
@ -503,7 +514,7 @@
<h3 style="font-size: 0.8rem; margin-top: var(--spacing-md); margin-bottom: var(--spacing-sm); color: var(--text-secondary);">Récentes</h3>
<div id="daily-recent-container"
hx-get="/api/daily/recent"
hx-trigger="load"
hx-trigger="load once delay:200ms"
hx-swap="innerHTML">
<!-- Les notes récentes apparaîtront ici -->
</div>
@ -521,7 +532,7 @@
</svg>
</button>
</div>
<div id="file-tree" hx-get="/api/tree" hx-trigger="load" hx-swap="innerHTML">
<div id="file-tree" hx-get="/api/tree" hx-trigger="load once delay:250ms" hx-swap="innerHTML">
<!-- L'arborescence des fichiers apparaîtra ici -->
<p style="color: var(--text-muted); font-size: 0.85rem;">Chargement...</p>
</div>
@ -546,7 +557,7 @@
<!-- Bouton À propos -->
<button
class="sidebar-action-btn"
title="À propos de Project Notes"
title="À propos de PersoNotes"
hx-get="/api/about"
hx-target="#editor-container"
hx-swap="innerHTML"
@ -562,7 +573,7 @@
<main id="main-content">
<div id="editor-container"
hx-get="/api/home"
hx-trigger="load"
hx-trigger="load once"
hx-swap="innerHTML">
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 50vh; text-align: center; color: var(--text-secondary);">
<svg style="width: 64px; height: 64px; margin-bottom: 1rem; opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@ -10,7 +10,8 @@
class="search-result-link"
hx-get="/api/notes/{{.Path}}"
hx-target="#editor-container"
hx-swap="innerHTML">
hx-swap="innerHTML"
hx-push-url="true">
<div class="search-result-icon">📄</div>
<div class="search-result-content">
<div class="search-result-header">