Change d'interface plus légére, modification side barre

This commit is contained in:
2025-12-24 16:14:17 +01:00
parent cc1d6880a7
commit 917a31d5a8
46 changed files with 7484 additions and 298 deletions

View File

@ -133,13 +133,13 @@ class FavoritesManager {
attachFavoriteButtons() {
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
this.getFavoritesPaths().then(favoritePaths => {
debug('Chemins favoris:', favoritePaths);
// Supprimer tous les boutons favoris existants APRÈS avoir récupéré la liste
document.querySelectorAll('.add-to-favorites').forEach(btn => btn.remove());
// Dossiers
const folderHeaders = document.querySelectorAll('.folder-header');
debug('Nombre de folder-header trouvés:', folderHeaders.length);
@ -153,7 +153,7 @@ class FavoritesManager {
if (path) {
const button = document.createElement('button');
button.className = 'add-to-favorites';
button.innerHTML = '';
button.innerHTML = '<i data-lucide="star" class="icon-sm"></i>';
button.title = 'Ajouter aux favoris';
// Extraire le nom avant d'ajouter le bouton
@ -191,11 +191,11 @@ class FavoritesManager {
if (path) {
const button = document.createElement('button');
button.className = 'add-to-favorites';
button.innerHTML = '';
button.innerHTML = '<i data-lucide="star" class="icon-sm"></i>';
button.title = 'Ajouter aux favoris';
// Extraire le nom avant d'ajouter le bouton
const name = fileItem.textContent.trim().replace('📄', '').trim().replace('.md', '');
const name = fileItem.textContent.trim().replace('.md', '');
button.onclick = (e) => {
e.preventDefault();
@ -220,6 +220,11 @@ class FavoritesManager {
});
debug('attachFavoriteButtons: Terminé');
// Initialiser les icônes Lucide après création des boutons
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
}
}

View File

@ -13,6 +13,9 @@ class FileTree {
init() {
this.setupEventListeners();
// Restaurer l'état des dossiers au démarrage
setTimeout(() => this.restoreFolderStates(), 500);
debug('FileTree initialized with event delegation');
}
@ -67,17 +70,60 @@ class FileTree {
const children = folderItem.querySelector('.folder-children');
const toggle = header.querySelector('.folder-toggle');
const icon = header.querySelector('.folder-icon');
const folderPath = folderItem.getAttribute('data-path');
if (children.style.display === 'none') {
// Ouvrir le dossier
children.style.display = 'block';
toggle.classList.add('expanded');
icon.textContent = '📂';
icon.innerHTML = '<i data-lucide="folder-open" class="icon-sm"></i>';
this.saveFolderState(folderPath, true);
} else {
// Fermer le dossier
children.style.display = 'none';
toggle.classList.remove('expanded');
icon.textContent = '📁';
icon.innerHTML = '<i data-lucide="folder" class="icon-sm"></i>';
this.saveFolderState(folderPath, false);
}
}
saveFolderState(folderPath, isExpanded) {
if (!folderPath) return;
const expandedFolders = this.getExpandedFolders();
if (isExpanded) {
expandedFolders.add(folderPath);
} else {
expandedFolders.delete(folderPath);
}
localStorage.setItem('expanded-folders', JSON.stringify([...expandedFolders]));
}
getExpandedFolders() {
const saved = localStorage.getItem('expanded-folders');
return saved ? new Set(JSON.parse(saved)) : new Set();
}
restoreFolderStates() {
const expandedFolders = this.getExpandedFolders();
document.querySelectorAll('.folder-item').forEach(folderItem => {
const folderPath = folderItem.getAttribute('data-path');
if (folderPath && expandedFolders.has(folderPath)) {
const header = folderItem.querySelector('.folder-header');
const children = folderItem.querySelector('.folder-children');
const toggle = header?.querySelector('.folder-toggle');
const icon = header?.querySelector('.folder-icon');
if (children && toggle && icon) {
children.style.display = 'block';
toggle.classList.add('expanded');
icon.innerHTML = '<i data-lucide="folder-open" class="icon-sm"></i>';
}
}
});
// Réinitialiser les icônes Lucide
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
}
@ -159,8 +205,9 @@ class FileTree {
// Vérifier si le swap concerne le file-tree
const target = event.detail?.target;
if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) {
debug('FileTree: afterSwap detected, updating attributes...');
debug('FileTree: afterSwap detected, updating attributes and restoring folder states...');
this.updateDraggableAttributes();
setTimeout(() => this.restoreFolderStates(), 50);
}
});
@ -169,8 +216,9 @@ class FileTree {
const target = event.detail?.target;
// Ignorer les swaps de statut (auto-save-status, save-status)
if (target && target.id === 'file-tree') {
debug('FileTree: oobAfterSwap detected, updating attributes...');
debug('FileTree: oobAfterSwap detected, updating attributes and restoring folder states...');
this.updateDraggableAttributes();
setTimeout(() => this.restoreFolderStates(), 50);
}
});
@ -181,6 +229,7 @@ class FileTree {
setTimeout(() => {
this.setupEventListeners();
this.updateDraggableAttributes();
this.restoreFolderStates();
}, 50);
});
}
@ -678,7 +727,7 @@ class SelectionManager {
const checkbox = document.querySelector(`.selection-checkbox[data-path="${path}"]`);
const isDir = checkbox?.dataset.isDir === 'true';
li.innerHTML = `${isDir ? '📁' : '📄'} <code>${path}</code>`;
li.innerHTML = `${isDir ? '<i data-lucide="folder" class="icon-sm"></i>' : '<i data-lucide="file-text" class="icon-sm"></i>'} <code>${path}</code>`;
ul.appendChild(li);
});

View File

@ -231,8 +231,10 @@ class I18n {
// Create singleton instance
export const i18n = new I18n();
// Export convenience function
// Export convenience functions
export const t = (key, args) => i18n.t(key, args);
export const loadTranslations = () => i18n.loadTranslations();
export const translatePage = () => i18n.translatePage();
// Initialize on import
i18n.init().then(() => {

View File

@ -181,12 +181,12 @@ class LanguageManager {
// Header buttons
const homeButton = document.querySelector('button[hx-get="/api/home"]');
if (homeButton && !homeButton.hasAttribute('data-i18n')) {
homeButton.innerHTML = `🏠 ${t('menu.home')}`;
homeButton.innerHTML = `<i data-lucide="home" class="icon-sm"></i> ${t('menu.home')}`;
}
const newNoteButton = document.querySelector('header button[onclick="showNewNoteModal()"]');
if (newNoteButton && !newNoteButton.hasAttribute('data-i18n')) {
newNoteButton.innerHTML = ` ${t('menu.newNote')}`;
newNoteButton.innerHTML = `<i data-lucide="file-plus" class="icon-sm"></i> ${t('menu.newNote')}`;
}
// Search placeholder
@ -199,7 +199,7 @@ class LanguageManager {
const newNoteModal = document.getElementById('new-note-modal');
if (newNoteModal) {
const title = newNoteModal.querySelector('h2');
if (title) title.textContent = `📝 ${t('newNoteModal.title')}`;
if (title) title.innerHTML = `<i data-lucide="file-text" class="icon-sm"></i> ${t('newNoteModal.title')}`;
const label = newNoteModal.querySelector('label[for="note-name"]');
if (label) label.textContent = t('newNoteModal.label');
@ -218,7 +218,7 @@ class LanguageManager {
const newFolderModal = document.getElementById('new-folder-modal');
if (newFolderModal) {
const title = newFolderModal.querySelector('h2');
if (title) title.textContent = `📁 ${t('newFolderModal.title')}`;
if (title) title.innerHTML = `<i data-lucide="folder-plus" class="icon-sm"></i> ${t('newFolderModal.title')}`;
const label = newFolderModal.querySelector('label[for="folder-name"]');
if (label) label.textContent = t('newFolderModal.label');
@ -256,16 +256,16 @@ class LanguageManager {
// Theme modal
const modalTitle = document.querySelector('.theme-modal-content h2');
if (modalTitle) {
modalTitle.textContent = `⚙️ ${t('settings.title')}`;
modalTitle.innerHTML = `<i data-lucide="settings" class="icon-sm"></i> ${t('settings.title')}`;
}
// Translate tabs
const tabs = document.querySelectorAll('.settings-tab');
if (tabs.length >= 4) {
tabs[0].innerHTML = `🎨 ${t('tabs.themes')}`;
tabs[1].innerHTML = `🔤 ${t('tabs.fonts')}`;
tabs[2].innerHTML = `⌨️ ${t('tabs.shortcuts')}`;
tabs[3].innerHTML = `⚙️ ${t('tabs.other')}`;
tabs[0].innerHTML = `<i data-lucide="palette" class="icon-sm"></i> ${t('tabs.themes')}`;
tabs[1].innerHTML = `<i data-lucide="type" class="icon-sm"></i> ${t('tabs.fonts')}`;
tabs[2].innerHTML = `<i data-lucide="keyboard" class="icon-sm"></i> ${t('tabs.shortcuts')}`;
tabs[3].innerHTML = `<i data-lucide="settings" class="icon-sm"></i> ${t('tabs.other')}`;
}
// Translate close button in settings
@ -281,20 +281,20 @@ class LanguageManager {
if (langSection) {
const heading = langSection.querySelector('h3');
if (heading) {
heading.textContent = `🌍 ${t('languages.title')}`;
heading.innerHTML = `<i data-lucide="languages" class="icon-sm"></i> ${t('languages.title')}`;
}
}
// Sidebar sections
const searchSectionTitle = document.querySelector('.sidebar-section-title');
if (searchSectionTitle && searchSectionTitle.textContent.includes('🔍')) {
searchSectionTitle.textContent = `🔍 ${t('search.title') || 'Recherche'}`;
if (searchSectionTitle && (searchSectionTitle.textContent.includes('🔍') || searchSectionTitle.querySelector('[data-lucide="search"]'))) {
searchSectionTitle.innerHTML = `<i data-lucide="search" class="icon-sm"></i> ${t('search.title') || 'Recherche'}`;
}
// Sidebar "Nouveau dossier" button
const newFolderBtn = document.querySelector('.folder-create-btn');
if (newFolderBtn && !newFolderBtn.hasAttribute('data-i18n')) {
newFolderBtn.innerHTML = `📁 ${t('fileTree.newFolder')}`;
newFolderBtn.innerHTML = `<i data-lucide="folder-plus" class="icon-sm"></i> ${t('fileTree.newFolder')}`;
}
// Sidebar "Paramètres" button span

View File

@ -309,7 +309,7 @@ class LinkInserter {
this.results = [];
this.resultsContainer.innerHTML = `
<div class="link-inserter-no-results">
<div class="link-inserter-no-results-icon">🔍</div>
<div class="link-inserter-no-results-icon"><i data-lucide="search" class="icon-lg"></i></div>
<p>Aucune note trouvée pour « <strong>${this.escapeHtml(query)}</strong> »</p>
</div>
`;
@ -318,7 +318,7 @@ class LinkInserter {
showError() {
this.resultsContainer.innerHTML = `
<div class="link-inserter-error">
<div class="link-inserter-error-icon">⚠️</div>
<div class="link-inserter-error-icon"><i data-lucide="alert-triangle" class="icon-lg"></i></div>
<p>Erreur lors de la recherche</p>
</div>
`;

View File

@ -12,3 +12,7 @@ import './vim-mode-manager.js';
import './favorites.js';
import './sidebar-sections.js';
import './keyboard-shortcuts.js';
import { initPublicToggle } from './public-toggle.js';
// Initialiser le toggle public
initPublicToggle();

View File

@ -0,0 +1,145 @@
// public-toggle.js - Gestion du statut public/privé des notes
import { t } from './i18n.js';
/**
* Initialise le gestionnaire de toggle public
*/
export function initPublicToggle() {
// Event delegation sur le body pour gérer les boutons dynamiques
document.body.addEventListener('click', (e) => {
const btn = e.target.closest('#toggle-public-btn');
if (!btn) return;
e.preventDefault();
togglePublicStatus(btn);
});
}
/**
* Bascule le statut public d'une note
* @param {HTMLElement} btn - Le bouton cliqué
*/
async function togglePublicStatus(btn) {
const path = btn.dataset.path;
const isCurrentlyPublic = btn.dataset.isPublic === 'true';
if (!path) {
console.error('Pas de chemin de note trouvé');
return;
}
// Désactiver le bouton pendant la requête
btn.disabled = true;
const originalText = btn.textContent;
btn.textContent = '⏳ ' + t('public.loading');
try {
const response = await fetch('/api/public/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({ path }),
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const data = await response.json();
const isNowPublic = data.status === 'public';
// Mettre à jour le bouton
btn.dataset.isPublic = isNowPublic;
// Mettre à jour le texte et le titre avec i18n
const textKey = isNowPublic ? 'public.buttonPublic' : 'public.buttonPrivate';
const titleKey = isNowPublic ? 'public.titlePublic' : 'public.titlePrivate';
btn.innerHTML = `<span data-i18n="${textKey}">${isNowPublic ? '🌐 ' + t('public.buttonPublic') : '🔒 ' + t('public.buttonPrivate')}</span>`;
btn.title = t(titleKey);
btn.setAttribute('data-i18n-title', titleKey);
// Ajouter/retirer la classe CSS
if (isNowPublic) {
btn.classList.add('public-active');
} else {
btn.classList.remove('public-active');
}
// Afficher une notification de succès
const messageKey = isNowPublic ? 'public.notificationPublished' : 'public.notificationUnpublished';
showNotification(t(messageKey));
} catch (error) {
console.error('Error toggling public status:', error);
btn.textContent = originalText;
showNotification(t('public.notificationError'), 'error');
} finally {
btn.disabled = false;
}
}
/**
* Affiche une notification temporaire
* @param {string} message - Le message à afficher
* @param {string} type - Type de notification (success, error)
*/
function showNotification(message, type = 'success') {
// Créer l'élément de notification
const notification = document.createElement('div');
notification.className = `public-notification ${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 2rem;
right: 2rem;
background: ${type === 'error' ? 'var(--error-red, #e74c3c)' : 'var(--success-green, #2ecc71)'};
color: white;
padding: 1rem 1.5rem;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 10000;
animation: slideIn 0.3s ease-out;
font-weight: 500;
`;
// Ajouter l'animation
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
if (!document.querySelector('style[data-public-notification]')) {
style.setAttribute('data-public-notification', 'true');
document.head.appendChild(style);
}
document.body.appendChild(notification);
// Retirer après 3 secondes
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}

View File

@ -51,7 +51,7 @@ class SearchModal {
<div class="search-modal-body">
<div class="search-modal-results">
<div class="search-modal-help">
<div class="search-modal-help-title">💡 Recherche avancée</div>
<div class="search-modal-help-title"><i data-lucide="lightbulb" class="icon-sm"></i> Recherche avancée</div>
<div class="search-modal-help-items">
<div class="search-modal-help-item">
<code>tag:projet</code>
@ -289,7 +289,7 @@ class SearchModal {
this.results = [];
this.resultsContainer.innerHTML = `
<div class="search-modal-help">
<div class="search-modal-help-title">💡 Recherche avancée</div>
<div class="search-modal-help-title"><i data-lucide="lightbulb" class="icon-sm"></i> Recherche avancée</div>
<div class="search-modal-help-items">
<div class="search-modal-help-item">
<code>tag:projet</code>
@ -325,7 +325,7 @@ class SearchModal {
this.results = [];
this.resultsContainer.innerHTML = `
<div class="search-modal-no-results">
<div class="search-modal-no-results-icon">🔍</div>
<div class="search-modal-no-results-icon"><i data-lucide="search" class="icon-lg"></i></div>
<p class="search-modal-no-results-text">Aucun résultat pour « <strong>${this.escapeHtml(query)}</strong> »</p>
<p class="search-modal-no-results-hint">Essayez d'autres mots-clés ou utilisez les filtres</p>
</div>
@ -335,7 +335,7 @@ class SearchModal {
showError() {
this.resultsContainer.innerHTML = `
<div class="search-modal-error">
<div class="search-modal-error-icon">⚠️</div>
<div class="search-modal-error-icon"><i data-lucide="alert-triangle" class="icon-lg"></i></div>
<p>Une erreur s'est produite lors de la recherche</p>
</div>
`;

View File

@ -9,49 +9,49 @@ class ThemeManager {
{
id: 'material-dark',
name: 'Material Dark',
icon: '🌙',
icon: 'moon',
description: 'Thème professionnel inspiré de Material Design'
},
{
id: 'monokai-dark',
name: 'Monokai Dark',
icon: '🎨',
icon: 'palette',
description: 'Palette Monokai classique pour les développeurs'
},
{
id: 'dracula',
name: 'Dracula',
icon: '🧛',
icon: 'moon-star',
description: 'Thème sombre élégant avec des accents violets et cyan'
},
{
id: 'one-dark',
name: 'One Dark',
icon: '',
icon: 'zap',
description: 'Thème populaire d\'Atom avec des couleurs douces'
},
{
id: 'solarized-dark',
name: 'Solarized Dark',
icon: '☀️',
icon: 'sun',
description: 'Palette scientifiquement optimisée pour réduire la fatigue oculaire'
},
{
id: 'nord',
name: 'Nord',
icon: '❄️',
icon: 'snowflake',
description: 'Palette arctique apaisante avec des tons bleus froids'
},
{
id: 'catppuccin',
name: 'Catppuccin',
icon: '🌸',
icon: 'flower-2',
description: 'Thème pastel doux et chaleureux avec des accents roses et bleus'
},
{
id: 'everforest',
name: 'Everforest',
icon: '🌲',
icon: 'tree-pine',
description: 'Palette naturelle inspirée de la forêt avec des tons verts et beiges'
}
];