Compare commits
No commits in common. "f8692177f997335741ea21596720db9e0460e194" and "9b798f5fe42536aae2e1bdf8658ce53a62f5321f" have entirely different histories.
f8692177f9
...
9b798f5fe4
341
public/app.js
341
public/app.js
@ -11,21 +11,6 @@ const listBtn = document.getElementById("listBtn");
|
|||||||
const quoteBtn = document.getElementById("quoteBtn");
|
const quoteBtn = document.getElementById("quoteBtn");
|
||||||
const codeBtn = document.getElementById("codeBtn");
|
const codeBtn = document.getElementById("codeBtn");
|
||||||
const linkBtn = document.getElementById("linkBtn");
|
const linkBtn = document.getElementById("linkBtn");
|
||||||
const imageBtn = document.getElementById("imageBtn");
|
|
||||||
|
|
||||||
// Элементы для загрузки изображений
|
|
||||||
const imageInput = document.getElementById("imageInput");
|
|
||||||
const imagePreviewContainer = document.getElementById("imagePreviewContainer");
|
|
||||||
const imagePreviewList = document.getElementById("imagePreviewList");
|
|
||||||
const clearImagesBtn = document.getElementById("clearImagesBtn");
|
|
||||||
|
|
||||||
// Модальное окно для просмотра изображений
|
|
||||||
const imageModal = document.getElementById("imageModal");
|
|
||||||
const modalImage = document.getElementById("modalImage");
|
|
||||||
const modalClose = document.querySelector(".image-modal-close");
|
|
||||||
|
|
||||||
// Массив для хранения выбранных изображений
|
|
||||||
let selectedImages = [];
|
|
||||||
|
|
||||||
// Глобальные переменные для заметок и фильтрации
|
// Глобальные переменные для заметок и фильтрации
|
||||||
let allNotes = [];
|
let allNotes = [];
|
||||||
@ -149,12 +134,12 @@ function renderTags() {
|
|||||||
|
|
||||||
// Добавляем обработчики кликов для тегов
|
// Добавляем обработчики кликов для тегов
|
||||||
tagsContainer.querySelectorAll(".tag").forEach((tagElement) => {
|
tagsContainer.querySelectorAll(".tag").forEach((tagElement) => {
|
||||||
tagElement.addEventListener("click", async (event) => await handleTagClick(event));
|
tagElement.addEventListener("click", handleTagClick);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик клика на тег
|
// Обработчик клика на тег
|
||||||
async function handleTagClick(event) {
|
function handleTagClick(event) {
|
||||||
const clickedTag = event.target.closest(".tag").dataset.tag;
|
const clickedTag = event.target.closest(".tag").dataset.tag;
|
||||||
|
|
||||||
// Если кликнули на тот же тег, снимаем фильтр
|
// Если кликнули на тот же тег, снимаем фильтр
|
||||||
@ -165,7 +150,7 @@ async function handleTagClick(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перерисовываем заметки и теги
|
// Перерисовываем заметки и теги
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderTags();
|
renderTags();
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
}
|
}
|
||||||
@ -323,155 +308,6 @@ linkBtn.addEventListener("click", function () {
|
|||||||
insertMarkdown("[Текст ссылки](URL)");
|
insertMarkdown("[Текст ссылки](URL)");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик для кнопки загрузки изображений
|
|
||||||
imageBtn.addEventListener("click", function () {
|
|
||||||
imageInput.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик выбора файлов
|
|
||||||
imageInput.addEventListener("change", function (event) {
|
|
||||||
const files = Array.from(event.target.files);
|
|
||||||
files.forEach(file => {
|
|
||||||
if (file.type.startsWith('image/')) {
|
|
||||||
selectedImages.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updateImagePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик очистки всех изображений
|
|
||||||
clearImagesBtn.addEventListener("click", function () {
|
|
||||||
selectedImages = [];
|
|
||||||
updateImagePreview();
|
|
||||||
imageInput.value = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчики модального окна
|
|
||||||
modalClose.addEventListener("click", function () {
|
|
||||||
imageModal.style.display = "none";
|
|
||||||
});
|
|
||||||
|
|
||||||
imageModal.addEventListener("click", function (event) {
|
|
||||||
if (event.target === imageModal) {
|
|
||||||
imageModal.style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Закрытие модального окна по Escape
|
|
||||||
document.addEventListener("keydown", function (event) {
|
|
||||||
if (event.key === "Escape" && imageModal.style.display === "block") {
|
|
||||||
imageModal.style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Функция для обновления превью изображений
|
|
||||||
function updateImagePreview() {
|
|
||||||
if (selectedImages.length === 0) {
|
|
||||||
imagePreviewContainer.style.display = "none";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePreviewContainer.style.display = "block";
|
|
||||||
imagePreviewList.innerHTML = "";
|
|
||||||
|
|
||||||
selectedImages.forEach((file, index) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function (e) {
|
|
||||||
const previewItem = document.createElement("div");
|
|
||||||
previewItem.className = "image-preview-item";
|
|
||||||
|
|
||||||
previewItem.innerHTML = `
|
|
||||||
<img src="${e.target.result}" alt="Preview">
|
|
||||||
<button class="remove-image-btn" data-index="${index}">×</button>
|
|
||||||
<div class="image-info">${file.name}</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
imagePreviewList.appendChild(previewItem);
|
|
||||||
|
|
||||||
// Обработчик удаления изображения
|
|
||||||
const removeBtn = previewItem.querySelector(".remove-image-btn");
|
|
||||||
removeBtn.addEventListener("click", function () {
|
|
||||||
selectedImages.splice(index, 1);
|
|
||||||
updateImagePreview();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для отображения изображения в модальном окне
|
|
||||||
function showImageModal(imageSrc) {
|
|
||||||
console.log('showImageModal called with:', imageSrc);
|
|
||||||
try {
|
|
||||||
modalImage.src = imageSrc;
|
|
||||||
imageModal.style.display = "block";
|
|
||||||
console.log('Modal opened successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in showImageModal:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для загрузки изображений на сервер
|
|
||||||
async function uploadImages(noteId) {
|
|
||||||
if (selectedImages.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
selectedImages.forEach(file => {
|
|
||||||
formData.append("images", file);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/notes/${noteId}/images`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Ошибка загрузки изображений");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
return result.images || [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Ошибка загрузки изображений:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для получения изображений заметки
|
|
||||||
async function getNoteImages(noteId) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/notes/${noteId}/images`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Ошибка получения изображений");
|
|
||||||
}
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Ошибка получения изображений:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для удаления изображения заметки
|
|
||||||
async function deleteNoteImage(noteId, imageId) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/notes/${noteId}/images/${imageId}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Ошибка удаления изображения");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Ошибка удаления изображения:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция для загрузки заметок с сервера
|
// Функция для загрузки заметок с сервера
|
||||||
async function loadNotes() {
|
async function loadNotes() {
|
||||||
try {
|
try {
|
||||||
@ -481,7 +317,7 @@ async function loadNotes() {
|
|||||||
}
|
}
|
||||||
const notes = await response.json();
|
const notes = await response.json();
|
||||||
allNotes = notes; // Сохраняем все заметки в глобальную переменную
|
allNotes = notes; // Сохраняем все заметки в глобальную переменную
|
||||||
await renderNotes(notes);
|
renderNotes(notes);
|
||||||
renderCalendar(); // Обновляем календарь после загрузки заметок
|
renderCalendar(); // Обновляем календарь после загрузки заметок
|
||||||
renderTags(); // Обновляем теги после загрузки заметок
|
renderTags(); // Обновляем теги после загрузки заметок
|
||||||
renderCalendarMobile(); // Обновляем мобильный календарь после загрузки заметок
|
renderCalendarMobile(); // Обновляем мобильный календарь после загрузки заметок
|
||||||
@ -497,7 +333,7 @@ async function searchNotes(query) {
|
|||||||
if (!query || query.trim() === "") {
|
if (!query || query.trim() === "") {
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
searchResults = [];
|
searchResults = [];
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,11 +356,11 @@ async function searchNotes(query) {
|
|||||||
|
|
||||||
searchResults = await response.json();
|
searchResults = await response.json();
|
||||||
searchQuery = query.trim();
|
searchQuery = query.trim();
|
||||||
await renderNotes(searchResults);
|
renderNotes(searchResults);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка поиска:", error);
|
console.error("Ошибка поиска:", error);
|
||||||
searchResults = [];
|
searchResults = [];
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +378,7 @@ function highlightSearchText(content, query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Функция для отображения заметок
|
// Функция для отображения заметок
|
||||||
async function renderNotes(notes) {
|
function renderNotes(notes) {
|
||||||
notesList.innerHTML = "";
|
notesList.innerHTML = "";
|
||||||
|
|
||||||
// Фильтруем заметки по дате и тегам
|
// Фильтруем заметки по дате и тегам
|
||||||
@ -578,7 +414,7 @@ async function renderNotes(notes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
|
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
|
||||||
for (const note of notesToDisplay) {
|
notesToDisplay.forEach(function (note) {
|
||||||
let contentToProcess = note.content;
|
let contentToProcess = note.content;
|
||||||
|
|
||||||
// Сначала подсвечиваем найденный текст в исходном markdown
|
// Сначала подсвечиваем найденный текст в исходном markdown
|
||||||
@ -591,25 +427,8 @@ async function renderNotes(notes) {
|
|||||||
|
|
||||||
const parsedContent = marked.parse(contentWithClickableTags);
|
const parsedContent = marked.parse(contentWithClickableTags);
|
||||||
|
|
||||||
// Получаем изображения заметки
|
|
||||||
const noteImages = await getNoteImages(note.id);
|
|
||||||
let imagesHtml = "";
|
|
||||||
|
|
||||||
if (noteImages.length > 0) {
|
|
||||||
imagesHtml = '<div class="note-images-container">';
|
|
||||||
noteImages.forEach(image => {
|
|
||||||
imagesHtml += `
|
|
||||||
<div class="note-image-item">
|
|
||||||
<img src="${image.file_path}" alt="${image.original_name}" class="note-image" data-image-src="${image.file_path}">
|
|
||||||
<button class="remove-note-image-btn" data-note-id="${note.id}" data-image-id="${image.id}" title="Удалить изображение">×</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
imagesHtml += '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const noteHtml = `
|
const noteHtml = `
|
||||||
<div id="note" class="container" data-note-id="${note.id}">
|
<div id="note" class="container">
|
||||||
<div class="date">
|
<div class="date">
|
||||||
${note.date} ${note.time}
|
${note.date} ${note.time}
|
||||||
<div id="editBtn" class="notesHeaderBtn" data-id="${
|
<div id="editBtn" class="notesHeaderBtn" data-id="${
|
||||||
@ -623,11 +442,10 @@ async function renderNotes(notes) {
|
|||||||
/"/g,
|
/"/g,
|
||||||
"""
|
"""
|
||||||
)}">${parsedContent}</div>
|
)}">${parsedContent}</div>
|
||||||
${imagesHtml}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
notesList.insertAdjacentHTML("afterbegin", noteHtml);
|
notesList.insertAdjacentHTML("afterbegin", noteHtml);
|
||||||
}
|
});
|
||||||
|
|
||||||
// Добавляем обработчики событий для кнопок редактирования и удаления
|
// Добавляем обработчики событий для кнопок редактирования и удаления
|
||||||
addNoteEventListeners();
|
addNoteEventListeners();
|
||||||
@ -635,9 +453,6 @@ async function renderNotes(notes) {
|
|||||||
// Добавляем обработчики кликов для тегов в заметках
|
// Добавляем обработчики кликов для тегов в заметках
|
||||||
addTagClickListeners();
|
addTagClickListeners();
|
||||||
|
|
||||||
// Добавляем обработчики для изображений в заметках
|
|
||||||
addImageEventListeners();
|
|
||||||
|
|
||||||
// Обрабатываем длинные заметки
|
// Обрабатываем длинные заметки
|
||||||
handleLongNotes();
|
handleLongNotes();
|
||||||
}
|
}
|
||||||
@ -703,7 +518,7 @@ function addNoteEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перезагружаем заметки
|
// Перезагружаем заметки
|
||||||
await loadNotes();
|
loadNotes();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка удаления заметки");
|
alert("Ошибка удаления заметки");
|
||||||
@ -719,15 +534,6 @@ function addNoteEventListeners() {
|
|||||||
const noteContainer = event.target.closest("#note");
|
const noteContainer = event.target.closest("#note");
|
||||||
const noteContent = noteContainer.querySelector(".textNote");
|
const noteContent = noteContainer.querySelector(".textNote");
|
||||||
|
|
||||||
// Разворачиваем заметку при редактировании
|
|
||||||
noteContent.classList.remove("collapsed");
|
|
||||||
|
|
||||||
// Скрываем кнопку "Показать полностью" если она есть
|
|
||||||
const showMoreBtn = noteContainer.querySelector(".show-more-btn");
|
|
||||||
if (showMoreBtn) {
|
|
||||||
showMoreBtn.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем контейнер для markdown кнопок
|
// Создаем контейнер для markdown кнопок
|
||||||
const markdownButtonsContainer = document.createElement("div");
|
const markdownButtonsContainer = document.createElement("div");
|
||||||
markdownButtonsContainer.classList.add("markdown-buttons");
|
markdownButtonsContainer.classList.add("markdown-buttons");
|
||||||
@ -802,7 +608,7 @@ function addNoteEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перезагружаем заметки
|
// Перезагружаем заметки
|
||||||
await loadNotes();
|
loadNotes();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка сохранения заметки");
|
alert("Ошибка сохранения заметки");
|
||||||
@ -847,7 +653,7 @@ function addNoteEventListeners() {
|
|||||||
// Функция для добавления обработчиков кликов на теги в заметках
|
// Функция для добавления обработчиков кликов на теги в заметках
|
||||||
function addTagClickListeners() {
|
function addTagClickListeners() {
|
||||||
document.querySelectorAll(".textNote .tag-in-note").forEach((tagElement) => {
|
document.querySelectorAll(".textNote .tag-in-note").forEach((tagElement) => {
|
||||||
tagElement.addEventListener("click", async function (event) {
|
tagElement.addEventListener("click", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
@ -861,69 +667,16 @@ function addTagClickListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перерисовываем заметки и теги
|
// Перерисовываем заметки и теги
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderTags();
|
renderTags();
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для добавления обработчиков для изображений в заметках
|
|
||||||
function addImageEventListeners() {
|
|
||||||
// Обработчики для кликов на изображения (открытие в модальном окне)
|
|
||||||
document.querySelectorAll(".note-image").forEach((imageElement) => {
|
|
||||||
// Проверяем, не добавлен ли уже обработчик
|
|
||||||
if (imageElement._clickHandler) {
|
|
||||||
return; // Пропускаем, если обработчик уже добавлен
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем новый обработчик
|
|
||||||
imageElement._clickHandler = function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const imageSrc = this.dataset.imageSrc;
|
|
||||||
console.log('Image clicked, src:', imageSrc); // Для отладки
|
|
||||||
if (imageSrc) {
|
|
||||||
showImageModal(imageSrc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
imageElement.addEventListener("click", imageElement._clickHandler);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчики для кнопок удаления изображений
|
|
||||||
document.querySelectorAll(".remove-note-image-btn").forEach((buttonElement) => {
|
|
||||||
// Удаляем старые обработчики, если они есть
|
|
||||||
if (buttonElement._clickHandler) {
|
|
||||||
buttonElement.removeEventListener("click", buttonElement._clickHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем новый обработчик
|
|
||||||
buttonElement._clickHandler = async function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const noteId = this.dataset.noteId;
|
|
||||||
const imageId = this.dataset.imageId;
|
|
||||||
|
|
||||||
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
|
|
||||||
const success = await deleteNoteImage(noteId, imageId);
|
|
||||||
if (success) {
|
|
||||||
await loadNotes(); // Перезагружаем заметки
|
|
||||||
} else {
|
|
||||||
alert("Ошибка удаления изображения");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
buttonElement.addEventListener("click", buttonElement._clickHandler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Функция сохранения заметки (вынесена отдельно для повторного использования)
|
// Функция сохранения заметки (вынесена отдельно для повторного использования)
|
||||||
async function saveNote() {
|
async function saveNote() {
|
||||||
if (noteInput.value.trim() !== "" || selectedImages.length > 0) {
|
if (noteInput.value.trim() !== "") {
|
||||||
try {
|
try {
|
||||||
const { date, time } = getFormattedDateTime();
|
const { date, time } = getFormattedDateTime();
|
||||||
|
|
||||||
@ -933,7 +686,7 @@ async function saveNote() {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
content: noteInput.value || " ", // Минимальный контент, если только изображения
|
content: noteInput.value,
|
||||||
date: date,
|
date: date,
|
||||||
time: time,
|
time: time,
|
||||||
}),
|
}),
|
||||||
@ -943,21 +696,10 @@ async function saveNote() {
|
|||||||
throw new Error("Ошибка сохранения заметки");
|
throw new Error("Ошибка сохранения заметки");
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteData = await response.json();
|
// Очищаем поле ввода и перезагружаем заметки
|
||||||
const noteId = noteData.id;
|
|
||||||
|
|
||||||
// Загружаем изображения, если они есть
|
|
||||||
if (selectedImages.length > 0) {
|
|
||||||
await uploadImages(noteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очищаем поле ввода и изображения, перезагружаем заметки
|
|
||||||
noteInput.value = "";
|
noteInput.value = "";
|
||||||
noteInput.style.height = "auto";
|
noteInput.style.height = "auto";
|
||||||
selectedImages = [];
|
loadNotes();
|
||||||
updateImagePreview();
|
|
||||||
imageInput.value = "";
|
|
||||||
await loadNotes();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка:", error);
|
console.error("Ошибка:", error);
|
||||||
alert("Ошибка сохранения заметки");
|
alert("Ошибка сохранения заметки");
|
||||||
@ -1148,7 +890,7 @@ function renderCalendar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClick(event));
|
dayDiv.addEventListener("click", handleDayClick);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
@ -1185,7 +927,7 @@ function renderCalendar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClick(event));
|
dayDiv.addEventListener("click", handleDayClick);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
@ -1217,14 +959,14 @@ function renderCalendar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClick(event));
|
dayDiv.addEventListener("click", handleDayClick);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик клика на день в календаре
|
// Обработчик клика на день в календаре
|
||||||
async function handleDayClick(event) {
|
function handleDayClick(event) {
|
||||||
const clickedDate = event.target.dataset.date;
|
const clickedDate = event.target.dataset.date;
|
||||||
|
|
||||||
// Если кликнули на тот же день, снимаем фильтр
|
// Если кликнули на тот же день, снимаем фильтр
|
||||||
@ -1235,7 +977,7 @@ async function handleDayClick(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перерисовываем заметки, календарь и теги
|
// Перерисовываем заметки, календарь и теги
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
renderTags();
|
renderTags();
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
@ -1277,7 +1019,7 @@ function updateFilterIndicator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Функция для сброса фильтра (глобальная)
|
// Функция для сброса фильтра (глобальная)
|
||||||
window.clearFilter = async function () {
|
window.clearFilter = function () {
|
||||||
selectedDateFilter = null;
|
selectedDateFilter = null;
|
||||||
selectedTagFilter = null;
|
selectedTagFilter = null;
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
@ -1295,15 +1037,12 @@ window.clearFilter = async function () {
|
|||||||
clearSearchBtn.style.display = "none";
|
clearSearchBtn.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
renderTags();
|
renderTags();
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Глобальные функции для работы с изображениями (оставляем только showImageModal для совместимости)
|
|
||||||
// window.showImageModal удалена, так как она создавала рекурсию
|
|
||||||
|
|
||||||
// Обработчики для кнопок навигации календаря
|
// Обработчики для кнопок навигации календаря
|
||||||
const prevMonthBtn = document.getElementById("prevMonth");
|
const prevMonthBtn = document.getElementById("prevMonth");
|
||||||
const nextMonthBtn = document.getElementById("nextMonth");
|
const nextMonthBtn = document.getElementById("nextMonth");
|
||||||
@ -1358,23 +1097,23 @@ function initSearch() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик клика на кнопку очистки поиска
|
// Обработчик клика на кнопку очистки поиска
|
||||||
clearSearchBtn.addEventListener("click", async function () {
|
clearSearchBtn.addEventListener("click", function () {
|
||||||
searchInput.value = "";
|
searchInput.value = "";
|
||||||
this.style.display = "none";
|
this.style.display = "none";
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
searchResults = [];
|
searchResults = [];
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик клавиши Escape для очистки поиска
|
// Обработчик клавиши Escape для очистки поиска
|
||||||
searchInput.addEventListener("keydown", async function (event) {
|
searchInput.addEventListener("keydown", function (event) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.value = "";
|
this.value = "";
|
||||||
clearSearchBtn.style.display = "none";
|
clearSearchBtn.style.display = "none";
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
searchResults = [];
|
searchResults = [];
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1458,7 +1197,7 @@ function renderCalendarMobile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClickMobile(event));
|
dayDiv.addEventListener("click", handleDayClickMobile);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
@ -1495,7 +1234,7 @@ function renderCalendarMobile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClickMobile(event));
|
dayDiv.addEventListener("click", handleDayClickMobile);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
@ -1527,14 +1266,14 @@ function renderCalendarMobile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчик клика
|
// Добавляем обработчик клика
|
||||||
dayDiv.addEventListener("click", async (event) => await handleDayClickMobile(event));
|
dayDiv.addEventListener("click", handleDayClickMobile);
|
||||||
|
|
||||||
calendarDays.appendChild(dayDiv);
|
calendarDays.appendChild(dayDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик клика на день в календаре для мобильной версии
|
// Обработчик клика на день в календаре для мобильной версии
|
||||||
async function handleDayClickMobile(event) {
|
function handleDayClickMobile(event) {
|
||||||
const clickedDate = event.target.dataset.date;
|
const clickedDate = event.target.dataset.date;
|
||||||
|
|
||||||
// Если кликнули на тот же день, снимаем фильтр
|
// Если кликнули на тот же день, снимаем фильтр
|
||||||
@ -1545,7 +1284,7 @@ async function handleDayClickMobile(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перерисовываем заметки, оба календаря и теги
|
// Перерисовываем заметки, оба календаря и теги
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
renderCalendarMobile();
|
renderCalendarMobile();
|
||||||
renderTags();
|
renderTags();
|
||||||
@ -1577,12 +1316,12 @@ function renderTagsMobile() {
|
|||||||
|
|
||||||
// Добавляем обработчики кликов для тегов
|
// Добавляем обработчики кликов для тегов
|
||||||
tagsContainer.querySelectorAll(".tag").forEach((tagElement) => {
|
tagsContainer.querySelectorAll(".tag").forEach((tagElement) => {
|
||||||
tagElement.addEventListener("click", async (event) => await handleTagClickMobile(event));
|
tagElement.addEventListener("click", handleTagClickMobile);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик клика на тег в мобильном слайдере
|
// Обработчик клика на тег в мобильном слайдере
|
||||||
async function handleTagClickMobile(event) {
|
function handleTagClickMobile(event) {
|
||||||
const clickedTag = event.target.closest(".tag").dataset.tag;
|
const clickedTag = event.target.closest(".tag").dataset.tag;
|
||||||
|
|
||||||
// Если кликнули на тот же тег, снимаем фильтр
|
// Если кликнули на тот же тег, снимаем фильтр
|
||||||
@ -1593,7 +1332,7 @@ async function handleTagClickMobile(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Перерисовываем заметки, теги и оба календаря
|
// Перерисовываем заметки, теги и оба календаря
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
renderTags();
|
renderTags();
|
||||||
renderTagsMobile();
|
renderTagsMobile();
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
@ -1704,7 +1443,7 @@ function initSearchMobile() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик клика на кнопку очистки поиска
|
// Обработчик клика на кнопку очистки поиска
|
||||||
clearSearchBtn.addEventListener("click", async function () {
|
clearSearchBtn.addEventListener("click", function () {
|
||||||
searchInput.value = "";
|
searchInput.value = "";
|
||||||
this.style.display = "none";
|
this.style.display = "none";
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
@ -1718,7 +1457,7 @@ function initSearchMobile() {
|
|||||||
if (mainClearSearchBtn) {
|
if (mainClearSearchBtn) {
|
||||||
mainClearSearchBtn.style.display = "none";
|
mainClearSearchBtn.style.display = "none";
|
||||||
}
|
}
|
||||||
await renderNotes(allNotes);
|
renderNotes(allNotes);
|
||||||
updateFilterIndicator();
|
updateFilterIndicator();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -197,9 +197,6 @@
|
|||||||
<button class="btnMarkdown" id="linkBtn" title="Ссылка">
|
<button class="btnMarkdown" id="linkBtn" title="Ссылка">
|
||||||
<span class="iconify" data-icon="mdi:link"></span>
|
<span class="iconify" data-icon="mdi:link"></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="imageBtn" title="Загрузить изображения">
|
|
||||||
<span class="iconify" data-icon="mdi:image-plus"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
@ -207,19 +204,6 @@
|
|||||||
id="noteInput"
|
id="noteInput"
|
||||||
placeholder="Ваша заметка..."
|
placeholder="Ваша заметка..."
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<!-- Скрытый input для загрузки изображений -->
|
|
||||||
<input type="file" id="imageInput" accept="image/*" multiple style="display: none;">
|
|
||||||
|
|
||||||
<!-- Контейнер для отображения загруженных изображений -->
|
|
||||||
<div id="imagePreviewContainer" class="image-preview-container" style="display: none;">
|
|
||||||
<div class="image-preview-header">
|
|
||||||
<span>Загруженные изображения:</span>
|
|
||||||
<button type="button" id="clearImagesBtn" class="clear-images-btn">Очистить все</button>
|
|
||||||
</div>
|
|
||||||
<div id="imagePreviewList" class="image-preview-list"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="save-button-container">
|
<div class="save-button-container">
|
||||||
<button class="btnSave" id="saveBtn">Сохранить</button>
|
<button class="btnSave" id="saveBtn">Сохранить</button>
|
||||||
<span class="save-hint">или нажмите Alt + Enter</span>
|
<span class="save-hint">или нажмите Alt + Enter</span>
|
||||||
@ -232,12 +216,6 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>Создатель: <span>Fovway</span></p>
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно для просмотра изображений -->
|
|
||||||
<div id="imageModal" class="image-modal">
|
|
||||||
<span class="image-modal-close">×</span>
|
|
||||||
<img class="image-modal-content" id="modalImage">
|
|
||||||
</div>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
222
public/style.css
222
public/style.css
@ -1298,225 +1298,3 @@ textarea:focus {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для загрузки изображений */
|
|
||||||
.image-preview-container {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #495057;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-images-btn {
|
|
||||||
background: #dc3545;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-images-btn:hover {
|
|
||||||
background: #c82333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item {
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item img {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item .remove-image-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
right: 2px;
|
|
||||||
background: rgba(220, 53, 69, 0.8);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item .remove-image-btn:hover {
|
|
||||||
background: rgba(220, 53, 69, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item .image-info {
|
|
||||||
padding: 4px 6px;
|
|
||||||
font-size: 10px;
|
|
||||||
color: #6c757d;
|
|
||||||
background: #f8f9fa;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для изображений в заметках */
|
|
||||||
.note-image {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 10px 0;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image:hover {
|
|
||||||
transform: scale(1.02);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image::after {
|
|
||||||
content: "🔍";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
color: white;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 16px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image:hover::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-images-container {
|
|
||||||
margin: 10px 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image-item {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image-item .note-image {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image-item .remove-note-image-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
background: rgba(220, 53, 69, 0.8);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image-item:hover .remove-note-image-btn {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image-item .remove-note-image-btn:hover {
|
|
||||||
background: rgba(220, 53, 69, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Модальное окно для просмотра изображений */
|
|
||||||
.image-modal {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1000;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0,0,0,0.9);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-modal-content {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-modal-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 35px;
|
|
||||||
color: #f1f1f1;
|
|
||||||
font-size: 40px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-modal-close:hover {
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность для изображений */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.image-preview-list {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-images-container {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-item img {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-image {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
267
server.js
267
server.js
@ -27,7 +27,7 @@ if (!fs.existsSync(databaseDir)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Настройка multer для загрузки аватарок
|
// Настройка multer для загрузки аватарок
|
||||||
const avatarStorage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: function (req, file, cb) {
|
destination: function (req, file, cb) {
|
||||||
cb(null, uploadsDir);
|
cb(null, uploadsDir);
|
||||||
},
|
},
|
||||||
@ -44,26 +44,8 @@ const avatarStorage = multer.diskStorage({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Настройка multer для загрузки изображений заметок
|
|
||||||
const noteImageStorage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, uploadsDir);
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
|
||||||
cb(
|
|
||||||
null,
|
|
||||||
"note-image-" +
|
|
||||||
req.session.userId +
|
|
||||||
"-" +
|
|
||||||
uniqueSuffix +
|
|
||||||
path.extname(file.originalname)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: avatarStorage,
|
storage: storage,
|
||||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB максимум
|
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB максимум
|
||||||
fileFilter: function (req, file, cb) {
|
fileFilter: function (req, file, cb) {
|
||||||
const filetypes = /jpeg|jpg|png|gif/;
|
const filetypes = /jpeg|jpg|png|gif/;
|
||||||
@ -79,23 +61,6 @@ const upload = multer({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadNoteImages = multer({
|
|
||||||
storage: noteImageStorage,
|
|
||||||
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB максимум для изображений заметок
|
|
||||||
fileFilter: function (req, file, cb) {
|
|
||||||
const filetypes = /jpeg|jpg|png|gif|webp/;
|
|
||||||
const mimetype = filetypes.test(file.mimetype);
|
|
||||||
const extname = filetypes.test(
|
|
||||||
path.extname(file.originalname).toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mimetype && extname) {
|
|
||||||
return cb(null, true);
|
|
||||||
}
|
|
||||||
cb(new Error("Только изображения (jpeg, jpg, png, gif, webp) разрешены!"));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Middleware для безопасности
|
// Middleware для безопасности
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
@ -178,20 +143,6 @@ function createTables() {
|
|||||||
)
|
)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const createNoteImagesTable = `
|
|
||||||
CREATE TABLE IF NOT EXISTS note_images (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
note_id INTEGER NOT NULL,
|
|
||||||
filename TEXT NOT NULL,
|
|
||||||
original_name TEXT NOT NULL,
|
|
||||||
file_path TEXT NOT NULL,
|
|
||||||
file_size INTEGER NOT NULL,
|
|
||||||
mime_type TEXT NOT NULL,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
|
|
||||||
const createUsersTable = `
|
const createUsersTable = `
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -211,14 +162,6 @@ function createTables() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
db.run(createNoteImagesTable, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка создания таблицы изображений заметок:", err.message);
|
|
||||||
} else {
|
|
||||||
console.log("Таблица изображений заметок готова");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.run(createUsersTable, (err) => {
|
db.run(createUsersTable, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка создания таблицы пользователей:", err.message);
|
console.error("Ошибка создания таблицы пользователей:", err.message);
|
||||||
@ -529,31 +472,8 @@ app.delete("/api/notes/:id", requireAuth, (req, res) => {
|
|||||||
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сначала удаляем все изображения заметки
|
|
||||||
const getImagesSql = "SELECT file_path FROM note_images WHERE note_id = ?";
|
|
||||||
db.all(getImagesSql, [id], (err, images) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка получения изображений:", err.message);
|
|
||||||
} else {
|
|
||||||
// Удаляем файлы изображений
|
|
||||||
images.forEach((image) => {
|
|
||||||
const imagePath = path.join(__dirname, "public", image.file_path);
|
|
||||||
if (fs.existsSync(imagePath)) {
|
|
||||||
fs.unlinkSync(imagePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удаляем записи об изображениях из БД
|
|
||||||
const deleteImagesSql = "DELETE FROM note_images WHERE note_id = ?";
|
|
||||||
db.run(deleteImagesSql, [id], (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка удаления изображений:", err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Удаляем саму заметку
|
|
||||||
const deleteSql = "DELETE FROM notes WHERE id = ?";
|
const deleteSql = "DELETE FROM notes WHERE id = ?";
|
||||||
|
|
||||||
db.run(deleteSql, id, function (err) {
|
db.run(deleteSql, id, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка удаления заметки:", err.message);
|
console.error("Ошибка удаления заметки:", err.message);
|
||||||
@ -565,187 +485,6 @@ app.delete("/api/notes/:id", requireAuth, (req, res) => {
|
|||||||
res.json({ message: "Заметка удалена" });
|
res.json({ message: "Заметка удалена" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// API для загрузки изображений к заметке
|
|
||||||
app.post(
|
|
||||||
"/api/notes/:id/images",
|
|
||||||
requireAuth,
|
|
||||||
uploadNoteImages.array("images", 10), // Максимум 10 изображений
|
|
||||||
(req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const files = req.files;
|
|
||||||
|
|
||||||
if (!files || files.length === 0) {
|
|
||||||
return res.status(400).json({ error: "Файлы не загружены" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что заметка принадлежит текущему пользователю
|
|
||||||
const checkSql = "SELECT user_id FROM notes WHERE id = ?";
|
|
||||||
db.get(checkSql, [id], (err, row) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка проверки доступа:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return res.status(404).json({ error: "Заметка не найдена" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row.user_id !== req.session.userId) {
|
|
||||||
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сохраняем информацию об изображениях в БД
|
|
||||||
const insertSql = `
|
|
||||||
INSERT INTO note_images (note_id, filename, original_name, file_path, file_size, mime_type)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
`;
|
|
||||||
|
|
||||||
const uploadedImages = [];
|
|
||||||
let completed = 0;
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
const filePath = "/uploads/" + file.filename;
|
|
||||||
const params = [
|
|
||||||
id,
|
|
||||||
file.filename,
|
|
||||||
file.originalname,
|
|
||||||
filePath,
|
|
||||||
file.size,
|
|
||||||
file.mimetype,
|
|
||||||
];
|
|
||||||
|
|
||||||
db.run(insertSql, params, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка сохранения изображения:", err.message);
|
|
||||||
// Удаляем файл, если не удалось сохранить в БД
|
|
||||||
const imagePath = path.join(__dirname, "public", filePath);
|
|
||||||
if (fs.existsSync(imagePath)) {
|
|
||||||
fs.unlinkSync(imagePath);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uploadedImages.push({
|
|
||||||
id: this.lastID,
|
|
||||||
filename: file.filename,
|
|
||||||
original_name: file.originalname,
|
|
||||||
file_path: filePath,
|
|
||||||
file_size: file.size,
|
|
||||||
mime_type: file.mimetype,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
completed++;
|
|
||||||
if (completed === files.length) {
|
|
||||||
if (uploadedImages.length === 0) {
|
|
||||||
return res.status(500).json({ error: "Не удалось загрузить изображения" });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: `Загружено ${uploadedImages.length} изображений`,
|
|
||||||
images: uploadedImages
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// API для получения изображений заметки
|
|
||||||
app.get("/api/notes/:id/images", requireAuth, (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
|
|
||||||
// Проверяем, что заметка принадлежит текущему пользователю
|
|
||||||
const checkSql = "SELECT user_id FROM notes WHERE id = ?";
|
|
||||||
db.get(checkSql, [id], (err, row) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка проверки доступа:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return res.status(404).json({ error: "Заметка не найдена" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row.user_id !== req.session.userId) {
|
|
||||||
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем изображения заметки
|
|
||||||
const getImagesSql = `
|
|
||||||
SELECT id, filename, original_name, file_path, file_size, mime_type, created_at
|
|
||||||
FROM note_images
|
|
||||||
WHERE note_id = ?
|
|
||||||
ORDER BY created_at ASC
|
|
||||||
`;
|
|
||||||
|
|
||||||
db.all(getImagesSql, [id], (err, images) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка получения изображений:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(images);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// API для удаления изображения заметки
|
|
||||||
app.delete("/api/notes/:noteId/images/:imageId", requireAuth, (req, res) => {
|
|
||||||
const { noteId, imageId } = req.params;
|
|
||||||
|
|
||||||
// Проверяем, что заметка принадлежит текущему пользователю
|
|
||||||
const checkSql = "SELECT user_id FROM notes WHERE id = ?";
|
|
||||||
db.get(checkSql, [noteId], (err, row) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка проверки доступа:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!row) {
|
|
||||||
return res.status(404).json({ error: "Заметка не найдена" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row.user_id !== req.session.userId) {
|
|
||||||
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем информацию об изображении
|
|
||||||
const getImageSql = "SELECT file_path FROM note_images WHERE id = ? AND note_id = ?";
|
|
||||||
db.get(getImageSql, [imageId, noteId], (err, image) => {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка получения изображения:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image) {
|
|
||||||
return res.status(404).json({ error: "Изображение не найдено" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удаляем файл изображения
|
|
||||||
const imagePath = path.join(__dirname, "public", image.file_path);
|
|
||||||
if (fs.existsSync(imagePath)) {
|
|
||||||
fs.unlinkSync(imagePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удаляем запись из БД
|
|
||||||
const deleteSql = "DELETE FROM note_images WHERE id = ? AND note_id = ?";
|
|
||||||
db.run(deleteSql, [imageId, noteId], function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error("Ошибка удаления изображения:", err.message);
|
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.changes === 0) {
|
|
||||||
return res.status(404).json({ error: "Изображение не найдено" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true, message: "Изображение удалено" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Страница личного кабинета
|
// Страница личного кабинета
|
||||||
|
|||||||
292
test_images.html
292
test_images.html
@ -1,292 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Тест загрузки изображений</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.test-section {
|
|
||||||
margin: 20px 0;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
input[type="file"] {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.image-preview {
|
|
||||||
max-width: 200px;
|
|
||||||
max-height: 200px;
|
|
||||||
margin: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Тест функциональности загрузки изображений</h1>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>1. Регистрация тестового пользователя</h2>
|
|
||||||
<button onclick="registerTestUser()">Зарегистрировать тестового пользователя</button>
|
|
||||||
<div id="registerResult"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>2. Вход в систему</h2>
|
|
||||||
<button onclick="loginTestUser()">Войти как тестовый пользователь</button>
|
|
||||||
<div id="loginResult"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>3. Создание заметки с изображениями</h2>
|
|
||||||
<input type="file" id="imageInput" accept="image/*" multiple>
|
|
||||||
<button onclick="createNoteWithImages()">Создать заметку с изображениями</button>
|
|
||||||
<div id="noteResult"></div>
|
|
||||||
<div id="imagePreviews"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>4. Получение изображений заметки</h2>
|
|
||||||
<input type="number" id="noteIdInput" placeholder="ID заметки">
|
|
||||||
<button onclick="getNoteImages()">Получить изображения</button>
|
|
||||||
<div id="getImagesResult"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-section">
|
|
||||||
<h2>5. Удаление изображения</h2>
|
|
||||||
<input type="number" id="deleteNoteIdInput" placeholder="ID заметки">
|
|
||||||
<input type="number" id="deleteImageIdInput" placeholder="ID изображения">
|
|
||||||
<button onclick="deleteImage()">Удалить изображение</button>
|
|
||||||
<div id="deleteResult"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let authToken = null;
|
|
||||||
let selectedFiles = [];
|
|
||||||
|
|
||||||
// Обработчик выбора файлов
|
|
||||||
document.getElementById('imageInput').addEventListener('change', function(e) {
|
|
||||||
selectedFiles = Array.from(e.target.files);
|
|
||||||
displayImagePreviews();
|
|
||||||
});
|
|
||||||
|
|
||||||
function displayImagePreviews() {
|
|
||||||
const container = document.getElementById('imagePreviews');
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
selectedFiles.forEach((file, index) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = e.target.result;
|
|
||||||
img.className = 'image-preview';
|
|
||||||
img.title = file.name;
|
|
||||||
container.appendChild(img);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function registerTestUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/register', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: 'testuser',
|
|
||||||
password: 'testpass123',
|
|
||||||
confirmPassword: 'testpass123'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
const resultDiv = document.getElementById('registerResult');
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
resultDiv.innerHTML = '<div class="success">✓ Пользователь успешно зарегистрирован</div>';
|
|
||||||
} else {
|
|
||||||
resultDiv.innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('registerResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loginTestUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: 'testuser',
|
|
||||||
password: 'testpass123'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
const resultDiv = document.getElementById('loginResult');
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
resultDiv.innerHTML = '<div class="success">✓ Успешный вход в систему</div>';
|
|
||||||
authToken = 'authenticated'; // В реальном приложении здесь был бы JWT токен
|
|
||||||
} else {
|
|
||||||
resultDiv.innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('loginResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createNoteWithImages() {
|
|
||||||
if (!authToken) {
|
|
||||||
document.getElementById('noteResult').innerHTML = '<div class="error">✗ Сначала войдите в систему</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Сначала создаем заметку
|
|
||||||
const noteResponse = await fetch('/api/notes', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
content: 'Тестовая заметка с изображениями',
|
|
||||||
date: new Date().toLocaleDateString('ru-RU'),
|
|
||||||
time: new Date().toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const noteResult = await noteResponse.json();
|
|
||||||
|
|
||||||
if (!noteResponse.ok) {
|
|
||||||
throw new Error(noteResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const noteId = noteResult.id;
|
|
||||||
document.getElementById('noteResult').innerHTML = `<div class="success">✓ Заметка создана с ID: ${noteId}</div>`;
|
|
||||||
|
|
||||||
// Теперь загружаем изображения
|
|
||||||
if (selectedFiles.length > 0) {
|
|
||||||
const formData = new FormData();
|
|
||||||
selectedFiles.forEach(file => {
|
|
||||||
formData.append('images', file);
|
|
||||||
});
|
|
||||||
|
|
||||||
const imageResponse = await fetch(`/api/notes/${noteId}/images`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const imageResult = await imageResponse.json();
|
|
||||||
|
|
||||||
if (imageResponse.ok) {
|
|
||||||
document.getElementById('noteResult').innerHTML += `<div class="success">✓ Загружено ${imageResult.images.length} изображений</div>`;
|
|
||||||
document.getElementById('noteIdInput').value = noteId;
|
|
||||||
} else {
|
|
||||||
document.getElementById('noteResult').innerHTML += `<div class="error">✗ Ошибка загрузки изображений: ${imageResult.error}</div>`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById('noteResult').innerHTML += '<div class="info">ℹ Изображения не выбраны</div>';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('noteResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteImages() {
|
|
||||||
const noteId = document.getElementById('noteIdInput').value;
|
|
||||||
if (!noteId) {
|
|
||||||
document.getElementById('getImagesResult').innerHTML = '<div class="error">✗ Введите ID заметки</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/notes/${noteId}/images`);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const resultDiv = document.getElementById('getImagesResult');
|
|
||||||
resultDiv.innerHTML = `<div class="success">✓ Найдено ${result.length} изображений:</div>`;
|
|
||||||
|
|
||||||
result.forEach(image => {
|
|
||||||
resultDiv.innerHTML += `
|
|
||||||
<div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
|
|
||||||
<img src="${image.file_path}" style="max-width: 100px; max-height: 100px; margin-right: 10px;">
|
|
||||||
<div>
|
|
||||||
<strong>ID:</strong> ${image.id}<br>
|
|
||||||
<strong>Имя файла:</strong> ${image.original_name}<br>
|
|
||||||
<strong>Размер:</strong> ${(image.file_size / 1024).toFixed(2)} KB<br>
|
|
||||||
<strong>Тип:</strong> ${image.mime_type}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
document.getElementById('getImagesResult').innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('getImagesResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteImage() {
|
|
||||||
const noteId = document.getElementById('deleteNoteIdInput').value;
|
|
||||||
const imageId = document.getElementById('deleteImageIdInput').value;
|
|
||||||
|
|
||||||
if (!noteId || !imageId) {
|
|
||||||
document.getElementById('deleteResult').innerHTML = '<div class="error">✗ Введите ID заметки и ID изображения</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/notes/${noteId}/images/${imageId}`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
document.getElementById('deleteResult').innerHTML = '<div class="success">✓ Изображение успешно удалено</div>';
|
|
||||||
} else {
|
|
||||||
document.getElementById('deleteResult').innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('deleteResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user