`;
// Auto-save sur changement
setTimeout(() => {
const titleInput = card.querySelector('.note-title-input-inline');
const contentTextarea = card.querySelector('.note-content-textarea-inline');
if (titleInput) titleInput.oninput = utils.debounce(() => saveNoteInline(note.id), 1000);
if (contentTextarea) contentTextarea.oninput = utils.debounce(() => saveNoteInline(note.id), 1000);
}, 0);
} else {
// Mode compact
let html = `
${escapeHtml(note.title)}
`;
if (note.content) {
html += `
${escapeHtml(utils.truncate(note.content, 100))}
`;
}
if (note.image_filename) {
html += ``;
}
// Afficher les todos
if (note.todos && note.todos.length > 0) {
const displayTodos = note.todos.slice(0, 3);
html += `
`).join('');
}
function formatSummary(summary) {
// Convertir le Markdown de base en HTML
let html = escapeHtml(summary);
// Titres
html = html.replace(/\n## (.*?)\n/g, '
$1
');
html = html.replace(/\n# (.*?)\n/g, '
$1
');
// Listes à puces
html = html.replace(/\n- (.*?)(?=\n|$)/g, '
$1
');
html = html.replace(/(
.*<\/li>)/s, '
$1
');
// Listes numérotées
html = html.replace(/\n\d+\. (.*?)(?=\n|$)/g, '
$1
');
// Gras
html = html.replace(/\*\*(.*?)\*\*/g, '$1');
// Italique
html = html.replace(/\*(.*?)\*/g, '$1');
// Sauts de ligne
html = html.replace(/\n\n/g, '
');
html = html.replace(/\n/g, ' ');
return html;
}
async function summarizeRss() {
const btn = document.getElementById('summarizeRssBtn');
if (!btn) return;
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '';
try {
const result = await api.post('/api/rss/summarize', {});
// Vérifier si les résumés sont activés
const settings = await api.get('/api/settings');
const summaryEnabled = settings.rss_summary_enabled === '1' || settings.rss_summary_enabled === 'true';
if (summaryEnabled) {
// Si les résumés sont activés, juste recharger le bloc RSS
alert('Résumé généré avec succès !');
await loadRssArticles();
} else {
// Sinon créer une note avec le résumé
const newNote = await api.post('/api/notes', {
title: `📰 Résumé RSS - ${new Date().toLocaleDateString('fr-FR')}`,
content: result.summary
});
alert('Résumé généré et sauvegardé dans une nouvelle note !');
await loadNotes();
// Expand the new note
state.expandedNoteId = newNote.id;
renderNotes();
}
} catch (error) {
console.error('Erreur génération résumé RSS:', error);
if (error.message && error.message.includes('400')) {
alert('Veuillez configurer votre clé API OpenRouter dans l\'administration');
} else {
alert('Erreur lors de la génération du résumé RSS');
}
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
}
}
// ==================== OPENROUTER ====================
let allModels = [];
async function loadOpenRouterSettings() {
try {
const settings = await api.get('/api/settings');
const models = await api.get('/api/rss/models');
allModels = models;
// Populate form
const apiKeyInput = document.getElementById('openrouterApiKey');
const modelSelect = document.getElementById('openrouterModel');
const summaryEnabled = document.getElementById('rssSummaryEnabled');
const summaryPrompt = document.getElementById('rssSummaryPrompt');
if (apiKeyInput) {
apiKeyInput.value = settings.openrouter_api_key || '';
}
if (summaryEnabled) {
summaryEnabled.checked = settings.rss_summary_enabled === 'true' || settings.rss_summary_enabled === '1';
}
if (summaryPrompt) {
summaryPrompt.value = settings.rss_summary_prompt || `Tu es un assistant spécialisé dans la synthèse d'actualités. Analyse les articles suivants et crée un résumé structuré et informatif.
Pour chaque article, identifie:
- Le sujet principal
- Les points clés
- L'importance de l'information
Ensuite, génère un résumé global qui:
1. Regroupe les thèmes communs
2. Hiérarchise les informations par importance
3. Présente une vue d'ensemble claire et concise
4. Utilise un style professionnel mais accessible
Format de sortie en Markdown avec des sections claires.`;
}
// Populate model selector
renderModelSelect(models, settings.openrouter_model);
} catch (error) {
console.error('Erreur chargement paramètres OpenRouter:', error);
}
}
function renderModelSelect(models, selectedModel) {
const select = document.getElementById('openrouterModel');
if (!select) return;
select.innerHTML = models.map(model => `
`).join('');
}
function filterModels() {
const searchInput = document.getElementById('modelSearch');
const select = document.getElementById('openrouterModel');
if (!searchInput || !select) return;
const query = searchInput.value.toLowerCase();
if (!query) {
renderModelSelect(allModels, select.value);
return;
}
const filtered = allModels.filter(model =>
model.name.toLowerCase().includes(query) ||
model.provider.toLowerCase().includes(query) ||
model.id.toLowerCase().includes(query)
);
renderModelSelect(filtered, select.value);
}
async function saveOpenRouterSettings() {
const apiKeyInput = document.getElementById('openrouterApiKey');
const modelSelect = document.getElementById('openrouterModel');
const summaryEnabled = document.getElementById('rssSummaryEnabled');
const summaryPrompt = document.getElementById('rssSummaryPrompt');
try {
// Save all settings
await api.put('/api/settings/openrouter_api_key', { value: apiKeyInput.value });
await api.put('/api/settings/openrouter_model', { value: modelSelect.value });
await api.put('/api/settings/rss_summary_enabled', { value: summaryEnabled.checked ? '1' : '0' });
await api.put('/api/settings/rss_summary_prompt', { value: summaryPrompt.value });
alert('Paramètres OpenRouter sauvegardés avec succès');
} catch (error) {
console.error('Erreur sauvegarde paramètres OpenRouter:', error);
alert('Erreur lors de la sauvegarde des paramètres');
}
}
// ==================== INIT ====================
async function init() {
// Vérifier l'authentification
await initAuth();
// Charger les données
await Promise.all([loadNotes(), loadTodos(), loadRssArticles()]);
// Setup search
setupSearch();
// Event listeners
const newNoteBtn = document.getElementById('newNoteBtn');
if (newNoteBtn) {
newNoteBtn.addEventListener('click', createNewNote);
}
// View filters (Active/Archived)
document.querySelectorAll('.view-filter').forEach(btn => {
btn.addEventListener('click', () => setNoteView(btn.dataset.view));
});
// Admin button
const adminBtn = document.getElementById('adminBtn');
if (adminBtn) {
adminBtn.addEventListener('click', openAdminModal);
}
const closeAdminModalBtn = document.getElementById('closeAdminModal');
if (closeAdminModalBtn) {
closeAdminModalBtn.addEventListener('click', closeAdminModal);
}
const createUserBtn = document.getElementById('createUserBtn');
if (createUserBtn) {
createUserBtn.addEventListener('click', createUser);
}
const adminModalBackdrop = document.getElementById('adminModal');
if (adminModalBackdrop) {
adminModalBackdrop.addEventListener('click', (e) => {
if (e.target === adminModalBackdrop) closeAdminModal();
});
}
const closeEditor = document.getElementById('closeEditor');
if (closeEditor) {
closeEditor.addEventListener('click', closeNoteModal);
}
const saveNoteBtn = document.getElementById('saveNote');
if (saveNoteBtn) {
saveNoteBtn.addEventListener('click', async () => {
if (currentNoteId) {
await saveCurrentNote();
alert('Note enregistrée !');
}
});
}
const deleteNote = document.getElementById('deleteNote');
if (deleteNote) {
deleteNote.addEventListener('click', deleteCurrentNote);
}
const addImageBtn = document.getElementById('addImageBtn');
if (addImageBtn) {
addImageBtn.addEventListener('click', addNoteImage);
}
const imageInput = document.getElementById('imageInput');
if (imageInput) {
imageInput.addEventListener('change', handleImageUploadInline);
}
const fileInput = document.getElementById('fileInput');
if (fileInput) {
fileInput.addEventListener('change', handleFileUploadInline);
}
const removeImage = document.getElementById('removeImage');
if (removeImage) {
removeImage.addEventListener('click', removeNoteImage);
}
const addNoteTodoBtn = document.getElementById('addNoteTodo');
if (addNoteTodoBtn) {
addNoteTodoBtn.addEventListener('click', addNoteTodo);
}
const addTodoBtn = document.getElementById('addTodoBtn');
if (addTodoBtn) {
addTodoBtn.addEventListener('click', addTodo);
}
const todoInput = document.getElementById('todoInput');
if (todoInput) {
todoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addTodo();
});
}
// Todo filters
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => setTodoFilter(btn.dataset.filter));
});
// Event delegation pour les todos de la sidebar
const todoList = document.getElementById('todoList');
if (todoList) {
todoList.addEventListener('change', (e) => {
if (e.target.classList.contains('todo-checkbox')) {
const todoId = parseInt(e.target.dataset.todoId);
toggleTodo(todoId, e.target.checked);
}
});
todoList.addEventListener('click', (e) => {
if (e.target.classList.contains('todo-delete')) {
const todoId = parseInt(e.target.dataset.todoId);
deleteTodo(todoId);
}
});
}
// Event delegation pour les todos des notes
const noteTodosList = document.getElementById('noteTodosList');
if (noteTodosList) {
noteTodosList.addEventListener('change', (e) => {
if (e.target.classList.contains('todo-checkbox')) {
const todoId = parseInt(e.target.dataset.noteTodoId);
toggleNoteTodo(todoId, e.target.checked);
}
});
noteTodosList.addEventListener('click', (e) => {
if (e.target.classList.contains('todo-delete')) {
const todoId = parseInt(e.target.dataset.noteTodoId);
deleteNoteTodo(todoId);
}
});
}
// Event delegation pour les boutons d'administration des utilisateurs
const usersTableBody = document.getElementById('usersTableBody');
if (usersTableBody) {
usersTableBody.addEventListener('click', (e) => {
// Bouton changer mot de passe
if (e.target.classList.contains('btn-edit-user') || e.target.closest('.btn-edit-user')) {
const btn = e.target.classList.contains('btn-edit-user') ? e.target : e.target.closest('.btn-edit-user');
const userId = parseInt(btn.dataset.userId);
const username = btn.dataset.username;
changeUserPassword(userId, username);
}
// Bouton supprimer utilisateur
else if (e.target.classList.contains('btn-delete-user') || e.target.closest('.btn-delete-user')) {
const btn = e.target.classList.contains('btn-delete-user') ? e.target : e.target.closest('.btn-delete-user');
const userId = parseInt(btn.dataset.userId);
const username = btn.dataset.username;
deleteUser(userId, username);
}
});
}
// Close editor on backdrop click
const noteEditorBackdrop = document.getElementById('noteEditorBackdrop');
if (noteEditorBackdrop) {
noteEditorBackdrop.addEventListener('click', (e) => {
if (e.target === noteEditorBackdrop) closeNoteModal();
});
}
// Event delegation for notes grid
const notesGrid = document.getElementById('notesGrid');
if (notesGrid) {
// Click on compact note to expand
notesGrid.addEventListener('click', (e) => {
const compactNote = e.target.closest('.note-card-compact');
if (compactNote) {
const noteId = parseInt(compactNote.dataset.noteId);
expandNote(noteId);
return;
}
// Collapse note button
if (e.target.closest('.btn-collapse-note')) {
collapseNote();
return;
}
// Archive note button
const archiveBtn = e.target.closest('.btn-archive-note');
if (archiveBtn) {
const noteId = parseInt(archiveBtn.dataset.noteId);
const archived = archiveBtn.dataset.archived === 'true';
archiveNote(noteId, !archived);
return;
}
// Delete note button
const deleteBtn = e.target.closest('.btn-delete-note-inline');
if (deleteBtn) {
const noteId = parseInt(deleteBtn.dataset.noteId);
deleteNoteInline(noteId);
return;
}
// Add image button
const addImageBtn = e.target.closest('.btn-add-image-inline');
if (addImageBtn) {
const noteId = parseInt(addImageBtn.dataset.noteId);
triggerImageUpload(noteId);
return;
}
// Remove image button
const removeImageBtn = e.target.closest('.btn-remove-image-inline');
if (removeImageBtn) {
const noteId = parseInt(removeImageBtn.dataset.noteId);
removeImageInline(noteId);
return;
}
// Add todo button
const addTodoBtn = e.target.closest('.btn-add-todo-inline');
if (addTodoBtn) {
const noteId = parseInt(addTodoBtn.dataset.noteId);
const inputElement = document.querySelector(`.add-todo-inline-input[data-note-id="${noteId}"]`);
if (inputElement) {
addNoteTodoInline(noteId, inputElement);
}
return;
}
// Add file button
const addFileBtn = e.target.closest('.btn-add-file-inline');
if (addFileBtn) {
const noteId = parseInt(addFileBtn.dataset.noteId);
triggerFileUpload(noteId);
return;
}
// Delete todo inline button
if (e.target.classList.contains('todo-delete-inline')) {
const todoId = parseInt(e.target.dataset.todoInlineId);
deleteNoteTodoInline(todoId);
return;
}
// Download file button
const downloadBtn = e.target.closest('.btn-download-file');
if (downloadBtn) {
const fileId = parseInt(downloadBtn.dataset.fileId);
downloadFile(fileId);
return;
}
// Delete file button
const deleteFileBtn = e.target.closest('.btn-delete-file');
if (deleteFileBtn) {
const fileId = parseInt(deleteFileBtn.dataset.fileId);
deleteFile(fileId);
return;
}
});
// Checkbox change for inline todos
notesGrid.addEventListener('change', (e) => {
if (e.target.classList.contains('todo-checkbox-inline')) {
const todoId = parseInt(e.target.dataset.todoInlineId);
toggleNoteTodoInline(todoId, e.target.checked);
}
});
// Enter key on add todo input
notesGrid.addEventListener('keypress', (e) => {
if (e.target.classList.contains('add-todo-inline-input') && e.key === 'Enter') {
const noteId = parseInt(e.target.dataset.noteId);
addNoteTodoInline(noteId, e.target);
}
});
}
// Close modal on Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (state.expandedNoteId) {
collapseNote();
} else {
closeNoteModal();
closeAdminModal();
closePasswordChangeModal();
}
}
});
// ==================== ADMIN TAB SWITCHING ====================
document.querySelectorAll('.admin-tab').forEach(tab => {
tab.addEventListener('click', () => {
switchAdminTab(tab.dataset.tab);
});
});
// ==================== PASSWORD CHANGE MODAL ====================
const closePasswordModal = document.getElementById('closeChangePasswordModal');
if (closePasswordModal) {
closePasswordModal.addEventListener('click', closePasswordChangeModal);
}
const cancelPasswordChange = document.getElementById('cancelChangePassword');
if (cancelPasswordChange) {
cancelPasswordChange.addEventListener('click', closePasswordChangeModal);
}
const confirmPasswordBtn = document.getElementById('confirmChangePassword');
if (confirmPasswordBtn) {
confirmPasswordBtn.addEventListener('click', confirmPasswordChange);
}
// Enter key to confirm password change
const newPasswordInput = document.getElementById('newPassword');
const confirmPasswordInput = document.getElementById('confirmPassword');
if (newPasswordInput && confirmPasswordInput) {
[newPasswordInput, confirmPasswordInput].forEach(input => {
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') confirmPasswordChange();
});
});
}
// ==================== RSS FEEDS ====================
const addRssFeedBtn = document.getElementById('addRssFeedBtn');
if (addRssFeedBtn) {
addRssFeedBtn.addEventListener('click', addRssFeed);
}
const rssUrlInput = document.getElementById('rssUrlInput');
if (rssUrlInput) {
rssUrlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addRssFeed();
});
}
const fetchRssBtn = document.getElementById('fetchRssBtn');
if (fetchRssBtn) {
fetchRssBtn.addEventListener('click', fetchRssArticles);
}
// Event delegation for RSS feeds list
const rssFeedsList = document.getElementById('rssFeedsList');
if (rssFeedsList) {
rssFeedsList.addEventListener('change', (e) => {
if (e.target.type === 'checkbox' && e.target.dataset.feedId) {
const feedId = parseInt(e.target.dataset.feedId);
toggleRssFeed(feedId, e.target.checked);
}
});
rssFeedsList.addEventListener('click', (e) => {
const deleteBtn = e.target.closest('.btn-delete-feed');
if (deleteBtn) {
const feedId = parseInt(deleteBtn.dataset.feedId);
deleteRssFeed(feedId);
}
});
}
// RSS articles click to open in new tab
const rssArticlesList = document.getElementById('rssArticlesList');
if (rssArticlesList) {
rssArticlesList.addEventListener('click', (e) => {
const article = e.target.closest('.rss-article');
if (article && article.dataset.link) {
window.open(article.dataset.link, '_blank');
}
});
}
// RSS summarize button
const summarizeRssBtn = document.getElementById('summarizeRssBtn');
if (summarizeRssBtn) {
summarizeRssBtn.addEventListener('click', summarizeRss);
}
// ==================== OPENROUTER ====================
const modelSearch = document.getElementById('modelSearch');
if (modelSearch) {
modelSearch.addEventListener('input', utils.debounce(filterModels, 300));
}
const saveOpenRouterBtn = document.getElementById('saveOpenRouterSettings');
if (saveOpenRouterBtn) {
saveOpenRouterBtn.addEventListener('click', saveOpenRouterSettings);
}
}
// Démarrer l'application
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}