Changement des ilink vers markdown pur
This commit is contained in:
@ -81,6 +81,7 @@ func main() {
|
|||||||
mux.Handle("/api/daily", apiHandler) // Daily notes
|
mux.Handle("/api/daily", apiHandler) // Daily notes
|
||||||
mux.Handle("/api/daily/", apiHandler) // Daily notes
|
mux.Handle("/api/daily/", apiHandler) // Daily notes
|
||||||
mux.Handle("/api/favorites", apiHandler) // Favorites
|
mux.Handle("/api/favorites", apiHandler) // Favorites
|
||||||
|
mux.Handle("/api/folder/", apiHandler) // Folder view
|
||||||
mux.Handle("/api/notes/", apiHandler)
|
mux.Handle("/api/notes/", apiHandler)
|
||||||
mux.Handle("/api/tree", apiHandler)
|
mux.Handle("/api/tree", apiHandler)
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* DailyNotes - Gère les raccourcis et interactions pour les daily notes
|
* DailyNotes - Gère les raccourcis et interactions pour les daily notes
|
||||||
*/
|
*/
|
||||||
@ -23,7 +24,7 @@ function initDailyNotesShortcut() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Daily notes shortcuts initialized (Ctrl/Cmd+D)');
|
debug('Daily notes shortcuts initialized (Ctrl/Cmd+D)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
45
frontend/src/debug.js
Normal file
45
frontend/src/debug.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Debug utility - Conditional logging
|
||||||
|
* Set DEBUG to true to enable console logs, false to disable
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Change this to false in production to disable all debug logs
|
||||||
|
export const DEBUG = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional console.log
|
||||||
|
* Only logs if DEBUG is true
|
||||||
|
*/
|
||||||
|
export function debug(...args) {
|
||||||
|
if (DEBUG) {
|
||||||
|
console.log(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional console.warn
|
||||||
|
* Only logs if DEBUG is true
|
||||||
|
*/
|
||||||
|
export function debugWarn(...args) {
|
||||||
|
if (DEBUG) {
|
||||||
|
console.warn(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional console.error
|
||||||
|
* Always logs errors regardless of DEBUG flag
|
||||||
|
*/
|
||||||
|
export function debugError(...args) {
|
||||||
|
console.error(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional console.info
|
||||||
|
* Only logs if DEBUG is true
|
||||||
|
*/
|
||||||
|
export function debugInfo(...args) {
|
||||||
|
if (DEBUG) {
|
||||||
|
console.info(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState } from '@codemirror/state';
|
||||||
import { EditorView } from '@codemirror/view';
|
import { EditorView } from '@codemirror/view';
|
||||||
import { basicSetup } from '@codemirror/basic-setup';
|
import { basicSetup } from '@codemirror/basic-setup';
|
||||||
@ -13,7 +14,7 @@ let vimExtension = null;
|
|||||||
try {
|
try {
|
||||||
const { vim } = await import('@replit/codemirror-vim');
|
const { vim } = await import('@replit/codemirror-vim');
|
||||||
vimExtension = vim;
|
vimExtension = vim;
|
||||||
console.log('✅ Vim extension loaded and ready');
|
debug('✅ Vim extension loaded and ready');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Vim extension not available:', error.message);
|
console.warn('⚠️ Vim extension not available:', error.message);
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ class MarkdownEditor {
|
|||||||
if (window.vimModeManager && window.vimModeManager.isEnabled()) {
|
if (window.vimModeManager && window.vimModeManager.isEnabled()) {
|
||||||
if (vimExtension) {
|
if (vimExtension) {
|
||||||
extensions.push(vimExtension());
|
extensions.push(vimExtension());
|
||||||
console.log('✅ Vim mode enabled in editor');
|
debug('✅ Vim mode enabled in editor');
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ Vim mode requested but extension not loaded yet');
|
console.warn('⚠️ Vim mode requested but extension not loaded yet');
|
||||||
}
|
}
|
||||||
@ -246,10 +247,28 @@ class MarkdownEditor {
|
|||||||
const html = marked.parse(contentWithoutFrontMatter);
|
const html = marked.parse(contentWithoutFrontMatter);
|
||||||
// Permettre les attributs HTMX et onclick dans DOMPurify
|
// Permettre les attributs HTMX et onclick dans DOMPurify
|
||||||
const cleanHtml = DOMPurify.sanitize(html, {
|
const cleanHtml = DOMPurify.sanitize(html, {
|
||||||
ADD_ATTR: ['hx-get', 'hx-target', 'hx-swap', 'onclick']
|
ADD_ATTR: ['hx-get', 'hx-target', 'hx-swap', 'hx-push-url', 'onclick']
|
||||||
});
|
});
|
||||||
this.preview.innerHTML = cleanHtml;
|
this.preview.innerHTML = cleanHtml;
|
||||||
|
|
||||||
|
// Post-processing : convertir les liens Markdown vers .md en liens HTMX cliquables
|
||||||
|
this.preview.querySelectorAll('a[href$=".md"]').forEach(link => {
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
// Ne traiter que les liens relatifs (pas les URLs complètes http://)
|
||||||
|
if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//')) {
|
||||||
|
debug('[Preview] Converting Markdown link to HTMX:', href);
|
||||||
|
|
||||||
|
// Transformer en lien HTMX interne
|
||||||
|
link.setAttribute('hx-get', `/api/notes/${href}`);
|
||||||
|
link.setAttribute('hx-target', '#editor-container');
|
||||||
|
link.setAttribute('hx-swap', 'innerHTML');
|
||||||
|
link.setAttribute('hx-push-url', 'true');
|
||||||
|
link.setAttribute('href', '#');
|
||||||
|
link.setAttribute('onclick', 'return false;');
|
||||||
|
link.classList.add('internal-link');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Traiter les nouveaux éléments HTMX
|
// Traiter les nouveaux éléments HTMX
|
||||||
if (typeof htmx !== 'undefined') {
|
if (typeof htmx !== 'undefined') {
|
||||||
htmx.process(this.preview);
|
htmx.process(this.preview);
|
||||||
@ -289,7 +308,7 @@ class MarkdownEditor {
|
|||||||
const targetElement = link.getAttribute('hx-target') || '#editor-container';
|
const targetElement = link.getAttribute('hx-target') || '#editor-container';
|
||||||
const swapMethod = link.getAttribute('hx-swap') || 'innerHTML';
|
const swapMethod = link.getAttribute('hx-swap') || 'innerHTML';
|
||||||
|
|
||||||
console.log('[InternalLink] Clicked:', target);
|
debug('[InternalLink] Clicked:', target);
|
||||||
|
|
||||||
if (target && typeof htmx !== 'undefined') {
|
if (target && typeof htmx !== 'undefined') {
|
||||||
htmx.ajax('GET', target, {
|
htmx.ajax('GET', target, {
|
||||||
@ -300,7 +319,7 @@ class MarkdownEditor {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[Preview] Setup', freshLinks.length, 'internal link handlers');
|
debug('[Preview] Setup', freshLinks.length, 'internal link handlers');
|
||||||
}
|
}
|
||||||
|
|
||||||
syncToTextarea() {
|
syncToTextarea() {
|
||||||
@ -334,7 +353,7 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reloadWithVimMode() {
|
async reloadWithVimMode() {
|
||||||
console.log('Reloading editor with Vim mode...');
|
debug('Reloading editor with Vim mode...');
|
||||||
await this.initEditor();
|
await this.initEditor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -654,7 +673,7 @@ class SlashCommands {
|
|||||||
|
|
||||||
// Commande spéciale avec modal (comme /ilink)
|
// Commande spéciale avec modal (comme /ilink)
|
||||||
if (command.isModal && command.handler) {
|
if (command.isModal && command.handler) {
|
||||||
console.log('Executing modal command:', command.name);
|
debug('Executing modal command:', command.name);
|
||||||
// NE PAS cacher la palette tout de suite car le handler a besoin de slashPos
|
// NE PAS cacher la palette tout de suite car le handler a besoin de slashPos
|
||||||
// La palette sera cachée par le handler lui-même
|
// La palette sera cachée par le handler lui-même
|
||||||
command.handler();
|
command.handler();
|
||||||
@ -685,7 +704,7 @@ class SlashCommands {
|
|||||||
// Sauvegarder la position du slash IMMÉDIATEMENT avant toute autre opération
|
// Sauvegarder la position du slash IMMÉDIATEMENT avant toute autre opération
|
||||||
const savedSlashPos = this.slashPos;
|
const savedSlashPos = this.slashPos;
|
||||||
|
|
||||||
console.log('[SlashCommands] openLinkInserter - savedSlashPos:', savedSlashPos);
|
debug('[SlashCommands] openLinkInserter - savedSlashPos:', savedSlashPos);
|
||||||
|
|
||||||
if (!savedSlashPos) {
|
if (!savedSlashPos) {
|
||||||
console.error('[SlashCommands] No slash position available!');
|
console.error('[SlashCommands] No slash position available!');
|
||||||
@ -698,7 +717,7 @@ class SlashCommands {
|
|||||||
|
|
||||||
// S'assurer que le LinkInserter global existe, le créer si nécessaire
|
// S'assurer que le LinkInserter global existe, le créer si nécessaire
|
||||||
if (!window.linkInserter) {
|
if (!window.linkInserter) {
|
||||||
console.log('Initializing LinkInserter...');
|
debug('Initializing LinkInserter...');
|
||||||
window.linkInserter = new LinkInserter();
|
window.linkInserter = new LinkInserter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,14 +725,14 @@ class SlashCommands {
|
|||||||
window.linkInserter.open({
|
window.linkInserter.open({
|
||||||
editorView: this.editorView,
|
editorView: this.editorView,
|
||||||
onSelect: ({ title, path }) => {
|
onSelect: ({ title, path }) => {
|
||||||
console.log('[SlashCommands] onSelect callback received:', { title, path });
|
debug('[SlashCommands] onSelect callback received:', { title, path });
|
||||||
console.log('[SlashCommands] savedSlashPos:', savedSlashPos);
|
debug('[SlashCommands] savedSlashPos:', savedSlashPos);
|
||||||
|
|
||||||
// Créer un lien HTMX cliquable dans le preview
|
// Créer un lien Markdown standard
|
||||||
// Format : <a href="#" onclick="return false;" hx-get="/api/notes/path" hx-target="#editor-container" hx-swap="innerHTML">Title</a>
|
// Format : [Title](path/to/note.md)
|
||||||
// Le onclick="return false;" empêche le comportement par défaut du # qui pourrait rediriger
|
// Le post-processing dans updatePreview() le rendra cliquable avec HTMX
|
||||||
const linkHtml = `<a href="#" onclick="return false;" hx-get="/api/notes/${path}" hx-target="#editor-container" hx-swap="innerHTML">${title}</a>`;
|
const linkMarkdown = `[${title}](${path})`;
|
||||||
console.log('[SlashCommands] Inserting:', linkHtml);
|
debug('[SlashCommands] Inserting Markdown link:', linkMarkdown);
|
||||||
|
|
||||||
const { state, dispatch } = this.editorView;
|
const { state, dispatch } = this.editorView;
|
||||||
const { from } = state.selection.main;
|
const { from } = state.selection.main;
|
||||||
@ -721,15 +740,15 @@ class SlashCommands {
|
|||||||
// Remplacer depuis le "/" jusqu'au curseur actuel
|
// Remplacer depuis le "/" jusqu'au curseur actuel
|
||||||
const replaceFrom = savedSlashPos.absolutePos;
|
const replaceFrom = savedSlashPos.absolutePos;
|
||||||
|
|
||||||
console.log('[SlashCommands] Replacing from', replaceFrom, 'to', from);
|
debug('[SlashCommands] Replacing from', replaceFrom, 'to', from);
|
||||||
|
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
changes: { from: replaceFrom, to: from, insert: linkHtml },
|
changes: { from: replaceFrom, to: from, insert: linkMarkdown },
|
||||||
selection: { anchor: replaceFrom + linkHtml.length }
|
selection: { anchor: replaceFrom + linkMarkdown.length }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.editorView.focus();
|
this.editorView.focus();
|
||||||
console.log('[SlashCommands] Link inserted successfully');
|
debug('[SlashCommands] Markdown link inserted successfully');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* Favorites - Gère le système de favoris
|
* Favorites - Gère le système de favoris
|
||||||
*/
|
*/
|
||||||
@ -8,33 +9,33 @@ class FavoritesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
console.log('FavoritesManager: Initialisation...');
|
debug('FavoritesManager: Initialisation...');
|
||||||
|
|
||||||
// Charger les favoris au démarrage
|
// Charger les favoris au démarrage
|
||||||
this.refreshFavorites();
|
this.refreshFavorites();
|
||||||
|
|
||||||
// Écouter les événements HTMX pour mettre à jour les boutons
|
// Écouter les événements HTMX pour mettre à jour les boutons
|
||||||
document.body.addEventListener('htmx:afterSwap', (event) => {
|
document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||||
console.log('HTMX afterSwap:', event.detail.target.id);
|
debug('HTMX afterSwap:', event.detail.target.id);
|
||||||
|
|
||||||
if (event.detail.target.id === 'file-tree') {
|
if (event.detail.target.id === 'file-tree') {
|
||||||
console.log('File-tree chargé, ajout des boutons favoris...');
|
debug('File-tree chargé, ajout des boutons favoris...');
|
||||||
setTimeout(() => this.attachFavoriteButtons(), 100);
|
setTimeout(() => this.attachFavoriteButtons(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.detail.target.id === 'favorites-list') {
|
if (event.detail.target.id === 'favorites-list') {
|
||||||
console.log('Favoris rechargés, mise à jour des boutons...');
|
debug('Favoris rechargés, mise à jour des boutons...');
|
||||||
setTimeout(() => this.attachFavoriteButtons(), 100);
|
setTimeout(() => this.attachFavoriteButtons(), 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attacher les boutons après un délai pour laisser HTMX charger le file-tree
|
// Attacher les boutons après un délai pour laisser HTMX charger le file-tree
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Tentative d\'attachement des boutons favoris après délai...');
|
debug('Tentative d\'attachement des boutons favoris après délai...');
|
||||||
this.attachFavoriteButtons();
|
this.attachFavoriteButtons();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
console.log('FavoritesManager: Initialisé');
|
debug('FavoritesManager: Initialisé');
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshFavorites() {
|
refreshFavorites() {
|
||||||
@ -47,7 +48,7 @@ class FavoritesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addFavorite(path, isDir, title) {
|
async addFavorite(path, isDir, title) {
|
||||||
console.log('addFavorite appelé avec:', { path, isDir, title });
|
debug('addFavorite appelé avec:', { path, isDir, title });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Utiliser URLSearchParams au lieu de FormData pour le format application/x-www-form-urlencoded
|
// Utiliser URLSearchParams au lieu de FormData pour le format application/x-www-form-urlencoded
|
||||||
@ -56,7 +57,7 @@ class FavoritesManager {
|
|||||||
params.append('is_dir', isDir ? 'true' : 'false');
|
params.append('is_dir', isDir ? 'true' : 'false');
|
||||||
params.append('title', title || '');
|
params.append('title', title || '');
|
||||||
|
|
||||||
console.log('Params créés:', {
|
debug('Params créés:', {
|
||||||
path: params.get('path'),
|
path: params.get('path'),
|
||||||
is_dir: params.get('is_dir'),
|
is_dir: params.get('is_dir'),
|
||||||
title: params.get('title')
|
title: params.get('title')
|
||||||
@ -74,9 +75,9 @@ class FavoritesManager {
|
|||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
document.getElementById('favorites-list').innerHTML = html;
|
document.getElementById('favorites-list').innerHTML = html;
|
||||||
this.attachFavoriteButtons();
|
this.attachFavoriteButtons();
|
||||||
console.log('Favori ajouté:', path);
|
debug('Favori ajouté:', path);
|
||||||
} else if (response.status === 409) {
|
} else if (response.status === 409) {
|
||||||
console.log('Déjà en favoris');
|
debug('Déjà en favoris');
|
||||||
} else {
|
} else {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error('Erreur ajout favori:', response.status, response.statusText, errorText);
|
console.error('Erreur ajout favori:', response.status, response.statusText, errorText);
|
||||||
@ -103,7 +104,7 @@ class FavoritesManager {
|
|||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
document.getElementById('favorites-list').innerHTML = html;
|
document.getElementById('favorites-list').innerHTML = html;
|
||||||
this.attachFavoriteButtons();
|
this.attachFavoriteButtons();
|
||||||
console.log('Favori retiré:', path);
|
debug('Favori retiré:', path);
|
||||||
} else {
|
} else {
|
||||||
console.error('Erreur retrait favori:', response.statusText);
|
console.error('Erreur retrait favori:', response.statusText);
|
||||||
}
|
}
|
||||||
@ -130,96 +131,95 @@ class FavoritesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachFavoriteButtons() {
|
attachFavoriteButtons() {
|
||||||
console.log('attachFavoriteButtons: Début...');
|
debug('attachFavoriteButtons: Début...');
|
||||||
|
|
||||||
|
// Supprimer tous les boutons favoris existants pour les recréer avec le bon état
|
||||||
|
document.querySelectorAll('.add-to-favorites').forEach(btn => btn.remove());
|
||||||
|
|
||||||
// Ajouter des boutons étoile aux éléments du file tree
|
// Ajouter des boutons étoile aux éléments du file tree
|
||||||
this.getFavoritesPaths().then(favoritePaths => {
|
this.getFavoritesPaths().then(favoritePaths => {
|
||||||
console.log('Chemins favoris:', favoritePaths);
|
debug('Chemins favoris:', favoritePaths);
|
||||||
|
|
||||||
// Dossiers
|
// Dossiers
|
||||||
const folderHeaders = document.querySelectorAll('.folder-header');
|
const folderHeaders = document.querySelectorAll('.folder-header');
|
||||||
console.log('Nombre de folder-header trouvés:', folderHeaders.length);
|
debug('Nombre de folder-header trouvés:', folderHeaders.length);
|
||||||
|
|
||||||
folderHeaders.forEach(header => {
|
folderHeaders.forEach(header => {
|
||||||
if (!header.querySelector('.add-to-favorites')) {
|
const folderItem = header.closest('.folder-item');
|
||||||
const folderItem = header.closest('.folder-item');
|
const path = folderItem?.getAttribute('data-path');
|
||||||
const path = folderItem?.getAttribute('data-path');
|
|
||||||
|
debug('Dossier trouvé:', path);
|
||||||
console.log('Dossier trouvé:', path);
|
|
||||||
|
if (path) {
|
||||||
if (path) {
|
const button = document.createElement('button');
|
||||||
const button = document.createElement('button');
|
button.className = 'add-to-favorites';
|
||||||
button.className = 'add-to-favorites';
|
button.innerHTML = '⭐';
|
||||||
button.innerHTML = '⭐';
|
button.title = 'Ajouter aux favoris';
|
||||||
button.title = 'Ajouter aux favoris';
|
|
||||||
|
// Extraire le nom avant d'ajouter le bouton
|
||||||
// Extraire le nom avant d'ajouter le bouton
|
const name = header.querySelector('.folder-name')?.textContent?.trim() || path.split('/').pop();
|
||||||
const name = header.querySelector('.folder-name')?.textContent?.trim() || path.split('/').pop();
|
|
||||||
|
button.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
debug('Ajout dossier aux favoris:', path, name);
|
||||||
|
this.addFavorite(path, true, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (favoritePaths.includes(path)) {
|
||||||
|
button.classList.add('is-favorite');
|
||||||
|
button.title = 'Retirer des favoris';
|
||||||
button.onclick = (e) => {
|
button.onclick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log('Ajout dossier aux favoris:', path, name);
|
debug('Retrait dossier des favoris:', path);
|
||||||
this.addFavorite(path, true, name);
|
this.removeFavorite(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (favoritePaths.includes(path)) {
|
|
||||||
button.classList.add('is-favorite');
|
|
||||||
button.title = 'Retirer des favoris';
|
|
||||||
button.onclick = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
console.log('Retrait dossier des favoris:', path);
|
|
||||||
this.removeFavorite(path);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
header.appendChild(button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header.appendChild(button);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fichiers
|
// Fichiers
|
||||||
const fileItems = document.querySelectorAll('.file-item');
|
const fileItems = document.querySelectorAll('.file-item');
|
||||||
console.log('Nombre de file-item trouvés:', fileItems.length);
|
debug('Nombre de file-item trouvés:', fileItems.length);
|
||||||
|
|
||||||
fileItems.forEach(fileItem => {
|
fileItems.forEach(fileItem => {
|
||||||
if (!fileItem.querySelector('.add-to-favorites')) {
|
const path = fileItem.getAttribute('data-path');
|
||||||
const path = fileItem.getAttribute('data-path');
|
|
||||||
|
debug('Fichier trouvé:', path);
|
||||||
console.log('Fichier trouvé:', path);
|
|
||||||
|
if (path) {
|
||||||
if (path) {
|
const button = document.createElement('button');
|
||||||
const button = document.createElement('button');
|
button.className = 'add-to-favorites';
|
||||||
button.className = 'add-to-favorites';
|
button.innerHTML = '⭐';
|
||||||
button.innerHTML = '⭐';
|
button.title = 'Ajouter aux favoris';
|
||||||
button.title = 'Ajouter aux favoris';
|
|
||||||
|
// Extraire le nom avant d'ajouter le bouton
|
||||||
// Extraire le nom avant d'ajouter le bouton
|
const name = fileItem.textContent.trim().replace('📄', '').trim().replace('.md', '');
|
||||||
const name = fileItem.textContent.trim().replace('📄', '').trim().replace('.md', '');
|
|
||||||
|
button.onclick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
debug('Ajout fichier aux favoris:', path, name);
|
||||||
|
this.addFavorite(path, false, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (favoritePaths.includes(path)) {
|
||||||
|
button.classList.add('is-favorite');
|
||||||
|
button.title = 'Retirer des favoris';
|
||||||
button.onclick = (e) => {
|
button.onclick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log('Ajout fichier aux favoris:', path, name);
|
debug('Retrait fichier des favoris:', path);
|
||||||
this.addFavorite(path, false, name);
|
this.removeFavorite(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (favoritePaths.includes(path)) {
|
|
||||||
button.classList.add('is-favorite');
|
|
||||||
button.title = 'Retirer des favoris';
|
|
||||||
button.onclick = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
console.log('Retrait fichier des favoris:', path);
|
|
||||||
this.removeFavorite(path);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fileItem.appendChild(button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileItem.appendChild(button);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('attachFavoriteButtons: Terminé');
|
debug('attachFavoriteButtons: Terminé');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* FileTree - Gère l'arborescence hiérarchique avec drag & drop
|
* FileTree - Gère l'arborescence hiérarchique avec drag & drop
|
||||||
* Utilise la délégation d'événements pour éviter les problèmes de listeners perdus
|
* Utilise la délégation d'événements pour éviter les problèmes de listeners perdus
|
||||||
@ -12,7 +13,7 @@ class FileTree {
|
|||||||
init() {
|
init() {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|
||||||
console.log('FileTree initialized with event delegation');
|
debug('FileTree initialized with event delegation');
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
@ -112,27 +113,33 @@ class FileTree {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Drag over - délégué sur les folder-headers
|
// Drag over - délégué sur les folder-headers et la racine
|
||||||
this.dragOverHandler = (e) => {
|
this.dragOverHandler = (e) => {
|
||||||
const folderHeader = e.target.closest('.folder-header');
|
const folderHeader = e.target.closest('.folder-header');
|
||||||
if (folderHeader) {
|
const rootHeader = e.target.closest('.sidebar-section-header[data-section="notes"]');
|
||||||
this.handleDragOver(e, folderHeader);
|
const target = folderHeader || rootHeader;
|
||||||
|
if (target) {
|
||||||
|
this.handleDragOver(e, target);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Drag leave - délégué
|
// Drag leave - délégué
|
||||||
this.dragLeaveHandler = (e) => {
|
this.dragLeaveHandler = (e) => {
|
||||||
const folderHeader = e.target.closest('.folder-header');
|
const folderHeader = e.target.closest('.folder-header');
|
||||||
if (folderHeader) {
|
const rootHeader = e.target.closest('.sidebar-section-header[data-section="notes"]');
|
||||||
this.handleDragLeave(e, folderHeader);
|
const target = folderHeader || rootHeader;
|
||||||
|
if (target) {
|
||||||
|
this.handleDragLeave(e, target);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Drop - délégué
|
// Drop - délégué
|
||||||
this.dropHandler = (e) => {
|
this.dropHandler = (e) => {
|
||||||
const folderHeader = e.target.closest('.folder-header');
|
const folderHeader = e.target.closest('.folder-header');
|
||||||
if (folderHeader) {
|
const rootHeader = e.target.closest('.sidebar-section-header[data-section="notes"]');
|
||||||
this.handleDrop(e, folderHeader);
|
const target = folderHeader || rootHeader;
|
||||||
|
if (target) {
|
||||||
|
this.handleDrop(e, target);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -152,7 +159,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...');
|
debug('FileTree: afterSwap detected, updating attributes...');
|
||||||
this.updateDraggableAttributes();
|
this.updateDraggableAttributes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -162,14 +169,14 @@ class FileTree {
|
|||||||
const target = event.detail?.target;
|
const target = event.detail?.target;
|
||||||
// Ignorer les swaps de statut (auto-save-status, save-status)
|
// 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...');
|
debug('FileTree: oobAfterSwap detected, updating attributes...');
|
||||||
this.updateDraggableAttributes();
|
this.updateDraggableAttributes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Écouter les restaurations d'historique (bouton retour du navigateur)
|
// Écouter les restaurations d'historique (bouton retour du navigateur)
|
||||||
document.body.addEventListener('htmx:historyRestore', () => {
|
document.body.addEventListener('htmx:historyRestore', () => {
|
||||||
console.log('FileTree: History restored, re-initializing event listeners...');
|
debug('FileTree: History restored, re-initializing event listeners...');
|
||||||
// Réinitialiser complètement les event listeners après restauration de l'historique
|
// Réinitialiser complètement les event listeners après restauration de l'historique
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
@ -214,7 +221,7 @@ class FileTree {
|
|||||||
this.draggedPath = path;
|
this.draggedPath = path;
|
||||||
this.draggedType = type;
|
this.draggedType = type;
|
||||||
|
|
||||||
console.log('Drag start:', { type, path, name });
|
debug('Drag start:', { type, path, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragEnd(e) {
|
handleDragEnd(e) {
|
||||||
@ -242,20 +249,23 @@ class FileTree {
|
|||||||
this.draggedType = null;
|
this.draggedType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragOver(e, folderHeader) {
|
handleDragOver(e, target) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const folderItem = folderHeader.closest('.folder-item');
|
// Gérer soit un folder-header dans un folder-item, soit la racine (sidebar-section-header)
|
||||||
if (!folderItem) return;
|
const isRoot = target.classList.contains('sidebar-section-header');
|
||||||
|
const targetElement = isRoot ? target : target.closest('.folder-item');
|
||||||
|
|
||||||
const targetPath = folderItem.dataset.path;
|
if (!targetElement) return;
|
||||||
|
|
||||||
|
const targetPath = targetElement.dataset.path;
|
||||||
|
|
||||||
// Empêcher de déplacer un dossier dans lui-même ou dans ses enfants
|
// Empêcher de déplacer un dossier dans lui-même ou dans ses enfants
|
||||||
if (this.draggedType === 'folder' && this.draggedPath) {
|
if (this.draggedType === 'folder' && this.draggedPath) {
|
||||||
if (targetPath === this.draggedPath || targetPath.startsWith(this.draggedPath + '/')) {
|
if (targetPath === this.draggedPath || targetPath.startsWith(this.draggedPath + '/')) {
|
||||||
e.dataTransfer.dropEffect = 'none';
|
e.dataTransfer.dropEffect = 'none';
|
||||||
folderItem.classList.remove('drag-over');
|
targetElement.classList.remove('drag-over');
|
||||||
this.removeDestinationIndicator();
|
this.removeDestinationIndicator();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -263,34 +273,37 @@ class FileTree {
|
|||||||
|
|
||||||
e.dataTransfer.dropEffect = 'move';
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
|
||||||
if (folderItem && !folderItem.classList.contains('drag-over')) {
|
if (targetElement && !targetElement.classList.contains('drag-over')) {
|
||||||
// Retirer la classe des autres dossiers
|
// Retirer la classe des autres dossiers et de la racine
|
||||||
document.querySelectorAll('.folder-item.drag-over').forEach(f => {
|
document.querySelectorAll('.folder-item.drag-over, .sidebar-section-header.drag-over').forEach(f => {
|
||||||
if (f !== folderItem) {
|
if (f !== targetElement) {
|
||||||
f.classList.remove('drag-over');
|
f.classList.remove('drag-over');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
folderItem.classList.add('drag-over');
|
targetElement.classList.add('drag-over');
|
||||||
|
|
||||||
// Afficher l'indicateur de destination
|
// Afficher l'indicateur de destination
|
||||||
this.showDestinationIndicator(folderItem, targetPath);
|
this.showDestinationIndicator(targetElement, targetPath, isRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragLeave(e, folderHeader) {
|
handleDragLeave(e, target) {
|
||||||
const folderItem = folderHeader.closest('.folder-item');
|
// Gérer soit un folder-header dans un folder-item, soit la racine (sidebar-section-header)
|
||||||
if (!folderItem) return;
|
const isRoot = target.classList.contains('sidebar-section-header');
|
||||||
|
const targetElement = isRoot ? target : target.closest('.folder-item');
|
||||||
|
|
||||||
// Vérifier que la souris a vraiment quitté le dossier
|
if (!targetElement) return;
|
||||||
const rect = folderHeader.getBoundingClientRect();
|
|
||||||
|
// Vérifier que la souris a vraiment quitté l'élément
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
if (e.clientX < rect.left || e.clientX >= rect.right ||
|
if (e.clientX < rect.left || e.clientX >= rect.right ||
|
||||||
e.clientY < rect.top || e.clientY >= rect.bottom) {
|
e.clientY < rect.top || e.clientY >= rect.bottom) {
|
||||||
folderItem.classList.remove('drag-over');
|
targetElement.classList.remove('drag-over');
|
||||||
this.removeDestinationIndicator();
|
this.removeDestinationIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showDestinationIndicator(folderItem, targetPath) {
|
showDestinationIndicator(targetElement, targetPath, isRoot) {
|
||||||
let indicator = document.getElementById('drag-destination-indicator');
|
let indicator = document.getElementById('drag-destination-indicator');
|
||||||
if (!indicator) {
|
if (!indicator) {
|
||||||
indicator = document.createElement('div');
|
indicator = document.createElement('div');
|
||||||
@ -299,8 +312,7 @@ class FileTree {
|
|||||||
document.body.appendChild(indicator);
|
document.body.appendChild(indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderName = folderItem.querySelector('.folder-name').textContent.trim();
|
const folderName = targetElement.querySelector('.folder-name').textContent.trim();
|
||||||
const isRoot = folderItem.dataset.isRoot === 'true';
|
|
||||||
const displayPath = isRoot ? 'notes/' : targetPath;
|
const displayPath = isRoot ? 'notes/' : targetPath;
|
||||||
|
|
||||||
indicator.innerHTML = `
|
indicator.innerHTML = `
|
||||||
@ -318,14 +330,17 @@ class FileTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop(e, folderHeader) {
|
handleDrop(e, target) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const folderItem = folderHeader.closest('.folder-item');
|
// Gérer soit un folder-header dans un folder-item, soit la racine (sidebar-section-header)
|
||||||
if (!folderItem) return;
|
const isRoot = target.classList.contains('sidebar-section-header');
|
||||||
|
const targetElement = isRoot ? target : target.closest('.folder-item');
|
||||||
|
|
||||||
folderItem.classList.remove('drag-over');
|
if (!targetElement) return;
|
||||||
|
|
||||||
|
targetElement.classList.remove('drag-over');
|
||||||
|
|
||||||
// Supprimer l'indicateur de destination
|
// Supprimer l'indicateur de destination
|
||||||
this.removeDestinationIndicator();
|
this.removeDestinationIndicator();
|
||||||
@ -333,9 +348,9 @@ class FileTree {
|
|||||||
const sourcePath = e.dataTransfer.getData('application/note-path') ||
|
const sourcePath = e.dataTransfer.getData('application/note-path') ||
|
||||||
e.dataTransfer.getData('text/plain');
|
e.dataTransfer.getData('text/plain');
|
||||||
const sourceType = e.dataTransfer.getData('application/note-type');
|
const sourceType = e.dataTransfer.getData('application/note-type');
|
||||||
const targetFolderPath = folderItem.dataset.path;
|
const targetFolderPath = targetElement.dataset.path;
|
||||||
|
|
||||||
console.log('Drop event:', {
|
debug('Drop event:', {
|
||||||
sourcePath,
|
sourcePath,
|
||||||
sourceType,
|
sourceType,
|
||||||
targetFolderPath,
|
targetFolderPath,
|
||||||
@ -364,7 +379,7 @@ class FileTree {
|
|||||||
const sourceDir = sourcePath.includes('/') ?
|
const sourceDir = sourcePath.includes('/') ?
|
||||||
sourcePath.substring(0, sourcePath.lastIndexOf('/')) : '';
|
sourcePath.substring(0, sourcePath.lastIndexOf('/')) : '';
|
||||||
if (sourceDir === targetFolderPath) {
|
if (sourceDir === targetFolderPath) {
|
||||||
console.log('Déjà dans le même dossier parent, rien à faire');
|
debug('Déjà dans le même dossier parent, rien à faire');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,12 +392,12 @@ class FileTree {
|
|||||||
// Si targetFolderPath est vide (racine), ne pas ajouter de slash
|
// Si targetFolderPath est vide (racine), ne pas ajouter de slash
|
||||||
const destinationPath = targetFolderPath === '' ? itemName : targetFolderPath + '/' + itemName;
|
const destinationPath = targetFolderPath === '' ? itemName : targetFolderPath + '/' + itemName;
|
||||||
|
|
||||||
console.log(`Déplacement: ${sourcePath} → ${destinationPath}`);
|
debug(`Déplacement: ${sourcePath} → ${destinationPath}`);
|
||||||
this.moveFile(sourcePath, destinationPath);
|
this.moveFile(sourcePath, destinationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveFile(sourcePath, destinationPath) {
|
async moveFile(sourcePath, destinationPath) {
|
||||||
console.log('moveFile called:', { sourcePath, destinationPath });
|
debug('moveFile called:', { sourcePath, destinationPath });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Utiliser htmx.ajax() au lieu de fetch() manuel
|
// Utiliser htmx.ajax() au lieu de fetch() manuel
|
||||||
@ -392,7 +407,7 @@ class FileTree {
|
|||||||
values: { source: sourcePath, destination: destinationPath },
|
values: { source: sourcePath, destination: destinationPath },
|
||||||
swap: 'none' // On ne swap rien directement, le serveur utilise hx-swap-oob
|
swap: 'none' // On ne swap rien directement, le serveur utilise hx-swap-oob
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
console.log(`Fichier déplacé: ${sourcePath} -> ${destinationPath}`);
|
debug(`Fichier déplacé: ${sourcePath} -> ${destinationPath}`);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Erreur lors du déplacement:', error);
|
console.error('Erreur lors du déplacement:', error);
|
||||||
alert('Erreur lors du déplacement du fichier');
|
alert('Erreur lors du déplacement du fichier');
|
||||||
@ -504,7 +519,7 @@ window.handleNewFolder = async function(event) {
|
|||||||
swap: 'none' // On ne swap rien directement, le serveur utilise hx-swap-oob
|
swap: 'none' // On ne swap rien directement, le serveur utilise hx-swap-oob
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
window.hideNewFolderModal();
|
window.hideNewFolderModal();
|
||||||
console.log(`Dossier créé: ${folderName}`);
|
debug(`Dossier créé: ${folderName}`);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Erreur lors de la création du dossier:', error);
|
console.error('Erreur lors de la création du dossier:', error);
|
||||||
alert('Erreur lors de la création du dossier');
|
alert('Erreur lors de la création du dossier');
|
||||||
@ -722,7 +737,7 @@ class SelectionManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`${paths.length} élément(s) supprimé(s)`);
|
debug(`${paths.length} élément(s) supprimé(s)`);
|
||||||
|
|
||||||
// Fermer la modale
|
// Fermer la modale
|
||||||
this.hideDeleteConfirmationModal();
|
this.hideDeleteConfirmationModal();
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* Font Manager - Gère le changement de polices
|
* Font Manager - Gère le changement de polices
|
||||||
*/
|
*/
|
||||||
@ -67,7 +68,7 @@ class FontManager {
|
|||||||
const savedSize = localStorage.getItem('fontSize') || 'medium';
|
const savedSize = localStorage.getItem('fontSize') || 'medium';
|
||||||
this.applyFontSize(savedSize);
|
this.applyFontSize(savedSize);
|
||||||
|
|
||||||
console.log('FontManager initialized with font:', savedFont, 'size:', savedSize);
|
debug('FontManager initialized with font:', savedFont, 'size:', savedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFont(fontId) {
|
applyFont(fontId) {
|
||||||
@ -88,7 +89,7 @@ class FontManager {
|
|||||||
// Sauvegarder le choix
|
// Sauvegarder le choix
|
||||||
localStorage.setItem('selectedFont', fontId);
|
localStorage.setItem('selectedFont', fontId);
|
||||||
|
|
||||||
console.log('Police appliquée:', font.name);
|
debug('Police appliquée:', font.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFontSize(sizeId) {
|
applyFontSize(sizeId) {
|
||||||
@ -109,7 +110,7 @@ class FontManager {
|
|||||||
// Sauvegarder le choix
|
// Sauvegarder le choix
|
||||||
localStorage.setItem('fontSize', sizeId);
|
localStorage.setItem('fontSize', sizeId);
|
||||||
|
|
||||||
console.log('Taille de police appliquée:', sizeId, size);
|
debug('Taille de police appliquée:', sizeId, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentSize() {
|
getCurrentSize() {
|
||||||
@ -130,7 +131,7 @@ class FontManager {
|
|||||||
link.href = `https://fonts.googleapis.com/css2?family=${fontParam}&display=swap`;
|
link.href = `https://fonts.googleapis.com/css2?family=${fontParam}&display=swap`;
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
|
|
||||||
console.log('Google Font chargée:', fontParam);
|
debug('Google Font chargée:', fontParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFont() {
|
getCurrentFont() {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* Keyboard Shortcuts Manager - Gère tous les raccourcis clavier de l'application
|
* Keyboard Shortcuts Manager - Gère tous les raccourcis clavier de l'application
|
||||||
*/
|
*/
|
||||||
@ -25,7 +26,7 @@ class KeyboardShortcutsManager {
|
|||||||
this.handleKeydown(event);
|
this.handleKeydown(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Keyboard shortcuts initialized:', this.shortcuts.length, 'shortcuts');
|
debug('Keyboard shortcuts initialized:', this.shortcuts.length, 'shortcuts');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeydown(event) {
|
handleKeydown(event) {
|
||||||
@ -59,13 +60,13 @@ class KeyboardShortcutsManager {
|
|||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.focus();
|
searchInput.focus();
|
||||||
searchInput.select();
|
searchInput.select();
|
||||||
console.log('Search opened via Ctrl+K');
|
debug('Search opened via Ctrl+K');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveNote() {
|
saveNote() {
|
||||||
// Déclencher la sauvegarde de la note (géré par CodeMirror)
|
// Déclencher la sauvegarde de la note (géré par CodeMirror)
|
||||||
console.log('Save triggered via Ctrl+S');
|
debug('Save triggered via Ctrl+S');
|
||||||
// La sauvegarde est déjà gérée dans editor.js
|
// La sauvegarde est déjà gérée dans editor.js
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,14 +75,14 @@ class KeyboardShortcutsManager {
|
|||||||
const dailyBtn = document.querySelector('button[hx-get="/api/daily/today"]');
|
const dailyBtn = document.querySelector('button[hx-get="/api/daily/today"]');
|
||||||
if (dailyBtn) {
|
if (dailyBtn) {
|
||||||
dailyBtn.click();
|
dailyBtn.click();
|
||||||
console.log('Daily note opened via Ctrl+D');
|
debug('Daily note opened via Ctrl+D');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewNote() {
|
createNewNote() {
|
||||||
if (typeof showNewNoteModal === 'function') {
|
if (typeof showNewNoteModal === 'function') {
|
||||||
showNewNoteModal();
|
showNewNoteModal();
|
||||||
console.log('New note modal opened via Ctrl+N');
|
debug('New note modal opened via Ctrl+N');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,35 +90,35 @@ class KeyboardShortcutsManager {
|
|||||||
const homeBtn = document.querySelector('button[hx-get="/api/home"]');
|
const homeBtn = document.querySelector('button[hx-get="/api/home"]');
|
||||||
if (homeBtn) {
|
if (homeBtn) {
|
||||||
homeBtn.click();
|
homeBtn.click();
|
||||||
console.log('Home opened via Ctrl+H');
|
debug('Home opened via Ctrl+H');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
if (typeof toggleSidebar === 'function') {
|
if (typeof toggleSidebar === 'function') {
|
||||||
toggleSidebar();
|
toggleSidebar();
|
||||||
console.log('Sidebar toggled via Ctrl+B');
|
debug('Sidebar toggled via Ctrl+B');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openSettings() {
|
openSettings() {
|
||||||
if (typeof openThemeModal === 'function') {
|
if (typeof openThemeModal === 'function') {
|
||||||
openThemeModal();
|
openThemeModal();
|
||||||
console.log('Settings opened via Ctrl+,');
|
debug('Settings opened via Ctrl+,');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePreview() {
|
togglePreview() {
|
||||||
if (typeof togglePreview === 'function') {
|
if (typeof togglePreview === 'function') {
|
||||||
togglePreview();
|
togglePreview();
|
||||||
console.log('Preview toggled via Ctrl+/');
|
debug('Preview toggled via Ctrl+/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewFolder() {
|
createNewFolder() {
|
||||||
if (typeof showNewFolderModal === 'function') {
|
if (typeof showNewFolderModal === 'function') {
|
||||||
showNewFolderModal();
|
showNewFolderModal();
|
||||||
console.log('New folder modal opened via Ctrl+Shift+F');
|
debug('New folder modal opened via Ctrl+Shift+F');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +148,7 @@ class KeyboardShortcutsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Escape pressed');
|
debug('Escape pressed');
|
||||||
}
|
}
|
||||||
|
|
||||||
getShortcuts() {
|
getShortcuts() {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* LinkInserter - Modal de recherche pour insérer des liens vers d'autres notes
|
* LinkInserter - Modal de recherche pour insérer des liens vers d'autres notes
|
||||||
* Intégré dans l'éditeur CodeMirror 6
|
* Intégré dans l'éditeur CodeMirror 6
|
||||||
@ -137,7 +138,7 @@ class LinkInserter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleKeyNavigation(event) {
|
handleKeyNavigation(event) {
|
||||||
console.log('[LinkInserter] Key pressed:', event.key, 'Results:', this.results.length);
|
debug('[LinkInserter] Key pressed:', event.key, 'Results:', this.results.length);
|
||||||
|
|
||||||
if (this.results.length === 0) {
|
if (this.results.length === 0) {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
@ -149,27 +150,27 @@ class LinkInserter {
|
|||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
console.log('[LinkInserter] Arrow Down - moving to index:', this.selectedIndex + 1);
|
debug('[LinkInserter] Arrow Down - moving to index:', this.selectedIndex + 1);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
|
this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
|
||||||
this.updateSelection();
|
this.updateSelection();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
console.log('[LinkInserter] Arrow Up - moving to index:', this.selectedIndex - 1);
|
debug('[LinkInserter] Arrow Up - moving to index:', this.selectedIndex - 1);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
|
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
|
||||||
this.updateSelection();
|
this.updateSelection();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
console.log('[LinkInserter] Enter pressed - calling selectResult()');
|
debug('[LinkInserter] Enter pressed - calling selectResult()');
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.selectResult();
|
this.selectResult();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
console.log('[LinkInserter] Escape pressed - closing modal');
|
debug('[LinkInserter] Escape pressed - closing modal');
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.close();
|
this.close();
|
||||||
break;
|
break;
|
||||||
@ -189,7 +190,7 @@ class LinkInserter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectResult() {
|
selectResult() {
|
||||||
console.log('[LinkInserter] selectResult called, results:', this.results.length);
|
debug('[LinkInserter] selectResult called, results:', this.results.length);
|
||||||
|
|
||||||
if (this.results.length === 0) {
|
if (this.results.length === 0) {
|
||||||
console.warn('[LinkInserter] No results to select');
|
console.warn('[LinkInserter] No results to select');
|
||||||
@ -197,11 +198,11 @@ class LinkInserter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selected = this.results[this.selectedIndex];
|
const selected = this.results[this.selectedIndex];
|
||||||
console.log('[LinkInserter] Selected:', selected);
|
debug('[LinkInserter] Selected:', selected);
|
||||||
console.log('[LinkInserter] Callback exists:', !!this.callback);
|
debug('[LinkInserter] Callback exists:', !!this.callback);
|
||||||
|
|
||||||
if (selected && this.callback) {
|
if (selected && this.callback) {
|
||||||
console.log('[LinkInserter] Calling callback with:', { title: selected.title, path: selected.path });
|
debug('[LinkInserter] Calling callback with:', { title: selected.title, path: selected.path });
|
||||||
|
|
||||||
// Sauvegarder le callback localement avant de fermer
|
// Sauvegarder le callback localement avant de fermer
|
||||||
const callback = this.callback;
|
const callback = this.callback;
|
||||||
@ -211,7 +212,7 @@ class LinkInserter {
|
|||||||
|
|
||||||
// Puis appeler le callback après un petit délai pour que le modal se ferme proprement
|
// Puis appeler le callback après un petit délai pour que le modal se ferme proprement
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('[LinkInserter] Executing callback now...');
|
debug('[LinkInserter] Executing callback now...');
|
||||||
callback({
|
callback({
|
||||||
title: selected.title,
|
title: selected.title,
|
||||||
path: selected.path
|
path: selected.path
|
||||||
@ -254,7 +255,7 @@ class LinkInserter {
|
|||||||
|
|
||||||
// Click handler
|
// Click handler
|
||||||
item.addEventListener('click', (e) => {
|
item.addEventListener('click', (e) => {
|
||||||
console.log('[LinkInserter] Item clicked, index:', index);
|
debug('[LinkInserter] Item clicked, index:', index);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.selectedIndex = index;
|
this.selectedIndex = index;
|
||||||
@ -336,7 +337,7 @@ class LinkInserter {
|
|||||||
* @param {Function} options.onSelect - Callback appelé avec {title, path}
|
* @param {Function} options.onSelect - Callback appelé avec {title, path}
|
||||||
*/
|
*/
|
||||||
open({ editorView, onSelect }) {
|
open({ editorView, onSelect }) {
|
||||||
console.log('[LinkInserter] open() called with callback:', !!onSelect);
|
debug('[LinkInserter] open() called with callback:', !!onSelect);
|
||||||
|
|
||||||
if (this.isOpen) return;
|
if (this.isOpen) return;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* SearchModal - Système de recherche modale avec raccourcis clavier
|
* SearchModal - Système de recherche modale avec raccourcis clavier
|
||||||
* Inspiré des Command Palettes modernes (VSCode, Notion, etc.)
|
* Inspiré des Command Palettes modernes (VSCode, Notion, etc.)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* SidebarSections - Gère les sections rétractables de la sidebar
|
* SidebarSections - Gère les sections rétractables de la sidebar
|
||||||
* Permet de replier/déplier les favoris et le répertoire de notes
|
* Permet de replier/déplier les favoris et le répertoire de notes
|
||||||
@ -12,7 +13,7 @@ class SidebarSections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
console.log('SidebarSections: Initialisation...');
|
debug('SidebarSections: Initialisation...');
|
||||||
|
|
||||||
// Restaurer l'état sauvegardé au démarrage
|
// Restaurer l'état sauvegardé au démarrage
|
||||||
this.restoreStates();
|
this.restoreStates();
|
||||||
@ -22,12 +23,12 @@ class SidebarSections {
|
|||||||
const targetId = event.detail?.target?.id;
|
const targetId = event.detail?.target?.id;
|
||||||
|
|
||||||
if (targetId === 'favorites-list') {
|
if (targetId === 'favorites-list') {
|
||||||
console.log('Favoris rechargés, restauration de l\'état...');
|
debug('Favoris rechargés, restauration de l\'état...');
|
||||||
setTimeout(() => this.restoreSectionState('favorites'), 50);
|
setTimeout(() => this.restoreSectionState('favorites'), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetId === 'file-tree') {
|
if (targetId === 'file-tree') {
|
||||||
console.log('File-tree rechargé, restauration de l\'état...');
|
debug('File-tree rechargé, restauration de l\'état...');
|
||||||
setTimeout(() => this.restoreSectionState('notes'), 50);
|
setTimeout(() => this.restoreSectionState('notes'), 50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -38,14 +39,14 @@ class SidebarSections {
|
|||||||
// Ne restaurer l'état que pour les swaps du file-tree complet
|
// 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
|
// Les swaps de statut (auto-save-status) ne doivent pas déclencher la restauration
|
||||||
if (targetId === 'file-tree') {
|
if (targetId === 'file-tree') {
|
||||||
console.log('File-tree rechargé (oob), restauration de l\'état...');
|
debug('File-tree rechargé (oob), restauration de l\'état...');
|
||||||
setTimeout(() => this.restoreSectionState('notes'), 50);
|
setTimeout(() => this.restoreSectionState('notes'), 50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Écouter les restaurations d'historique (bouton retour du navigateur)
|
// Écouter les restaurations d'historique (bouton retour du navigateur)
|
||||||
document.body.addEventListener('htmx:historyRestore', () => {
|
document.body.addEventListener('htmx:historyRestore', () => {
|
||||||
console.log('SidebarSections: History restored, restoring section states...');
|
debug('SidebarSections: History restored, restoring section states...');
|
||||||
// Restaurer les états des sections après restauration de l'historique
|
// Restaurer les états des sections après restauration de l'historique
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.restoreSectionState('favorites');
|
this.restoreSectionState('favorites');
|
||||||
@ -53,7 +54,7 @@ class SidebarSections {
|
|||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('SidebarSections: Initialisé');
|
debug('SidebarSections: Initialisé');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,7 +76,7 @@ class SidebarSections {
|
|||||||
if (!section) return;
|
if (!section) return;
|
||||||
|
|
||||||
localStorage.setItem(section.key, isExpanded.toString());
|
localStorage.setItem(section.key, isExpanded.toString());
|
||||||
console.log(`État sauvegardé: ${sectionName} = ${isExpanded}`);
|
debug(`État sauvegardé: ${sectionName} = ${isExpanded}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,7 +114,7 @@ class SidebarSections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setSectionState(sectionName, newState);
|
this.setSectionState(sectionName, newState);
|
||||||
console.log(`Section ${sectionName} ${newState ? 'ouverte' : 'fermée'}`);
|
debug(`Section ${sectionName} ${newState ? 'ouverte' : 'fermée'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,7 +149,7 @@ class SidebarSections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`État restauré: ${sectionName} = ${isExpanded ? 'ouvert' : 'fermé'}`);
|
debug(`État restauré: ${sectionName} = ${isExpanded ? 'ouvert' : 'fermé'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* ThemeManager - Gère le système de thèmes de l'application
|
* ThemeManager - Gère le système de thèmes de l'application
|
||||||
* Permet de changer entre différents thèmes et persiste le choix dans localStorage
|
* Permet de changer entre différents thèmes et persiste le choix dans localStorage
|
||||||
@ -70,7 +71,7 @@ class ThemeManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('ThemeManager initialized with theme:', this.currentTheme);
|
debug('ThemeManager initialized with theme:', this.currentTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTheme() {
|
loadTheme() {
|
||||||
@ -91,7 +92,7 @@ class ThemeManager {
|
|||||||
// Mettre à jour les cartes de thème si la modale est ouverte
|
// Mettre à jour les cartes de thème si la modale est ouverte
|
||||||
this.updateThemeCards();
|
this.updateThemeCards();
|
||||||
|
|
||||||
console.log('Theme applied:', themeId);
|
debug('Theme applied:', themeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
openThemeModal() {
|
openThemeModal() {
|
||||||
@ -163,7 +164,7 @@ window.selectTheme = function(themeId) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.switchSettingsTab = function(tabName) {
|
window.switchSettingsTab = function(tabName) {
|
||||||
console.log('Switching to tab:', tabName);
|
debug('Switching to tab:', tabName);
|
||||||
|
|
||||||
// Désactiver tous les onglets
|
// Désactiver tous les onglets
|
||||||
const tabs = document.querySelectorAll('.settings-tab');
|
const tabs = document.querySelectorAll('.settings-tab');
|
||||||
@ -191,7 +192,7 @@ window.switchSettingsTab = function(tabName) {
|
|||||||
const section = document.getElementById(sectionId);
|
const section = document.getElementById(sectionId);
|
||||||
if (section) {
|
if (section) {
|
||||||
section.style.display = 'block';
|
section.style.display = 'block';
|
||||||
console.log('Showing section:', sectionId);
|
debug('Showing section:', sectionId);
|
||||||
} else {
|
} else {
|
||||||
console.error('Section not found:', sectionId);
|
console.error('Section not found:', sectionId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
// Fonction pour détecter si on est sur mobile
|
// Fonction pour détecter si on est sur mobile
|
||||||
function isMobileDevice() {
|
function isMobileDevice() {
|
||||||
return window.innerWidth <= 768;
|
return window.innerWidth <= 768;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { debug, debugError } from './debug.js';
|
||||||
/**
|
/**
|
||||||
* Vim Mode Manager - Gère l'activation/désactivation du mode Vim dans CodeMirror
|
* Vim Mode Manager - Gère l'activation/désactivation du mode Vim dans CodeMirror
|
||||||
*/
|
*/
|
||||||
@ -8,7 +9,7 @@ class VimModeManager {
|
|||||||
this.vim = null; // Extension Vim de CodeMirror
|
this.vim = null; // Extension Vim de CodeMirror
|
||||||
this.editorView = null; // Instance EditorView actuelle
|
this.editorView = null; // Instance EditorView actuelle
|
||||||
|
|
||||||
console.log('VimModeManager initialized, enabled:', this.enabled);
|
debug('VimModeManager initialized, enabled:', this.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +61,7 @@ class VimModeManager {
|
|||||||
// Import dynamique du package Vim
|
// Import dynamique du package Vim
|
||||||
const { vim } = await import('@replit/codemirror-vim');
|
const { vim } = await import('@replit/codemirror-vim');
|
||||||
this.vim = vim;
|
this.vim = vim;
|
||||||
console.log('✅ Vim extension loaded successfully');
|
debug('✅ Vim extension loaded successfully');
|
||||||
return this.vim;
|
return this.vim;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Vim mode is not available. The @replit/codemirror-vim package is not installed.');
|
console.warn('⚠️ Vim mode is not available. The @replit/codemirror-vim package is not installed.');
|
||||||
@ -118,14 +119,14 @@ if (typeof window !== 'undefined') {
|
|||||||
|
|
||||||
// Afficher un message
|
// Afficher un message
|
||||||
const message = enabled ? '✅ Mode Vim activé' : '❌ Mode Vim désactivé';
|
const message = enabled ? '✅ Mode Vim activé' : '❌ Mode Vim désactivé';
|
||||||
console.log(message);
|
debug(message);
|
||||||
|
|
||||||
// Recharger l'éditeur actuel si il existe
|
// Recharger l'éditeur actuel si il existe
|
||||||
if (window.currentMarkdownEditor && window.currentMarkdownEditor.reloadWithVimMode) {
|
if (window.currentMarkdownEditor && window.currentMarkdownEditor.reloadWithVimMode) {
|
||||||
await window.currentMarkdownEditor.reloadWithVimMode();
|
await window.currentMarkdownEditor.reloadWithVimMode();
|
||||||
console.log('Editor reloaded with Vim mode:', enabled);
|
debug('Editor reloaded with Vim mode:', enabled);
|
||||||
} else {
|
} else {
|
||||||
console.log('No editor to reload. Vim mode will be applied when opening a note.');
|
debug('No editor to reload. Vim mode will be applied when opening a note.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -114,6 +114,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.handleFavorites(w, r)
|
h.handleFavorites(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(path, "/api/folder/") {
|
||||||
|
h.handleFolderView(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,15 +282,17 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Utiliser le template editor.html pour afficher la page d'accueil
|
// 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
|
Backlinks []BacklinkInfo
|
||||||
|
Breadcrumb template.HTML
|
||||||
}{
|
}{
|
||||||
Filename: "🏠 Accueil - Index",
|
Filename: "🏠 Accueil - Index",
|
||||||
Content: content,
|
Content: content,
|
||||||
IsHome: true,
|
IsHome: true,
|
||||||
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
||||||
|
Breadcrumb: h.generateBreadcrumb(""),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.templates.ExecuteTemplate(w, "editor.html", data)
|
err := h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||||
@ -338,12 +344,19 @@ func (h *Handler) generateHomeMarkdown() string {
|
|||||||
// Section des notes récemment modifiées (après les favoris)
|
// Section des notes récemment modifiées (après les favoris)
|
||||||
h.generateRecentNotesSection(&sb)
|
h.generateRecentNotesSection(&sb)
|
||||||
|
|
||||||
// Titre de l'arborescence avec le nombre de notes
|
// Section de toutes les notes avec accordéon
|
||||||
sb.WriteString(fmt.Sprintf("## 📂 Toutes les notes (%d)\n\n", noteCount))
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('all-notes')\">\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" <h2 class=\"home-section-title\">📂 Toutes les notes (%d)</h2>\n", noteCount))
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-all-notes\">\n")
|
||||||
|
|
||||||
// Générer l'arborescence en Markdown
|
// Générer l'arborescence en Markdown
|
||||||
h.generateMarkdownTree(&sb, tree, 0)
|
h.generateMarkdownTree(&sb, tree, 0)
|
||||||
|
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString("</div>\n")
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,18 +368,24 @@ func (h *Handler) generateTagsSection(sb *strings.Builder) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("## 🏷️ Tags\n\n")
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
sb.WriteString("<div class=\"tags-cloud\">\n")
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('tags')\">\n")
|
||||||
|
sb.WriteString(" <h2 class=\"home-section-title\">🏷️ Tags</h2>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-tags\">\n")
|
||||||
|
sb.WriteString(" <div class=\"tags-cloud\">\n")
|
||||||
|
|
||||||
for _, tc := range tags {
|
for _, tc := range tags {
|
||||||
// Créer un lien HTML discret et fonctionnel
|
// Créer un lien HTML discret et fonctionnel
|
||||||
sb.WriteString(fmt.Sprintf(
|
sb.WriteString(fmt.Sprintf(
|
||||||
`<a href="#" class="tag-item" hx-get="/api/search?query=tag:%s" hx-target="#search-results" hx-swap="innerHTML"><kbd class="tag-badge">#%s</kbd> <mark class="tag-count">%d</mark></a>`,
|
` <a href="#" class="tag-item" hx-get="/api/search?query=tag:%s" hx-target="#search-results" hx-swap="innerHTML"><kbd class="tag-badge">#%s</kbd> <mark class="tag-count">%d</mark></a>`,
|
||||||
tc.Tag, tc.Tag, tc.Count,
|
tc.Tag, tc.Tag, tc.Count,
|
||||||
))
|
))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
sb.WriteString("</div>\n\n")
|
sb.WriteString("</div>\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,36 +396,42 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("## ⭐ Favoris\n\n")
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
sb.WriteString("<div class=\"note-tree favorites-tree\">\n")
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('favorites')\">\n")
|
||||||
|
sb.WriteString(" <h2 class=\"home-section-title\">⭐ Favoris</h2>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-favorites\">\n")
|
||||||
|
sb.WriteString(" <div class=\"note-tree favorites-tree\">\n")
|
||||||
|
|
||||||
for _, fav := range favorites.Items {
|
for _, fav := range favorites.Items {
|
||||||
safeID := "fav-" + strings.ReplaceAll(strings.ReplaceAll(fav.Path, "/", "-"), "\\", "-")
|
safeID := "fav-" + strings.ReplaceAll(strings.ReplaceAll(fav.Path, "/", "-"), "\\", "-")
|
||||||
|
|
||||||
if fav.IsDir {
|
if fav.IsDir {
|
||||||
// Dossier - avec accordéon
|
// Dossier - avec accordéon
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", safeID))
|
||||||
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
||||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
||||||
|
|
||||||
// Lister le contenu du dossier
|
// Lister le contenu du dossier
|
||||||
h.generateFavoriteFolderContent(sb, fav.Path, 2)
|
h.generateFavoriteFolderContent(sb, fav.Path, 3)
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||||
} else {
|
} else {
|
||||||
// Fichier
|
// Fichier
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"file indent-level-1\">\n"))
|
sb.WriteString(fmt.Sprintf(" <div class=\"file indent-level-1\">\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">", fav.Path))
|
sb.WriteString(fmt.Sprintf(" <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">", fav.Path))
|
||||||
sb.WriteString(fmt.Sprintf("📄 %s", fav.Title))
|
sb.WriteString(fmt.Sprintf("📄 %s", fav.Title))
|
||||||
sb.WriteString("</a>\n")
|
sb.WriteString("</a>\n")
|
||||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
sb.WriteString("</div>\n\n")
|
sb.WriteString("</div>\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,8 +443,12 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("## 🕒 Récemment modifiés\n\n")
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
sb.WriteString("<div class=\"recent-notes-container\">\n")
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('recent')\">\n")
|
||||||
|
sb.WriteString(" <h2 class=\"home-section-title\">🕒 Récemment modifiés</h2>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-recent\">\n")
|
||||||
|
sb.WriteString(" <div class=\"recent-notes-container\">\n")
|
||||||
|
|
||||||
for _, doc := range recentDocs {
|
for _, doc := range recentDocs {
|
||||||
// Extraire les premières lignes du corps (max 150 caractères)
|
// Extraire les premières lignes du corps (max 150 caractères)
|
||||||
@ -434,13 +463,13 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
|||||||
dateStr = doc.Date
|
dateStr = doc.Date
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(" <div class=\"recent-note-card\">\n")
|
sb.WriteString(" <div class=\"recent-note-card\">\n")
|
||||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">\n", doc.Path))
|
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">\n", doc.Path))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\">📅 %s</span>\n", dateStr))
|
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\">📅 %s</span>\n", dateStr))
|
||||||
if len(doc.Tags) > 0 {
|
if len(doc.Tags) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
||||||
for i, tag := range doc.Tags {
|
for i, tag := range doc.Tags {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteString(" ")
|
sb.WriteString(" ")
|
||||||
@ -449,14 +478,16 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder) {
|
|||||||
}
|
}
|
||||||
sb.WriteString("</span>\n")
|
sb.WriteString("</span>\n")
|
||||||
}
|
}
|
||||||
sb.WriteString(" </div>\n")
|
sb.WriteString(" </div>\n")
|
||||||
if preview != "" {
|
if preview != "" {
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-preview\">%s</div>\n", preview))
|
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-preview\">%s</div>\n", preview))
|
||||||
}
|
}
|
||||||
sb.WriteString(" </a>\n")
|
sb.WriteString(" </a>\n")
|
||||||
sb.WriteString(" </div>\n")
|
sb.WriteString(" </div>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
sb.WriteString("</div>\n\n")
|
sb.WriteString("</div>\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,15 +819,17 @@ func (h *Handler) handleGetNote(w http.ResponseWriter, r *http.Request, filename
|
|||||||
backlinkData := h.buildBacklinkData(backlinks)
|
backlinkData := h.buildBacklinkData(backlinks)
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
Filename string
|
Filename string
|
||||||
Content string
|
Content string
|
||||||
IsHome bool
|
IsHome bool
|
||||||
Backlinks []BacklinkInfo
|
Backlinks []BacklinkInfo
|
||||||
|
Breadcrumb template.HTML
|
||||||
}{
|
}{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
IsHome: false,
|
IsHome: false,
|
||||||
Backlinks: backlinkData,
|
Backlinks: backlinkData,
|
||||||
|
Breadcrumb: h.generateBreadcrumb(filename),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||||
@ -1264,3 +1297,190 @@ func (h *Handler) buildBacklinkData(paths []string) []BacklinkInfo {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleFolderView affiche le contenu d'un dossier
|
||||||
|
func (h *Handler) handleFolderView(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si ce n'est pas une requête HTMX, rediriger vers la page principale
|
||||||
|
if r.Header.Get("HX-Request") == "" {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraire le chemin du dossier depuis l'URL
|
||||||
|
folderPath := strings.TrimPrefix(r.URL.Path, "/api/folder/")
|
||||||
|
folderPath = strings.TrimPrefix(folderPath, "/")
|
||||||
|
|
||||||
|
// Sécurité : vérifier le chemin
|
||||||
|
cleanPath := filepath.Clean(folderPath)
|
||||||
|
if strings.HasPrefix(cleanPath, "..") || filepath.IsAbs(cleanPath) {
|
||||||
|
http.Error(w, "Chemin invalide", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construire le chemin absolu
|
||||||
|
absPath := filepath.Join(h.notesDir, cleanPath)
|
||||||
|
|
||||||
|
// Vérifier que c'est bien un dossier
|
||||||
|
info, err := os.Stat(absPath)
|
||||||
|
if err != nil || !info.IsDir() {
|
||||||
|
http.Error(w, "Dossier non trouvé", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer le contenu de la page
|
||||||
|
content := h.generateFolderViewMarkdown(cleanPath)
|
||||||
|
|
||||||
|
// Utiliser le template editor.html
|
||||||
|
data := struct {
|
||||||
|
Filename string
|
||||||
|
Content string
|
||||||
|
IsHome bool
|
||||||
|
Backlinks []BacklinkInfo
|
||||||
|
Breadcrumb template.HTML
|
||||||
|
}{
|
||||||
|
Filename: cleanPath,
|
||||||
|
Content: content,
|
||||||
|
IsHome: true, // Pas d'édition pour une vue de dossier
|
||||||
|
Backlinks: nil,
|
||||||
|
Breadcrumb: h.generateBreadcrumb(cleanPath),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Printf("Erreur d'exécution du template folder view: %v", err)
|
||||||
|
http.Error(w, "Erreur interne", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateBreadcrumb génère un fil d'Ariane HTML cliquable
|
||||||
|
func (h *Handler) generateBreadcrumb(path string) template.HTML {
|
||||||
|
if path == "" {
|
||||||
|
return template.HTML(`<strong>📁 Racine</strong>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(filepath.ToSlash(path), "/")
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString(`<span class="breadcrumb">`)
|
||||||
|
|
||||||
|
// Lien racine
|
||||||
|
sb.WriteString(`<a href="#" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link">📁 Racine</a>`)
|
||||||
|
|
||||||
|
// Construire les liens pour chaque partie
|
||||||
|
currentPath := ""
|
||||||
|
for i, part := range parts {
|
||||||
|
sb.WriteString(` <span class="breadcrumb-separator">›</span> `)
|
||||||
|
|
||||||
|
if currentPath == "" {
|
||||||
|
currentPath = part
|
||||||
|
} else {
|
||||||
|
currentPath = currentPath + "/" + part
|
||||||
|
}
|
||||||
|
|
||||||
|
// Le dernier élément (fichier) n'est pas cliquable
|
||||||
|
if i == len(parts)-1 && strings.HasSuffix(part, ".md") {
|
||||||
|
// C'est un fichier, pas cliquable
|
||||||
|
displayName := strings.TrimSuffix(part, ".md")
|
||||||
|
sb.WriteString(fmt.Sprintf(`<strong>%s</strong>`, displayName))
|
||||||
|
} else {
|
||||||
|
// C'est un dossier, cliquable
|
||||||
|
sb.WriteString(fmt.Sprintf(
|
||||||
|
`<a href="#" hx-get="/api/folder/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link">📂 %s</a>`,
|
||||||
|
currentPath, part,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(`</span>`)
|
||||||
|
return template.HTML(sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateFolderViewMarkdown génère le contenu Markdown pour l'affichage d'un dossier
|
||||||
|
func (h *Handler) generateFolderViewMarkdown(folderPath string) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// En-tête
|
||||||
|
if folderPath == "" {
|
||||||
|
sb.WriteString("# 📁 Racine\n\n")
|
||||||
|
} else {
|
||||||
|
folderName := filepath.Base(folderPath)
|
||||||
|
sb.WriteString(fmt.Sprintf("# 📂 %s\n\n", folderName))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("_Contenu du dossier_\n\n")
|
||||||
|
|
||||||
|
// Lister le contenu
|
||||||
|
absPath := filepath.Join(h.notesDir, folderPath)
|
||||||
|
entries, err := os.ReadDir(absPath)
|
||||||
|
if err != nil {
|
||||||
|
sb.WriteString("❌ Erreur lors de la lecture du dossier\n")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Séparer dossiers et fichiers
|
||||||
|
var folders []os.DirEntry
|
||||||
|
var files []os.DirEntry
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
// Ignorer les fichiers cachés
|
||||||
|
if strings.HasPrefix(entry.Name(), ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
folders = append(folders, entry)
|
||||||
|
} else if strings.HasSuffix(entry.Name(), ".md") {
|
||||||
|
files = append(files, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher les dossiers
|
||||||
|
if len(folders) > 0 {
|
||||||
|
sb.WriteString("## 📁 Dossiers\n\n")
|
||||||
|
sb.WriteString("<div class=\"folder-list\">\n")
|
||||||
|
for _, folder := range folders {
|
||||||
|
subPath := filepath.Join(folderPath, folder.Name())
|
||||||
|
sb.WriteString(fmt.Sprintf(
|
||||||
|
`<div class="folder-item"><a href="#" hx-get="/api/folder/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true">📂 %s</a></div>`,
|
||||||
|
filepath.ToSlash(subPath), folder.Name(),
|
||||||
|
))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
sb.WriteString("</div>\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher les fichiers
|
||||||
|
if len(files) > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("## 📄 Notes (%d)\n\n", len(files)))
|
||||||
|
sb.WriteString("<div class=\"file-list\">\n")
|
||||||
|
for _, file := range files {
|
||||||
|
filePath := filepath.Join(folderPath, file.Name())
|
||||||
|
displayName := strings.TrimSuffix(file.Name(), ".md")
|
||||||
|
|
||||||
|
// Lire le titre du front matter si possible
|
||||||
|
fullPath := filepath.Join(h.notesDir, filePath)
|
||||||
|
fm, _, err := indexer.ExtractFrontMatterAndBody(fullPath)
|
||||||
|
if err == nil && fm.Title != "" {
|
||||||
|
displayName = fm.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(fmt.Sprintf(
|
||||||
|
`<div class="file-item"><a href="#" hx-get="/api/notes/%s" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true">📄 %s</a></div>`,
|
||||||
|
filepath.ToSlash(filePath), displayName,
|
||||||
|
))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
sb.WriteString("</div>\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(folders) == 0 && len(files) == 0 {
|
||||||
|
sb.WriteString("_Ce dossier est vide_\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ type Document struct {
|
|||||||
LastModified string
|
LastModified string
|
||||||
Body string
|
Body string
|
||||||
Summary string
|
Summary string
|
||||||
|
Links []string // Liens Markdown vers d'autres notes
|
||||||
|
|
||||||
lowerTitle string
|
lowerTitle string
|
||||||
lowerBody string
|
lowerBody string
|
||||||
@ -115,11 +116,11 @@ func (i *Indexer) Load(root string) error {
|
|||||||
indexed[tag] = list
|
indexed[tag] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build backlinks index
|
// Build backlinks index from Markdown links
|
||||||
backlinksMap := make(map[string][]string)
|
backlinksMap := make(map[string][]string)
|
||||||
for sourcePath, doc := range documents {
|
for sourcePath, doc := range documents {
|
||||||
links := extractInternalLinks(doc.Body)
|
// Use the Links field which contains extracted Markdown links
|
||||||
for _, targetPath := range links {
|
for _, targetPath := range doc.Links {
|
||||||
// Add sourcePath to the backlinks of targetPath
|
// Add sourcePath to the backlinks of targetPath
|
||||||
if _, ok := backlinksMap[targetPath]; !ok {
|
if _, ok := backlinksMap[targetPath]; !ok {
|
||||||
backlinksMap[targetPath] = make([]string, 0)
|
backlinksMap[targetPath] = make([]string, 0)
|
||||||
@ -169,6 +170,45 @@ func normalizeTags(tags []string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractMarkdownLinks extrait tous les liens Markdown du body
|
||||||
|
// Format : [texte](chemin/vers/note.md)
|
||||||
|
// Retourne une liste de chemins vers d'autres notes
|
||||||
|
func extractMarkdownLinks(body string) []string {
|
||||||
|
// Regex pour capturer [texte](chemin.md)
|
||||||
|
// Groupe 1 : texte du lien, Groupe 2 : chemin
|
||||||
|
re := regexp.MustCompile(`\[([^\]]+)\]\(([^)]+\.md)\)`)
|
||||||
|
matches := re.FindAllStringSubmatch(body, -1)
|
||||||
|
|
||||||
|
links := make([]string, 0, len(matches))
|
||||||
|
seen := make(map[string]bool) // Éviter les doublons
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
if len(match) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
linkPath := strings.TrimSpace(match[2])
|
||||||
|
|
||||||
|
// Ignorer les URLs absolues (http://, https://, //)
|
||||||
|
if strings.HasPrefix(linkPath, "http://") ||
|
||||||
|
strings.HasPrefix(linkPath, "https://") ||
|
||||||
|
strings.HasPrefix(linkPath, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliser le chemin (convertir \ en / pour Windows)
|
||||||
|
linkPath = filepath.ToSlash(linkPath)
|
||||||
|
|
||||||
|
// Éviter les doublons
|
||||||
|
if !seen[linkPath] {
|
||||||
|
seen[linkPath] = true
|
||||||
|
links = append(links, linkPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
func buildDocument(path string, fm FullFrontMatter, body string, tags []string) *Document {
|
func buildDocument(path string, fm FullFrontMatter, body string, tags []string) *Document {
|
||||||
title := strings.TrimSpace(fm.Title)
|
title := strings.TrimSpace(fm.Title)
|
||||||
if title == "" {
|
if title == "" {
|
||||||
@ -176,6 +216,7 @@ func buildDocument(path string, fm FullFrontMatter, body string, tags []string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
summary := buildSummary(body)
|
summary := buildSummary(body)
|
||||||
|
links := extractMarkdownLinks(body)
|
||||||
|
|
||||||
lowerTags := make([]string, len(tags))
|
lowerTags := make([]string, len(tags))
|
||||||
for idx, tag := range tags {
|
for idx, tag := range tags {
|
||||||
@ -190,6 +231,7 @@ func buildDocument(path string, fm FullFrontMatter, body string, tags []string)
|
|||||||
LastModified: strings.TrimSpace(fm.LastModified),
|
LastModified: strings.TrimSpace(fm.LastModified),
|
||||||
Body: body,
|
Body: body,
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
|
Links: links,
|
||||||
lowerTitle: strings.ToLower(title),
|
lowerTitle: strings.ToLower(title),
|
||||||
lowerBody: strings.ToLower(body),
|
lowerBody: strings.ToLower(body),
|
||||||
lowerTags: lowerTags,
|
lowerTags: lowerTags,
|
||||||
|
|||||||
@ -14,54 +14,33 @@
|
|||||||
"added_at": "2025-11-11T14:20:49.985321698+01:00",
|
"added_at": "2025-11-11T14:20:49.985321698+01:00",
|
||||||
"order": 1
|
"order": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "research/tech/websockets.md",
|
|
||||||
"is_dir": false,
|
|
||||||
"title": "websockets",
|
|
||||||
"added_at": "2025-11-11T14:20:55.347335695+01:00",
|
|
||||||
"order": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "tasks/backlog.md",
|
|
||||||
"is_dir": false,
|
|
||||||
"title": "backlog",
|
|
||||||
"added_at": "2025-11-11T14:20:57.762787363+01:00",
|
|
||||||
"order": 3
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"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": 4
|
"order": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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": 5
|
"order": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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": 6
|
"order": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "meetings/2025",
|
"path": "documentation/guides",
|
||||||
"is_dir": true,
|
"is_dir": true,
|
||||||
"title": "2025",
|
"title": "guides",
|
||||||
"added_at": "2025-11-11T14:22:21.531283601+01:00",
|
"added_at": "2025-11-12T18:18:20.53353467+01:00",
|
||||||
"order": 7
|
"order": 5
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "meetings/outscale.md",
|
|
||||||
"is_dir": false,
|
|
||||||
"title": "outscale",
|
|
||||||
"added_at": "2025-11-11T14:22:22.519332518+01:00",
|
|
||||||
"order": 8
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Sprint Planning January
|
title: Sprint Planning January
|
||||||
date: 10-11-2025
|
date: 10-11-2025
|
||||||
last_modified: 12-11-2025:10:26
|
last_modified: 12-11-2025:19:55
|
||||||
tags:
|
tags:
|
||||||
- meeting
|
- meeting
|
||||||
- planning
|
- planning
|
||||||
@ -22,6 +22,8 @@ tags:
|
|||||||
|
|
||||||
## Vélocité
|
## Vélocité
|
||||||
|
|
||||||
|
<a href="#" onclick="return false;" hx-get="/api/notes/un-dossier/test/Poppy-test.md" hx-target="#editor-container" hx-swap="innerHTML">Poppy Test</a>
|
||||||
|
|
||||||
20 story points pour ce sprint.
|
20 story points pour ce sprint.
|
||||||
|
|
||||||
## Risques
|
## Risques
|
||||||
@ -30,4 +32,4 @@ tags:
|
|||||||
- Tests E2E à mettre en place
|
- Tests E2E à mettre en place
|
||||||
|
|
||||||
|
|
||||||
/il
|
C'est une note pour être sur que c'est bien la dernière note éditée.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Scratch Pad
|
title: Scratch Pad
|
||||||
date: 10-11-2025
|
date: 10-11-2025
|
||||||
last_modified: 10-11-2025:20:05
|
last_modified: 12-11-2025:20:13
|
||||||
tags:
|
tags:
|
||||||
- default
|
- default
|
||||||
---
|
---
|
||||||
@ -26,3 +26,4 @@ const hello = () => {
|
|||||||
console.log('Hello World');
|
console.log('Hello World');
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
<a href="#" onclick="return false;" hx-get="/api/notes/projets/backend/api-design.md" hx-target="#editor-container" hx-swap="innerHTML">API Design</a>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Poppy Test
|
title: Poppy Test
|
||||||
date: 10-11-2025
|
date: 10-11-2025
|
||||||
last_modified: 11-11-2025:18:41
|
last_modified: 12-11-2025:20:16
|
||||||
---
|
---
|
||||||
|
|
||||||
# Poppy Test
|
# Poppy Test
|
||||||
@ -14,4 +14,9 @@ On verra bien à la fin.
|
|||||||
|
|
||||||
<a href="#" onclick="return false;" hx-get="/api/notes/un-dossier/test/Poppy-test.md" hx-target="#editor-container" hx-swap="innerHTML">Poppy Test</a>
|
<a href="#" onclick="return false;" hx-get="/api/notes/un-dossier/test/Poppy-test.md" hx-target="#editor-container" hx-swap="innerHTML">Poppy Test</a>
|
||||||
|
|
||||||
<a href="#" onclick="return false;" hx-get="/api/notes/research/design/typography.md" hx-target="#editor-container" hx-swap="innerHTML">Typography Research</a>
|
<a href="#" onclick="return false;" hx-get="/api/notes/research/design/typography.md" hx-target="#editor-container" hx-swap="innerHTML">Typography Research</a>
|
||||||
|
|
||||||
|
|
||||||
|
[UI Design Inspiration](research/design/ui-inspiration.md)
|
||||||
|
|
||||||
|
/i
|
||||||
174
static/theme.css
174
static/theme.css
@ -1261,6 +1261,22 @@ body, html {
|
|||||||
box-shadow: var(--shadow-glow);
|
box-shadow: var(--shadow-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Style pour la racine en drag-over */
|
||||||
|
.sidebar-section-header.drag-over {
|
||||||
|
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)) !important;
|
||||||
|
color: white !important;
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
animation: pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-header.drag-over .folder-name,
|
||||||
|
.sidebar-section-header.drag-over .root-hint,
|
||||||
|
.sidebar-section-header.drag-over .folder-icon {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Indicateur de destination pendant le drag */
|
/* Indicateur de destination pendant le drag */
|
||||||
.drag-destination-indicator {
|
.drag-destination-indicator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -3400,25 +3416,14 @@ body, html {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-sm);
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
scrollbar-width: thin;
|
/* Masquer la scrollbar mais garder le scroll */
|
||||||
scrollbar-color: var(--border-primary) transparent;
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE et Edge */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Masquer la scrollbar pour Chrome, Safari et Opera */
|
||||||
.recent-notes-container::-webkit-scrollbar {
|
.recent-notes-container::-webkit-scrollbar {
|
||||||
width: 6px;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
.recent-notes-container::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-notes-container::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--border-primary);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recent-notes-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--border-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.recent-note-card {
|
.recent-note-card {
|
||||||
@ -3482,3 +3487,140 @@ body, html {
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Home Page Sections with Accordions
|
||||||
|
======================================== */
|
||||||
|
.home-section {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section:hover {
|
||||||
|
border-color: var(--border-secondary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section-header:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section-header:active {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-section-content {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: all var(--transition-medium);
|
||||||
|
max-height: 600px;
|
||||||
|
/* Masquer la scrollbar mais garder le scroll */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE et Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Masquer la scrollbar pour Chrome, Safari et Opera */
|
||||||
|
.home-section-content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust nested containers for accordion layout */
|
||||||
|
.home-section .recent-notes-container {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Breadcrumb Navigation
|
||||||
|
======================================== */
|
||||||
|
.breadcrumb {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--accent-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Folder and File Lists (Folder View)
|
||||||
|
======================================== */
|
||||||
|
/* Styles spécifiques pour la vue de dossier dans l'éditeur */
|
||||||
|
#editor-content .folder-list,
|
||||||
|
#editor-content .file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-content .folder-list .folder-item,
|
||||||
|
#editor-content .file-list .file-item {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-content .folder-list .folder-item:hover,
|
||||||
|
#editor-content .file-list .file-item:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-content .folder-list .folder-item a,
|
||||||
|
#editor-content .file-list .file-item a {
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-content .folder-list .folder-item:hover a,
|
||||||
|
#editor-content .file-list .file-item:hover a {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,17 @@
|
|||||||
<div class="editor-header">
|
<div class="editor-header">
|
||||||
<label for="editor">
|
<label for="editor">
|
||||||
{{if .IsHome}}
|
{{if .IsHome}}
|
||||||
<strong>{{.Filename}}</strong>
|
{{if .Breadcrumb}}
|
||||||
|
{{.Breadcrumb}}
|
||||||
|
{{else}}
|
||||||
|
<strong>{{.Filename}}</strong>
|
||||||
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
Édition de : <strong>{{.Filename}}</strong>
|
{{if .Breadcrumb}}
|
||||||
|
{{.Breadcrumb}}
|
||||||
|
{{else}}
|
||||||
|
Édition de : <strong>{{.Filename}}</strong>
|
||||||
|
{{end}}
|
||||||
<span id="auto-save-status" class="auto-save-status"></span>
|
<span id="auto-save-status" class="auto-save-status"></span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!-- Indicateur de racine (maintenant cliquable et rétractable) -->
|
<!-- Indicateur de racine (maintenant cliquable et rétractable) -->
|
||||||
<div class="sidebar-section-header" data-section="notes" onclick="toggleSidebarSection('notes', event)" style="cursor: pointer;">
|
<div class="sidebar-section-header" data-section="notes" data-path="" data-is-dir="true" onclick="toggleSidebarSection('notes', event)" style="cursor: pointer;">
|
||||||
<span class="section-toggle expanded">▶</span>
|
<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>
|
||||||
|
|||||||
@ -483,21 +483,26 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="sidebar-section-title">📅 Daily Notes</h2>
|
<div class="sidebar-section-header" data-section="daily-notes" onclick="toggleSidebarSection('daily-notes', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
|
||||||
<div id="daily-calendar-container"
|
<span class="section-toggle expanded">▶</span>
|
||||||
hx-get="/api/daily/calendar/{{.Now.Format "2006/01"}}"
|
<h2 class="sidebar-section-title" style="margin: 0; flex: 1;">📅 Daily Notes</h2>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sidebar-section-content" id="daily-notes-content" style="display: block;">
|
||||||
|
<div id="daily-calendar-container"
|
||||||
|
hx-get="/api/daily/calendar/{{.Now.Format "2006/01"}}"
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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 once delay:200ms"
|
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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -622,16 +627,13 @@
|
|||||||
// Fonction pour gérer l'accordéon des dossiers dans la page d'accueil
|
// Fonction pour gérer l'accordéon des dossiers dans la page d'accueil
|
||||||
window.toggleFolder = function(folderId) {
|
window.toggleFolder = function(folderId) {
|
||||||
const content = document.getElementById('folder-' + folderId);
|
const content = document.getElementById('folder-' + folderId);
|
||||||
const icon = document.getElementById('icon-' + folderId);
|
|
||||||
|
|
||||||
if (content && icon) {
|
if (content) {
|
||||||
if (content.style.display === 'none') {
|
if (content.style.display === 'none') {
|
||||||
content.style.display = 'block';
|
content.style.display = 'block';
|
||||||
icon.textContent = '📁';
|
|
||||||
localStorage.setItem('folder-' + folderId, 'open');
|
localStorage.setItem('folder-' + folderId, 'open');
|
||||||
} else {
|
} else {
|
||||||
content.style.display = 'none';
|
content.style.display = 'none';
|
||||||
icon.textContent = '📂';
|
|
||||||
localStorage.setItem('folder-' + folderId, 'closed');
|
localStorage.setItem('folder-' + folderId, 'closed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,14 +641,12 @@
|
|||||||
|
|
||||||
// Restaurer l'état des dossiers depuis localStorage
|
// Restaurer l'état des dossiers depuis localStorage
|
||||||
window.restoreFolderStates = function() {
|
window.restoreFolderStates = function() {
|
||||||
document.querySelectorAll('.folder-content').forEach(function(content) {
|
document.querySelectorAll('.folder-content, .home-section-content').forEach(function(content) {
|
||||||
const folderId = content.id.replace('folder-', '');
|
const folderId = content.id.replace('folder-', '');
|
||||||
const state = localStorage.getItem('folder-' + folderId);
|
const state = localStorage.getItem('folder-' + folderId);
|
||||||
const icon = document.getElementById('icon-' + folderId);
|
|
||||||
|
|
||||||
if (state === 'closed' && icon) {
|
if (state === 'closed') {
|
||||||
content.style.display = 'none';
|
content.style.display = 'none';
|
||||||
icon.textContent = '📂';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user