diff --git a/public/app.js b/public/app.js
index de7cf7e..3fecf47 100644
--- a/public/app.js
+++ b/public/app.js
@@ -6,11 +6,13 @@ const notesList = document.getElementById("notes-container");
// Получаем кнопки markdown
const boldBtn = document.getElementById("boldBtn");
const italicBtn = document.getElementById("italicBtn");
+const colorBtn = document.getElementById("colorBtn");
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");
+const checkboxBtn = document.getElementById("checkboxBtn");
const imageBtn = document.getElementById("imageBtn");
// Элементы для загрузки изображений
@@ -224,6 +226,58 @@ noteInput.addEventListener("input", function () {
// Изначально запускаем для установки правильной высоты
autoExpandTextarea(noteInput);
+// Функция для вставки цветового тега
+function insertColorTag() {
+ // Создаем диалог выбора цвета
+ const colorDialog = document.createElement("input");
+ colorDialog.type = "color";
+ colorDialog.style.display = "none";
+ document.body.appendChild(colorDialog);
+
+ // Обработчик изменения цвета
+ colorDialog.addEventListener("change", function () {
+ const selectedColor = this.value;
+ insertColorMarkdown(selectedColor);
+ document.body.removeChild(this);
+ });
+
+ // Обработчик отмены
+ colorDialog.addEventListener("cancel", function () {
+ document.body.removeChild(this);
+ });
+
+ // Показываем диалог выбора цвета
+ colorDialog.click();
+}
+
+// Функция для вставки цветового markdown
+function insertColorMarkdown(color) {
+ 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);
+
+ let replacement;
+ if (selected.trim() === "") {
+ // Если текст не выделен, вставляем шаблон
+ replacement = `Текст`;
+ } else {
+ // Если текст выделен, оборачиваем его в цветовой тег
+ replacement = `${selected}`;
+ }
+
+ noteInput.value = before + replacement + after;
+
+ // Устанавливаем курсор после вставленного текста
+ const cursorPosition = start + replacement.length;
+ noteInput.setSelectionRange(cursorPosition, cursorPosition);
+
+ noteInput.focus();
+}
+
// Функция для вставки markdown
function insertMarkdown(tag) {
const start = noteInput.selectionStart;
@@ -248,8 +302,13 @@ function insertMarkdown(tag) {
noteInput.value = `${before}[Текст ссылки](URL)${after}`;
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
noteInput.setSelectionRange(cursorPosition, cursorPosition + 12);
- } else if (tag === "- " || tag === "> " || tag === "# ") {
- // Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
+ } else if (
+ tag === "- " ||
+ tag === "> " ||
+ tag === "# " ||
+ tag === "- [ ] "
+ ) {
+ // Для списка, цитаты, заголовка и чекбокса помещаем курсор после тега
noteInput.value = `${before}${tag}${after}`;
const cursorPosition = start + tag.length;
noteInput.setSelectionRange(cursorPosition, cursorPosition);
@@ -266,8 +325,13 @@ function insertMarkdown(tag) {
noteInput.value = `${before}[${selected}](URL)${after}`;
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
noteInput.setSelectionRange(cursorPosition, cursorPosition + 3);
- } else if (tag === "- " || tag === "> " || tag === "# ") {
- // Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
+ } else if (
+ tag === "- " ||
+ tag === "> " ||
+ tag === "# " ||
+ tag === "- [ ] "
+ ) {
+ // Для списка, цитаты, заголовка и чекбокса добавляем тег перед выделенным текстом
noteInput.value = `${before}${tag}${selected}${after}`;
const cursorPosition = start + tag.length + selected.length;
noteInput.setSelectionRange(cursorPosition, cursorPosition);
@@ -282,6 +346,58 @@ function insertMarkdown(tag) {
noteInput.focus();
}
+// Функция для вставки цветового тега в режиме редактирования
+function insertColorTagForEdit(textarea) {
+ // Создаем диалог выбора цвета
+ const colorDialog = document.createElement("input");
+ colorDialog.type = "color";
+ colorDialog.style.display = "none";
+ document.body.appendChild(colorDialog);
+
+ // Обработчик изменения цвета
+ colorDialog.addEventListener("change", function () {
+ const selectedColor = this.value;
+ insertColorMarkdownForEdit(textarea, selectedColor);
+ document.body.removeChild(this);
+ });
+
+ // Обработчик отмены
+ colorDialog.addEventListener("cancel", function () {
+ document.body.removeChild(this);
+ });
+
+ // Показываем диалог выбора цвета
+ colorDialog.click();
+}
+
+// Функция для вставки цветового markdown в режиме редактирования
+function insertColorMarkdownForEdit(textarea, color) {
+ 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);
+
+ let replacement;
+ if (selected.trim() === "") {
+ // Если текст не выделен, вставляем шаблон
+ replacement = `Текст`;
+ } else {
+ // Если текст выделен, оборачиваем его в цветовой тег
+ replacement = `${selected}`;
+ }
+
+ textarea.value = before + replacement + after;
+
+ // Устанавливаем курсор после вставленного текста
+ const cursorPosition = start + replacement.length;
+ textarea.setSelectionRange(cursorPosition, cursorPosition);
+
+ textarea.focus();
+}
+
// Функция для вставки markdown в режиме редактирования
function insertMarkdownForEdit(textarea, tag) {
const start = textarea.selectionStart;
@@ -306,8 +422,13 @@ function insertMarkdownForEdit(textarea, tag) {
textarea.value = `${before}[Текст ссылки](URL)${after}`;
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
textarea.setSelectionRange(cursorPosition, cursorPosition + 12);
- } else if (tag === "- " || tag === "> " || tag === "# ") {
- // Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
+ } else if (
+ tag === "- " ||
+ tag === "> " ||
+ tag === "# " ||
+ tag === "- [ ] "
+ ) {
+ // Для списка, цитаты, заголовка и чекбокса помещаем курсор после тега
textarea.value = `${before}${tag}${after}`;
const cursorPosition = start + tag.length;
textarea.setSelectionRange(cursorPosition, cursorPosition);
@@ -324,8 +445,13 @@ function insertMarkdownForEdit(textarea, tag) {
textarea.value = `${before}[${selected}](URL)${after}`;
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
textarea.setSelectionRange(cursorPosition, cursorPosition + 3);
- } else if (tag === "- " || tag === "> " || tag === "# ") {
- // Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
+ } else if (
+ tag === "- " ||
+ tag === "> " ||
+ tag === "# " ||
+ tag === "- [ ] "
+ ) {
+ // Для списка, цитаты, заголовка и чекбокса добавляем тег перед выделенным текстом
textarea.value = `${before}${tag}${selected}${after}`;
const cursorPosition = start + tag.length + selected.length;
textarea.setSelectionRange(cursorPosition, cursorPosition);
@@ -349,6 +475,10 @@ italicBtn.addEventListener("click", function () {
insertMarkdown("*");
});
+colorBtn.addEventListener("click", function () {
+ insertColorTag();
+});
+
headerBtn.addEventListener("click", function () {
insertMarkdown("# ");
});
@@ -369,6 +499,10 @@ linkBtn.addEventListener("click", function () {
insertMarkdown("[Текст ссылки](URL)");
});
+checkboxBtn.addEventListener("click", function () {
+ insertMarkdown("- [ ] ");
+});
+
// Обработчик для кнопки загрузки изображений
imageBtn.addEventListener("click", function (event) {
event.preventDefault();
@@ -768,6 +902,30 @@ function highlightSearchText(content, query) {
return content.replace(regex, '$1');
}
+// Настройка marked.js для поддержки чекбоксов
+const renderer = new marked.Renderer();
+
+// Переопределяем рендеринг списков, чтобы чекбоксы были кликабельными (без disabled)
+const originalListItem = renderer.listitem.bind(renderer);
+renderer.listitem = function (text, task, checked) {
+ if (task) {
+ // Удаляем disabled чекбокс из текста, если он есть
+ let cleanText = text.replace(/]*disabled[^>]*>/gi, "").trim();
+ // Создаем чекбокс БЕЗ disabled атрибута
+ return `
${cleanText}\n`;
+ }
+ return originalListItem(text, task, checked);
+};
+
+marked.setOptions({
+ gfm: true, // GitHub Flavored Markdown
+ breaks: true,
+ renderer: renderer,
+ html: true, // Разрешить HTML теги
+});
+
// Функция для отображения заметок
async function renderNotes(notes) {
notesList.innerHTML = "";
@@ -835,10 +993,31 @@ async function renderNotes(notes) {
imagesHtml += "";
}
+ // Форматируем дату создания и дату изменения
+ let dateDisplay = `${note.date} ${note.time}`;
+ if (note.updated_at && note.created_at !== note.updated_at) {
+ // Если дата изменения отличается от даты создания, показываем обе даты
+ const createdDate = new Date(note.created_at);
+ const updatedDate = new Date(note.updated_at);
+
+ const formatDate = (date) => {
+ const day = String(date.getDate()).padStart(2, "0");
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const year = date.getFullYear();
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
+ return `${day}.${month}.${year} ${hours}:${minutes}`;
+ };
+
+ dateDisplay = `Создано: ${formatDate(
+ createdDate
+ )}
Изменено: ${formatDate(updatedDate)}`;
+ }
+
const noteHtml = `
- ${note.date} ${note.time}
+ ${dateDisplay}
@@ -853,7 +1032,7 @@ async function renderNotes(notes) {
${imagesHtml}
`;
- notesList.insertAdjacentHTML("afterbegin", noteHtml);
+ notesList.insertAdjacentHTML("beforeend", noteHtml);
}
// Добавляем обработчики событий для кнопок редактирования и удаления
@@ -865,6 +1044,9 @@ async function renderNotes(notes) {
// Добавляем обработчики для изображений в заметках
addImageEventListeners();
+ // Добавляем обработчики для чекбоксов в заметках
+ addCheckboxEventListeners();
+
// Обрабатываем длинные заметки
handleLongNotes();
@@ -966,11 +1148,17 @@ function addNoteEventListeners() {
const markdownButtons = [
{ id: "editBoldBtn", icon: "mdi:format-bold", tag: "**" },
{ id: "editItalicBtn", icon: "mdi:format-italic", tag: "*" },
+ { id: "editColorBtn", icon: "mdi:palette", tag: "color" },
{ id: "editHeaderBtn", icon: "mdi:format-header-1", tag: "# " },
{ id: "editListBtn", icon: "mdi:format-list-bulleted", tag: "- " },
{ id: "editQuoteBtn", icon: "mdi:format-quote-close", tag: "> " },
{ id: "editCodeBtn", icon: "mdi:code-tags", tag: "`" },
{ id: "editLinkBtn", icon: "mdi:link", tag: "[Текст ссылки](URL)" },
+ {
+ id: "editCheckboxBtn",
+ icon: "mdi:checkbox-marked-outline",
+ tag: "- [ ] ",
+ },
{ id: "editImageBtn", icon: "mdi:image-plus", tag: "image" },
];
@@ -994,6 +1182,121 @@ function addNoteEventListeners() {
autoExpandTextarea(textarea);
});
+ // Добавляем обработчик клавиатуры для автоматического продолжения списков
+ textarea.addEventListener("keydown", function (event) {
+ if (event.key === "Enter") {
+ // Автоматическое продолжение списков в режиме редактирования
+ const start = textarea.selectionStart;
+ const text = textarea.value;
+ const lines = text.split("\n");
+
+ // Определяем текущую строку
+ let currentLineIndex = 0;
+ let currentLineStart = 0;
+ let currentLine = "";
+
+ for (let i = 0; i < lines.length; i++) {
+ const lineLength = lines[i].length;
+ if (currentLineStart + lineLength >= start) {
+ currentLineIndex = i;
+ currentLine = lines[i];
+ break;
+ }
+ currentLineStart += lineLength + 1; // +1 для символа новой строки
+ }
+
+ // Проверяем, является ли текущая строка списком
+ const listPatterns = [
+ /^(\s*)- \[ \] /, // Чекбокс (не отмечен): - [ ]
+ /^(\s*)- \[x\] /i, // Чекбокс (отмечен): - [x]
+ /^(\s*)- /, // Неупорядоченный список: -
+ /^(\s*)\* /, // Неупорядоченный список: *
+ /^(\s*)\+ /, // Неупорядоченный список: +
+ /^(\s*)(\d+)\. /, // Упорядоченный список: 1. 2. 3.
+ /^(\s*)(\w+)\. /, // Буквенный список: a. b. c.
+ ];
+
+ let listMatch = null;
+ let listType = null;
+
+ for (const pattern of listPatterns) {
+ const match = currentLine.match(pattern);
+ if (match) {
+ listMatch = match;
+ if (pattern === listPatterns[0] || pattern === listPatterns[1]) {
+ listType = "checkbox";
+ } else if (
+ pattern === listPatterns[2] ||
+ pattern === listPatterns[3] ||
+ pattern === listPatterns[4]
+ ) {
+ listType = "unordered";
+ } else {
+ listType = "ordered";
+ }
+ break;
+ }
+ }
+
+ if (listMatch) {
+ event.preventDefault();
+
+ const indent = listMatch[1] || ""; // Отступы перед маркером
+ const marker = listMatch[0].slice(indent.length); // Маркер списка без отступов
+
+ // Получаем текст после маркера
+ const afterMarker = currentLine.slice(listMatch[0].length);
+
+ if (afterMarker.trim() === "") {
+ // Если строка пустая после маркера, выходим из списка
+ const beforeCursor = text.substring(0, start);
+ const afterCursor = text.substring(start);
+
+ // Удаляем маркер и отступы текущей строки
+ const newBefore = beforeCursor.replace(
+ /\n\s*- \[ \] \s*$|\n\s*- \[x\] \s*$|\n\s*[-*+]\s*$|\n\s*\d+\.\s*$|\n\s*\w+\.\s*$/i,
+ "\n"
+ );
+ textarea.value = newBefore + afterCursor;
+
+ // Устанавливаем курсор после удаленного маркера
+ const newCursorPos = newBefore.length;
+ textarea.setSelectionRange(newCursorPos, newCursorPos);
+ } else {
+ // Продолжаем список
+ const beforeCursor = text.substring(0, start);
+ const afterCursor = text.substring(start);
+
+ let newMarker = "";
+ if (listType === "checkbox") {
+ // Для чекбоксов всегда создаем новый пустой чекбокс
+ newMarker = indent + "- [ ] ";
+ } else if (listType === "unordered") {
+ newMarker = indent + marker;
+ } else if (listType === "ordered") {
+ // Для упорядоченных списков увеличиваем номер
+ const number = parseInt(listMatch[2]);
+ const nextNumber = number + 1;
+ const numberStr = listMatch[2].replace(
+ /\d+/,
+ nextNumber.toString()
+ );
+ newMarker = indent + numberStr + ". ";
+ }
+
+ textarea.value = beforeCursor + "\n" + newMarker + afterCursor;
+
+ // Устанавливаем курсор после нового маркера
+ const newCursorPos = start + 1 + newMarker.length;
+ textarea.setSelectionRange(newCursorPos, newCursorPos);
+ }
+
+ // Обновляем высоту textarea
+ autoExpandTextarea(textarea);
+ }
+ }
+ });
+
// Создаем элементы для загрузки изображений в режиме редактирования
const editImageInput = document.createElement("input");
editImageInput.type = "file";
@@ -1109,7 +1412,6 @@ function addNoteEventListeners() {
const saveEditNote = async function () {
if (textarea.value.trim() !== "" || editSelectedImages.length > 0) {
try {
- const { date, time } = getFormattedDateTime();
const response = await fetch(`/api/notes/${noteId}`, {
method: "PUT",
headers: {
@@ -1117,8 +1419,6 @@ function addNoteEventListeners() {
},
body: JSON.stringify({
content: textarea.value || " ", // Минимальный контент, если только изображения
- date: date,
- time: time,
}),
});
@@ -1190,6 +1490,9 @@ function addNoteEventListeners() {
if (button.tag === "image") {
// Для кнопки изображения открываем диалог выбора файлов
editImageInput.click();
+ } else if (button.tag === "color") {
+ // Для кнопки цвета открываем диалог выбора цвета
+ insertColorTagForEdit(textarea);
} else {
insertMarkdownForEdit(textarea, button.tag);
}
@@ -1284,6 +1587,142 @@ function addImageEventListeners() {
});
}
+// Функция для применения визуальных стилей к чекбоксу
+function applyCheckboxStyles(checkbox, checked) {
+ const parentLi = checkbox.closest("li");
+ if (!parentLi) return;
+
+ if (checked) {
+ parentLi.style.opacity = "0.65";
+ // Находим все элементы кроме чекбокса
+ const textNodes = parentLi.querySelectorAll("*:not(input)");
+ textNodes.forEach((node) => {
+ node.style.textDecoration = "line-through";
+ node.style.color = "#999";
+ });
+ // Обрабатываем текстовые узлы
+ Array.from(parentLi.childNodes).forEach((node) => {
+ if (node.nodeType === 3 && node.textContent.trim()) {
+ const span = document.createElement("span");
+ span.style.textDecoration = "line-through";
+ span.style.color = "#999";
+ span.textContent = node.textContent;
+ parentLi.replaceChild(span, node);
+ }
+ });
+ } else {
+ parentLi.style.opacity = "1";
+ const textNodes = parentLi.querySelectorAll("*:not(input)");
+ textNodes.forEach((node) => {
+ node.style.textDecoration = "none";
+ node.style.color = "";
+ });
+ }
+}
+
+// Функция для добавления обработчиков для чекбоксов в заметках
+function addCheckboxEventListeners() {
+ // Находим все чекбоксы в заметках
+ document
+ .querySelectorAll(".textNote input[type='checkbox']")
+ .forEach((checkbox) => {
+ // Применяем начальные стили для уже отмеченных чекбоксов
+ if (checkbox.checked) {
+ applyCheckboxStyles(checkbox, true);
+ }
+
+ // Удаляем старые обработчики, если они есть
+ if (checkbox._checkboxHandler) {
+ checkbox.removeEventListener("change", checkbox._checkboxHandler);
+ }
+
+ // Создаем новый обработчик
+ checkbox._checkboxHandler = async function (event) {
+ // Применяем визуальные эффекты сразу
+ applyCheckboxStyles(this, this.checked);
+
+ // Находим родительскую заметку
+ const noteContainer = this.closest("#note");
+ if (!noteContainer) return;
+
+ const noteId = noteContainer.dataset.noteId;
+ const textNoteElement = noteContainer.querySelector(".textNote");
+
+ // Получаем исходный markdown контент
+ let originalContent = textNoteElement.dataset.originalContent;
+
+ if (!originalContent) return;
+
+ // Находим индекс чекбокса в списке всех чекбоксов этой заметки
+ const allCheckboxes = textNoteElement.querySelectorAll(
+ "input[type='checkbox']"
+ );
+ const checkboxIndex = Array.from(allCheckboxes).indexOf(this);
+
+ // Находим все чекбоксы в markdown контенте
+ const checkboxRegex = /- \[([ x])\]/gi;
+ let matches = [];
+ let match;
+ while ((match = checkboxRegex.exec(originalContent)) !== null) {
+ matches.push({
+ index: match.index,
+ checked: match[1].toLowerCase() === "x",
+ fullMatch: match[0],
+ });
+ }
+
+ // Если нашли соответствующий чекбокс, изменяем его состояние
+ if (matches[checkboxIndex]) {
+ const targetMatch = matches[checkboxIndex];
+ const newState = this.checked ? "- [x]" : "- [ ]";
+
+ // Заменяем состояние чекбокса в исходном тексте
+ const beforeCheckbox = originalContent.substring(
+ 0,
+ targetMatch.index
+ );
+ const afterCheckbox = originalContent.substring(
+ targetMatch.index + targetMatch.fullMatch.length
+ );
+ originalContent = beforeCheckbox + newState + afterCheckbox;
+
+ // Обновляем data-атрибут
+ textNoteElement.dataset.originalContent = originalContent;
+
+ // Автоматически сохраняем изменения на сервере
+ try {
+ const response = await fetch(`/api/notes/${noteId}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ content: originalContent,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Ошибка сохранения заметки");
+ }
+
+ // Обновляем кэш заметок
+ const noteInCache = allNotes.find((n) => n.id == noteId);
+ if (noteInCache) {
+ noteInCache.content = originalContent;
+ }
+ } catch (error) {
+ console.error("Ошибка автосохранения чекбокса:", error);
+ // Если не удалось сохранить, возвращаем чекбокс в прежнее состояние
+ this.checked = !this.checked;
+ alert("Ошибка сохранения изменений");
+ }
+ }
+ };
+
+ checkbox.addEventListener("change", checkbox._checkboxHandler);
+ });
+}
+
// Функция сохранения заметки (вынесена отдельно для повторного использования)
async function saveNote() {
if (noteInput.value.trim() !== "" || selectedImages.length > 0) {
@@ -1409,6 +1848,114 @@ noteInput.addEventListener("keydown", function (event) {
if (event.altKey && event.key === "Enter") {
event.preventDefault(); // Предотвращаем стандартное поведение
saveNote();
+ } else if (event.key === "Enter") {
+ // Автоматическое продолжение списков
+ const textarea = event.target;
+ const start = textarea.selectionStart;
+ const text = textarea.value;
+ const lines = text.split("\n");
+
+ // Определяем текущую строку
+ let currentLineIndex = 0;
+ let currentLineStart = 0;
+ let currentLine = "";
+
+ for (let i = 0; i < lines.length; i++) {
+ const lineLength = lines[i].length;
+ if (currentLineStart + lineLength >= start) {
+ currentLineIndex = i;
+ currentLine = lines[i];
+ break;
+ }
+ currentLineStart += lineLength + 1; // +1 для символа новой строки
+ }
+
+ // Проверяем, является ли текущая строка списком
+ const listPatterns = [
+ /^(\s*)- \[ \] /, // Чекбокс (не отмечен): - [ ]
+ /^(\s*)- \[x\] /i, // Чекбокс (отмечен): - [x]
+ /^(\s*)- /, // Неупорядоченный список: -
+ /^(\s*)\* /, // Неупорядоченный список: *
+ /^(\s*)\+ /, // Неупорядоченный список: +
+ /^(\s*)(\d+)\. /, // Упорядоченный список: 1. 2. 3.
+ /^(\s*)(\w+)\. /, // Буквенный список: a. b. c.
+ ];
+
+ let listMatch = null;
+ let listType = null;
+
+ for (const pattern of listPatterns) {
+ const match = currentLine.match(pattern);
+ if (match) {
+ listMatch = match;
+ if (pattern === listPatterns[0] || pattern === listPatterns[1]) {
+ listType = "checkbox";
+ } else if (
+ pattern === listPatterns[2] ||
+ pattern === listPatterns[3] ||
+ pattern === listPatterns[4]
+ ) {
+ listType = "unordered";
+ } else {
+ listType = "ordered";
+ }
+ break;
+ }
+ }
+
+ if (listMatch) {
+ event.preventDefault();
+
+ const indent = listMatch[1] || ""; // Отступы перед маркером
+ const marker = listMatch[0].slice(indent.length); // Маркер списка без отступов
+
+ // Получаем текст после маркера
+ const afterMarker = currentLine.slice(listMatch[0].length);
+
+ if (afterMarker.trim() === "") {
+ // Если строка пустая после маркера, выходим из списка
+ const beforeCursor = text.substring(0, start);
+ const afterCursor = text.substring(start);
+
+ // Удаляем маркер и отступы текущей строки
+ const newBefore = beforeCursor.replace(
+ /\n\s*- \[ \] \s*$|\n\s*- \[x\] \s*$|\n\s*[-*+]\s*$|\n\s*\d+\.\s*$|\n\s*\w+\.\s*$/i,
+ "\n"
+ );
+ textarea.value = newBefore + afterCursor;
+
+ // Устанавливаем курсор после удаленного маркера
+ const newCursorPos = newBefore.length;
+ textarea.setSelectionRange(newCursorPos, newCursorPos);
+ } else {
+ // Продолжаем список
+ const beforeCursor = text.substring(0, start);
+ const afterCursor = text.substring(start);
+
+ let newMarker = "";
+ if (listType === "checkbox") {
+ // Для чекбоксов всегда создаем новый пустой чекбокс
+ newMarker = indent + "- [ ] ";
+ } else if (listType === "unordered") {
+ newMarker = indent + marker;
+ } else if (listType === "ordered") {
+ // Для упорядоченных списков увеличиваем номер
+ const number = parseInt(listMatch[2]);
+ const nextNumber = number + 1;
+ const numberStr = listMatch[2].replace(/\d+/, nextNumber.toString());
+ newMarker = indent + numberStr + ". ";
+ }
+
+ textarea.value = beforeCursor + "\n" + newMarker + afterCursor;
+
+ // Устанавливаем курсор после нового маркера
+ const newCursorPos = start + 1 + newMarker.length;
+ textarea.setSelectionRange(newCursorPos, newCursorPos);
+ }
+
+ // Обновляем высоту textarea
+ autoExpandTextarea(textarea);
+ }
}
});
diff --git a/public/notes.html b/public/notes.html
index 89353b7..52c9335 100644
--- a/public/notes.html
+++ b/public/notes.html
@@ -258,6 +258,9 @@
+
@@ -273,6 +276,12 @@
+