Change d'interface plus légére, modification side barre
This commit is contained in:
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
`;
|
||||
|
||||
@ -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();
|
||||
|
||||
145
frontend/src/public-toggle.js
Normal file
145
frontend/src/public-toggle.js
Normal 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);
|
||||
}
|
||||
@ -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>
|
||||
`;
|
||||
|
||||
@ -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'
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user