diff --git a/public/app.js b/public/app.js
index 3fecf47..1dc0199 100644
--- a/public/app.js
+++ b/public/app.js
@@ -9,6 +9,7 @@ const italicBtn = document.getElementById("italicBtn");
const colorBtn = document.getElementById("colorBtn");
const headerBtn = document.getElementById("headerBtn");
const listBtn = document.getElementById("listBtn");
+const numberedListBtn = document.getElementById("numberedListBtn");
const quoteBtn = document.getElementById("quoteBtn");
const codeBtn = document.getElementById("codeBtn");
const linkBtn = document.getElementById("linkBtn");
@@ -288,6 +289,20 @@ function insertMarkdown(tag) {
const selected = text.substring(start, end);
const after = text.substring(end);
+ // Мультистрочные преобразования списков (toggle)
+ if (
+ (tag === "1. " || tag === "- " || tag === "- [ ] ") &&
+ selected.includes("\n")
+ ) {
+ const mode =
+ tag === "1. " ? "ordered" : tag === "- " ? "unordered" : "todo";
+ const transformed = transformSelection(noteInput, mode);
+ noteInput.value = transformed.newValue;
+ noteInput.setSelectionRange(transformed.newSelStart, transformed.newSelEnd);
+ noteInput.focus();
+ return;
+ }
+
if (selected.startsWith(tag) && selected.endsWith(tag)) {
// Если теги уже есть, удаляем их
noteInput.value = `${before}${selected.slice(
@@ -304,6 +319,7 @@ function insertMarkdown(tag) {
noteInput.setSelectionRange(cursorPosition, cursorPosition + 12);
} else if (
tag === "- " ||
+ tag === "1. " ||
tag === "> " ||
tag === "# " ||
tag === "- [ ] "
@@ -327,6 +343,7 @@ function insertMarkdown(tag) {
noteInput.setSelectionRange(cursorPosition, cursorPosition + 3);
} else if (
tag === "- " ||
+ tag === "1. " ||
tag === "> " ||
tag === "# " ||
tag === "- [ ] "
@@ -408,6 +425,20 @@ function insertMarkdownForEdit(textarea, tag) {
const selected = text.substring(start, end);
const after = text.substring(end);
+ // Мультистрочные преобразования списков (toggle)
+ if (
+ (tag === "1. " || tag === "- " || tag === "- [ ] ") &&
+ selected.includes("\n")
+ ) {
+ const mode =
+ tag === "1. " ? "ordered" : tag === "- " ? "unordered" : "todo";
+ const transformed = transformSelection(textarea, mode);
+ textarea.value = transformed.newValue;
+ textarea.setSelectionRange(transformed.newSelStart, transformed.newSelEnd);
+ textarea.focus();
+ return;
+ }
+
if (selected.startsWith(tag) && selected.endsWith(tag)) {
// Если теги уже есть, удаляем их
textarea.value = `${before}${selected.slice(
@@ -424,6 +455,7 @@ function insertMarkdownForEdit(textarea, tag) {
textarea.setSelectionRange(cursorPosition, cursorPosition + 12);
} else if (
tag === "- " ||
+ tag === "1. " ||
tag === "> " ||
tag === "# " ||
tag === "- [ ] "
@@ -447,6 +479,7 @@ function insertMarkdownForEdit(textarea, tag) {
textarea.setSelectionRange(cursorPosition, cursorPosition + 3);
} else if (
tag === "- " ||
+ tag === "1. " ||
tag === "> " ||
tag === "# " ||
tag === "- [ ] "
@@ -466,6 +499,109 @@ function insertMarkdownForEdit(textarea, tag) {
textarea.focus();
}
+// ==================== МУЛЬТИСТРОЧНЫЕ СПИСКИ (TOGGLE) ====================
+function transformSelection(textarea, mode) {
+ const fullText = textarea.value;
+ const selStart = textarea.selectionStart;
+ const selEnd = textarea.selectionEnd;
+
+ // Расширяем до границ строк
+ let blockStart = fullText.lastIndexOf("\n", selStart - 1);
+ blockStart = blockStart === -1 ? 0 : blockStart + 1;
+ let blockEnd = fullText.indexOf("\n", selEnd);
+ blockEnd = blockEnd === -1 ? fullText.length : blockEnd;
+
+ const block = fullText.substring(blockStart, blockEnd);
+ const lines = block.split("\n");
+
+ const orderedRe = /^(\s*)(\d+)\.\s/;
+ const unorderedRe = /^(\s*)([-*+])\s/;
+ const todoRe = /^(\s*)- \[( |x)\] \s?/i;
+ const anyListPrefixRe = /^(\s*)(- \[(?: |x)\]\s?|[-*+]\s|\d+\.\s)/i;
+
+ function stripAnyPrefix(line) {
+ const m = line.match(anyListPrefixRe);
+ if (!m) return line;
+ return line.slice((m[1] + (m[2] || "")).length);
+ }
+
+ function toggleOrdered(inputLines) {
+ const nonEmpty = inputLines.filter((l) => l.trim() !== "");
+ const allHave =
+ nonEmpty.length > 0 && nonEmpty.every((l) => orderedRe.test(l));
+ if (allHave) {
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ const m = l.match(orderedRe);
+ if (!m) return l;
+ return m[1] + l.slice(m[0].length);
+ });
+ }
+ let index = 1;
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ // Снимаем любые префиксы и нумеруем заново
+ const indent = l.match(/^\s*/)?.[0] || "";
+ const content = stripAnyPrefix(l.trimStart());
+ const numbered = `${indent}${index}. ${content}`;
+ index += 1;
+ return numbered;
+ });
+ }
+
+ function toggleUnordered(inputLines) {
+ const nonEmpty = inputLines.filter((l) => l.trim() !== "");
+ const allHave =
+ nonEmpty.length > 0 && nonEmpty.every((l) => unorderedRe.test(l));
+ if (allHave) {
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ const m = l.match(unorderedRe);
+ if (!m) return l;
+ return m[1] + l.slice(m[0].length);
+ });
+ }
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ const indent = l.match(/^\s*/)?.[0] || "";
+ const content = stripAnyPrefix(l.trimStart());
+ return `${indent}- ${content}`;
+ });
+ }
+
+ function toggleTodo(inputLines) {
+ const nonEmpty = inputLines.filter((l) => l.trim() !== "");
+ const allHave =
+ nonEmpty.length > 0 && nonEmpty.every((l) => todoRe.test(l));
+ if (allHave) {
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ const m = l.match(todoRe);
+ if (!m) return l;
+ return m[1] + l.slice(m[0].length);
+ });
+ }
+ return inputLines.map((l) => {
+ if (!l.trim()) return l;
+ const indent = l.match(/^\s*/)?.[0] || "";
+ const content = stripAnyPrefix(l.trimStart());
+ return `${indent}- [ ] ${content}`;
+ });
+ }
+
+ let newLines;
+ if (mode === "ordered") newLines = toggleOrdered(lines);
+ else if (mode === "unordered") newLines = toggleUnordered(lines);
+ else newLines = toggleTodo(lines);
+
+ const newBlock = newLines.join("\n");
+ const newValue =
+ fullText.slice(0, blockStart) + newBlock + fullText.slice(blockEnd);
+ const newSelStart = blockStart;
+ const newSelEnd = blockStart + newBlock.length;
+ return { newValue, newSelStart, newSelEnd };
+}
+
// Обработчики для кнопок markdown
boldBtn.addEventListener("click", function () {
insertMarkdown("**");
@@ -487,6 +623,10 @@ listBtn.addEventListener("click", function () {
insertMarkdown("- ");
});
+numberedListBtn.addEventListener("click", function () {
+ insertMarkdown("1. ");
+});
+
quoteBtn.addEventListener("click", function () {
insertMarkdown("> ");
});
@@ -934,9 +1074,12 @@ async function renderNotes(notes) {
let notesToDisplay = notes;
if (selectedDateFilter) {
- notesToDisplay = notesToDisplay.filter(
- (note) => note.date === selectedDateFilter
- );
+ notesToDisplay = notesToDisplay.filter((note) => {
+ if (note.created_at) {
+ return formatDateFromTimestamp(note.created_at) === selectedDateFilter;
+ }
+ return false;
+ });
}
if (selectedTagFilter) {
@@ -993,10 +1136,9 @@ 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);
@@ -1011,19 +1153,19 @@ async function renderNotes(notes) {
dateDisplay = `Создано: ${formatDate(
createdDate
- )}
Изменено: ${formatDate(updatedDate)}`;
+ )} • Изменено: ${formatDate(updatedDate)}`;
}
const noteHtml = `