✨ Добавлены функции предпросмотра заметок и улучшены стили интерфейса
- Реализована возможность предпросмотра заметок с поддержкой Markdown и кликабельных тегов. - Добавлены кнопки для переключения между режимами редактирования и предпросмотра. - Обновлены стили для контейнера предпросмотра и элементов управления. - Оптимизированы обработчики событий для новых функций предпросмотра.
This commit is contained in:
parent
f9ba1796dc
commit
5b76167c3d
175
public/app.js
175
public/app.js
@ -17,6 +17,7 @@ const codeBtn = document.getElementById("codeBtn");
|
|||||||
const linkBtn = document.getElementById("linkBtn");
|
const linkBtn = document.getElementById("linkBtn");
|
||||||
const checkboxBtn = document.getElementById("checkboxBtn");
|
const checkboxBtn = document.getElementById("checkboxBtn");
|
||||||
const imageBtn = document.getElementById("imageBtn");
|
const imageBtn = document.getElementById("imageBtn");
|
||||||
|
const previewBtn = document.getElementById("previewBtn");
|
||||||
|
|
||||||
// Кнопка настроек
|
// Кнопка настроек
|
||||||
const settingsBtn = document.getElementById("settings-btn");
|
const settingsBtn = document.getElementById("settings-btn");
|
||||||
@ -27,6 +28,10 @@ const imagePreviewContainer = document.getElementById("imagePreviewContainer");
|
|||||||
const imagePreviewList = document.getElementById("imagePreviewList");
|
const imagePreviewList = document.getElementById("imagePreviewList");
|
||||||
const clearImagesBtn = document.getElementById("clearImagesBtn");
|
const clearImagesBtn = document.getElementById("clearImagesBtn");
|
||||||
|
|
||||||
|
// Элементы для предпросмотра заметки
|
||||||
|
const notePreviewContainer = document.getElementById("notePreviewContainer");
|
||||||
|
const notePreviewContent = document.getElementById("notePreviewContent");
|
||||||
|
|
||||||
// Модальное окно для просмотра изображений
|
// Модальное окно для просмотра изображений
|
||||||
const imageModal = document.getElementById("imageModal");
|
const imageModal = document.getElementById("imageModal");
|
||||||
const modalImage = document.getElementById("modalImage");
|
const modalImage = document.getElementById("modalImage");
|
||||||
@ -35,6 +40,9 @@ const modalClose = document.querySelector(".image-modal-close");
|
|||||||
// Массив для хранения выбранных изображений
|
// Массив для хранения выбранных изображений
|
||||||
let selectedImages = [];
|
let selectedImages = [];
|
||||||
|
|
||||||
|
// Флаг режима предпросмотра
|
||||||
|
let isPreviewMode = false;
|
||||||
|
|
||||||
// Глобальные переменные для заметок и фильтрации
|
// Глобальные переменные для заметок и фильтрации
|
||||||
let allNotes = [];
|
let allNotes = [];
|
||||||
let selectedDateFilter = null;
|
let selectedDateFilter = null;
|
||||||
@ -771,6 +779,52 @@ imageBtn.addEventListener("touchend", function (event) {
|
|||||||
imageInput.click();
|
imageInput.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Обработчик для кнопки предпросмотра
|
||||||
|
previewBtn.addEventListener("click", function () {
|
||||||
|
togglePreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Функция переключения режима предпросмотра
|
||||||
|
function togglePreview() {
|
||||||
|
isPreviewMode = !isPreviewMode;
|
||||||
|
|
||||||
|
if (isPreviewMode) {
|
||||||
|
// Показываем предпросмотр
|
||||||
|
noteInput.style.display = "none";
|
||||||
|
notePreviewContainer.style.display = "block";
|
||||||
|
|
||||||
|
// Получаем содержимое и рендерим его
|
||||||
|
const content = noteInput.value;
|
||||||
|
if (content.trim()) {
|
||||||
|
// Парсим markdown и делаем теги кликабельными
|
||||||
|
const htmlContent = marked.parse(content);
|
||||||
|
const contentWithTags = makeTagsClickable(htmlContent);
|
||||||
|
notePreviewContent.innerHTML = contentWithTags;
|
||||||
|
|
||||||
|
// Инициализируем lazy loading для изображений в превью
|
||||||
|
setTimeout(() => {
|
||||||
|
initLazyLoading();
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
notePreviewContent.innerHTML =
|
||||||
|
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Меняем иконку кнопки
|
||||||
|
previewBtn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:eye-off"></span>';
|
||||||
|
previewBtn.title = "Закрыть предпросмотр";
|
||||||
|
} else {
|
||||||
|
// Возвращаемся к редактированию
|
||||||
|
noteInput.style.display = "block";
|
||||||
|
notePreviewContainer.style.display = "none";
|
||||||
|
|
||||||
|
// Меняем иконку обратно
|
||||||
|
previewBtn.innerHTML = '<span class="iconify" data-icon="mdi:eye"></span>';
|
||||||
|
previewBtn.title = "Предпросмотр";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Обработчик выбора файлов
|
// Обработчик выбора файлов
|
||||||
imageInput.addEventListener("change", function (event) {
|
imageInput.addEventListener("change", function (event) {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
@ -1278,14 +1332,24 @@ async function renderNotes(notes) {
|
|||||||
<div class="date">
|
<div class="date">
|
||||||
<span class="date-text">${dateDisplay}${pinIndicator}</span>
|
<span class="date-text">${dateDisplay}${pinIndicator}</span>
|
||||||
<div class="note-actions">
|
<div class="note-actions">
|
||||||
<div id="pinBtn" class="notesHeaderBtn" data-id="${note.id}" title="${note.is_pinned ? "Открепить" : "Закрепить"}">
|
<div id="pinBtn" class="notesHeaderBtn" data-id="${
|
||||||
<span class="iconify" data-icon="mdi:pin${note.is_pinned ? "-off" : ""}"></span>
|
note.id
|
||||||
|
}" title="${note.is_pinned ? "Открепить" : "Закрепить"}">
|
||||||
|
<span class="iconify" data-icon="mdi:pin${
|
||||||
|
note.is_pinned ? "-off" : ""
|
||||||
|
}"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="archiveBtn" class="notesHeaderBtn" data-id="${note.id}" title="Архивировать">
|
<div id="archiveBtn" class="notesHeaderBtn" data-id="${
|
||||||
|
note.id
|
||||||
|
}" title="Архивировать">
|
||||||
<span class="iconify" data-icon="mdi:archive"></span>
|
<span class="iconify" data-icon="mdi:archive"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="editBtn" class="notesHeaderBtn" data-id="${note.id}">Ред.</div>
|
<div id="editBtn" class="notesHeaderBtn" data-id="${
|
||||||
<div id="deleteBtn" class="notesHeaderBtn" data-id="${note.id}">Удал.</div>
|
note.id
|
||||||
|
}">Ред.</div>
|
||||||
|
<div id="deleteBtn" class="notesHeaderBtn" data-id="${
|
||||||
|
note.id
|
||||||
|
}">Удал.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="textNote" data-original-content="${note.content.replace(
|
<div class="textNote" data-original-content="${note.content.replace(
|
||||||
@ -1377,7 +1441,7 @@ function addNoteEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// Перезагружаем заметки
|
// Перезагружаем заметки
|
||||||
await loadNotes(true);
|
await loadNotes(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1391,7 +1455,11 @@ function addNoteEventListeners() {
|
|||||||
document.querySelectorAll("#archiveBtn").forEach((btn) => {
|
document.querySelectorAll("#archiveBtn").forEach((btn) => {
|
||||||
btn.addEventListener("click", async function (event) {
|
btn.addEventListener("click", async function (event) {
|
||||||
const noteId = event.target.closest("#archiveBtn").dataset.id;
|
const noteId = event.target.closest("#archiveBtn").dataset.id;
|
||||||
if (confirm("Архивировать эту заметку? Её можно будет восстановить из настроек.")) {
|
if (
|
||||||
|
confirm(
|
||||||
|
"Архивировать эту заметку? Её можно будет восстановить из настроек."
|
||||||
|
)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/notes/${noteId}/archive`, {
|
const response = await fetch(`/api/notes/${noteId}/archive`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@ -1476,6 +1544,7 @@ function addNoteEventListeners() {
|
|||||||
tag: "- [ ] ",
|
tag: "- [ ] ",
|
||||||
},
|
},
|
||||||
{ id: "editImageBtn", icon: "mdi:image-plus", tag: "image" },
|
{ id: "editImageBtn", icon: "mdi:image-plus", tag: "image" },
|
||||||
|
{ id: "editPreviewBtn", icon: "mdi:eye", tag: "preview" },
|
||||||
];
|
];
|
||||||
|
|
||||||
markdownButtons.forEach((button) => {
|
markdownButtons.forEach((button) => {
|
||||||
@ -1741,6 +1810,13 @@ function addNoteEventListeners() {
|
|||||||
const saveEditNote = async function () {
|
const saveEditNote = async function () {
|
||||||
if (textarea.value.trim() !== "" || editSelectedImages.length > 0) {
|
if (textarea.value.trim() !== "" || editSelectedImages.length > 0) {
|
||||||
try {
|
try {
|
||||||
|
// Сбрасываем режим предпросмотра перед сохранением
|
||||||
|
if (isEditPreviewMode) {
|
||||||
|
isEditPreviewMode = false;
|
||||||
|
textarea.style.display = "block";
|
||||||
|
editPreviewContainer.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/notes/${noteId}`, {
|
const response = await fetch(`/api/notes/${noteId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
@ -1780,6 +1856,13 @@ function addNoteEventListeners() {
|
|||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Сбрасываем режим предпросмотра
|
||||||
|
if (isEditPreviewMode) {
|
||||||
|
isEditPreviewMode = false;
|
||||||
|
textarea.style.display = "block";
|
||||||
|
editPreviewContainer.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
// Очистить выбранные новые изображения и превью
|
// Очистить выбранные новые изображения и превью
|
||||||
editSelectedImages.length = 0;
|
editSelectedImages.length = 0;
|
||||||
if (editImagePreviewContainer) {
|
if (editImagePreviewContainer) {
|
||||||
@ -1829,10 +1912,29 @@ function addNoteEventListeners() {
|
|||||||
editImageInput.value = "";
|
editImageInput.value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Создаем контейнер для предпросмотра в режиме редактирования
|
||||||
|
const editPreviewContainer = document.createElement("div");
|
||||||
|
editPreviewContainer.classList.add("note-preview-container");
|
||||||
|
editPreviewContainer.style.display = "none";
|
||||||
|
|
||||||
|
const editPreviewHeader = document.createElement("div");
|
||||||
|
editPreviewHeader.classList.add("note-preview-header");
|
||||||
|
editPreviewHeader.innerHTML = "<span>Предпросмотр:</span>";
|
||||||
|
|
||||||
|
const editPreviewContent = document.createElement("div");
|
||||||
|
editPreviewContent.classList.add("note-preview-content");
|
||||||
|
|
||||||
|
editPreviewContainer.appendChild(editPreviewHeader);
|
||||||
|
editPreviewContainer.appendChild(editPreviewContent);
|
||||||
|
|
||||||
|
// Флаг для режима предпросмотра редактирования
|
||||||
|
let isEditPreviewMode = false;
|
||||||
|
|
||||||
// Очищаем текущий контент и вставляем markdown кнопки, textarea, элементы для изображений и контейнер с кнопкой сохранить
|
// Очищаем текущий контент и вставляем markdown кнопки, textarea, элементы для изображений и контейнер с кнопкой сохранить
|
||||||
noteContent.innerHTML = "";
|
noteContent.innerHTML = "";
|
||||||
noteContent.appendChild(markdownButtonsContainer);
|
noteContent.appendChild(markdownButtonsContainer);
|
||||||
noteContent.appendChild(textarea);
|
noteContent.appendChild(textarea);
|
||||||
|
noteContent.appendChild(editPreviewContainer);
|
||||||
noteContent.appendChild(editImageInput);
|
noteContent.appendChild(editImageInput);
|
||||||
noteContent.appendChild(editImagePreviewContainer);
|
noteContent.appendChild(editImagePreviewContainer);
|
||||||
noteContent.appendChild(saveButtonContainer);
|
noteContent.appendChild(saveButtonContainer);
|
||||||
@ -1853,6 +1955,46 @@ function addNoteEventListeners() {
|
|||||||
} else if (button.tag === "color") {
|
} else if (button.tag === "color") {
|
||||||
// Для кнопки цвета открываем диалог выбора цвета
|
// Для кнопки цвета открываем диалог выбора цвета
|
||||||
insertColorTagForEdit(textarea);
|
insertColorTagForEdit(textarea);
|
||||||
|
} else if (button.tag === "preview") {
|
||||||
|
// Для кнопки предпросмотра переключаем режим
|
||||||
|
isEditPreviewMode = !isEditPreviewMode;
|
||||||
|
|
||||||
|
if (isEditPreviewMode) {
|
||||||
|
// Показываем предпросмотр
|
||||||
|
textarea.style.display = "none";
|
||||||
|
editPreviewContainer.style.display = "block";
|
||||||
|
|
||||||
|
// Получаем содержимое и рендерим его
|
||||||
|
const content = textarea.value;
|
||||||
|
if (content.trim()) {
|
||||||
|
// Парсим markdown и делаем теги кликабельными
|
||||||
|
const htmlContent = marked.parse(content);
|
||||||
|
const contentWithTags = makeTagsClickable(htmlContent);
|
||||||
|
editPreviewContent.innerHTML = contentWithTags;
|
||||||
|
|
||||||
|
// Инициализируем lazy loading для изображений в превью
|
||||||
|
setTimeout(() => {
|
||||||
|
initLazyLoading();
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
editPreviewContent.innerHTML =
|
||||||
|
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Меняем иконку кнопки
|
||||||
|
btn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:eye-off"></span>';
|
||||||
|
btn.title = "Закрыть предпросмотр";
|
||||||
|
} else {
|
||||||
|
// Возвращаемся к редактированию
|
||||||
|
textarea.style.display = "block";
|
||||||
|
editPreviewContainer.style.display = "none";
|
||||||
|
|
||||||
|
// Меняем иконку обратно
|
||||||
|
btn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:eye"></span>';
|
||||||
|
btn.title = "Предпросмотр";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
insertMarkdownForEdit(textarea, button.tag);
|
insertMarkdownForEdit(textarea, button.tag);
|
||||||
}
|
}
|
||||||
@ -2163,6 +2305,17 @@ async function saveNote() {
|
|||||||
selectedImages = [];
|
selectedImages = [];
|
||||||
updateImagePreview();
|
updateImagePreview();
|
||||||
imageInput.value = "";
|
imageInput.value = "";
|
||||||
|
|
||||||
|
// Сбрасываем режим предпросмотра, если он был активен
|
||||||
|
if (isPreviewMode) {
|
||||||
|
isPreviewMode = false;
|
||||||
|
noteInput.style.display = "block";
|
||||||
|
notePreviewContainer.style.display = "none";
|
||||||
|
previewBtn.innerHTML =
|
||||||
|
'<span class="iconify" data-icon="mdi:eye"></span>';
|
||||||
|
previewBtn.title = "Предпросмотр";
|
||||||
|
}
|
||||||
|
|
||||||
await loadNotes(true);
|
await loadNotes(true);
|
||||||
|
|
||||||
// Показываем уведомление об успешном сохранении
|
// Показываем уведомление об успешном сохранении
|
||||||
@ -2407,8 +2560,12 @@ async function loadUserInfo() {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const user = await response.json();
|
const user = await response.json();
|
||||||
const userAvatar = document.getElementById("user-avatar");
|
const userAvatar = document.getElementById("user-avatar");
|
||||||
const userAvatarContainer = document.getElementById("user-avatar-container");
|
const userAvatarContainer = document.getElementById(
|
||||||
const userAvatarPlaceholder = document.getElementById("user-avatar-placeholder");
|
"user-avatar-container"
|
||||||
|
);
|
||||||
|
const userAvatarPlaceholder = document.getElementById(
|
||||||
|
"user-avatar-placeholder"
|
||||||
|
);
|
||||||
|
|
||||||
// Показываем аватарку или плейсхолдер
|
// Показываем аватарку или плейсхолдер
|
||||||
if (user.avatar) {
|
if (user.avatar) {
|
||||||
|
|||||||
@ -222,41 +222,36 @@
|
|||||||
style="display: none"
|
style="display: none"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div
|
<div
|
||||||
id="user-avatar-container"
|
id="user-avatar-container"
|
||||||
class="user-avatar-mini"
|
class="user-avatar-mini"
|
||||||
style="display: none"
|
style="display: none"
|
||||||
title="Перейти в профиль"
|
title="Перейти в профиль"
|
||||||
>
|
>
|
||||||
<img
|
<img id="user-avatar" src="" alt="Аватар" loading="lazy" />
|
||||||
id="user-avatar"
|
</div>
|
||||||
src=""
|
<div
|
||||||
alt="Аватар"
|
id="user-avatar-placeholder"
|
||||||
loading="lazy"
|
class="user-avatar-mini user-avatar-placeholder-mini"
|
||||||
/>
|
style="display: none"
|
||||||
</div>
|
title="Перейти в профиль"
|
||||||
<div
|
>
|
||||||
id="user-avatar-placeholder"
|
<span class="iconify" data-icon="mdi:account"></span>
|
||||||
class="user-avatar-mini user-avatar-placeholder-mini"
|
</div>
|
||||||
style="display: none"
|
<button
|
||||||
title="Перейти в профиль"
|
id="settings-btn"
|
||||||
>
|
class="settings-icon-btn"
|
||||||
<span class="iconify" data-icon="mdi:account"></span>
|
title="Настройки"
|
||||||
</div>
|
>
|
||||||
<button
|
<span class="iconify" data-icon="mdi:cog"></span>
|
||||||
id="settings-btn"
|
|
||||||
class="settings-icon-btn"
|
|
||||||
title="Настройки"
|
|
||||||
>
|
|
||||||
<span class="iconify" data-icon="mdi:cog"></span>
|
|
||||||
</button>
|
|
||||||
<form action="/logout" method="POST" style="display: inline">
|
|
||||||
<button type="submit" class="logout-btn">
|
|
||||||
<span class="iconify" data-icon="mdi:logout"></span> Выйти
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
<form action="/logout" method="POST" style="display: inline">
|
||||||
</div>
|
<button type="submit" class="logout-btn" title="Выйти">
|
||||||
|
<span class="iconify" data-icon="mdi:logout"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="markdown-buttons">
|
<div class="markdown-buttons">
|
||||||
@ -266,7 +261,11 @@
|
|||||||
<button class="btnMarkdown" id="italicBtn" title="Курсив">
|
<button class="btnMarkdown" id="italicBtn" title="Курсив">
|
||||||
<span class="iconify" data-icon="mdi:format-italic"></span>
|
<span class="iconify" data-icon="mdi:format-italic"></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="strikethroughBtn" title="Перечеркнутый">
|
<button
|
||||||
|
class="btnMarkdown"
|
||||||
|
id="strikethroughBtn"
|
||||||
|
title="Перечеркнутый"
|
||||||
|
>
|
||||||
<span class="iconify" data-icon="mdi:format-strikethrough"></span>
|
<span class="iconify" data-icon="mdi:format-strikethrough"></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="colorBtn" title="Цвет текста">
|
<button class="btnMarkdown" id="colorBtn" title="Цвет текста">
|
||||||
@ -274,8 +273,15 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="header-dropdown">
|
<div class="header-dropdown">
|
||||||
<button class="btnMarkdown" id="headerBtn" title="Заголовок">
|
<button class="btnMarkdown" id="headerBtn" title="Заголовок">
|
||||||
<span class="iconify" data-icon="mdi:format-header-pound"></span>
|
<span
|
||||||
<span class="iconify" data-icon="mdi:menu-down" style="font-size: 10px; margin-left: -2px;"></span>
|
class="iconify"
|
||||||
|
data-icon="mdi:format-header-pound"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="iconify"
|
||||||
|
data-icon="mdi:menu-down"
|
||||||
|
style="font-size: 10px; margin-left: -2px"
|
||||||
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="header-dropdown-menu" id="headerDropdown">
|
<div class="header-dropdown-menu" id="headerDropdown">
|
||||||
<button data-level="1">H1</button>
|
<button data-level="1">H1</button>
|
||||||
@ -318,6 +324,9 @@
|
|||||||
>
|
>
|
||||||
<span class="iconify" data-icon="mdi:image-plus"></span>
|
<span class="iconify" data-icon="mdi:image-plus"></span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btnMarkdown" id="previewBtn" title="Предпросмотр">
|
||||||
|
<span class="iconify" data-icon="mdi:eye"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
@ -326,6 +335,18 @@
|
|||||||
placeholder="Ваша заметка..."
|
placeholder="Ваша заметка..."
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
|
<!-- Контейнер для предпросмотра заметки -->
|
||||||
|
<div
|
||||||
|
id="notePreviewContainer"
|
||||||
|
class="note-preview-container"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
|
<div class="note-preview-header">
|
||||||
|
<span>Предпросмотр:</span>
|
||||||
|
</div>
|
||||||
|
<div id="notePreviewContent" class="note-preview-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Скрытый input для загрузки изображений -->
|
<!-- Скрытый input для загрузки изображений -->
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
|||||||
@ -58,12 +58,12 @@
|
|||||||
кабинет</span
|
кабинет</span
|
||||||
>
|
>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<a href="/notes" class="back-btn">← Назад к заметкам</a>
|
<a href="/notes" class="back-btn">
|
||||||
<form action="/logout" method="POST" style="display: inline">
|
<span class="iconify" data-icon="mdi:note-text"></span> К заметкам
|
||||||
<button type="submit" class="logout-btn">
|
</a>
|
||||||
<span class="iconify" data-icon="mdi:logout"></span> Выйти
|
<a href="/settings" class="back-btn">
|
||||||
</button>
|
<span class="iconify" data-icon="mdi:cog"></span> Настройки
|
||||||
</form>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -126,54 +126,6 @@
|
|||||||
<input type="email" id="email" placeholder="example@example.com" />
|
<input type="email" id="email" placeholder="example@example.com" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="accentColor">Цветовой акцент</label>
|
|
||||||
<div class="accent-color-picker">
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#007bff"
|
|
||||||
style="background-color: #007bff"
|
|
||||||
title="Синий"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#28a745"
|
|
||||||
style="background-color: #28a745"
|
|
||||||
title="Зеленый"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#dc3545"
|
|
||||||
style="background-color: #dc3545"
|
|
||||||
title="Красный"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#fd7e14"
|
|
||||||
style="background-color: #fd7e14"
|
|
||||||
title="Оранжевый"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#6f42c1"
|
|
||||||
style="background-color: #6f42c1"
|
|
||||||
title="Фиолетовый"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="color-option"
|
|
||||||
data-color="#e83e8c"
|
|
||||||
style="background-color: #e83e8c"
|
|
||||||
title="Розовый"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
id="accentColor"
|
|
||||||
value="#007bff"
|
|
||||||
style="margin-top: 10px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="updateProfileBtn" class="btnSave">
|
<button id="updateProfileBtn" class="btnSave">
|
||||||
Сохранить изменения
|
Сохранить изменения
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -11,7 +11,6 @@ const newPasswordInput = document.getElementById("newPassword");
|
|||||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||||
const changePasswordBtn = document.getElementById("changePasswordBtn");
|
const changePasswordBtn = document.getElementById("changePasswordBtn");
|
||||||
const messageContainer = document.getElementById("messageContainer");
|
const messageContainer = document.getElementById("messageContainer");
|
||||||
const accentColorInput = document.getElementById("accentColor");
|
|
||||||
|
|
||||||
// Lazy loading для изображений
|
// Lazy loading для изображений
|
||||||
function initLazyLoading() {
|
function initLazyLoading() {
|
||||||
@ -78,12 +77,8 @@ async function loadProfile() {
|
|||||||
// Заполняем поля
|
// Заполняем поля
|
||||||
usernameInput.value = user.username || "";
|
usernameInput.value = user.username || "";
|
||||||
emailInput.value = user.email || "";
|
emailInput.value = user.email || "";
|
||||||
accentColorInput.value = user.accent_color || "#007bff";
|
|
||||||
|
|
||||||
// Обновляем выбранный цвет в цветовых опциях
|
// Применяем цветовой акцент пользователя (для отображения)
|
||||||
updateColorPickerSelection(user.accent_color || "#007bff");
|
|
||||||
|
|
||||||
// Применяем цветовой акцент пользователя
|
|
||||||
const accentColor = user.accent_color || "#007bff";
|
const accentColor = user.accent_color || "#007bff";
|
||||||
document.documentElement.style.setProperty("--accent-color", accentColor);
|
document.documentElement.style.setProperty("--accent-color", accentColor);
|
||||||
|
|
||||||
@ -104,17 +99,6 @@ async function loadProfile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для обновления выбора цвета в цветовой палитре
|
|
||||||
function updateColorPickerSelection(selectedColor) {
|
|
||||||
const colorOptions = document.querySelectorAll(".color-option");
|
|
||||||
colorOptions.forEach((option) => {
|
|
||||||
option.classList.remove("selected");
|
|
||||||
if (option.dataset.color === selectedColor) {
|
|
||||||
option.classList.add("selected");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработчик загрузки аватарки
|
// Обработчик загрузки аватарки
|
||||||
avatarInput.addEventListener("change", async function (event) {
|
avatarInput.addEventListener("change", async function (event) {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
@ -200,7 +184,6 @@ deleteAvatarBtn.addEventListener("click", async function () {
|
|||||||
updateProfileBtn.addEventListener("click", async function () {
|
updateProfileBtn.addEventListener("click", async function () {
|
||||||
const username = usernameInput.value.trim();
|
const username = usernameInput.value.trim();
|
||||||
const email = emailInput.value.trim();
|
const email = emailInput.value.trim();
|
||||||
const accentColor = accentColorInput.value;
|
|
||||||
|
|
||||||
// Валидация
|
// Валидация
|
||||||
if (!username) {
|
if (!username) {
|
||||||
@ -227,7 +210,6 @@ updateProfileBtn.addEventListener("click", async function () {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username,
|
username,
|
||||||
email: email || null,
|
email: email || null,
|
||||||
accent_color: accentColor,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -238,9 +220,6 @@ updateProfileBtn.addEventListener("click", async function () {
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// Применяем новый цветовой акцент
|
|
||||||
document.documentElement.style.setProperty("--accent-color", accentColor);
|
|
||||||
|
|
||||||
showMessage(result.message || "Профиль успешно обновлен", "success");
|
showMessage(result.message || "Профиль успешно обновлен", "success");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка обновления профиля:", error);
|
console.error("Ошибка обновления профиля:", error);
|
||||||
@ -361,23 +340,6 @@ function setupLogoutHandler() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчики для цветовой палитры
|
|
||||||
function setupColorPicker() {
|
|
||||||
const colorOptions = document.querySelectorAll(".color-option");
|
|
||||||
colorOptions.forEach((option) => {
|
|
||||||
option.addEventListener("click", function () {
|
|
||||||
const selectedColor = this.dataset.color;
|
|
||||||
accentColorInput.value = selectedColor;
|
|
||||||
updateColorPickerSelection(selectedColor);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик для input color
|
|
||||||
accentColorInput.addEventListener("input", function () {
|
|
||||||
updateColorPickerSelection(this.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем профиль при загрузке страницы
|
// Загружаем профиль при загрузке страницы
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Проверяем аутентификацию при загрузке страницы
|
// Проверяем аутентификацию при загрузке страницы
|
||||||
@ -387,9 +349,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
// Инициализируем lazy loading для изображений
|
// Инициализируем lazy loading для изображений
|
||||||
initLazyLoading();
|
initLazyLoading();
|
||||||
|
|
||||||
// Настраиваем цветовую палитру
|
|
||||||
setupColorPicker();
|
|
||||||
|
|
||||||
// Добавляем обработчик для кнопки выхода
|
// Добавляем обработчик для кнопки выхода
|
||||||
setupLogoutHandler();
|
setupLogoutHandler();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,22 +54,23 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header class="notes-header">
|
<header class="notes-header">
|
||||||
<span
|
<span><span class="iconify" data-icon="mdi:cog"></span> Настройки</span>
|
||||||
><span class="iconify" data-icon="mdi:cog"></span> Настройки</span
|
|
||||||
>
|
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<a href="/profile" class="back-btn">
|
|
||||||
<span class="iconify" data-icon="mdi:account"></span> Профиль
|
|
||||||
</a>
|
|
||||||
<a href="/notes" class="back-btn">
|
<a href="/notes" class="back-btn">
|
||||||
<span class="iconify" data-icon="mdi:note-text"></span> К заметкам
|
<span class="iconify" data-icon="mdi:note-text"></span> К заметкам
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/profile" class="back-btn">
|
||||||
|
<span class="iconify" data-icon="mdi:account"></span> Профиль
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Табы навигации -->
|
<!-- Табы навигации -->
|
||||||
<div class="settings-tabs">
|
<div class="settings-tabs">
|
||||||
<button class="settings-tab active" data-tab="archive">
|
<button class="settings-tab active" data-tab="appearance">
|
||||||
|
<span class="iconify" data-icon="mdi:palette"></span> Внешний вид
|
||||||
|
</button>
|
||||||
|
<button class="settings-tab" data-tab="archive">
|
||||||
<span class="iconify" data-icon="mdi:archive"></span> Архив заметок
|
<span class="iconify" data-icon="mdi:archive"></span> Архив заметок
|
||||||
</button>
|
</button>
|
||||||
<button class="settings-tab" data-tab="logs">
|
<button class="settings-tab" data-tab="logs">
|
||||||
@ -79,8 +80,65 @@
|
|||||||
|
|
||||||
<!-- Контент табов -->
|
<!-- Контент табов -->
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
|
<!-- Внешний вид -->
|
||||||
|
<div class="tab-content active" id="appearance-tab">
|
||||||
|
<h3>Внешний вид</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="settings-accentColor">Цветовой акцент</label>
|
||||||
|
<div class="accent-color-picker">
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#007bff"
|
||||||
|
style="background-color: #007bff"
|
||||||
|
title="Синий"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#28a745"
|
||||||
|
style="background-color: #28a745"
|
||||||
|
title="Зеленый"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#dc3545"
|
||||||
|
style="background-color: #dc3545"
|
||||||
|
title="Красный"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#fd7e14"
|
||||||
|
style="background-color: #fd7e14"
|
||||||
|
title="Оранжевый"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#6f42c1"
|
||||||
|
style="background-color: #6f42c1"
|
||||||
|
title="Фиолетовый"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="color-option"
|
||||||
|
data-color="#e83e8c"
|
||||||
|
style="background-color: #e83e8c"
|
||||||
|
title="Розовый"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="settings-accentColor"
|
||||||
|
value="#007bff"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="updateAppearanceBtn" class="btnSave">
|
||||||
|
Сохранить изменения
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Архив заметок -->
|
<!-- Архив заметок -->
|
||||||
<div class="tab-content active" id="archive-tab">
|
<div class="tab-content" id="archive-tab">
|
||||||
<h3>Архивные заметки</h3>
|
<h3>Архивные заметки</h3>
|
||||||
<p style="color: #666; font-size: 14px; margin-bottom: 20px">
|
<p style="color: #666; font-size: 14px; margin-bottom: 20px">
|
||||||
Архивированные заметки можно восстановить или удалить окончательно
|
Архивированные заметки можно восстановить или удалить окончательно
|
||||||
@ -93,7 +151,7 @@
|
|||||||
<!-- История действий -->
|
<!-- История действий -->
|
||||||
<div class="tab-content" id="logs-tab">
|
<div class="tab-content" id="logs-tab">
|
||||||
<h3>История действий</h3>
|
<h3>История действий</h3>
|
||||||
|
|
||||||
<!-- Фильтры -->
|
<!-- Фильтры -->
|
||||||
<div class="logs-filters">
|
<div class="logs-filters">
|
||||||
<select id="logTypeFilter" class="log-filter-select">
|
<select id="logTypeFilter" class="log-filter-select">
|
||||||
@ -107,7 +165,9 @@
|
|||||||
<option value="note_pin">Закрепление</option>
|
<option value="note_pin">Закрепление</option>
|
||||||
<option value="note_archive">Архивирование</option>
|
<option value="note_archive">Архивирование</option>
|
||||||
<option value="note_unarchive">Восстановление</option>
|
<option value="note_unarchive">Восстановление</option>
|
||||||
<option value="note_delete_permanent">Окончательное удаление</option>
|
<option value="note_delete_permanent">
|
||||||
|
Окончательное удаление
|
||||||
|
</option>
|
||||||
<option value="profile_update">Обновление профиля</option>
|
<option value="profile_update">Обновление профиля</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="refreshLogs" class="btnSave">
|
<button id="refreshLogs" class="btnSave">
|
||||||
@ -131,14 +191,20 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="logsLoadMore" class="load-more-container" style="display: none">
|
<div
|
||||||
|
id="logsLoadMore"
|
||||||
|
class="load-more-container"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
<button id="loadMoreLogsBtn" class="btnSave">Загрузить еще</button>
|
<button id="loadMoreLogsBtn" class="btnSave">Загрузить еще</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="settings-message-container" class="message-container"></div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>Создатель: <span>Fovway</span></p>
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
</div>
|
</div>
|
||||||
@ -153,5 +219,3 @@
|
|||||||
<script src="/pwa.js"></script>
|
<script src="/pwa.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,12 @@ let logsOffset = 0;
|
|||||||
const logsLimit = 50;
|
const logsLimit = 50;
|
||||||
let hasMoreLogs = true;
|
let hasMoreLogs = true;
|
||||||
|
|
||||||
|
// DOM элементы для внешнего вида
|
||||||
|
const settingsAccentColorInput = document.getElementById(
|
||||||
|
"settings-accentColor"
|
||||||
|
);
|
||||||
|
const updateAppearanceBtn = document.getElementById("updateAppearanceBtn");
|
||||||
|
|
||||||
// Проверка аутентификации
|
// Проверка аутентификации
|
||||||
async function checkAuthentication() {
|
async function checkAuthentication() {
|
||||||
try {
|
try {
|
||||||
@ -27,19 +33,30 @@ async function checkAuthentication() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загрузка информации о пользователе для применения accent color
|
// Загрузка информации о пользователе для применения accent color и заполнения формы
|
||||||
async function loadUserInfo() {
|
async function loadUserInfo() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/user");
|
const response = await fetch("/api/user");
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const user = await response.json();
|
const user = await response.json();
|
||||||
const accentColor = user.accent_color || "#007bff";
|
const accentColor = user.accent_color || "#007bff";
|
||||||
|
|
||||||
|
// Применяем цветовой акцент
|
||||||
if (
|
if (
|
||||||
getComputedStyle(document.documentElement)
|
getComputedStyle(document.documentElement)
|
||||||
.getPropertyValue("--accent-color")
|
.getPropertyValue("--accent-color")
|
||||||
.trim() !== accentColor
|
.trim() !== accentColor
|
||||||
) {
|
) {
|
||||||
document.documentElement.style.setProperty("--accent-color", accentColor);
|
document.documentElement.style.setProperty(
|
||||||
|
"--accent-color",
|
||||||
|
accentColor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заполняем поле цветового акцента в настройках
|
||||||
|
if (settingsAccentColorInput) {
|
||||||
|
settingsAccentColorInput.value = accentColor;
|
||||||
|
updateColorPickerSelection(accentColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -47,6 +64,33 @@ async function loadUserInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для обновления выбора цвета в цветовой палитре
|
||||||
|
function updateColorPickerSelection(selectedColor) {
|
||||||
|
const colorOptions = document.querySelectorAll(
|
||||||
|
"#appearance-tab .color-option"
|
||||||
|
);
|
||||||
|
colorOptions.forEach((option) => {
|
||||||
|
option.classList.remove("selected");
|
||||||
|
if (option.dataset.color === selectedColor) {
|
||||||
|
option.classList.add("selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для показа сообщений
|
||||||
|
function showSettingsMessage(message, type = "success") {
|
||||||
|
const container = document.getElementById("settings-message-container");
|
||||||
|
if (container) {
|
||||||
|
container.textContent = message;
|
||||||
|
container.className = `message-container ${type}`;
|
||||||
|
container.style.display = "block";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
container.style.display = "none";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Переключение табов
|
// Переключение табов
|
||||||
function initTabs() {
|
function initTabs() {
|
||||||
const tabs = document.querySelectorAll(".settings-tab");
|
const tabs = document.querySelectorAll(".settings-tab");
|
||||||
@ -69,6 +113,8 @@ function initTabs() {
|
|||||||
loadArchivedNotes();
|
loadArchivedNotes();
|
||||||
} else if (tabName === "logs") {
|
} else if (tabName === "logs") {
|
||||||
loadLogs(true);
|
loadLogs(true);
|
||||||
|
} else if (tabName === "appearance") {
|
||||||
|
// Данные внешнего вида уже загружены в loadUserInfo()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -77,7 +123,8 @@ function initTabs() {
|
|||||||
// Загрузка архивных заметок
|
// Загрузка архивных заметок
|
||||||
async function loadArchivedNotes() {
|
async function loadArchivedNotes() {
|
||||||
const container = document.getElementById("archived-notes-container");
|
const container = document.getElementById("archived-notes-container");
|
||||||
container.innerHTML = '<p style="text-align: center; color: #999;">Загрузка...</p>';
|
container.innerHTML =
|
||||||
|
'<p style="text-align: center; color: #999;">Загрузка...</p>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/notes/archived");
|
const response = await fetch("/api/notes/archived");
|
||||||
@ -112,7 +159,8 @@ async function loadArchivedNotes() {
|
|||||||
|
|
||||||
// Преобразуем markdown в HTML для предпросмотра
|
// Преобразуем markdown в HTML для предпросмотра
|
||||||
const htmlContent = marked.parse(note.content);
|
const htmlContent = marked.parse(note.content);
|
||||||
const preview = htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : "");
|
const preview =
|
||||||
|
htmlContent.substring(0, 200) + (htmlContent.length > 200 ? "..." : "");
|
||||||
|
|
||||||
// Изображения
|
// Изображения
|
||||||
let imagesHtml = "";
|
let imagesHtml = "";
|
||||||
@ -165,9 +213,7 @@ function addArchivedNotesEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем элемент из списка
|
// Удаляем элемент из списка
|
||||||
document
|
document.querySelector(`[data-note-id="${noteId}"]`)?.remove();
|
||||||
.querySelector(`[data-note-id="${noteId}"]`)
|
|
||||||
?.remove();
|
|
||||||
|
|
||||||
// Проверяем, остались ли заметки
|
// Проверяем, остались ли заметки
|
||||||
const container = document.getElementById("archived-notes-container");
|
const container = document.getElementById("archived-notes-container");
|
||||||
@ -190,9 +236,7 @@ function addArchivedNotesEventListeners() {
|
|||||||
btn.addEventListener("click", async (e) => {
|
btn.addEventListener("click", async (e) => {
|
||||||
const noteId = e.target.closest("button").dataset.id;
|
const noteId = e.target.closest("button").dataset.id;
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm("Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!")
|
||||||
"Удалить эту заметку НАВСЕГДА? Это действие нельзя отменить!"
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/notes/archived/${noteId}`, {
|
const response = await fetch(`/api/notes/archived/${noteId}`, {
|
||||||
@ -204,9 +248,7 @@ function addArchivedNotesEventListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем элемент из списка
|
// Удаляем элемент из списка
|
||||||
document
|
document.querySelector(`[data-note-id="${noteId}"]`)?.remove();
|
||||||
.querySelector(`[data-note-id="${noteId}"]`)
|
|
||||||
?.remove();
|
|
||||||
|
|
||||||
// Проверяем, остались ли заметки
|
// Проверяем, остались ли заметки
|
||||||
const container = document.getElementById("archived-notes-container");
|
const container = document.getElementById("archived-notes-container");
|
||||||
@ -237,7 +279,8 @@ async function loadLogs(reset = false) {
|
|||||||
const filterValue = document.getElementById("logTypeFilter").value;
|
const filterValue = document.getElementById("logTypeFilter").value;
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">Загрузка...</td></tr>';
|
tbody.innerHTML =
|
||||||
|
'<tr><td colspan="4" style="text-align: center;">Загрузка...</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -270,7 +313,7 @@ async function loadLogs(reset = false) {
|
|||||||
|
|
||||||
logs.forEach((log) => {
|
logs.forEach((log) => {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
|
|
||||||
// Форматируем дату
|
// Форматируем дату
|
||||||
const created = new Date(log.created_at.replace(" ", "T") + "Z");
|
const created = new Date(log.created_at.replace(" ", "T") + "Z");
|
||||||
const dateStr = new Intl.DateTimeFormat("ru-RU", {
|
const dateStr = new Intl.DateTimeFormat("ru-RU", {
|
||||||
@ -301,7 +344,9 @@ async function loadLogs(reset = false) {
|
|||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${dateStr}</td>
|
<td>${dateStr}</td>
|
||||||
<td><span class="log-action-badge log-action-${log.action_type}">${actionText}</span></td>
|
<td><span class="log-action-badge log-action-${
|
||||||
|
log.action_type
|
||||||
|
}">${actionText}</span></td>
|
||||||
<td>${log.details || "-"}</td>
|
<td>${log.details || "-"}</td>
|
||||||
<td>${log.ip_address || "-"}</td>
|
<td>${log.ip_address || "-"}</td>
|
||||||
`;
|
`;
|
||||||
@ -337,9 +382,6 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|||||||
// Инициализируем табы
|
// Инициализируем табы
|
||||||
initTabs();
|
initTabs();
|
||||||
|
|
||||||
// Загружаем архивные заметки по умолчанию
|
|
||||||
loadArchivedNotes();
|
|
||||||
|
|
||||||
// Обработчик фильтра логов
|
// Обработчик фильтра логов
|
||||||
document.getElementById("logTypeFilter").addEventListener("change", () => {
|
document.getElementById("logTypeFilter").addEventListener("change", () => {
|
||||||
loadLogs(true);
|
loadLogs(true);
|
||||||
@ -354,6 +396,67 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|||||||
document.getElementById("loadMoreLogsBtn").addEventListener("click", () => {
|
document.getElementById("loadMoreLogsBtn").addEventListener("click", () => {
|
||||||
loadLogs(false);
|
loadLogs(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Обработчик кнопки сохранения внешнего вида
|
||||||
|
if (updateAppearanceBtn) {
|
||||||
|
updateAppearanceBtn.addEventListener("click", async function () {
|
||||||
|
const accentColor = settingsAccentColorInput.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user/profile", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
accent_color: accentColor,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Ошибка обновления цветового акцента");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Применяем новый цветовой акцент
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--accent-color",
|
||||||
|
accentColor
|
||||||
|
);
|
||||||
|
|
||||||
|
showSettingsMessage(
|
||||||
|
result.message || "Цветовой акцент успешно обновлен",
|
||||||
|
"success"
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка обновления цветового акцента:", error);
|
||||||
|
showSettingsMessage(error.message, "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для цветовой палитры
|
||||||
|
function setupAppearanceColorPicker() {
|
||||||
|
const colorOptions = document.querySelectorAll(
|
||||||
|
"#appearance-tab .color-option"
|
||||||
|
);
|
||||||
|
colorOptions.forEach((option) => {
|
||||||
|
option.addEventListener("click", function () {
|
||||||
|
const selectedColor = this.dataset.color;
|
||||||
|
settingsAccentColorInput.value = selectedColor;
|
||||||
|
updateColorPickerSelection(selectedColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик для input color
|
||||||
|
if (settingsAccentColorInput) {
|
||||||
|
settingsAccentColorInput.addEventListener("input", function () {
|
||||||
|
updateColorPickerSelection(this.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAppearanceColorPicker();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
173
public/style.css
173
public/style.css
@ -47,11 +47,7 @@ header .iconify {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для иконок в кнопках */
|
/* Стили для иконок в кнопках удалены, так как теперь кнопки имеют свои стили */
|
||||||
.logout-btn .iconify {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для иконок в секциях */
|
/* Стили для иконок в секциях */
|
||||||
.search-title .iconify,
|
.search-title .iconify,
|
||||||
@ -82,10 +78,7 @@ header .iconify[data-icon="mdi:account"],
|
|||||||
color: #9c27b0;
|
color: #9c27b0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Иконка выхода - красный */
|
/* Цвет иконки выхода задается в стилях .logout-btn .iconify */
|
||||||
.logout-btn .iconify[data-icon="mdi:logout"] {
|
|
||||||
color: #f44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Иконка входа - синий */
|
/* Иконка входа - синий */
|
||||||
header .iconify[data-icon="mdi:login"] {
|
header .iconify[data-icon="mdi:login"] {
|
||||||
@ -279,12 +272,19 @@ header {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-avatar-placeholder-mini .iconify {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Иконка настроек */
|
/* Иконка настроек */
|
||||||
.settings-icon-btn {
|
.settings-icon-btn {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid #ddd;
|
border: none;
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -296,7 +296,6 @@ header {
|
|||||||
|
|
||||||
.settings-icon-btn:hover {
|
.settings-icon-btn:hover {
|
||||||
background: var(--accent-color, #007bff);
|
background: var(--accent-color, #007bff);
|
||||||
border-color: var(--accent-color, #007bff);
|
|
||||||
color: white;
|
color: white;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
@ -305,6 +304,10 @@ header {
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #666;
|
color: #666;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-icon-btn:hover .iconify {
|
.settings-icon-btn:hover .iconify {
|
||||||
@ -312,18 +315,34 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
padding: 8px 16px;
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #ddd;
|
display: flex;
|
||||||
background-color: #f8f9fa;
|
align-items: center;
|
||||||
border-radius: 5px;
|
justify-content: center;
|
||||||
font-size: 14px;
|
|
||||||
color: #dc3545;
|
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-btn:hover {
|
.logout-btn:hover {
|
||||||
background-color: #dc3545;
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn .iconify {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #dc3545;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn:hover .iconify {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -929,26 +948,42 @@ textarea:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-container {
|
.message-container {
|
||||||
margin-top: 20px;
|
position: fixed;
|
||||||
margin-bottom: 80px; /* Отступ снизу, чтобы контент не обрезался футером */
|
top: 20px;
|
||||||
padding: 10px;
|
left: 50%;
|
||||||
border-radius: 5px;
|
transform: translateX(-50%);
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: none;
|
display: none;
|
||||||
|
z-index: 10000;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность для мобильных устройств */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.message-container {
|
||||||
|
min-width: 90vw;
|
||||||
|
max-width: 90vw;
|
||||||
|
left: 5vw;
|
||||||
|
transform: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container.success {
|
.message-container.success {
|
||||||
background-color: #d4edda;
|
background-color: #d4edda;
|
||||||
color: #155724;
|
color: #155724;
|
||||||
border: 1px solid #c3e6cb;
|
border: 1px solid #c3e6cb;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container.error {
|
.message-container.error {
|
||||||
background-color: #f8d7da;
|
background-color: #f8d7da;
|
||||||
color: #721c24;
|
color: #721c24;
|
||||||
border: 1px solid #f5c6cb;
|
border: 1px solid #f5c6cb;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
@ -1508,20 +1543,21 @@ textarea:focus {
|
|||||||
|
|
||||||
/* Адаптируем заголовок заметок */
|
/* Адаптируем заголовок заметок */
|
||||||
.notes-header {
|
.notes-header {
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notes-header-left {
|
.notes-header-left {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
width: 100%;
|
flex-shrink: 0;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1585,6 +1621,85 @@ textarea:focus {
|
|||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для контейнера предпросмотра заметки */
|
||||||
|
.note-preview-container {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 2px solid #007bff;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #007bff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content {
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content h1,
|
||||||
|
.note-preview-content h2,
|
||||||
|
.note-preview-content h3,
|
||||||
|
.note-preview-content h4,
|
||||||
|
.note-preview-content h5,
|
||||||
|
.note-preview-content h6 {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content ul,
|
||||||
|
.note-preview-content ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content code {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content pre {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content blockquote {
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding-left: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-preview-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.clear-images-btn {
|
.clear-images-btn {
|
||||||
background: #dc3545;
|
background: #dc3545;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user