Des tonnes de modifications notamment VIM / Couleurs / typos

This commit is contained in:
2025-11-11 15:41:51 +01:00
parent 439880b08f
commit 6face7a02f
59 changed files with 7857 additions and 960 deletions

View File

@ -13,7 +13,8 @@
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.6"
"@codemirror/view": "^6.38.6",
"@replit/codemirror-vim": "^6.2.2"
},
"devDependencies": {
"vite": "^7.2.2"
@ -59,6 +60,18 @@
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/commands": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
"integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^0.20.0",
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/language": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
@ -84,6 +97,17 @@
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/search": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
"integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
@ -126,70 +150,16 @@
}
},
"node_modules/@codemirror/commands": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
"integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
"integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@codemirror/language": "^0.20.0",
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/commands/node_modules/@codemirror/language": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
"integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@lezer/common": "^0.16.0",
"@lezer/highlight": "^0.16.0",
"@lezer/lr": "^0.16.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/commands/node_modules/@codemirror/state": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==",
"license": "MIT"
},
"node_modules/@codemirror/commands/node_modules/@codemirror/view": {
"version": "0.20.7",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^0.20.0",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/commands/node_modules/@lezer/common": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
"integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==",
"license": "MIT"
},
"node_modules/@codemirror/commands/node_modules/@lezer/highlight": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
"integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^0.16.0"
}
},
"node_modules/@codemirror/commands/node_modules/@lezer/lr": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
"integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^0.16.0"
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/lang-css": {
@ -278,33 +248,17 @@
}
},
"node_modules/@codemirror/search": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
"integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
"version": "6.5.11",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@codemirror/state": "^0.20.0",
"@codemirror/view": "^0.20.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search/node_modules/@codemirror/state": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==",
"license": "MIT"
},
"node_modules/@codemirror/search/node_modules/@codemirror/view": {
"version": "0.20.7",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^0.20.0",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
@ -853,6 +807,19 @@
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@replit/codemirror-vim": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@replit/codemirror-vim/-/codemirror-vim-6.3.0.tgz",
"integrity": "sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==",
"license": "MIT",
"peerDependencies": {
"@codemirror/commands": "6.x.x",
"@codemirror/language": "6.x.x",
"@codemirror/search": "6.x.x",
"@codemirror/state": "6.x.x",
"@codemirror/view": "6.x.x"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz",

View File

@ -18,6 +18,7 @@
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.6"
"@codemirror/view": "^6.38.6",
"@replit/codemirror-vim": "^6.2.2"
}
}

View File

@ -0,0 +1,93 @@
/**
* DailyNotes - Gère les raccourcis et interactions pour les daily notes
*/
/**
* Initialise le raccourci clavier pour la note du jour
* Ctrl/Cmd+D ouvre la note du jour
*/
function initDailyNotesShortcut() {
document.addEventListener('keydown', (event) => {
// Ctrl+D (Windows/Linux) ou Cmd+D (Mac)
if ((event.ctrlKey || event.metaKey) && event.key === 'd') {
event.preventDefault();
// Utiliser HTMX pour charger la note du jour
if (typeof htmx !== 'undefined') {
htmx.ajax('GET', '/api/daily/today', {
target: '#editor-container',
swap: 'innerHTML'
});
}
}
});
console.log('Daily notes shortcuts initialized (Ctrl/Cmd+D)');
}
/**
* Met à jour le calendrier et les notes récentes
* Appelé après la création ou modification d'une daily note
*/
window.refreshDailyNotes = function() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const calendarUrl = `/api/daily/calendar/${year}-${month}`;
// Recharger le calendrier
if (typeof htmx !== 'undefined') {
htmx.ajax('GET', calendarUrl, {
target: '#daily-calendar-container',
swap: 'innerHTML'
});
// Recharger les notes récentes
htmx.ajax('GET', '/api/daily/recent', {
target: '#daily-recent-container',
swap: 'innerHTML'
});
}
};
/**
* Écouter les événements HTMX pour rafraîchir le calendrier
* après sauvegarde d'une daily note
*/
document.body.addEventListener('htmx:afterSwap', (event) => {
const target = event.detail?.target;
// Si on a chargé l'éditeur avec une URL contenant /api/daily/
if (target && target.id === 'editor-container') {
const request = event.detail?.requestConfig;
if (request && request.path && request.path.includes('/api/daily/')) {
// On vient de charger une daily note, pas besoin de rafraîchir
return;
}
}
});
/**
* Écouter les soumissions de formulaire pour rafraîchir
* le calendrier après sauvegarde d'une daily note
*/
document.body.addEventListener('htmx:afterRequest', (event) => {
const target = event.detail?.target;
// Vérifier si c'est une soumission de formulaire d'édition
if (event.detail?.successful && target) {
// Vérifier si le chemin sauvegardé est une daily note
const pathInput = target.querySelector('input[name="path"]');
if (pathInput && pathInput.value.startsWith('daily/')) {
// Rafraîchir le calendrier et les notes récentes
window.refreshDailyNotes();
}
}
});
/**
* Initialisation automatique
*/
document.addEventListener('DOMContentLoaded', () => {
initDailyNotesShortcut();
});

View File

@ -6,6 +6,18 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { keymap } from '@codemirror/view';
import { indentWithTab } from '@codemirror/commands';
// Import du mode Vim
let vimExtension = null;
(async () => {
try {
const { vim } = await import('@replit/codemirror-vim');
vimExtension = vim;
console.log('✅ Vim extension loaded and ready');
} catch (error) {
console.warn('⚠️ Vim extension not available:', error.message);
}
})();
/**
* MarkdownEditor - Éditeur Markdown avec preview en temps réel
*/
@ -48,56 +60,88 @@ class MarkdownEditor {
});
}
// Initialiser CodeMirror 6
const startState = EditorState.create({
doc: this.textarea.value,
extensions: [
basicSetup,
markdown(),
oneDark,
keymap.of([indentWithTab]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// Debounce la mise à jour du preview
if (this._updateTimeout) {
clearTimeout(this._updateTimeout);
}
this._updateTimeout = setTimeout(() => {
this.updatePreview();
}, 150);
// Initialiser l'éditeur (avec ou sans Vim)
this.initEditor();
}
// Auto-save logic
if (this._autoSaveTimeout) {
clearTimeout(this._autoSaveTimeout);
}
this._autoSaveTimeout = setTimeout(() => {
const form = this.textarea.closest('form');
if (form) {
const saveStatus = document.getElementById('auto-save-status');
if (saveStatus) {
saveStatus.textContent = 'Sauvegarde...';
}
// Synchroniser le contenu de CodeMirror vers le textarea
this.syncToTextarea();
form.requestSubmit();
}
}, 2000); // Auto-save after 2 seconds of inactivity
getExtensions() {
const extensions = [
basicSetup,
markdown(),
oneDark,
keymap.of([indentWithTab]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// Debounce la mise à jour du preview
if (this._updateTimeout) {
clearTimeout(this._updateTimeout);
}
}),
// Keymap for Ctrl/Cmd+S
keymap.of([{
key: "Mod-s",
run: () => {
this._updateTimeout = setTimeout(() => {
this.updatePreview();
}, 150);
// Auto-save logic
if (this._autoSaveTimeout) {
clearTimeout(this._autoSaveTimeout);
}
this._autoSaveTimeout = setTimeout(() => {
const form = this.textarea.closest('form');
if (form) {
const saveStatus = document.getElementById('auto-save-status');
if (saveStatus) {
saveStatus.textContent = 'Sauvegarde...';
}
// Synchroniser le contenu de CodeMirror vers le textarea
this.syncToTextarea();
form.requestSubmit();
}
return true;
}, 2000); // Auto-save after 2 seconds of inactivity
}
}),
// Keymap for Ctrl/Cmd+S
keymap.of([{
key: "Mod-s",
run: () => {
const form = this.textarea.closest('form');
if (form) {
// Synchroniser le contenu de CodeMirror vers le textarea
this.syncToTextarea();
form.requestSubmit();
}
}])
]
return true;
}
}])
];
// Ajouter l'extension Vim si activée et disponible
if (window.vimModeManager && window.vimModeManager.isEnabled()) {
if (vimExtension) {
extensions.push(vimExtension());
console.log('✅ Vim mode enabled in editor');
} else {
console.warn('⚠️ Vim mode requested but extension not loaded yet');
}
}
return extensions;
}
initEditor() {
const currentContent = this.editorView
? this.editorView.state.doc.toString()
: this.textarea.value;
const extensions = this.getExtensions();
// Détruire l'ancien éditeur si il existe
if (this.editorView) {
this.editorView.destroy();
}
// Initialiser CodeMirror 6
const startState = EditorState.create({
doc: currentContent,
extensions
});
this.editorView = new EditorView({
@ -160,6 +204,13 @@ class MarkdownEditor {
// Initial preview update
this.updatePreview();
// Initialiser les SlashCommands si ce n'est pas déjà fait
if (this.editorView && !window.currentSlashCommands) {
window.currentSlashCommands = new SlashCommands({
editorView: this.editorView
});
}
}
stripFrontMatter(markdownContent) {
@ -242,6 +293,11 @@ class MarkdownEditor {
this.textarea = null;
this.preview = null;
}
async reloadWithVimMode() {
console.log('Reloading editor with Vim mode...');
await this.initEditor();
}
}
// Global instances
@ -360,9 +416,9 @@ class SlashCommands {
this.palette.id = 'slash-commands-palette';
this.palette.style.cssText = `
position: fixed;
background: #161b22;
background-color: #161b22 !important;
border: 1px solid #58a6ff;
background: var(--bg-secondary);
background-color: var(--bg-secondary) !important;
border: 1px solid var(--border-primary);
list-style: none;
padding: 0.5rem;
margin: 0;
@ -372,7 +428,7 @@ class SlashCommands {
min-width: 220px;
max-height: 320px;
overflow-y: auto;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.3), 0 0 20px rgba(88, 166, 255, 0.2);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.3);
opacity: 1 !important;
`;
@ -477,14 +533,14 @@ class SlashCommands {
filteredCommands.forEach((cmd, index) => {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #7d8590; margin-right: 0.5rem;">⌘</span>/${cmd.name}`;
li.innerHTML = `<span style="color: var(--text-muted); margin-right: 0.5rem;">⌘</span>/${cmd.name}`;
const isSelected = index === this.selectedIndex;
li.style.cssText = `
padding: 0.5rem 0.75rem;
cursor: pointer;
color: ${isSelected ? 'white' : '#e6edf3'};
background: ${isSelected ? 'linear-gradient(135deg, #58a6ff, #8b5cf6)' : 'transparent'};
color: ${isSelected ? 'var(--text-primary)' : 'var(--text-secondary)'};
background: ${isSelected ? 'var(--accent-primary)' : 'transparent'};
border-radius: 4px;
margin: 4px 0;
transition: all 150ms ease;
@ -632,12 +688,7 @@ function initializeMarkdownEditor(context) {
const markdownEditor = new MarkdownEditor(textarea, preview);
window.currentMarkdownEditor = markdownEditor;
if (markdownEditor.editorView) {
const slashCommands = new SlashCommands({
editorView: markdownEditor.editorView
});
window.currentSlashCommands = slashCommands;
}
// Note: SlashCommands sera créé automatiquement dans initEditor() qui est async
}
/**

246
frontend/src/favorites.js Normal file
View File

@ -0,0 +1,246 @@
/**
* Favorites - Gère le système de favoris
*/
class FavoritesManager {
constructor() {
this.init();
}
init() {
console.log('FavoritesManager: Initialisation...');
// Charger les favoris au démarrage
this.refreshFavorites();
// Écouter les événements HTMX pour mettre à jour les boutons
document.body.addEventListener('htmx:afterSwap', (event) => {
console.log('HTMX afterSwap:', event.detail.target.id);
if (event.detail.target.id === 'file-tree') {
console.log('File-tree chargé, ajout des boutons favoris...');
setTimeout(() => this.attachFavoriteButtons(), 100);
}
if (event.detail.target.id === 'favorites-list') {
console.log('Favoris rechargés, mise à jour des boutons...');
setTimeout(() => this.attachFavoriteButtons(), 100);
}
});
// Attacher les boutons après un délai pour laisser HTMX charger le file-tree
setTimeout(() => {
console.log('Tentative d\'attachement des boutons favoris après délai...');
this.attachFavoriteButtons();
}, 1000);
console.log('FavoritesManager: Initialisé');
}
refreshFavorites() {
if (typeof htmx !== 'undefined') {
htmx.ajax('GET', '/api/favorites', {
target: '#favorites-list',
swap: 'innerHTML'
});
}
}
async addFavorite(path, isDir, title) {
console.log('addFavorite appelé avec:', { path, isDir, title });
try {
// Utiliser URLSearchParams au lieu de FormData pour le format application/x-www-form-urlencoded
const params = new URLSearchParams();
params.append('path', path);
params.append('is_dir', isDir ? 'true' : 'false');
params.append('title', title || '');
console.log('Params créés:', {
path: params.get('path'),
is_dir: params.get('is_dir'),
title: params.get('title')
});
const response = await fetch('/api/favorites', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
});
if (response.ok) {
const html = await response.text();
document.getElementById('favorites-list').innerHTML = html;
this.attachFavoriteButtons();
console.log('Favori ajouté:', path);
} else if (response.status === 409) {
console.log('Déjà en favoris');
} else {
const errorText = await response.text();
console.error('Erreur ajout favori:', response.status, response.statusText, errorText);
}
} catch (error) {
console.error('Erreur ajout favori:', error);
}
}
async removeFavorite(path) {
try {
const params = new URLSearchParams();
params.append('path', path);
const response = await fetch('/api/favorites', {
method: 'DELETE',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
});
if (response.ok) {
const html = await response.text();
document.getElementById('favorites-list').innerHTML = html;
this.attachFavoriteButtons();
console.log('Favori retiré:', path);
} else {
console.error('Erreur retrait favori:', response.statusText);
}
} catch (error) {
console.error('Erreur retrait favori:', error);
}
}
async getFavoritesPaths() {
try {
const response = await fetch('/api/favorites');
const html = await response.text();
// Parser le HTML pour extraire les chemins
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const items = doc.querySelectorAll('.favorite-item');
return Array.from(items).map(item => item.getAttribute('data-path'));
} catch (error) {
console.error('Erreur chargement favoris:', error);
return [];
}
}
attachFavoriteButtons() {
console.log('attachFavoriteButtons: Début...');
// Ajouter des boutons étoile aux éléments du file tree
this.getFavoritesPaths().then(favoritePaths => {
console.log('Chemins favoris:', favoritePaths);
// Dossiers
const folderHeaders = document.querySelectorAll('.folder-header');
console.log('Nombre de folder-header trouvés:', folderHeaders.length);
folderHeaders.forEach(header => {
if (!header.querySelector('.add-to-favorites')) {
const folderItem = header.closest('.folder-item');
const path = folderItem?.getAttribute('data-path');
console.log('Dossier trouvé:', path);
if (path) {
const button = document.createElement('button');
button.className = 'add-to-favorites';
button.innerHTML = '⭐';
button.title = 'Ajouter aux favoris';
// Extraire le nom avant d'ajouter le bouton
const name = header.querySelector('.folder-name')?.textContent?.trim() || path.split('/').pop();
button.onclick = (e) => {
e.stopPropagation();
console.log('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) => {
e.stopPropagation();
console.log('Retrait dossier des favoris:', path);
this.removeFavorite(path);
};
}
header.appendChild(button);
}
}
});
// Fichiers
const fileItems = document.querySelectorAll('.file-item');
console.log('Nombre de file-item trouvés:', fileItems.length);
fileItems.forEach(fileItem => {
if (!fileItem.querySelector('.add-to-favorites')) {
const path = fileItem.getAttribute('data-path');
console.log('Fichier trouvé:', path);
if (path) {
const button = document.createElement('button');
button.className = 'add-to-favorites';
button.innerHTML = '⭐';
button.title = 'Ajouter aux favoris';
// Extraire le nom avant d'ajouter le bouton
const name = fileItem.textContent.trim().replace('📄', '').trim().replace('.md', '');
button.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
console.log('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) => {
e.preventDefault();
e.stopPropagation();
console.log('Retrait fichier des favoris:', path);
this.removeFavorite(path);
};
}
fileItem.appendChild(button);
}
}
});
console.log('attachFavoriteButtons: Terminé');
});
}
}
/**
* Fonctions globales pour les templates
*/
window.removeFavorite = function(path) {
if (window.favoritesManager) {
window.favoritesManager.removeFavorite(path);
}
};
/**
* Initialisation automatique
*/
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.favoritesManager = new FavoritesManager();
});
} else {
// DOM déjà chargé
window.favoritesManager = new FavoritesManager();
}

View File

@ -20,6 +20,11 @@ class FileTree {
// Event listener délégué pour les clics sur les folder-headers
sidebar.addEventListener('click', (e) => {
// Ignorer les clics sur les checkboxes
if (e.target.classList.contains('selection-checkbox')) {
return;
}
// Vérifier d'abord si c'est un folder-header ou un de ses enfants
const folderHeader = e.target.closest('.folder-header');
if (folderHeader && !e.target.closest('.file-item')) {
@ -489,4 +494,234 @@ document.addEventListener('keydown', (event) => {
*/
document.addEventListener('DOMContentLoaded', () => {
window.fileTree = new FileTree();
});
window.selectionManager = new SelectionManager();
});
/**
* SelectionManager - Gère le mode sélection et la suppression en masse
*/
class SelectionManager {
constructor() {
this.isSelectionMode = false;
this.selectedPaths = new Set();
this.init();
}
init() {
// Écouter les événements HTMX pour réinitialiser les listeners après les swaps
document.body.addEventListener('htmx:afterSwap', (event) => {
if (event.detail.target.id === 'file-tree' || event.detail.target.closest('#file-tree')) {
this.attachCheckboxListeners();
if (this.isSelectionMode) {
this.showCheckboxes();
}
}
});
document.body.addEventListener('htmx:oobAfterSwap', (event) => {
if (event.detail.target.id === 'file-tree') {
this.attachCheckboxListeners();
if (this.isSelectionMode) {
this.showCheckboxes();
}
}
});
// Attacher les listeners initiaux
setTimeout(() => this.attachCheckboxListeners(), 500);
}
attachCheckboxListeners() {
const checkboxes = document.querySelectorAll('.selection-checkbox');
checkboxes.forEach(checkbox => {
// Retirer l'ancien listener s'il existe
checkbox.removeEventListener('change', this.handleCheckboxChange);
// Ajouter le nouveau listener
checkbox.addEventListener('change', (e) => this.handleCheckboxChange(e));
});
}
handleCheckboxChange(e) {
const checkbox = e.target;
const path = checkbox.dataset.path;
if (checkbox.checked) {
window.selectionManager.selectedPaths.add(path);
} else {
window.selectionManager.selectedPaths.delete(path);
}
window.selectionManager.updateToolbar();
}
toggleSelectionMode() {
this.isSelectionMode = !this.isSelectionMode;
if (this.isSelectionMode) {
this.showCheckboxes();
document.getElementById('toggle-selection-mode')?.classList.add('active');
} else {
this.hideCheckboxes();
this.clearSelection();
document.getElementById('toggle-selection-mode')?.classList.remove('active');
}
}
showCheckboxes() {
const checkboxes = document.querySelectorAll('.selection-checkbox');
checkboxes.forEach(checkbox => {
checkbox.style.display = 'inline-block';
});
}
hideCheckboxes() {
const checkboxes = document.querySelectorAll('.selection-checkbox');
checkboxes.forEach(checkbox => {
checkbox.style.display = 'none';
checkbox.checked = false;
});
}
clearSelection() {
this.selectedPaths.clear();
this.updateToolbar();
}
updateToolbar() {
const toolbar = document.getElementById('selection-toolbar');
const countSpan = document.getElementById('selection-count');
if (this.selectedPaths.size > 0) {
toolbar.style.display = 'flex';
countSpan.textContent = `${this.selectedPaths.size} élément(s) sélectionné(s)`;
} else {
toolbar.style.display = 'none';
}
}
showDeleteConfirmationModal() {
const modal = document.getElementById('delete-confirmation-modal');
const countSpan = document.getElementById('delete-count');
const itemsList = document.getElementById('delete-items-list');
countSpan.textContent = this.selectedPaths.size;
// Générer la liste des éléments à supprimer
itemsList.innerHTML = '';
const ul = document.createElement('ul');
ul.style.margin = '0';
ul.style.padding = '0 0 0 1.5rem';
ul.style.color = 'var(--text-primary)';
this.selectedPaths.forEach(path => {
const li = document.createElement('li');
li.style.marginBottom = '0.5rem';
// Déterminer si c'est un dossier
const checkbox = document.querySelector(`.selection-checkbox[data-path="${path}"]`);
const isDir = checkbox?.dataset.isDir === 'true';
li.innerHTML = `${isDir ? '📁' : '📄'} <code>${path}</code>`;
ul.appendChild(li);
});
itemsList.appendChild(ul);
modal.style.display = 'flex';
}
hideDeleteConfirmationModal() {
const modal = document.getElementById('delete-confirmation-modal');
modal.style.display = 'none';
}
async deleteSelectedItems() {
const paths = Array.from(this.selectedPaths);
if (paths.length === 0) {
alert('Aucun élément sélectionné');
return;
}
try {
// Construire le corps de la requête au format query string
// Le backend attend: paths[]=path1&paths[]=path2
const params = new URLSearchParams();
paths.forEach(path => {
params.append('paths[]', path);
});
// Utiliser fetch() avec le corps en query string
const response = await fetch('/api/files/delete-multiple', {
method: 'DELETE',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params.toString()
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const html = await response.text();
// Parser le HTML pour trouver les éléments avec hx-swap-oob
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Traiter les swaps out-of-band manuellement
doc.querySelectorAll('[hx-swap-oob]').forEach(element => {
const targetId = element.id;
const target = document.getElementById(targetId);
if (target) {
target.innerHTML = element.innerHTML;
// Déclencher l'événement htmx pour que les listeners se réattachent
htmx.process(target);
}
});
console.log(`${paths.length} élément(s) supprimé(s)`);
// Fermer la modale
this.hideDeleteConfirmationModal();
// Réinitialiser la sélection et garder le mode sélection actif
this.clearSelection();
// Réattacher les listeners sur les nouvelles checkboxes
setTimeout(() => {
this.attachCheckboxListeners();
if (this.isSelectionMode) {
this.showCheckboxes();
}
}, 100);
} catch (error) {
console.error('Erreur lors de la suppression:', error);
alert('Erreur lors de la suppression des éléments: ' + error.message);
}
}
}
/**
* Fonctions globales pour les boutons
*/
window.toggleSelectionMode = function() {
window.selectionManager.toggleSelectionMode();
};
window.deleteSelected = function() {
window.selectionManager.showDeleteConfirmationModal();
};
window.cancelSelection = function() {
window.selectionManager.toggleSelectionMode();
};
window.hideDeleteConfirmationModal = function() {
window.selectionManager.hideDeleteConfirmationModal();
};
window.confirmDelete = function() {
window.selectionManager.deleteSelectedItems();
};

View File

@ -0,0 +1,184 @@
/**
* Font Manager - Gère le changement de polices
*/
class FontManager {
constructor() {
this.fonts = [
{
id: 'fira-code',
name: 'Fira Code',
family: "'Fira Code', 'Courier New', monospace",
googleFont: 'Fira+Code:wght@300;400;500;600;700'
},
{
id: 'sans-serif',
name: 'Sans-serif',
family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
googleFont: null
},
{
id: 'inter',
name: 'Inter',
family: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
googleFont: 'Inter:wght@300;400;500;600;700'
},
{
id: 'poppins',
name: 'Poppins',
family: "'Poppins', -apple-system, BlinkMacSystemFont, sans-serif",
googleFont: 'Poppins:wght@300;400;500;600;700'
},
{
id: 'public-sans',
name: 'Public Sans',
family: "'Public Sans', -apple-system, BlinkMacSystemFont, sans-serif",
googleFont: 'Public+Sans:wght@300;400;500;600;700'
},
{
id: 'jetbrains-mono',
name: 'JetBrains Mono',
family: "'JetBrains Mono', 'Courier New', monospace",
googleFont: 'JetBrains+Mono:wght@300;400;500;600;700'
},
{
id: 'cascadia-code',
name: 'Cascadia Code',
family: "'Cascadia Code', 'Courier New', monospace",
googleFont: 'Cascadia+Code:wght@300;400;500;600;700'
},
{
id: 'source-code-pro',
name: 'Source Code Pro',
family: "'Source Code Pro', 'Courier New', monospace",
googleFont: 'Source+Code+Pro:wght@300;400;500;600;700'
}
];
this.init();
}
init() {
// Charger la police sauvegardée
const savedFont = localStorage.getItem('selectedFont') || 'jetbrains-mono';
this.applyFont(savedFont);
// Charger la taille sauvegardée
const savedSize = localStorage.getItem('fontSize') || 'medium';
this.applyFontSize(savedSize);
console.log('FontManager initialized with font:', savedFont, 'size:', savedSize);
}
applyFont(fontId) {
const font = this.fonts.find(f => f.id === fontId);
if (!font) {
console.error('Police non trouvée:', fontId);
return;
}
// Charger la police Google Fonts si nécessaire
if (font.googleFont) {
this.loadGoogleFont(font.googleFont);
}
// Appliquer la police au body
document.body.style.fontFamily = font.family;
// Sauvegarder le choix
localStorage.setItem('selectedFont', fontId);
console.log('Police appliquée:', font.name);
}
applyFontSize(sizeId) {
// Définir les tailles en utilisant une variable CSS root
const sizes = {
'small': '14px',
'medium': '16px',
'large': '18px',
'x-large': '20px'
};
const size = sizes[sizeId] || sizes['medium'];
// Appliquer la taille via une variable CSS sur :root
// Cela affectera tous les éléments qui utilisent rem
document.documentElement.style.fontSize = size;
// Sauvegarder le choix
localStorage.setItem('fontSize', sizeId);
console.log('Taille de police appliquée:', sizeId, size);
}
getCurrentSize() {
return localStorage.getItem('fontSize') || 'medium';
}
loadGoogleFont(fontParam) {
// Vérifier si la police n'est pas déjà chargée
const linkId = 'google-font-' + fontParam.split(':')[0].replace(/\+/g, '-');
if (document.getElementById(linkId)) {
return; // Déjà chargé
}
// Créer un nouveau link pour Google Fonts
const link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
link.href = `https://fonts.googleapis.com/css2?family=${fontParam}&display=swap`;
document.head.appendChild(link);
console.log('Google Font chargée:', fontParam);
}
getCurrentFont() {
return localStorage.getItem('selectedFont') || 'jetbrains-mono';
}
getFonts() {
return this.fonts;
}
}
// Initialisation automatique
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.fontManager = new FontManager();
});
} else {
window.fontManager = new FontManager();
}
// Fonction globale pour changer la police
window.selectFont = function(fontId) {
if (window.fontManager) {
window.fontManager.applyFont(fontId);
// Mettre à jour l'interface (marquer comme active)
document.querySelectorAll('.font-card').forEach(card => {
card.classList.remove('active');
});
const selectedCard = document.querySelector(`.font-card[data-font="${fontId}"]`);
if (selectedCard) {
selectedCard.classList.add('active');
}
}
};
// Fonction globale pour changer la taille de police
window.selectFontSize = function(sizeId) {
if (window.fontManager) {
window.fontManager.applyFontSize(sizeId);
// Mettre à jour l'interface (marquer comme active)
document.querySelectorAll('.font-size-option').forEach(option => {
option.classList.remove('active');
});
const selectedOption = document.querySelector(`.font-size-option[data-size="${sizeId}"]`);
if (selectedOption) {
selectedOption.classList.add('active');
}
}
};

View File

@ -0,0 +1,165 @@
/**
* Keyboard Shortcuts Manager - Gère tous les raccourcis clavier de l'application
*/
class KeyboardShortcutsManager {
constructor() {
this.shortcuts = [
{ key: 'k', ctrl: true, description: 'Ouvrir la recherche', action: () => this.openSearch() },
{ key: 's', ctrl: true, description: 'Sauvegarder la note', action: () => this.saveNote() },
{ key: 'd', ctrl: true, description: 'Ouvrir la note du jour', action: () => this.openDailyNote() },
{ key: 'n', ctrl: true, description: 'Créer une nouvelle note', action: () => this.createNewNote() },
{ key: 'h', ctrl: true, description: 'Retour à la page d\'accueil', action: () => this.goHome() },
{ key: 'b', ctrl: true, description: 'Afficher/Masquer la sidebar', action: () => this.toggleSidebar() },
{ key: ',', ctrl: true, description: 'Ouvrir les paramètres', action: () => this.openSettings() },
{ key: 'p', ctrl: true, description: 'Afficher/Masquer la prévisualisation', action: () => this.togglePreview() },
{ key: 'f', ctrl: true, shift: true, description: 'Créer un nouveau dossier', action: () => this.createNewFolder() },
{ key: 'Escape', ctrl: false, description: 'Fermer les modales/dialogs', action: () => this.closeModals() }
];
this.init();
}
init() {
document.addEventListener('keydown', (event) => {
this.handleKeydown(event);
});
console.log('Keyboard shortcuts initialized:', this.shortcuts.length, 'shortcuts');
}
handleKeydown(event) {
// Ignorer si on tape dans un input/textarea (sauf pour les raccourcis système comme Ctrl+S)
const isInputField = event.target.tagName === 'INPUT' ||
event.target.tagName === 'TEXTAREA' ||
event.target.isContentEditable;
// Chercher un raccourci correspondant
for (const shortcut of this.shortcuts) {
const ctrlMatch = shortcut.ctrl ? (event.ctrlKey || event.metaKey) : !event.ctrlKey && !event.metaKey;
const shiftMatch = shortcut.shift ? event.shiftKey : !event.shiftKey;
const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase();
if (ctrlMatch && shiftMatch && keyMatch) {
// Certains raccourcis fonctionnent même dans les champs de saisie
const allowInInput = ['s', 'k', 'd', 'h', 'b', ',', '/'].includes(shortcut.key.toLowerCase());
if (!isInputField || allowInInput) {
event.preventDefault();
shortcut.action();
return;
}
}
}
}
openSearch() {
// Déclencher le focus sur le champ de recherche
const searchInput = document.querySelector('header input[type="search"]');
if (searchInput) {
searchInput.focus();
searchInput.select();
console.log('Search opened via Ctrl+K');
}
}
saveNote() {
// Déclencher la sauvegarde de la note (géré par CodeMirror)
console.log('Save triggered via Ctrl+S');
// La sauvegarde est déjà gérée dans editor.js
}
openDailyNote() {
// Ouvrir la note du jour
const dailyBtn = document.querySelector('button[hx-get="/api/daily/today"]');
if (dailyBtn) {
dailyBtn.click();
console.log('Daily note opened via Ctrl+D');
}
}
createNewNote() {
if (typeof showNewNoteModal === 'function') {
showNewNoteModal();
console.log('New note modal opened via Ctrl+N');
}
}
goHome() {
const homeBtn = document.querySelector('button[hx-get="/api/home"]');
if (homeBtn) {
homeBtn.click();
console.log('Home opened via Ctrl+H');
}
}
toggleSidebar() {
if (typeof toggleSidebar === 'function') {
toggleSidebar();
console.log('Sidebar toggled via Ctrl+B');
}
}
openSettings() {
if (typeof openThemeModal === 'function') {
openThemeModal();
console.log('Settings opened via Ctrl+,');
}
}
togglePreview() {
if (typeof togglePreview === 'function') {
togglePreview();
console.log('Preview toggled via Ctrl+/');
}
}
createNewFolder() {
if (typeof showNewFolderModal === 'function') {
showNewFolderModal();
console.log('New folder modal opened via Ctrl+Shift+F');
}
}
closeModals() {
// Fermer les modales ouvertes
if (typeof hideNewNoteModal === 'function') {
const noteModal = document.getElementById('new-note-modal');
if (noteModal && noteModal.style.display !== 'none') {
hideNewNoteModal();
return;
}
}
if (typeof hideNewFolderModal === 'function') {
const folderModal = document.getElementById('new-folder-modal');
if (folderModal && folderModal.style.display !== 'none') {
hideNewFolderModal();
return;
}
}
if (typeof closeThemeModal === 'function') {
const themeModal = document.getElementById('theme-modal');
if (themeModal && themeModal.style.display !== 'none') {
closeThemeModal();
return;
}
}
console.log('Escape pressed');
}
getShortcuts() {
return this.shortcuts;
}
}
// Initialisation automatique
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.keyboardShortcuts = new KeyboardShortcutsManager();
});
} else {
window.keyboardShortcuts = new KeyboardShortcutsManager();
}

View File

@ -2,3 +2,4 @@ import './editor.js';
import './file-tree.js';
import './ui.js';
import './search.js';
import './daily-notes.js';

View File

@ -0,0 +1,205 @@
/**
* 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
*/
class ThemeManager {
constructor() {
this.themes = [
{
id: 'material-dark',
name: 'Material Dark',
icon: '🌙',
description: 'Thème professionnel inspiré de Material Design'
},
{
id: 'monokai-dark',
name: 'Monokai Dark',
icon: '🎨',
description: 'Palette Monokai classique pour les développeurs'
},
{
id: 'dracula',
name: 'Dracula',
icon: '🧛',
description: 'Thème sombre élégant avec des accents violets et cyan'
},
{
id: 'one-dark',
name: 'One Dark',
icon: '⚡',
description: 'Thème populaire d\'Atom avec des couleurs douces'
},
{
id: 'solarized-dark',
name: 'Solarized Dark',
icon: '☀️',
description: 'Palette scientifiquement optimisée pour réduire la fatigue oculaire'
},
{
id: 'nord',
name: 'Nord',
icon: '❄️',
description: 'Palette arctique apaisante avec des tons bleus froids'
},
{
id: 'catppuccin',
name: 'Catppuccin',
icon: '🌸',
description: 'Thème pastel doux et chaleureux avec des accents roses et bleus'
},
{
id: 'everforest',
name: 'Everforest',
icon: '🌲',
description: 'Palette naturelle inspirée de la forêt avec des tons verts et beiges'
}
];
this.currentTheme = this.loadTheme();
this.init();
}
init() {
// Appliquer le thème sauvegardé
this.applyTheme(this.currentTheme);
// Écouter les événements HTMX pour réinitialiser les listeners
document.body.addEventListener('htmx:afterSwap', (event) => {
if (event.detail.target.id === 'sidebar' || event.detail.target.closest('#sidebar')) {
this.attachModalListeners();
}
});
console.log('ThemeManager initialized with theme:', this.currentTheme);
}
loadTheme() {
// Charger le thème depuis localStorage, par défaut 'material-dark'
return localStorage.getItem('app-theme') || 'material-dark';
}
saveTheme(themeId) {
localStorage.setItem('app-theme', themeId);
}
applyTheme(themeId) {
// Appliquer le thème sur l'élément racine
document.documentElement.setAttribute('data-theme', themeId);
this.currentTheme = themeId;
this.saveTheme(themeId);
// Mettre à jour les cartes de thème si la modale est ouverte
this.updateThemeCards();
console.log('Theme applied:', themeId);
}
openThemeModal() {
const modal = document.getElementById('theme-modal');
if (modal) {
modal.style.display = 'flex';
this.updateThemeCards();
}
}
closeThemeModal() {
const modal = document.getElementById('theme-modal');
if (modal) {
modal.style.display = 'none';
}
}
updateThemeCards() {
// Mettre à jour l'état actif des cartes de thème
const cards = document.querySelectorAll('.theme-card');
cards.forEach(card => {
const themeId = card.dataset.theme;
if (themeId === this.currentTheme) {
card.classList.add('active');
} else {
card.classList.remove('active');
}
});
}
attachModalListeners() {
// Ré-attacher les listeners après un swap HTMX
const settingsBtn = document.getElementById('theme-settings-btn');
if (settingsBtn) {
settingsBtn.replaceWith(settingsBtn.cloneNode(true));
const newBtn = document.getElementById('theme-settings-btn');
newBtn.addEventListener('click', () => this.openThemeModal());
}
}
getThemes() {
return this.themes;
}
getCurrentTheme() {
return this.currentTheme;
}
}
/**
* Fonctions globales pour les boutons
*/
window.openThemeModal = function() {
if (window.themeManager) {
window.themeManager.openThemeModal();
}
};
window.closeThemeModal = function() {
if (window.themeManager) {
window.themeManager.closeThemeModal();
}
};
window.selectTheme = function(themeId) {
if (window.themeManager) {
window.themeManager.applyTheme(themeId);
}
};
window.switchSettingsTab = function(tabName) {
console.log('Switching to tab:', tabName);
// Désactiver tous les onglets
const tabs = document.querySelectorAll('.settings-tab');
tabs.forEach(tab => tab.classList.remove('active'));
// Cacher toutes les sections
document.getElementById('themes-section').style.display = 'none';
document.getElementById('fonts-section').style.display = 'none';
document.getElementById('editor-section').style.display = 'none';
// Activer l'onglet cliqué
const activeTab = Array.from(tabs).find(tab => {
const text = tab.textContent.toLowerCase();
if (tabName === 'themes') return text.includes('thème');
if (tabName === 'fonts') return text.includes('police');
if (tabName === 'editor') return text.includes('éditeur');
return false;
});
if (activeTab) {
activeTab.classList.add('active');
}
// Afficher la section correspondante
const sectionId = tabName + '-section';
const section = document.getElementById(sectionId);
if (section) {
section.style.display = 'block';
console.log('Showing section:', sectionId);
} else {
console.error('Section not found:', sectionId);
}
};
/**
* Initialisation automatique
*/
document.addEventListener('DOMContentLoaded', () => {
window.themeManager = new ThemeManager();
});

View File

@ -0,0 +1,139 @@
/**
* Vim Mode Manager - Gère l'activation/désactivation du mode Vim dans CodeMirror
*/
class VimModeManager {
constructor() {
this.enabled = this.loadPreference();
this.vim = null; // Extension Vim de CodeMirror
this.editorView = null; // Instance EditorView actuelle
console.log('VimModeManager initialized, enabled:', this.enabled);
}
/**
* Charge la préférence du mode Vim depuis localStorage
*/
loadPreference() {
const saved = localStorage.getItem('vimModeEnabled');
return saved === 'true';
}
/**
* Sauvegarde la préférence du mode Vim
*/
savePreference(enabled) {
localStorage.setItem('vimModeEnabled', enabled ? 'true' : 'false');
}
/**
* Récupère l'état actuel du mode Vim
*/
isEnabled() {
return this.enabled;
}
/**
* Active ou désactive le mode Vim
*/
async toggle() {
this.enabled = !this.enabled;
this.savePreference(this.enabled);
// Recharger l'éditeur si il existe
if (window.currentEditor && window.currentEditor.reloadWithVimMode) {
await window.currentEditor.reloadWithVimMode(this.enabled);
}
return this.enabled;
}
/**
* Charge l'extension Vim de façon asynchrone
*/
async loadVimExtension() {
if (this.vim) {
return this.vim;
}
try {
// Import dynamique du package Vim
const { vim } = await import('@replit/codemirror-vim');
this.vim = vim;
console.log('✅ Vim extension loaded successfully');
return this.vim;
} catch (error) {
console.warn('⚠️ Vim mode is not available. The @replit/codemirror-vim package is not installed.');
console.info('To install it, run: cd frontend && npm install && npm run build');
this.vim = false; // Marquer comme échoué
this.enabled = false; // Désactiver automatiquement
this.savePreference(false);
return null;
}
}
/**
* Obtient l'extension Vim pour CodeMirror (si activée)
*/
async getVimExtension() {
if (!this.enabled) {
return null;
}
// Si déjà essayé et échoué
if (this.vim === false) {
return null;
}
if (this.vim) {
return this.vim();
}
const vimModule = await this.loadVimExtension();
return vimModule ? vimModule() : null;
}
}
// Instance globale
const vimModeManager = new VimModeManager();
// Export pour utilisation dans d'autres modules
if (typeof window !== 'undefined') {
window.vimModeManager = vimModeManager;
// Fonction globale pour le toggle dans la modale
window.toggleVimMode = async function() {
const checkbox = document.getElementById('vim-mode-toggle');
if (!checkbox) return;
const enabled = await vimModeManager.toggle();
checkbox.checked = enabled;
// Vérifier si le package est disponible
if (enabled && vimModeManager.vim === false) {
alert('❌ Le mode Vim n\'est pas disponible.\n\nLe package @replit/codemirror-vim n\'est pas installé.\n\nPour l\'installer, exécutez :\ncd frontend\nnpm install\nnpm run build');
checkbox.checked = false;
return;
}
// Afficher un message
const message = enabled ? '✅ Mode Vim activé' : '❌ Mode Vim désactivé';
console.log(message);
// Recharger l'éditeur actuel si il existe
if (window.currentMarkdownEditor && window.currentMarkdownEditor.reloadWithVimMode) {
await window.currentMarkdownEditor.reloadWithVimMode();
console.log('Editor reloaded with Vim mode:', enabled);
} else {
console.log('No editor to reload. Vim mode will be applied when opening a note.');
}
};
// Initialiser l'état du checkbox au chargement
document.addEventListener('DOMContentLoaded', () => {
const checkbox = document.getElementById('vim-mode-toggle');
if (checkbox) {
checkbox.checked = vimModeManager.isEnabled();
}
});
}