diff --git a/public/app.js b/public/app.js
index af87151..ec8b8e1 100644
--- a/public/app.js
+++ b/public/app.js
@@ -33,6 +33,8 @@ let selectedDateFilter = null;
let selectedTagFilter = null;
let searchQuery = "";
let searchResults = [];
+let notesCache = null; // Кэш для заметок
+let lastLoadTime = 0; // Время последней загрузки
// Функция для получения текущей даты и времени
function getFormattedDateTime() {
@@ -473,7 +475,24 @@ async function deleteNoteImage(noteId, imageId) {
}
// Функция для загрузки заметок с сервера
-async function loadNotes() {
+async function loadNotes(forceReload = false) {
+ const now = Date.now();
+ const CACHE_DURATION = 30000; // 30 секунд кэширования
+
+ // Используем кэш, если он не устарел и не требуется принудительная перезагрузка
+ if (!forceReload && notesCache && (now - lastLoadTime) < CACHE_DURATION) {
+ allNotes = notesCache;
+ await renderNotes(notesCache);
+ renderCalendar();
+ renderTags();
+ renderCalendarMobile();
+ renderTagsMobile();
+ return;
+ }
+
+ // Показываем индикатор загрузки
+ showLoadingIndicator();
+
try {
const response = await fetch("/api/notes");
if (!response.ok) {
@@ -481,6 +500,8 @@ async function loadNotes() {
}
const notes = await response.json();
allNotes = notes; // Сохраняем все заметки в глобальную переменную
+ notesCache = notes; // Сохраняем в кэш
+ lastLoadTime = now;
await renderNotes(notes);
renderCalendar(); // Обновляем календарь после загрузки заметок
renderTags(); // Обновляем теги после загрузки заметок
@@ -489,6 +510,59 @@ async function loadNotes() {
} catch (error) {
console.error("Ошибка:", error);
notesList.innerHTML = "
Ошибка загрузки заметок
";
+ } finally {
+ // Скрываем индикатор загрузки
+ hideLoadingIndicator();
+ }
+}
+
+// Функция для показа индикатора загрузки
+function showLoadingIndicator() {
+ if (!document.getElementById("loading-indicator")) {
+ const loadingDiv = document.createElement("div");
+ loadingDiv.id = "loading-indicator";
+ loadingDiv.innerHTML = `
+
+
+
+ Загрузка заметок...
+
+
+
+ `;
+ document.body.appendChild(loadingDiv);
+ }
+}
+
+// Функция для скрытия индикатора загрузки
+function hideLoadingIndicator() {
+ const loadingIndicator = document.getElementById("loading-indicator");
+ if (loadingIndicator) {
+ loadingIndicator.remove();
}
}
@@ -591,8 +665,8 @@ async function renderNotes(notes) {
const parsedContent = marked.parse(contentWithClickableTags);
- // Получаем изображения заметки
- const noteImages = await getNoteImages(note.id);
+ // Используем изображения, которые уже пришли с заметкой
+ const noteImages = Array.isArray(note.images) ? note.images : [];
let imagesHtml = "";
if (noteImages.length > 0) {
@@ -703,7 +777,7 @@ function addNoteEventListeners() {
}
// Перезагружаем заметки
- await loadNotes();
+ await loadNotes(true);
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка удаления заметки");
@@ -802,7 +876,7 @@ function addNoteEventListeners() {
}
// Перезагружаем заметки
- await loadNotes();
+ await loadNotes(true);
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка сохранения заметки");
@@ -909,7 +983,7 @@ function addImageEventListeners() {
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
const success = await deleteNoteImage(noteId, imageId);
if (success) {
- await loadNotes(); // Перезагружаем заметки
+ await loadNotes(true); // Перезагружаем заметки
} else {
alert("Ошибка удаления изображения");
}
@@ -957,7 +1031,7 @@ async function saveNote() {
selectedImages = [];
updateImagePreview();
imageInput.value = "";
- await loadNotes();
+ await loadNotes(true);
} catch (error) {
console.error("Ошибка:", error);
alert("Ошибка сохранения заметки");
diff --git a/server.js b/server.js
index 8e52ebe..0367f5f 100644
--- a/server.js
+++ b/server.js
@@ -224,10 +224,32 @@ function createTables() {
console.error("Ошибка создания таблицы пользователей:", err.message);
} else {
console.log("Таблица пользователей готова");
+ createIndexes();
}
});
}
+// Создание индексов для оптимизации запросов
+function createIndexes() {
+ const indexes = [
+ "CREATE INDEX IF NOT EXISTS idx_notes_user_id ON notes(user_id)",
+ "CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at)",
+ "CREATE INDEX IF NOT EXISTS idx_notes_date ON notes(date)",
+ "CREATE INDEX IF NOT EXISTS idx_note_images_note_id ON note_images(note_id)",
+ "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)"
+ ];
+
+ indexes.forEach((indexSql, i) => {
+ db.run(indexSql, (err) => {
+ if (err) {
+ console.error(`Ошибка создания индекса ${i + 1}:`, err.message);
+ } else {
+ console.log(`Индекс ${i + 1} создан успешно`);
+ }
+ });
+ });
+}
+
// Middleware для аутентификации
function requireAuth(req, res, next) {
if (req.session.authenticated) {
@@ -397,52 +419,110 @@ app.get("/notes", requireAuth, (req, res) => {
res.sendFile(path.join(__dirname, "public", "notes.html"));
});
-// API для поиска заметок (должен быть ПЕРЕД /api/notes/:id)
+// API для поиска заметок с изображениями (должен быть ПЕРЕД /api/notes/:id)
app.get("/api/notes/search", requireAuth, (req, res) => {
const { q, tag, date } = req.query;
- let sql = "SELECT * FROM notes WHERE user_id = ?";
+ let whereClause = "WHERE n.user_id = ?";
let params = [req.session.userId];
// Поиск по тексту
if (q && q.trim()) {
- sql += " AND content LIKE ?";
+ whereClause += " AND n.content LIKE ?";
params.push(`%${q.trim()}%`);
}
// Поиск по тегу
if (tag && tag.trim()) {
- sql += " AND content LIKE ?";
+ whereClause += " AND n.content LIKE ?";
params.push(`%#${tag.trim()}%`);
}
// Поиск по дате
if (date && date.trim()) {
- sql += " AND date = ?";
+ whereClause += " AND n.date = ?";
params.push(date.trim());
}
- sql += " ORDER BY created_at DESC";
+ const sql = `
+ SELECT
+ n.*,
+ CASE
+ WHEN COUNT(ni.id) = 0 THEN '[]'
+ ELSE json_group_array(
+ json_object(
+ 'id', ni.id,
+ 'filename', ni.filename,
+ 'original_name', ni.original_name,
+ 'file_path', ni.file_path,
+ 'file_size', ni.file_size,
+ 'mime_type', ni.mime_type,
+ 'created_at', ni.created_at
+ )
+ )
+ END as images
+ FROM notes n
+ LEFT JOIN note_images ni ON n.id = ni.note_id
+ ${whereClause}
+ GROUP BY n.id
+ ORDER BY n.created_at DESC
+ `;
db.all(sql, params, (err, rows) => {
if (err) {
console.error("Ошибка поиска заметок:", err.message);
return res.status(500).json({ error: "Ошибка сервера" });
}
- res.json(rows);
+
+ // Парсим JSON строки изображений
+ const notesWithImages = rows.map(row => ({
+ ...row,
+ images: row.images === '[]' ? [] : JSON.parse(row.images)
+ }));
+
+ res.json(notesWithImages);
});
});
-// API для получения всех заметок
+// API для получения всех заметок с изображениями
app.get("/api/notes", requireAuth, (req, res) => {
- const sql = "SELECT * FROM notes WHERE user_id = ? ORDER BY created_at ASC";
+ const sql = `
+ SELECT
+ n.*,
+ CASE
+ WHEN COUNT(ni.id) = 0 THEN '[]'
+ ELSE json_group_array(
+ json_object(
+ 'id', ni.id,
+ 'filename', ni.filename,
+ 'original_name', ni.original_name,
+ 'file_path', ni.file_path,
+ 'file_size', ni.file_size,
+ 'mime_type', ni.mime_type,
+ 'created_at', ni.created_at
+ )
+ )
+ END as images
+ FROM notes n
+ LEFT JOIN note_images ni ON n.id = ni.note_id
+ WHERE n.user_id = ?
+ GROUP BY n.id
+ ORDER BY n.created_at ASC
+ `;
db.all(sql, [req.session.userId], (err, rows) => {
if (err) {
console.error("Ошибка получения заметок:", err.message);
return res.status(500).json({ error: "Ошибка сервера" });
}
- res.json(rows);
+
+ // Парсим JSON строки изображений
+ const notesWithImages = rows.map(row => ({
+ ...row,
+ images: row.images === '[]' ? [] : JSON.parse(row.images)
+ }));
+
+ res.json(notesWithImages);
});
});