443 lines
17 KiB
JavaScript
443 lines
17 KiB
JavaScript
// DOM элементы
|
||
const noteInput = document.getElementById("noteInput");
|
||
const saveBtn = document.getElementById("saveBtn");
|
||
const notesList = document.getElementById("notesList");
|
||
|
||
// Получаем кнопки markdown
|
||
const boldBtn = document.getElementById("boldBtn");
|
||
const italicBtn = document.getElementById("italicBtn");
|
||
const headerBtn = document.getElementById("headerBtn");
|
||
const listBtn = document.getElementById("listBtn");
|
||
const quoteBtn = document.getElementById("quoteBtn");
|
||
const codeBtn = document.getElementById("codeBtn");
|
||
const linkBtn = document.getElementById("linkBtn");
|
||
|
||
// Функция для получения текущей даты и времени
|
||
function getFormattedDateTime() {
|
||
let now = new Date();
|
||
let day = String(now.getDate()).padStart(2, "0");
|
||
let month = String(now.getMonth() + 1).padStart(2, "0");
|
||
let year = now.getFullYear();
|
||
let hours = String(now.getHours()).padStart(2, "0");
|
||
let minutes = String(now.getMinutes()).padStart(2, "0");
|
||
|
||
return {
|
||
date: `${day}.${month}.${year}`,
|
||
time: `${hours}:${minutes}`,
|
||
};
|
||
}
|
||
|
||
// Функция для авторасширения текстового поля
|
||
function autoExpandTextarea(textarea) {
|
||
textarea.style.height = "auto";
|
||
textarea.style.height = textarea.scrollHeight + "px";
|
||
}
|
||
|
||
// Привязываем авторасширение к текстовому полю для создания заметки
|
||
noteInput.addEventListener("input", function () {
|
||
autoExpandTextarea(noteInput);
|
||
});
|
||
|
||
// Изначально запускаем для установки правильной высоты
|
||
autoExpandTextarea(noteInput);
|
||
|
||
// Функция для вставки markdown
|
||
function insertMarkdown(tag) {
|
||
const start = noteInput.selectionStart;
|
||
const end = noteInput.selectionEnd;
|
||
const text = noteInput.value;
|
||
|
||
const before = text.substring(0, start);
|
||
const selected = text.substring(start, end);
|
||
const after = text.substring(end);
|
||
|
||
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
||
// Если теги уже есть, удаляем их
|
||
noteInput.value = `${before}${selected.slice(
|
||
tag.length,
|
||
-tag.length
|
||
)}${after}`;
|
||
noteInput.setSelectionRange(start, end - 2 * tag.length);
|
||
} else if (selected.trim() === "") {
|
||
// Если текст не выделен
|
||
if (tag === "[Текст ссылки](URL)") {
|
||
// Для ссылок создаем шаблон с двумя кавычками
|
||
noteInput.value = `${before}[Текст ссылки](URL)${after}`;
|
||
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition + 12);
|
||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
||
noteInput.value = `${before}${tag}${after}`;
|
||
const cursorPosition = start + tag.length;
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||
} else {
|
||
// Для остальных типов создаем два тега
|
||
noteInput.value = `${before}${tag}${tag}${after}`;
|
||
const cursorPosition = start + tag.length;
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||
}
|
||
} else {
|
||
// Если текст выделен
|
||
if (tag === "[Текст ссылки](URL)") {
|
||
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
||
noteInput.value = `${before}[${selected}](URL)${after}`;
|
||
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition + 3);
|
||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
||
noteInput.value = `${before}${tag}${selected}${after}`;
|
||
const cursorPosition = start + tag.length + selected.length;
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||
} else {
|
||
// Для остальных типов оборачиваем выделенный текст
|
||
noteInput.value = `${before}${tag}${selected}${tag}${after}`;
|
||
const cursorPosition = start + tag.length + selected.length + tag.length;
|
||
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||
}
|
||
}
|
||
|
||
noteInput.focus();
|
||
}
|
||
|
||
// Функция для вставки markdown в режиме редактирования
|
||
function insertMarkdownForEdit(textarea, tag) {
|
||
const start = textarea.selectionStart;
|
||
const end = textarea.selectionEnd;
|
||
const text = textarea.value;
|
||
|
||
const before = text.substring(0, start);
|
||
const selected = text.substring(start, end);
|
||
const after = text.substring(end);
|
||
|
||
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
||
// Если теги уже есть, удаляем их
|
||
textarea.value = `${before}${selected.slice(
|
||
tag.length,
|
||
-tag.length
|
||
)}${after}`;
|
||
textarea.setSelectionRange(start, end - 2 * tag.length);
|
||
} else if (selected.trim() === "") {
|
||
// Если текст не выделен
|
||
if (tag === "[Текст ссылки](URL)") {
|
||
// Для ссылок создаем шаблон с двумя кавычками
|
||
textarea.value = `${before}[Текст ссылки](URL)${after}`;
|
||
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition + 12);
|
||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
||
textarea.value = `${before}${tag}${after}`;
|
||
const cursorPosition = start + tag.length;
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||
} else {
|
||
// Для остальных типов создаем два тега
|
||
textarea.value = `${before}${tag}${tag}${after}`;
|
||
const cursorPosition = start + tag.length;
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||
}
|
||
} else {
|
||
// Если текст выделен
|
||
if (tag === "[Текст ссылки](URL)") {
|
||
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
||
textarea.value = `${before}[${selected}](URL)${after}`;
|
||
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition + 3);
|
||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
||
textarea.value = `${before}${tag}${selected}${after}`;
|
||
const cursorPosition = start + tag.length + selected.length;
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||
} else {
|
||
// Для остальных типов оборачиваем выделенный текст
|
||
textarea.value = `${before}${tag}${selected}${tag}${after}`;
|
||
const cursorPosition = start + tag.length + selected.length + tag.length;
|
||
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||
}
|
||
}
|
||
|
||
textarea.focus();
|
||
}
|
||
|
||
// Обработчики для кнопок markdown
|
||
boldBtn.addEventListener("click", function () {
|
||
insertMarkdown("**");
|
||
});
|
||
|
||
italicBtn.addEventListener("click", function () {
|
||
insertMarkdown("*");
|
||
});
|
||
|
||
headerBtn.addEventListener("click", function () {
|
||
insertMarkdown("# ");
|
||
});
|
||
|
||
listBtn.addEventListener("click", function () {
|
||
insertMarkdown("- ");
|
||
});
|
||
|
||
quoteBtn.addEventListener("click", function () {
|
||
insertMarkdown("> ");
|
||
});
|
||
|
||
codeBtn.addEventListener("click", function () {
|
||
insertMarkdown("`");
|
||
});
|
||
|
||
linkBtn.addEventListener("click", function () {
|
||
insertMarkdown("[Текст ссылки](URL)");
|
||
});
|
||
|
||
// Функция для загрузки заметок с сервера
|
||
async function loadNotes() {
|
||
try {
|
||
const response = await fetch("/api/notes");
|
||
if (!response.ok) {
|
||
throw new Error("Ошибка загрузки заметок");
|
||
}
|
||
const notes = await response.json();
|
||
renderNotes(notes);
|
||
} catch (error) {
|
||
console.error("Ошибка:", error);
|
||
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
|
||
}
|
||
}
|
||
|
||
// Функция для отображения заметок
|
||
function renderNotes(notes) {
|
||
notesList.innerHTML = "";
|
||
|
||
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
|
||
notes.forEach(function (note) {
|
||
const noteHtml = `
|
||
<div id="note" class="container">
|
||
<div class="date">
|
||
${note.date} ${note.time}
|
||
<div id="editBtn" class="notesHeaderBtn" data-id="${
|
||
note.id
|
||
}">Редактировать</div>
|
||
<div id="deleteBtn" class="notesHeaderBtn" data-id="${
|
||
note.id
|
||
}">Удалить</div>
|
||
</div>
|
||
<div class="textNote" data-original-content="${note.content.replace(
|
||
/"/g,
|
||
"""
|
||
)}">${marked.parse(note.content)}</div>
|
||
</div>
|
||
`;
|
||
notesList.insertAdjacentHTML("afterbegin", noteHtml);
|
||
});
|
||
|
||
// Добавляем обработчики событий для кнопок редактирования и удаления
|
||
addNoteEventListeners();
|
||
}
|
||
|
||
// Функция для добавления обработчиков событий к заметкам
|
||
function addNoteEventListeners() {
|
||
// Обработчик удаления
|
||
document.querySelectorAll("#deleteBtn").forEach((btn) => {
|
||
btn.addEventListener("click", async function (event) {
|
||
const noteId = event.target.dataset.id;
|
||
if (confirm("Вы уверены, что хотите удалить эту заметку?")) {
|
||
try {
|
||
const response = await fetch(`/api/notes/${noteId}`, {
|
||
method: "DELETE",
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Ошибка удаления заметки");
|
||
}
|
||
|
||
// Перезагружаем заметки
|
||
loadNotes();
|
||
} catch (error) {
|
||
console.error("Ошибка:", error);
|
||
alert("Ошибка удаления заметки");
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Обработчик редактирования
|
||
document.querySelectorAll("#editBtn").forEach((btn) => {
|
||
btn.addEventListener("click", function (event) {
|
||
const noteId = event.target.dataset.id;
|
||
const noteContainer = event.target.closest("#note");
|
||
const noteContent = noteContainer.querySelector(".textNote");
|
||
|
||
// Создаем контейнер для markdown кнопок
|
||
const markdownButtonsContainer = document.createElement("div");
|
||
markdownButtonsContainer.classList.add("markdown-buttons");
|
||
|
||
// Создаем markdown кнопки
|
||
const markdownButtons = [
|
||
{ id: "editBoldBtn", icon: "fas fa-bold", tag: "**" },
|
||
{ id: "editItalicBtn", icon: "fas fa-italic", tag: "*" },
|
||
{ id: "editHeaderBtn", icon: "fas fa-heading", tag: "# " },
|
||
{ id: "editListBtn", icon: "fas fa-list-ul", tag: "- " },
|
||
{ id: "editQuoteBtn", icon: "fas fa-quote-right", tag: "> " },
|
||
{ id: "editCodeBtn", icon: "fas fa-code", tag: "`" },
|
||
{ id: "editLinkBtn", icon: "fas fa-link", tag: "[Текст ссылки](URL)" },
|
||
];
|
||
|
||
markdownButtons.forEach((button) => {
|
||
const btn = document.createElement("button");
|
||
btn.classList.add("btnMarkdown");
|
||
btn.id = button.id;
|
||
btn.innerHTML = `<i class="${button.icon}"></i>`;
|
||
markdownButtonsContainer.appendChild(btn);
|
||
});
|
||
|
||
// Создаем textarea с уже существующим классом textInput
|
||
const textarea = document.createElement("textarea");
|
||
textarea.classList.add("textInput");
|
||
// Получаем исходный markdown контент из data-атрибута или используем textContent как fallback
|
||
textarea.value =
|
||
noteContent.dataset.originalContent || noteContent.textContent;
|
||
|
||
// Привязываем авторасширение к textarea для редактирования
|
||
textarea.addEventListener("input", function () {
|
||
autoExpandTextarea(textarea);
|
||
});
|
||
autoExpandTextarea(textarea);
|
||
|
||
// Кнопка сохранить
|
||
const saveEditBtn = document.createElement("button");
|
||
saveEditBtn.textContent = "Сохранить";
|
||
saveEditBtn.classList.add("btnSave");
|
||
|
||
// Очищаем текущий контент и вставляем markdown кнопки, textarea и кнопку сохранить
|
||
noteContent.innerHTML = "";
|
||
noteContent.appendChild(markdownButtonsContainer);
|
||
noteContent.appendChild(textarea);
|
||
noteContent.appendChild(saveEditBtn);
|
||
|
||
// Добавляем обработчики для markdown кнопок редактирования
|
||
markdownButtons.forEach((button) => {
|
||
const btn = document.getElementById(button.id);
|
||
btn.addEventListener("click", function () {
|
||
insertMarkdownForEdit(textarea, button.tag);
|
||
});
|
||
});
|
||
|
||
// Обработчик сохранения редактирования
|
||
saveEditBtn.addEventListener("click", async function () {
|
||
if (textarea.value.trim() !== "") {
|
||
try {
|
||
const { date, time } = getFormattedDateTime();
|
||
const response = await fetch(`/api/notes/${noteId}`, {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
content: textarea.value,
|
||
date: date,
|
||
time: time,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Ошибка сохранения заметки");
|
||
}
|
||
|
||
// Перезагружаем заметки
|
||
loadNotes();
|
||
} catch (error) {
|
||
console.error("Ошибка:", error);
|
||
alert("Ошибка сохранения заметки");
|
||
}
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
// Функция сохранения заметки (вынесена отдельно для повторного использования)
|
||
async function saveNote() {
|
||
if (noteInput.value.trim() !== "") {
|
||
try {
|
||
const { date, time } = getFormattedDateTime();
|
||
|
||
const response = await fetch("/api/notes", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
content: noteInput.value,
|
||
date: date,
|
||
time: time,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Ошибка сохранения заметки");
|
||
}
|
||
|
||
// Очищаем поле ввода и перезагружаем заметки
|
||
noteInput.value = "";
|
||
noteInput.style.height = "auto";
|
||
loadNotes();
|
||
} catch (error) {
|
||
console.error("Ошибка:", error);
|
||
alert("Ошибка сохранения заметки");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обработчик сохранения новой заметки
|
||
saveBtn.addEventListener("click", saveNote);
|
||
|
||
// Обработчик горячей клавиши Alt+Enter для сохранения заметки
|
||
noteInput.addEventListener("keydown", function (event) {
|
||
if (event.altKey && event.key === "Enter") {
|
||
event.preventDefault(); // Предотвращаем стандартное поведение
|
||
saveNote();
|
||
}
|
||
});
|
||
|
||
// Загружаем заметки при загрузке страницы
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
loadUserInfo();
|
||
loadNotes();
|
||
});
|
||
|
||
// Функция для загрузки информации о пользователе
|
||
async function loadUserInfo() {
|
||
try {
|
||
const response = await fetch("/api/user");
|
||
if (response.ok) {
|
||
const user = await response.json();
|
||
const usernameDisplay = document.getElementById("username-display");
|
||
const userAvatar = document.getElementById("user-avatar");
|
||
const userAvatarContainer = document.getElementById(
|
||
"user-avatar-container"
|
||
);
|
||
|
||
if (usernameDisplay) {
|
||
usernameDisplay.textContent = `👤 ${user.username}`;
|
||
|
||
// Делаем ник кликабельным для перехода в личный кабинет
|
||
usernameDisplay.style.cursor = "pointer";
|
||
usernameDisplay.addEventListener("click", function () {
|
||
window.location.href = "/profile";
|
||
});
|
||
}
|
||
|
||
// Отображаем аватарку, если она есть
|
||
if (user.avatar && userAvatar && userAvatarContainer) {
|
||
userAvatar.src = user.avatar;
|
||
userAvatarContainer.style.display = "inline-block";
|
||
|
||
// Аватарка также кликабельна
|
||
userAvatarContainer.style.cursor = "pointer";
|
||
userAvatarContainer.addEventListener("click", function () {
|
||
window.location.href = "/profile";
|
||
});
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error("Ошибка загрузки информации о пользователе:", error);
|
||
}
|
||
}
|