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