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** Version: **v1**
Base URL: `http://localhost:8080/api/v1` Base URL: `http://localhost:8080/api/v1`
@ -20,7 +20,7 @@ Base URL: `http://localhost:8080/api/v1`
## Vue d'ensemble ## 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 - **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 - **Lecture** : Télécharger une note en JSON ou Markdown brut

View File

@ -1,6 +1,6 @@
# Architecture Overview # 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 ## Design Philosophy
@ -68,7 +68,7 @@ User → Browser
│ ├─ Inject file tree │ ├─ Inject file tree
│ └─ Return HTML │ └─ 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 FileTree (file-tree.js)
│ ├─ Initialize Search (search.js) │ ├─ Initialize Search (search.js)
@ -237,8 +237,8 @@ frontend/src/
↓ (Vite build) ↓ (Vite build)
static/dist/ static/dist/
├── project-notes-frontend.es.js (1.0 MB - ES modules) ├── personotes-frontend.es.js (1.0 MB - ES modules)
└── project-notes-frontend.umd.js (679 KB - UMD) └── personotes-frontend.umd.js (679 KB - UMD)
↓ (Loaded by browser) ↓ (Loaded by browser)
@ -472,7 +472,7 @@ HTTP Request
```nginx ```nginx
location / { location / {
auth_basic "Project Notes"; auth_basic "PersoNotes";
auth_basic_user_file /etc/nginx/.htpasswd; auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080; proxy_pass http://localhost:8080;
} }

View File

@ -1,6 +1,6 @@
# Changelog # 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/), 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). 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 - Graceful fallback if package not installed
- **About Page** - **About Page**
- New "About Project Notes" page accessible from sidebar - New "About PersoNotes" page accessible from sidebar
- Features overview with keyboard shortcuts reference - Features overview with keyboard shortcuts reference
- Visual guide to all shortcuts with `<kbd>` styling - Visual guide to all shortcuts with `<kbd>` styling
- Accessible via button next to settings - 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): Output files (loaded by templates):
- `static/dist/project-notes-frontend.es.js` (ES module) - `static/dist/personotes-frontend.es.js` (ES module)
- `static/dist/project-notes-frontend.umd.js` (UMD format) - `static/dist/personotes-frontend.umd.js` (UMD format)
Frontend dependencies (from `frontend/package.json`): Frontend dependencies (from `frontend/package.json`):
- `@codemirror/basic-setup` (^0.20.0) - Base editor functionality - `@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/` 1. Vite reads all source files from `frontend/src/`
2. Resolves npm dependencies (@codemirror packages) 2. Resolves npm dependencies (@codemirror packages)
3. Bundles everything into two formats: 3. Bundles everything into two formats:
- ES module (`project-notes-frontend.es.js`) - 1.0 MB - ES module (`personotes-frontend.es.js`) - 1.0 MB
- UMD (`project-notes-frontend.umd.js`) - 679 KB - UMD (`personotes-frontend.umd.js`) - 679 KB
4. Outputs to `static/dist/` where Go server can serve them 4. Outputs to `static/dist/` where Go server can serve them
5. Templates load the ES module version via `<script type="module">` 5. Templates load the ES module version via `<script type="module">`
@ -737,13 +737,13 @@ Loaded in `templates/index.html`:
### Build Output ### Build Output
The Vite build process produces: 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/personotes-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.umd.js` - UMD format (679 KB, legacy compatibility)
## Project Structure ## Project Structure
``` ```
project-notes/ personotes/
├── cmd/ ├── cmd/
│ └── server/ │ └── server/
│ └── main.go # Server entry point │ └── main.go # Server entry point
@ -776,8 +776,8 @@ project-notes/
│ └── vite.config.js # Vite build configuration │ └── vite.config.js # Vite build configuration
├── static/ ├── static/
│ ├── dist/ # Compiled frontend (generated) │ ├── dist/ # Compiled frontend (generated)
│ │ ├── project-notes-frontend.es.js │ │ ├── personotes-frontend.es.js
│ │ └── project-notes-frontend.umd.js │ │ └── personotes-frontend.umd.js
│ └── theme.css # Material Darker theme │ └── theme.css # Material Darker theme
├── templates/ ├── templates/
│ ├── index.html # Main page layout │ ├── index.html # Main page layout

View File

@ -2,7 +2,7 @@
## ✅ Fonctionnalité Implémentée ## ✅ 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 ## 📁 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. 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 - 🛠️ Super Easy to build
- 🚀 Powerful REST API - 🚀 Powerful REST API
![Project Notes Interface](image.png) ![PersoNotes Interface](image.png)
## Features ## Features
@ -56,8 +56,8 @@ A lightweight, web-based Markdown note-taking application with a Go backend and
1. **Clone the repository:** 1. **Clone the repository:**
```bash ```bash
git clone https://github.com/mathieu/project-notes.git git clone https://github.com/mathieu/personotes.git
cd project-notes cd personotes
``` ```
2. **Download Go modules:** 2. **Download Go modules:**
```bash ```bash

View File

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

View File

@ -2,7 +2,7 @@
## Hybrid Architecture ## 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 ### Core Components
@ -240,7 +240,7 @@ Internet → Reverse Proxy (nginx/Caddy)
Basic Auth / OAuth Basic Auth / OAuth
Project Notes (Go) PersoNotes (Go)
Filesystem (notes/) Filesystem (notes/)
``` ```
@ -248,7 +248,7 @@ Internet → Reverse Proxy (nginx/Caddy)
Example nginx config: Example nginx config:
```nginx ```nginx
location / { location / {
auth_basic "Project Notes"; auth_basic "PersoNotes";
auth_basic_user_file /etc/nginx/.htpasswd; auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080; proxy_pass http://localhost:8080;
} }

View File

@ -1,6 +1,6 @@
# Daily Notes - Documentation # 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 ## 🎯 Fonctionnalités
@ -385,4 +385,4 @@ if ((event.ctrlKey || event.metaKey) && event.key === 'j') {
**Version** : 1.0.0 **Version** : 1.0.0
**Date** : 2025-01-11 **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 cp server /usr/local/bin/project-notes
# Créer un utilisateur dédié # 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 # Créer le dossier de notes
mkdir -p /var/notes/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 ## 📋 Liste des Raccourcis

View File

@ -1,6 +1,6 @@
# Documentation # 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 ## Guides Disponibles

View File

@ -2,7 +2,7 @@
## Vue d'ensemble ## 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 ## 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 ## 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 ### Activation

View File

@ -1,6 +1,6 @@
# Usage Guide # 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 ## Table of Contents
@ -19,14 +19,14 @@ Complete guide for using Project Notes - from creating your first note to advanc
## Quick Start ## 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` 1. **Open the application** at `http://localhost:8080`
2. **Press `Ctrl/Cmd+D`** to create today's daily note 2. **Press `Ctrl/Cmd+D`** to create today's daily note
3. **Start writing** - the editor saves automatically with `Ctrl/Cmd+S` 3. **Start writing** - the editor saves automatically with `Ctrl/Cmd+S`
4. **Press `Ctrl/Cmd+K`** to search your notes anytime 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 ## 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) ### Quick Search Modal (Recommended)

View File

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

View File

@ -10,6 +10,12 @@ class FileTree {
} }
init() { 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 // 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 // Cela évite de perdre les listeners après les swaps htmx
const sidebar = document.getElementById('sidebar'); const sidebar = document.getElementById('sidebar');
@ -18,8 +24,13 @@ class FileTree {
return; return;
} }
// Event listener délégué pour les clics sur les folder-headers // Supprimer les anciens listeners s'ils existent
sidebar.addEventListener('click', (e) => { 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 // Ignorer les clics sur les checkboxes
if (e.target.classList.contains('selection-checkbox')) { if (e.target.classList.contains('selection-checkbox')) {
return; return;
@ -41,12 +52,13 @@ class FileTree {
// Ne pas bloquer la propagation pour les fichiers // Ne pas bloquer la propagation pour les fichiers
return; return;
} }
}); };
// Attacher le handler
sidebar.addEventListener('click', this.clickHandler);
// Event listeners délégués pour le drag & drop // Event listeners délégués pour le drag & drop
this.setupDelegatedDragAndDrop(sidebar); this.setupDelegatedDragAndDrop(sidebar);
console.log('FileTree initialized with event delegation');
} }
toggleFolder(header) { toggleFolder(header) {
@ -69,8 +81,17 @@ class FileTree {
} }
setupDelegatedDragAndDrop(sidebar) { 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 // Drag start - délégué pour fichiers et dossiers
sidebar.addEventListener('dragstart', (e) => { this.dragStartHandler = (e) => {
const fileItem = e.target.closest('.file-item'); const fileItem = e.target.closest('.file-item');
const folderHeader = e.target.closest('.folder-header'); const folderHeader = e.target.closest('.folder-header');
@ -79,41 +100,48 @@ class FileTree {
} else if (folderHeader && folderHeader.draggable) { } else if (folderHeader && folderHeader.draggable) {
this.handleDragStart(e, 'folder', folderHeader); this.handleDragStart(e, 'folder', folderHeader);
} }
}); };
// Drag end - délégué // Drag end - délégué
sidebar.addEventListener('dragend', (e) => { this.dragEndHandler = (e) => {
const fileItem = e.target.closest('.file-item'); const fileItem = e.target.closest('.file-item');
const folderHeader = e.target.closest('.folder-header'); const folderHeader = e.target.closest('.folder-header');
if (fileItem || folderHeader) { if (fileItem || folderHeader) {
this.handleDragEnd(e); this.handleDragEnd(e);
} }
}); };
// Drag over - délégué sur les folder-headers // Drag over - délégué sur les folder-headers
sidebar.addEventListener('dragover', (e) => { this.dragOverHandler = (e) => {
const folderHeader = e.target.closest('.folder-header'); const folderHeader = e.target.closest('.folder-header');
if (folderHeader) { if (folderHeader) {
this.handleDragOver(e, folderHeader); this.handleDragOver(e, folderHeader);
} }
}); };
// Drag leave - délégué // Drag leave - délégué
sidebar.addEventListener('dragleave', (e) => { this.dragLeaveHandler = (e) => {
const folderHeader = e.target.closest('.folder-header'); const folderHeader = e.target.closest('.folder-header');
if (folderHeader) { if (folderHeader) {
this.handleDragLeave(e, folderHeader); this.handleDragLeave(e, folderHeader);
} }
}); };
// Drop - délégué // Drop - délégué
sidebar.addEventListener('drop', (e) => { this.dropHandler = (e) => {
const folderHeader = e.target.closest('.folder-header'); const folderHeader = e.target.closest('.folder-header');
if (folderHeader) { if (folderHeader) {
this.handleDrop(e, 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) // Rendre les dossiers draggables (sauf racine)
this.updateDraggableAttributes(); this.updateDraggableAttributes();
@ -124,6 +152,7 @@ class FileTree {
// Vérifier si le swap concerne le file-tree // Vérifier si le swap concerne le file-tree
const target = event.detail?.target; const target = event.detail?.target;
if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) { if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) {
console.log('FileTree: afterSwap detected, updating attributes...');
this.updateDraggableAttributes(); this.updateDraggableAttributes();
} }
}); });
@ -131,10 +160,22 @@ class FileTree {
// Écouter aussi les swaps out-of-band (oob) qui mettent à jour le file-tree // Écouter aussi les swaps out-of-band (oob) qui mettent à jour le file-tree
document.body.addEventListener('htmx:oobAfterSwap', (event) => { document.body.addEventListener('htmx:oobAfterSwap', (event) => {
const target = event.detail?.target; const target = event.detail?.target;
// Ignorer les swaps de statut (auto-save-status, save-status)
if (target && target.id === 'file-tree') { if (target && target.id === 'file-tree') {
console.log('FileTree: oobAfterSwap detected, updating attributes...');
this.updateDraggableAttributes(); 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() { updateDraggableAttributes() {
@ -415,7 +456,8 @@ window.handleNewNote = function(event) {
if (typeof htmx !== 'undefined') { if (typeof htmx !== 'undefined') {
htmx.ajax('GET', `/api/notes/${encodeURIComponent(noteName)}`, { htmx.ajax('GET', `/api/notes/${encodeURIComponent(noteName)}`, {
target: '#editor-container', 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: { build: {
lib: { lib: {
entry: 'src/main.js', // This will be our main entry point entry: 'src/main.js', // This will be our main entry point
name: 'ProjectNotesFrontend', name: 'PersoNotesFrontend',
fileName: (format) => `project-notes-frontend.${format}.js` fileName: (format) => `personotes-frontend.${format}.js`
}, },
outDir: '../static/dist', // Output to a new 'dist' folder inside the existing 'static' directory outDir: '../static/dist', // Output to a new 'dist' folder inside the existing 'static' directory
emptyOutDir: true, emptyOutDir: true,

View File

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

View File

@ -212,6 +212,12 @@ func (h *Handler) handleDailyCalendar(w http.ResponseWriter, r *http.Request, ye
return 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 // Parser année et mois
year, err := strconv.Atoi(yearStr) year, err := strconv.Atoi(yearStr)
if err != nil || year < 1900 || year > 2100 { if err != nil || year < 1900 || year > 2100 {
@ -353,6 +359,12 @@ func (h *Handler) handleDailyRecent(w http.ResponseWriter, r *http.Request) {
return 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) // Chercher les daily notes des 14 derniers jours (au cas où certaines manquent)
recentNotes := make([]*DailyNoteInfo, 0, 7) 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) // handleGetFavorites retourne la liste des favoris (HTML)
func (h *Handler) handleGetFavorites(w http.ResponseWriter, r *http.Request) { 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() favorites, err := h.loadFavorites()
if err != nil { if err != nil {
h.logger.Printf("Erreur chargement favoris: %v", err) h.logger.Printf("Erreur chargement favoris: %v", err)
// En cas d'erreur, retourner une liste vide plutôt qu'une erreur 500 // En cas d'erreur, retourner une liste vide plutôt qu'une erreur 500
favorites = &FavoritesData{Items: []Favorite{}} favorites = &FavoritesData{Items: []Favorite{}}
} }
// Enrichir avec les informations des fichiers // Enrichir avec les informations des fichiers
enrichedFavorites := []map[string]interface{}{} enrichedFavorites := []map[string]interface{}{}
for _, fav := range favorites.Items { for _, fav := range favorites.Items {
absPath := filepath.Join(h.notesDir, fav.Path) absPath := filepath.Join(h.notesDir, fav.Path)
// Vérifier si le fichier/dossier existe toujours // Vérifier si le fichier/dossier existe toujours
if _, err := os.Stat(absPath); os.IsNotExist(err) { if _, err := os.Stat(absPath); os.IsNotExist(err) {
continue // Skip les favoris qui n'existent plus continue // Skip les favoris qui n'existent plus
} }
item := map[string]interface{}{ item := map[string]interface{}{
"Path": fav.Path, "Path": fav.Path,
"IsDir": fav.IsDir, "IsDir": fav.IsDir,
"Title": fav.Title, "Title": fav.Title,
"Icon": getIcon(fav.IsDir, fav.Path), "Icon": getIcon(fav.IsDir, fav.Path),
} }
enrichedFavorites = append(enrichedFavorites, item) enrichedFavorites = append(enrichedFavorites, item)
} }
data := map[string]interface{}{ data := map[string]interface{}{
"Favorites": enrichedFavorites, "Favorites": enrichedFavorites,
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := h.templates.ExecuteTemplate(w, "favorites.html", data); err != nil { if err := h.templates.ExecuteTemplate(w, "favorites.html", data); err != nil {
h.logger.Printf("Erreur template favoris: %v", err) 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) http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return return
} }
// Retourner la liste mise à jour // Retourner la liste mise à jour
h.handleGetFavorites(w, r) h.renderFavoritesList(w)
} }
// handleRemoveFavorite retire un élément des favoris // handleRemoveFavorite retire un élément des favoris
func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) { func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil { // Pour DELETE, il faut toujours lire le body manuellement
// Pour DELETE, il faut lire le body manuellement // car ParseForm() ne lit pas le body pour les méthodes DELETE
body, _ := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
r.Body.Close() if err != nil {
values, _ := url.ParseQuery(string(body)) h.logger.Printf("Erreur lecture body: %v", err)
r.Form = values http.Error(w, "Erreur lecture requête", http.StatusBadRequest)
return
} }
r.Body.Close()
path := r.FormValue("path")
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 == "" { if path == "" {
h.logger.Printf("Chemin requis manquant dans la requête")
http.Error(w, "Chemin requis", http.StatusBadRequest) http.Error(w, "Chemin requis", http.StatusBadRequest)
return return
} }
@ -243,15 +260,15 @@ func (h *Handler) handleRemoveFavorite(w http.ResponseWriter, r *http.Request) {
} }
favorites.Items = newItems favorites.Items = newItems
if err := h.saveFavorites(favorites); err != nil { if err := h.saveFavorites(favorites); err != nil {
h.logger.Printf("Erreur sauvegarde favoris: %v", err) h.logger.Printf("Erreur sauvegarde favoris: %v", err)
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError) http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
return return
} }
// Retourner la liste mise à jour // Retourner la liste mise à jour
h.handleGetFavorites(w, r) h.renderFavoritesList(w)
} }
// handleReorderFavorites réorganise l'ordre des favoris // handleReorderFavorites réorganise l'ordre des favoris

View File

@ -16,7 +16,7 @@ import (
yaml "gopkg.in/yaml.v3" 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 // 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 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() tree, err := h.buildFileTree()
if err != nil { if err != nil {
h.logger.Printf("erreur lors de la construction de l arborescence: %v", err) 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 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 // Générer le contenu Markdown avec la liste de toutes les notes
content := h.generateHomeMarkdown() content := h.generateHomeMarkdown()
// Utiliser le template editor.html pour afficher la page d'accueil // Utiliser le template editor.html pour afficher la page d'accueil
data := struct { data := struct {
Filename string Filename string
Content string Content string
IsHome bool IsHome bool
Backlinks []BacklinkInfo
}{ }{
Filename: "🏠 Accueil - Index", Filename: "🏠 Accueil - Index",
Content: content, Content: content,
IsHome: true, IsHome: true,
Backlinks: nil, // Pas de backlinks pour la page d'accueil
} }
err := h.templates.ExecuteTemplate(w, "editor.html", data) 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..." initialContent := "---\n" + string(fmBytes) + "---\n\n# " + newFM.Title + "\n\nCommencez à écrire votre note ici..."
data := struct { data := struct {
Filename string Filename string
Content string Content string
IsHome bool IsHome bool
Backlinks []BacklinkInfo
}{ }{
Filename: filename, Filename: filename,
Content: initialContent, Content: initialContent,
IsHome: false, IsHome: false,
Backlinks: nil, // Pas de backlinks pour une nouvelle note
} }
err = h.templates.ExecuteTemplate(w, "editor.html", data) err = h.templates.ExecuteTemplate(w, "editor.html", data)
@ -599,6 +615,11 @@ func (h *Handler) handleSearch(w http.ResponseWriter, r *http.Request) {
return 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")) query := strings.TrimSpace(r.URL.Query().Get("query"))
if query == "" { if query == "" {
query = strings.TrimSpace(r.URL.Query().Get("tag")) 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) { 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) fullPath := filepath.Join(h.notesDir, filename)
content, err := os.ReadFile(fullPath) content, err := os.ReadFile(fullPath)
if err != nil { 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 // Pour les notes existantes, ne pas recharger le file-tree (évite de fermer les dossiers ouverts)
h.renderFileTreeOOB(w) // 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 // Répondre avec les statuts de sauvegarde OOB
nowStr := time.Now().Format("15:04:05") nowStr := time.Now().Format("15:04:05")

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
"github.com/fsnotify/fsnotify" "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. // 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", "added_at": "2025-11-11T13:55:41.091354066+01:00",
"order": 0 "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", "path": "research/ai",
"is_dir": true, "is_dir": true,
"title": "ai", "title": "ai",
"added_at": "2025-11-11T13:55:49.371541279+01:00", "added_at": "2025-11-11T13:55:49.371541279+01:00",
"order": 3 "order": 1
},
{
"path": "research/design/typography.md",
"is_dir": false,
"title": "typography",
"added_at": "2025-11-11T13:55:51.238574069+01:00",
"order": 4
}, },
{ {
"path": "research/design/ui-inspiration.md", "path": "research/design/ui-inspiration.md",
"is_dir": false, "is_dir": false,
"title": "ui-inspiration", "title": "ui-inspiration",
"added_at": "2025-11-11T14:20:49.985321698+01:00", "added_at": "2025-11-11T14:20:49.985321698+01:00",
"order": 5 "order": 2
},
{
"path": "research/tech/go-performance.md",
"is_dir": false,
"title": "go-performance",
"added_at": "2025-11-11T14:20:53.861619294+01:00",
"order": 6
}, },
{ {
"path": "research/tech/websockets.md", "path": "research/tech/websockets.md",
"is_dir": false, "is_dir": false,
"title": "websockets", "title": "websockets",
"added_at": "2025-11-11T14:20:55.347335695+01:00", "added_at": "2025-11-11T14:20:55.347335695+01:00",
"order": 7 "order": 3
}, },
{ {
"path": "tasks/backlog.md", "path": "tasks/backlog.md",
"is_dir": false, "is_dir": false,
"title": "backlog", "title": "backlog",
"added_at": "2025-11-11T14:20:57.762787363+01:00", "added_at": "2025-11-11T14:20:57.762787363+01:00",
"order": 8 "order": 4
}, },
{ {
"path": "ideas/client-feedback.md", "path": "ideas/client-feedback.md",
"is_dir": false, "is_dir": false,
"title": "client-feedback", "title": "client-feedback",
"added_at": "2025-11-11T14:22:16.497953232+01:00", "added_at": "2025-11-11T14:22:16.497953232+01:00",
"order": 9 "order": 5
}, },
{ {
"path": "ideas/collaboration.md", "path": "ideas/collaboration.md",
"is_dir": false, "is_dir": false,
"title": "collaboration", "title": "collaboration",
"added_at": "2025-11-11T14:22:18.012032002+01:00", "added_at": "2025-11-11T14:22:18.012032002+01:00",
"order": 10 "order": 6
}, },
{ {
"path": "ideas/mobile-app.md", "path": "ideas/mobile-app.md",
"is_dir": false, "is_dir": false,
"title": "mobile-app", "title": "mobile-app",
"added_at": "2025-11-11T14:22:19.048311608+01:00", "added_at": "2025-11-11T14:22:19.048311608+01:00",
"order": 11 "order": 7
}, },
{ {
"path": "meetings/2025", "path": "meetings/2025",
"is_dir": true, "is_dir": true,
"title": "2025", "title": "2025",
"added_at": "2025-11-11T14:22:21.531283601+01:00", "added_at": "2025-11-11T14:22:21.531283601+01:00",
"order": 12 "order": 8
}, },
{ {
"path": "meetings/outscale.md", "path": "meetings/outscale.md",
"is_dir": false, "is_dir": false,
"title": "outscale", "title": "outscale",
"added_at": "2025-11-11T14:22:22.519332518+01:00", "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 date: 08-11-2025
last_modified: 09-11-2025:01:13 last_modified: 09-11-2025:01:13
tags: tags:
@ -17,7 +17,7 @@ C'est mon application de prise de note
## J'espére qu'elle va bien marcher ## 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. 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" title: Sprint Planning January
date: "10-11-2025" date: 10-11-2025
last_modified: "10-11-2025:19:21" last_modified: 12-11-2025:10:26
tags: ["meeting", "planning"] tags:
- meeting
- planning
--- ---
# Sprint Planning - Janvier 2025 # Sprint Planning - Janvier 2025
@ -26,3 +28,6 @@ tags: ["meeting", "planning"]
- Complexité du drag & drop de dossiers - Complexité du drag & drop de dossiers
- Tests E2E à mettre en place - Tests E2E à mettre en place
/il

View File

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

View File

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

View File

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

View File

@ -11,4 +11,4 @@ ddddddddlffdfdddddddddddddd
[texte](url) [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"] tags: ["default"]
--- ---
# Welcome to Project Notes # Welcome to PersoNotes
This is your personal note-taking app. 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 * Inspired by Material Design with dark palette
*/ */
@ -219,6 +219,38 @@ aside h2,
margin: 0 0 var(--spacing-sm) 0; 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 { aside hr {
border: none; border: none;
border-top: 1px solid var(--border-primary); 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 * 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 id="about-content" style="padding: 3rem; max-width: 900px; margin: 0 auto;">
<div style="text-align: center; margin-bottom: 3rem;"> <div style="text-align: center; margin-bottom: 3rem;">
<h1 style="font-size: 2.5rem; color: #c792ea; margin-bottom: 1rem;"> <h1 style="font-size: 2.5rem; color: #c792ea; margin-bottom: 1rem;">
📝 About Project Notes 📝 About PersoNotes
</h1> </h1>
<p style="font-size: 1.2rem; color: var(--text-secondary); margin-bottom: 2rem;"> <p style="font-size: 1.2rem; color: var(--text-secondary); margin-bottom: 2rem;">
Un gestionnaire de notes Markdown moderne et puissant Un gestionnaire de notes Markdown moderne et puissant

View File

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

View File

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

View File

@ -10,7 +10,7 @@
{{end}} {{end}}
</label> </label>
{{if .IsHome}} {{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 🔄 Actualiser
</button> </button>
{{else}} {{else}}
@ -32,7 +32,7 @@
<ul class="backlinks-list"> <ul class="backlinks-list">
{{range .Backlinks}} {{range .Backlinks}}
<li class="backlink-item"> <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}} 📄 {{.Title}}
</a> </a>
</li> </li>

View File

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

View File

@ -1,21 +1,25 @@
<!-- Indicateur de racine (non cliquable) --> <!-- Indicateur de racine (maintenant cliquable et rétractable) -->
<div class="root-indicator"> <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-icon">🏠</span>
<span class="folder-name">Racine</span> <span class="folder-name">Racine</span>
<span class="root-hint">(notes/)</span> <span class="root-hint">(notes/)</span>
</div> </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}}
{{if .Tree.Children}} {{if .Tree.Children}}
{{template "tree-node" .Tree}} {{template "tree-node" .Tree}}
{{else}} {{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p> <p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}} {{end}}
{{else}} {{else}}
<p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p> <p style="color: var(--text-muted); font-size: 0.85rem;">Aucune note trouvée.</p>
{{end}} {{end}}
</div>
{{define "tree-node"}} {{define "tree-node"}}
{{range .Children}} {{range .Children}}
@ -43,6 +47,7 @@
hx-get="/api/notes/{{.Path}}" hx-get="/api/notes/{{.Path}}"
hx-target="#editor-container" hx-target="#editor-container"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-push-url="true"
draggable="true"> draggable="true">
📄 {{.Name}} 📄 {{.Name}}
</a> </a>

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <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/font-manager.js"></script>
<script src="/frontend/src/vim-mode-manager.js"></script> <script src="/frontend/src/vim-mode-manager.js"></script>
<script src="/frontend/src/favorites.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 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> </head>
<body> <body>
<header> <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;"> <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> <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> </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 <input
type="search" type="search"
name="query" name="query"
@ -44,6 +48,7 @@
hx-get="/api/home" hx-get="/api/home"
hx-target="#editor-container" hx-target="#editor-container"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-push-url="true"
style="white-space: nowrap;" style="white-space: nowrap;"
title="Retour à la page d'accueil (Ctrl/Cmd+H)"> title="Retour à la page d'accueil (Ctrl/Cmd+H)">
🏠 Accueil 🏠 Accueil
@ -52,6 +57,7 @@
hx-get="/api/daily/today" hx-get="/api/daily/today"
hx-target="#editor-container" hx-target="#editor-container"
hx-swap="innerHTML" hx-swap="innerHTML"
hx-push-url="true"
style="white-space: nowrap;" style="white-space: nowrap;"
title="Note du jour (Ctrl/Cmd+D)"> title="Note du jour (Ctrl/Cmd+D)">
📅 Note du jour 📅 Note du jour
@ -478,13 +484,18 @@
<hr> <hr>
<section> <section>
<h2 class="sidebar-section-title">⭐ Favoris</h2> <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;">
<div id="favorites-list" <span class="section-toggle expanded"></span>
hx-get="/api/favorites" <h2 class="sidebar-section-title" style="margin: 0; flex: 1;">⭐ Favoris</h2>
hx-trigger="load" </div>
hx-swap="innerHTML"> <div class="sidebar-section-content" id="favorites-content" style="display: block;">
<!-- Les favoris apparaîtront ici --> <div id="favorites-list"
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center;">Chargement...</p> 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> </div>
</section> </section>
@ -494,7 +505,7 @@
<h2 class="sidebar-section-title">📅 Daily Notes</h2> <h2 class="sidebar-section-title">📅 Daily Notes</h2>
<div id="daily-calendar-container" <div id="daily-calendar-container"
hx-get="/api/daily/calendar/{{.Now.Format "2006/01"}}" hx-get="/api/daily/calendar/{{.Now.Format "2006/01"}}"
hx-trigger="load" hx-trigger="load once delay:150ms"
hx-swap="innerHTML"> hx-swap="innerHTML">
<!-- Le calendrier apparaîtra ici --> <!-- Le calendrier apparaîtra ici -->
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center;">Chargement...</p> <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> <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" <div id="daily-recent-container"
hx-get="/api/daily/recent" hx-get="/api/daily/recent"
hx-trigger="load" hx-trigger="load once delay:200ms"
hx-swap="innerHTML"> hx-swap="innerHTML">
<!-- Les notes récentes apparaîtront ici --> <!-- Les notes récentes apparaîtront ici -->
</div> </div>
@ -521,7 +532,7 @@
</svg> </svg>
</button> </button>
</div> </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 --> <!-- L'arborescence des fichiers apparaîtra ici -->
<p style="color: var(--text-muted); font-size: 0.85rem;">Chargement...</p> <p style="color: var(--text-muted); font-size: 0.85rem;">Chargement...</p>
</div> </div>
@ -546,7 +557,7 @@
<!-- Bouton À propos --> <!-- Bouton À propos -->
<button <button
class="sidebar-action-btn" class="sidebar-action-btn"
title="À propos de Project Notes" title="À propos de PersoNotes"
hx-get="/api/about" hx-get="/api/about"
hx-target="#editor-container" hx-target="#editor-container"
hx-swap="innerHTML" hx-swap="innerHTML"
@ -562,7 +573,7 @@
<main id="main-content"> <main id="main-content">
<div id="editor-container" <div id="editor-container"
hx-get="/api/home" hx-get="/api/home"
hx-trigger="load" hx-trigger="load once"
hx-swap="innerHTML"> 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);"> <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"> <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" class="search-result-link"
hx-get="/api/notes/{{.Path}}" hx-get="/api/notes/{{.Path}}"
hx-target="#editor-container" hx-target="#editor-container"
hx-swap="innerHTML"> hx-swap="innerHTML"
hx-push-url="true">
<div class="search-result-icon">📄</div> <div class="search-result-icon">📄</div>
<div class="search-result-content"> <div class="search-result-content">
<div class="search-result-header"> <div class="search-result-header">