✨ Добавлены индексы для оптимизации запросов и улучшена обработка заметок с изображениями
- Реализована функция создания индексов для таблиц в базе данных, что улучшает производительность запросов - Обновлены API для получения и поиска заметок, теперь они возвращают изображения, связанные с заметками - Добавлен кэш для заметок на клиенте с возможностью принудительной перезагрузки - Внедрен индикатор загрузки при загрузке заметок для улучшения пользовательского опыта
This commit is contained in:
parent
f8692177f9
commit
092c01dff4
@ -33,6 +33,8 @@ let selectedDateFilter = null;
|
|||||||
let selectedTagFilter = null;
|
let selectedTagFilter = null;
|
||||||
let searchQuery = "";
|
let searchQuery = "";
|
||||||
let searchResults = [];
|
let searchResults = [];
|
||||||
|
let notesCache = null; // Кэш для заметок
|
||||||
|
let lastLoadTime = 0; // Время последней загрузки
|
||||||
|
|
||||||
// Функция для получения текущей даты и времени
|
// Функция для получения текущей даты и времени
|
||||||
function getFormattedDateTime() {
|
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 {
|
try {
|
||||||
const response = await fetch("/api/notes");
|
const response = await fetch("/api/notes");
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -481,6 +500,8 @@ async function loadNotes() {
|
|||||||
}
|
}
|
||||||
const notes = await response.json();
|
const notes = await response.json();
|
||||||
allNotes = notes; // Сохраняем все заметки в глобальную переменную
|
allNotes = notes; // Сохраняем все заметки в глобальную переменную
|
||||||
|
notesCache = notes; // Сохраняем в кэш
|
||||||
|
lastLoadTime = now;
|
||||||
await renderNotes(notes);
|
await renderNotes(notes);
|
||||||
renderCalendar(); // Обновляем календарь после загрузки заметок
|
renderCalendar(); // Обновляем календарь после загрузки заметок
|
||||||
renderTags(); // Обновляем теги после загрузки заметок
|
renderTags(); // Обновляем теги после загрузки заметок
|
||||||
@ -489,6 +510,59 @@ async function loadNotes() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
|
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
|
||||||
|
} finally {
|
||||||
|
// Скрываем индикатор загрузки
|
||||||
|
hideLoadingIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для показа индикатора загрузки
|
||||||
|
function showLoadingIndicator() {
|
||||||
|
if (!document.getElementById("loading-indicator")) {
|
||||||
|
const loadingDiv = document.createElement("div");
|
||||||
|
loadingDiv.id = "loading-indicator";
|
||||||
|
loadingDiv.innerHTML = `
|
||||||
|
<div style="
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 10000;
|
||||||
|
font-size: 16px;
|
||||||
|
">
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<div style="
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #007bff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
"></div>
|
||||||
|
Загрузка заметок...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
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 parsedContent = marked.parse(contentWithClickableTags);
|
||||||
|
|
||||||
// Получаем изображения заметки
|
// Используем изображения, которые уже пришли с заметкой
|
||||||
const noteImages = await getNoteImages(note.id);
|
const noteImages = Array.isArray(note.images) ? note.images : [];
|
||||||
let imagesHtml = "";
|
let imagesHtml = "";
|
||||||
|
|
||||||
if (noteImages.length > 0) {
|
if (noteImages.length > 0) {
|
||||||
@ -703,7 +777,7 @@ function addNoteEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перезагружаем заметки
|
// Перезагружаем заметки
|
||||||
await loadNotes();
|
await loadNotes(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка удаления заметки");
|
alert("Ошибка удаления заметки");
|
||||||
@ -802,7 +876,7 @@ function addNoteEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перезагружаем заметки
|
// Перезагружаем заметки
|
||||||
await loadNotes();
|
await loadNotes(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка сохранения заметки");
|
alert("Ошибка сохранения заметки");
|
||||||
@ -909,7 +983,7 @@ function addImageEventListeners() {
|
|||||||
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
|
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
|
||||||
const success = await deleteNoteImage(noteId, imageId);
|
const success = await deleteNoteImage(noteId, imageId);
|
||||||
if (success) {
|
if (success) {
|
||||||
await loadNotes(); // Перезагружаем заметки
|
await loadNotes(true); // Перезагружаем заметки
|
||||||
} else {
|
} else {
|
||||||
alert("Ошибка удаления изображения");
|
alert("Ошибка удаления изображения");
|
||||||
}
|
}
|
||||||
@ -957,7 +1031,7 @@ async function saveNote() {
|
|||||||
selectedImages = [];
|
selectedImages = [];
|
||||||
updateImagePreview();
|
updateImagePreview();
|
||||||
imageInput.value = "";
|
imageInput.value = "";
|
||||||
await loadNotes();
|
await loadNotes(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка сохранения заметки");
|
alert("Ошибка сохранения заметки");
|
||||||
|
|||||||
100
server.js
100
server.js
@ -224,10 +224,32 @@ function createTables() {
|
|||||||
console.error("Ошибка создания таблицы пользователей:", err.message);
|
console.error("Ошибка создания таблицы пользователей:", err.message);
|
||||||
} else {
|
} else {
|
||||||
console.log("Таблица пользователей готова");
|
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 для аутентификации
|
// Middleware для аутентификации
|
||||||
function requireAuth(req, res, next) {
|
function requireAuth(req, res, next) {
|
||||||
if (req.session.authenticated) {
|
if (req.session.authenticated) {
|
||||||
@ -397,52 +419,110 @@ app.get("/notes", requireAuth, (req, res) => {
|
|||||||
res.sendFile(path.join(__dirname, "public", "notes.html"));
|
res.sendFile(path.join(__dirname, "public", "notes.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// API для поиска заметок (должен быть ПЕРЕД /api/notes/:id)
|
// API для поиска заметок с изображениями (должен быть ПЕРЕД /api/notes/:id)
|
||||||
app.get("/api/notes/search", requireAuth, (req, res) => {
|
app.get("/api/notes/search", requireAuth, (req, res) => {
|
||||||
const { q, tag, date } = req.query;
|
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];
|
let params = [req.session.userId];
|
||||||
|
|
||||||
// Поиск по тексту
|
// Поиск по тексту
|
||||||
if (q && q.trim()) {
|
if (q && q.trim()) {
|
||||||
sql += " AND content LIKE ?";
|
whereClause += " AND n.content LIKE ?";
|
||||||
params.push(`%${q.trim()}%`);
|
params.push(`%${q.trim()}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поиск по тегу
|
// Поиск по тегу
|
||||||
if (tag && tag.trim()) {
|
if (tag && tag.trim()) {
|
||||||
sql += " AND content LIKE ?";
|
whereClause += " AND n.content LIKE ?";
|
||||||
params.push(`%#${tag.trim()}%`);
|
params.push(`%#${tag.trim()}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поиск по дате
|
// Поиск по дате
|
||||||
if (date && date.trim()) {
|
if (date && date.trim()) {
|
||||||
sql += " AND date = ?";
|
whereClause += " AND n.date = ?";
|
||||||
params.push(date.trim());
|
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) => {
|
db.all(sql, params, (err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка поиска заметок:", err.message);
|
console.error("Ошибка поиска заметок:", err.message);
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
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) => {
|
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) => {
|
db.all(sql, [req.session.userId], (err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка получения заметок:", err.message);
|
console.error("Ошибка получения заметок:", err.message);
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user