✨ Добавлена поддержка спойлеров в редактор заметок и улучшены индексы базы данных
- Реализована функция вставки спойлеров в режиме редактирования заметок. - Обновлены стили для кнопок markdown в редакторе. - Добавлен новый индекс для колонки `pinned_at` в таблице `notes`. - Обновлены SQL-запросы для сортировки заметок с учетом нового поля `pinned_at`.
This commit is contained in:
parent
1479205261
commit
372cea2e92
6
.cursor/rules/rules.mdc
Normal file
6
.cursor/rules/rules.mdc
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
Не создавай диагностические страницы и документации!
|
||||
Если добавляешь новые функции или что-то редактируешь в редакторе создания заметок, то добавляй их и в редактор редактирования!
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -34,8 +34,8 @@ Thumbs.db
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Cursor IDE
|
||||
.cursor/
|
||||
# # Cursor IDE
|
||||
# .cursor/
|
||||
|
||||
# Загруженные файлы пользователей
|
||||
public/uploads/
|
||||
|
||||
@ -932,6 +932,32 @@ function insertMarkdownForEdit(textarea, tag) {
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
// Функция для вставки спойлера в режиме редактирования
|
||||
function insertSpoilerForEdit(textarea) {
|
||||
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 newText;
|
||||
let newCursorPos;
|
||||
|
||||
if (selected) {
|
||||
newText = before + "||" + selected + "||" + after;
|
||||
newCursorPos = start + selected.length + 4;
|
||||
} else {
|
||||
newText = before + "||скрытый текст||" + after;
|
||||
newCursorPos = start + 2;
|
||||
}
|
||||
|
||||
textarea.value = newText;
|
||||
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
// ==================== МУЛЬТИСТРОЧНЫЕ СПИСКИ (TOGGLE) ====================
|
||||
function transformSelection(textarea, mode) {
|
||||
const fullText = textarea.value;
|
||||
@ -2118,7 +2144,10 @@ function addNoteEventListeners() {
|
||||
|
||||
// Создаем контейнер для markdown кнопок
|
||||
const markdownButtonsContainer = document.createElement("div");
|
||||
markdownButtonsContainer.classList.add("markdown-buttons");
|
||||
markdownButtonsContainer.classList.add(
|
||||
"markdown-buttons",
|
||||
"markdown-buttons--edit"
|
||||
);
|
||||
|
||||
// Создаем markdown кнопки
|
||||
const markdownButtons = [
|
||||
@ -2130,6 +2159,7 @@ function addNoteEventListeners() {
|
||||
tag: "~~",
|
||||
},
|
||||
{ id: "editColorBtn", icon: "mdi:palette", tag: "color" },
|
||||
{ id: "editSpoilerBtn", icon: "mdi:eye-off", tag: "spoiler" },
|
||||
{ id: "editHeaderBtn", icon: "mdi:format-header-pound", tag: "header" },
|
||||
{ id: "editListBtn", icon: "mdi:format-list-bulleted", tag: "- " },
|
||||
{
|
||||
@ -2589,6 +2619,9 @@ function addNoteEventListeners() {
|
||||
console.error("Ошибка:", error);
|
||||
showNotification("Ошибка сохранения заметки", "error");
|
||||
}
|
||||
} else {
|
||||
showNotification("Введите текст заметки", "warning");
|
||||
textarea.focus();
|
||||
}
|
||||
};
|
||||
|
||||
@ -2711,6 +2744,9 @@ function addNoteEventListeners() {
|
||||
} else if (button.tag === "color") {
|
||||
// Для кнопки цвета открываем диалог выбора цвета
|
||||
insertColorTagForEdit(textarea);
|
||||
} else if (button.tag === "spoiler") {
|
||||
// Вставка спойлера в режиме редактирования
|
||||
insertSpoilerForEdit(textarea);
|
||||
} else if (button.tag === "preview") {
|
||||
// Для кнопки предпросмотра переключаем режим
|
||||
isEditPreviewMode = !isEditPreviewMode;
|
||||
@ -2734,6 +2770,9 @@ function addNoteEventListeners() {
|
||||
editPreviewContent
|
||||
);
|
||||
|
||||
// Добавляем обработчики спойлеров внутри предпросмотра редактирования
|
||||
addSpoilerEventListeners();
|
||||
|
||||
// Инициализируем lazy loading для изображений в превью
|
||||
setTimeout(() => {
|
||||
initLazyLoading();
|
||||
@ -3045,8 +3084,12 @@ function addSpoilerEventListeners() {
|
||||
|
||||
// Создаем новый обработчик
|
||||
spoiler._clickHandler = function (event) {
|
||||
// Если уже раскрыт — не мешаем выделению текста
|
||||
if (this.classList.contains("revealed")) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
this.classList.toggle("revealed");
|
||||
this.classList.add("revealed");
|
||||
console.log("Спойлер кликнут:", this.textContent);
|
||||
};
|
||||
|
||||
@ -3179,6 +3222,9 @@ async function saveNote() {
|
||||
|
||||
showNotification("Ошибка сохранения заметки", "error");
|
||||
}
|
||||
} else {
|
||||
showNotification("Введите текст заметки", "warning");
|
||||
noteInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,8 +75,12 @@ function addSpoilerEventListeners() {
|
||||
|
||||
// Создаем новый обработчик
|
||||
spoiler._clickHandler = function (event) {
|
||||
// Если уже раскрыт — не мешаем выделению текста
|
||||
if (this.classList.contains("revealed")) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
this.classList.toggle("revealed");
|
||||
this.classList.add("revealed");
|
||||
console.log("Спойлер кликнут:", this.textContent);
|
||||
};
|
||||
|
||||
|
||||
@ -1087,6 +1087,45 @@ textarea:focus {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Кнопки markdown в редакторе редактирования: те же отступы и поведение */
|
||||
.markdown-buttons.markdown-buttons--edit {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.markdown-buttons.markdown-buttons--edit .btnMarkdown {
|
||||
padding: 5px 10px;
|
||||
margin-right: 5px;
|
||||
border: 1px solid var(--border-secondary);
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.markdown-buttons.markdown-buttons--edit .btnMarkdown:hover {
|
||||
background-color: var(--bg-quaternary);
|
||||
border-color: var(--border-focus);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.markdown-buttons.markdown-buttons--edit .btnMarkdown {
|
||||
min-height: 48px;
|
||||
padding: 8px 12px;
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-buttons .btnMarkdown {
|
||||
padding: 5px 10px;
|
||||
margin-right: 5px;
|
||||
@ -3320,6 +3359,9 @@ textarea:focus {
|
||||
border-color: #c3e6cb;
|
||||
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.25);
|
||||
text-shadow: none;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.spoiler.revealed::before {
|
||||
|
||||
27
server.js
27
server.js
@ -266,6 +266,7 @@ function createIndexes() {
|
||||
"CREATE INDEX IF NOT EXISTS idx_notes_date ON notes(date)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_notes_is_pinned ON notes(is_pinned)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_notes_is_archived ON notes(is_archived)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_notes_pinned_at ON notes(pinned_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_note_images_note_id ON note_images(note_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_action_logs_user_id ON action_logs(user_id)",
|
||||
@ -510,6 +511,7 @@ function runMigrations() {
|
||||
const hasUpdatedAt = columns.some((col) => col.name === "updated_at");
|
||||
const hasPinned = columns.some((col) => col.name === "is_pinned");
|
||||
const hasArchived = columns.some((col) => col.name === "is_archived");
|
||||
const hasPinnedAt = columns.some((col) => col.name === "pinned_at");
|
||||
|
||||
// Добавляем updated_at если нужно
|
||||
if (!hasUpdatedAt) {
|
||||
@ -564,6 +566,17 @@ function runMigrations() {
|
||||
);
|
||||
}
|
||||
|
||||
// Добавляем pinned_at если нужно
|
||||
if (!hasPinnedAt) {
|
||||
db.run("ALTER TABLE notes ADD COLUMN pinned_at DATETIME", (err) => {
|
||||
if (err) {
|
||||
console.error("Ошибка добавления колонки pinned_at:", err.message);
|
||||
} else {
|
||||
console.log("Колонка pinned_at добавлена в таблицу notes");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Создаем индексы после всех изменений
|
||||
if (hasUpdatedAt && hasPinned && hasArchived) {
|
||||
createIndexes();
|
||||
@ -870,7 +883,7 @@ app.get("/api/notes/search", requireApiAuth, (req, res) => {
|
||||
LEFT JOIN note_images ni ON n.id = ni.note_id
|
||||
${whereClause}
|
||||
GROUP BY n.id
|
||||
ORDER BY n.created_at DESC
|
||||
ORDER BY n.is_pinned DESC, n.pinned_at DESC, n.created_at DESC
|
||||
`;
|
||||
|
||||
db.all(sql, params, (err, rows) => {
|
||||
@ -912,7 +925,7 @@ app.get("/api/notes", requireApiAuth, (req, res) => {
|
||||
LEFT JOIN note_images ni ON n.id = ni.note_id
|
||||
WHERE n.user_id = ? AND n.is_archived = 0
|
||||
GROUP BY n.id
|
||||
ORDER BY n.is_pinned DESC, n.created_at DESC
|
||||
ORDER BY n.is_pinned DESC, n.pinned_at DESC, n.created_at DESC
|
||||
`;
|
||||
|
||||
db.all(sql, [req.session.userId], (err, rows) => {
|
||||
@ -1519,9 +1532,11 @@ app.put("/api/notes/:id/pin", requireApiAuth, (req, res) => {
|
||||
}
|
||||
|
||||
const newPinState = row.is_pinned ? 0 : 1;
|
||||
const updateSql = "UPDATE notes SET is_pinned = ? WHERE id = ?";
|
||||
const updateSql = newPinState
|
||||
? "UPDATE notes SET is_pinned = 1, pinned_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
: "UPDATE notes SET is_pinned = 0, pinned_at = NULL WHERE id = ?";
|
||||
|
||||
db.run(updateSql, [newPinState, id], function (err) {
|
||||
db.run(updateSql, [id], function (err) {
|
||||
if (err) {
|
||||
console.error("Ошибка изменения закрепления:", err.message);
|
||||
return res.status(500).json({ error: "Ошибка сервера" });
|
||||
@ -1563,7 +1578,7 @@ app.put("/api/notes/:id/archive", requireApiAuth, (req, res) => {
|
||||
}
|
||||
|
||||
const updateSql =
|
||||
"UPDATE notes SET is_archived = 1, is_pinned = 0 WHERE id = ?";
|
||||
"UPDATE notes SET is_archived = 1, is_pinned = 0, pinned_at = NULL WHERE id = ?";
|
||||
|
||||
db.run(updateSql, [id], function (err) {
|
||||
if (err) {
|
||||
@ -1650,7 +1665,7 @@ app.get("/api/notes/archived", requireApiAuth, (req, res) => {
|
||||
LEFT JOIN note_images ni ON n.id = ni.note_id
|
||||
WHERE n.user_id = ? AND n.is_archived = 1
|
||||
GROUP BY n.id
|
||||
ORDER BY n.created_at DESC
|
||||
ORDER BY n.is_pinned DESC, n.pinned_at DESC, n.created_at DESC
|
||||
`;
|
||||
|
||||
db.all(sql, [req.session.userId], (err, rows) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user