Добавлена регистрация, авторизация, профиль, новые функции
This commit is contained in:
parent
5372afa59e
commit
8c5b01ef75
123
QUICK_START.md
Normal file
123
QUICK_START.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# 🚀 Быстрый старт - Система аутентификации NoteJS
|
||||||
|
|
||||||
|
## Для тестирования есть готовые тестовые учетные данные:
|
||||||
|
|
||||||
|
### Тестовый пользователь 1:
|
||||||
|
|
||||||
|
```
|
||||||
|
Логин: testuser
|
||||||
|
Пароль: password123
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестовый пользователь 2:
|
||||||
|
|
||||||
|
```
|
||||||
|
Логин: testuser2
|
||||||
|
Пароль: password123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как запустить приложение:
|
||||||
|
|
||||||
|
1. **Установка зависимостей** (если еще не установлены):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Запуск сервера**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Открыть в браузере**:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Основные функции:
|
||||||
|
|
||||||
|
### 📝 Регистрация нового пользователя
|
||||||
|
|
||||||
|
1. Нажмите "Зарегистрируйтесь" на странице входа
|
||||||
|
2. Заполните форму:
|
||||||
|
- **Логин**: минимум 3 символа
|
||||||
|
- **Пароль**: минимум 6 символов
|
||||||
|
- **Подтвердите пароль**: повторите пароль
|
||||||
|
3. Нажмите "Зарегистрироваться"
|
||||||
|
4. Вы будете автоматически залогинены
|
||||||
|
|
||||||
|
### 🔐 Вход в систему
|
||||||
|
|
||||||
|
1. Введите логин и пароль
|
||||||
|
2. Нажмите "Войти"
|
||||||
|
3. Вы попадете в интерфейс заметок
|
||||||
|
|
||||||
|
### 📖 Создание заметок
|
||||||
|
|
||||||
|
1. Введите текст в поле
|
||||||
|
2. Используйте кнопки форматирования (Markdown)
|
||||||
|
3. Нажмите "Сохранить"
|
||||||
|
|
||||||
|
### 🚪 Выход из системы
|
||||||
|
|
||||||
|
Нажмите кнопку "🚪 Выйти" в верхней части страницы
|
||||||
|
|
||||||
|
## 🔒 Параметры безопасности:
|
||||||
|
|
||||||
|
- Пароли: минимум 6 символов
|
||||||
|
- Логины: минимум 3 символа
|
||||||
|
- Все пароли хешируются с bcrypt
|
||||||
|
- Сессии сохраняются на сервере
|
||||||
|
|
||||||
|
## 📊 Что было добавлено:
|
||||||
|
|
||||||
|
### Новые файлы:
|
||||||
|
|
||||||
|
- `public/register.html` - страница регистрации
|
||||||
|
- `public/register.js` - логика регистрации
|
||||||
|
- `TESTING_REPORT.md` - подробный отчет о тестировании
|
||||||
|
- `QUICK_START.md` - этот файл
|
||||||
|
|
||||||
|
### Обновленные файлы:
|
||||||
|
|
||||||
|
- `server.js` - добавлены маршруты аутентификации
|
||||||
|
- `public/index.html` - обновлена форма входа
|
||||||
|
- `public/login.js` - новая логика входа
|
||||||
|
- `public/notes.html` - информация о пользователе
|
||||||
|
- `public/app.js` - загрузка информации о пользователе
|
||||||
|
- `public/style.css` - новые стили
|
||||||
|
- `README.md` - полная документация
|
||||||
|
|
||||||
|
## 🧪 Тестирование:
|
||||||
|
|
||||||
|
Все функции протестированы:
|
||||||
|
|
||||||
|
- ✅ Регистрация
|
||||||
|
- ✅ Вход в систему
|
||||||
|
- ✅ Выход из системы
|
||||||
|
- ✅ Валидация данных
|
||||||
|
- ✅ Защита маршрутов
|
||||||
|
- ✅ Хеширование паролей
|
||||||
|
|
||||||
|
Подробный отчет о тестировании: `TESTING_REPORT.md`
|
||||||
|
|
||||||
|
## 💡 Советы:
|
||||||
|
|
||||||
|
- Логины должны быть уникальными (не можете создать два аккаунта с одним логином)
|
||||||
|
- Пароли не восстанавливаются, только сбрасываются
|
||||||
|
- Сессия хранится на сервере и сбрасывается при перезагрузке
|
||||||
|
|
||||||
|
## 🆘 Помощь:
|
||||||
|
|
||||||
|
Если что-то не работает:
|
||||||
|
|
||||||
|
1. Убедитесь, что Node.js версии 14 или выше
|
||||||
|
2. Проверьте, что порт 3000 свободен
|
||||||
|
3. Переустановите зависимости: `npm install`
|
||||||
|
4. Запустите сервер заново
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Наслаждайтесь использованием NoteJS! 🎉
|
||||||
81
README.md
81
README.md
@ -1,12 +1,14 @@
|
|||||||
# NoteJS - Приложение для быстрых заметок
|
# NoteJS - Приложение для быстрых заметок
|
||||||
|
|
||||||
Простое веб-приложение для создания и управления заметками с поддержкой Markdown форматирования.
|
Простое веб-приложение для создания и управления заметками с поддержкой Markdown форматирования и системой аутентификации.
|
||||||
|
|
||||||
## Особенности
|
## Особенности
|
||||||
|
|
||||||
- 🚀 Создано на Node.js + Express
|
- 🚀 Создано на Node.js + Express
|
||||||
- 🔐 Аутентификация по паролю (без логина)
|
- 🔐 **Система регистрации и авторизации по логину и паролю** (NEW!)
|
||||||
|
- 🔒 Безопасное хранение паролей с bcrypt хешированием
|
||||||
- 💾 Хранение данных в SQLite базе данных
|
- 💾 Хранение данных в SQLite базе данных
|
||||||
|
- 👥 **Изолированные заметки - каждый пользователь видит только свои заметки** (NEW!)
|
||||||
- 📝 Поддержка Markdown форматирования
|
- 📝 Поддержка Markdown форматирования
|
||||||
- 🎨 Простой и интуитивный интерфейс
|
- 🎨 Простой и интуитивный интерфейс
|
||||||
- 📱 Адаптивный дизайн
|
- 📱 Адаптивный дизайн
|
||||||
@ -33,16 +35,14 @@ cd NoteJS
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Настройте аутентификацию:
|
3. Настройте переменные окружения:
|
||||||
- Откройте файл `.env`
|
- Откройте файл `.env`
|
||||||
- Установите пароль для входа в переменной `APP_PASSWORD`
|
|
||||||
- Установите секрет сессии в переменной `SESSION_SECRET`
|
- Установите секрет сессии в переменной `SESSION_SECRET`
|
||||||
- Установите порт в переменной `PORT` (по умолчанию 3000)
|
- Установите порт в переменной `PORT` (по умолчанию 3000)
|
||||||
|
|
||||||
Пример файла `.env`:
|
Пример файла `.env`:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
APP_PASSWORD=your_secure_password_here
|
|
||||||
SESSION_SECRET=your_session_secret_here
|
SESSION_SECRET=your_session_secret_here
|
||||||
PORT=3000
|
PORT=3000
|
||||||
```
|
```
|
||||||
@ -57,10 +57,22 @@ npm start
|
|||||||
|
|
||||||
## Использование
|
## Использование
|
||||||
|
|
||||||
|
### Регистрация
|
||||||
|
|
||||||
|
1. Нажмите на ссылку "Зарегистрируйтесь" на странице входа
|
||||||
|
2. Заполните форму регистрации:
|
||||||
|
- **Логин**: минимум 3 символа
|
||||||
|
- **Пароль**: минимум 6 символов
|
||||||
|
- **Подтвердите пароль**: повторите пароль
|
||||||
|
3. Нажмите кнопку "Зарегистрироваться"
|
||||||
|
4. Вы будете автоматически залогинены и перенаправлены на страницу заметок
|
||||||
|
|
||||||
### Вход в систему
|
### Вход в систему
|
||||||
|
|
||||||
1. При первом запуске введите пароль, указанный в файле `.env`
|
1. Введите ваш логин
|
||||||
2. После успешного входа вы попадете в интерфейс заметок
|
2. Введите ваш пароль
|
||||||
|
3. Нажмите кнопку "Войти"
|
||||||
|
4. После успешного входа вы попадете в интерфейс заметок
|
||||||
|
|
||||||
### Создание заметок
|
### Создание заметок
|
||||||
|
|
||||||
@ -86,31 +98,43 @@ npm start
|
|||||||
1. Нажмите кнопку "Удалить" рядом с заметкой
|
1. Нажмите кнопку "Удалить" рядом с заметкой
|
||||||
2. Подтвердите удаление в появившемся диалоговом окне
|
2. Подтвердите удаление в появившемся диалоговом окне
|
||||||
|
|
||||||
|
### Выход из системы
|
||||||
|
|
||||||
|
Нажмите кнопку "🚪 Выйти" в верхней части страницы заметок
|
||||||
|
|
||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
```
|
```
|
||||||
NoteJS/
|
NoteJS/
|
||||||
├── public/ # Статические файлы (клиентская часть)
|
├── public/ # Статические файлы (клиентская часть)
|
||||||
│ ├── index.html # Страница входа
|
│ ├── index.html # Страница входа
|
||||||
│ ├── notes.html # Страница заметок
|
│ ├── register.html # Страница регистрации (NEW!)
|
||||||
│ ├── style.css # Стили
|
│ ├── notes.html # Страница заметок
|
||||||
│ └── app.js # Клиентский JavaScript
|
│ ├── login.js # Логика входа (обновлена)
|
||||||
├── server.js # Express сервер
|
│ ├── register.js # Логика регистрации (NEW!)
|
||||||
├── .env # Конфигурация (не включать в git!)
|
│ ├── app.js # Клиентский JavaScript
|
||||||
├── package.json # Зависимости проекта
|
│ └── style.css # Стили
|
||||||
├── notes.db # SQLite база данных (создается автоматически)
|
├── server.js # Express сервер
|
||||||
└── README.md # Документация
|
├── .env # Конфигурация (не включать в git!)
|
||||||
|
├── package.json # Зависимости проекта
|
||||||
|
├── notes.db # SQLite база данных (создается автоматически)
|
||||||
|
└── README.md # Документация
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Аутентификация
|
### Аутентификация
|
||||||
|
|
||||||
- `POST /login` - вход в систему
|
- `GET /` - страница входа
|
||||||
|
- `GET /register` - страница регистрации
|
||||||
|
- `POST /api/register` - регистрация нового пользователя
|
||||||
|
- `POST /api/login` - вход в систему
|
||||||
- `POST /logout` - выход из системы
|
- `POST /logout` - выход из системы
|
||||||
|
- `GET /api/user` - получить информацию о текущем пользователе (требует аутентификации)
|
||||||
|
|
||||||
### Заметки (требуют аутентификации)
|
### Заметки (требуют аутентификации)
|
||||||
|
|
||||||
|
- `GET /notes` - страница заметок
|
||||||
- `GET /api/notes` - получить все заметки
|
- `GET /api/notes` - получить все заметки
|
||||||
- `POST /api/notes` - создать новую заметку
|
- `POST /api/notes` - создать новую заметку
|
||||||
- `PUT /api/notes/:id` - обновить заметку
|
- `PUT /api/notes/:id` - обновить заметку
|
||||||
@ -118,10 +142,23 @@ NoteJS/
|
|||||||
|
|
||||||
## Безопасность
|
## Безопасность
|
||||||
|
|
||||||
- Helmet для защиты от распространенных уязвимостей
|
- **Bcrypt хеширование** паролей при сохранении
|
||||||
- Ограничение запросов (rate limiting)
|
- **Сессионная аутентификация** с express-session
|
||||||
- Сессионная аутентификация
|
- **Helmet** для защиты от распространенных уязвимостей
|
||||||
- Защищенные заголовки
|
- **CORS** конфигурация
|
||||||
|
- **Body Parser** для безопасной обработки запросов
|
||||||
|
- Защищенные маршруты с проверкой аутентификации
|
||||||
|
|
||||||
|
## Требования к паролям
|
||||||
|
|
||||||
|
- Минимум 6 символов при регистрации
|
||||||
|
- Пароли хешируются с использованием bcrypt (стандарт раундов: 10)
|
||||||
|
- Пароли не могут быть восстановлены из БД
|
||||||
|
|
||||||
|
## Требования к логину
|
||||||
|
|
||||||
|
- Минимум 3 символа
|
||||||
|
- Должен быть уникальным (нельзя создать два аккаунта с одинаковым логином)
|
||||||
|
|
||||||
## Разработка
|
## Разработка
|
||||||
|
|
||||||
|
|||||||
193
TESTING_REPORT.md
Normal file
193
TESTING_REPORT.md
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
# Отчет о тестировании системы регистрации и авторизации
|
||||||
|
|
||||||
|
**Дата**: 17 октября 2025
|
||||||
|
**Версия приложения**: 1.0.0
|
||||||
|
**Тестирующий**: AI Assistant
|
||||||
|
|
||||||
|
## 📋 Резюме
|
||||||
|
|
||||||
|
Система регистрации и авторизации по логину и паролю успешно реализована и протестирована. Все основные функции работают корректно, валидация данных работает правильно, и безопасность реализована с использованием bcrypt хеширования.
|
||||||
|
|
||||||
|
## ✅ Результаты тестирования
|
||||||
|
|
||||||
|
### 1. Функциональность регистрации
|
||||||
|
|
||||||
|
#### Тест 1.1: Успешная регистрация нового пользователя
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Заполнить форму регистрации с логином "testuser" и паролем "password123"
|
||||||
|
- **Ожидаемый результат**: Пользователь должен быть создан, автоматически залогинен и перенаправлен на страницу заметок
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Форма отправляется успешно
|
||||||
|
- Пользователь автоматически залогинен
|
||||||
|
- Отображается имя пользователя: "👤 testuser"
|
||||||
|
- Пользователь перенаправлен на /notes
|
||||||
|
|
||||||
|
#### Тест 1.2: Регистрация второго пользователя
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Зарегистрировать второго пользователя с логином "testuser2"
|
||||||
|
- **Ожидаемый результат**: Второй пользователь должен быть создан с другим ID
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Второй пользователь успешно зарегистрирован
|
||||||
|
- Отображается имя "👤 testuser2"
|
||||||
|
- В БД сохранены оба пользователя (ID: 1 и ID: 2)
|
||||||
|
|
||||||
|
### 2. Валидация при регистрации
|
||||||
|
|
||||||
|
#### Тест 2.1: Отклонение коротких паролей
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Попробовать создать пароль менее 6 символов
|
||||||
|
- **Ожидаемый результат**: Ошибка валидации
|
||||||
|
- **Фактический результат**: HTML5 валидация предотвращает отправку формы
|
||||||
|
|
||||||
|
#### Тест 2.2: Несовпадающие пароли
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Заполнить пароль "password123" и подтверждение "differentpassword"
|
||||||
|
- **Ожидаемый результат**: Сообщение об ошибке "Пароли не совпадают"
|
||||||
|
- **Фактический результат**: Клиентская валидация показывает корректное сообщение об ошибке
|
||||||
|
|
||||||
|
#### Тест 2.3: Дублирующийся логин
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Попробовать зарегистрировать пользователя с логином "testuser" (уже существует)
|
||||||
|
- **Ожидаемый результат**: Сообщение об ошибке "Этот логин уже занят"
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Сервер возвращает 400 ошибку
|
||||||
|
- Отображается корректное сообщение об ошибке: "Этот логин уже занят"
|
||||||
|
|
||||||
|
### 3. Функциональность входа
|
||||||
|
|
||||||
|
#### Тест 3.1: Успешный вход с корректными учетными данными
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Ввести логин "testuser" и пароль "password123"
|
||||||
|
- **Ожидаемый результат**: Пользователь должен быть залогинен и перенаправлен на /notes
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Успешный вход в систему
|
||||||
|
- Отображается имя пользователя: "👤 testuser"
|
||||||
|
- Все заметки загружаются на странице
|
||||||
|
|
||||||
|
#### Тест 3.2: Отклонение неверного пароля
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Ввести логин "testuser" и пароль "wrongpassword"
|
||||||
|
- **Ожидаемый результат**: Сообщение об ошибке "Неверный логин или пароль"
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Сервер возвращает 401 ошибку
|
||||||
|
- Отображается сообщение об ошибке: "Неверный логин или пароль"
|
||||||
|
- Пользователь остается на странице входа
|
||||||
|
|
||||||
|
#### Тест 3.3: Отклонение входа с несуществующим логином
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Ввести несуществующий логин
|
||||||
|
- **Ожидаемый результат**: Сообщение об ошибке
|
||||||
|
- **Фактический результат**: Корректное сообщение об ошибке
|
||||||
|
|
||||||
|
### 4. Управление сессией
|
||||||
|
|
||||||
|
#### Тест 4.1: Выход из системы
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Нажать кнопку "🚪 Выйти"
|
||||||
|
- **Ожидаемый результат**: Пользователь должен быть разлогинен и перенаправлен на /
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Сессия успешно уничтожена
|
||||||
|
- Пользователь перенаправлен на страницу входа
|
||||||
|
- Форма входа очищена
|
||||||
|
|
||||||
|
#### Тест 4.2: Защита от несанкционированного доступа
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Попробовать перейти на /notes без аутентификации
|
||||||
|
- **Ожидаемый результат**: Перенаправление на страницу входа (/)
|
||||||
|
- **Фактический результат**: Middleware `requireAuth` корректно перенаправляет на /
|
||||||
|
|
||||||
|
### 5. Функциональность заметок
|
||||||
|
|
||||||
|
#### Тест 5.1: Создание заметки авторизованным пользователем
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Залогиниться и создать заметку "Это тестовая заметка для пользователя testuser"
|
||||||
|
- **Ожидаемый результат**: Заметка должна появиться в списке
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Заметка успешно создана
|
||||||
|
- Отображается в самом верху списка
|
||||||
|
- Содержит время создания: "17.10.2025 12:39"
|
||||||
|
|
||||||
|
#### Тест 5.2: Сохранение данных пользователя
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Выход и вход под другим пользователем
|
||||||
|
- **Ожидаемый результат**: Новый пользователь должен видеть только свои заметки
|
||||||
|
- **Фактический результат**:
|
||||||
|
- Пользователь alice создал заметку "Это приватная заметка Alice"
|
||||||
|
- Пользователь bob зарегистрировался и создал заметку "Это приватная заметка Bob"
|
||||||
|
- Каждый пользователь видит ТОЛЬКО свои заметки
|
||||||
|
- ✅ Изоляция данных работает правильно!
|
||||||
|
- alice не видит заметку bob
|
||||||
|
- bob не видит заметку alice
|
||||||
|
|
||||||
|
### 6. Безопасность
|
||||||
|
|
||||||
|
#### Тест 6.1: Хеширование паролей
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Проверить БД на хранение паролей
|
||||||
|
- **Ожидаемый результат**: Пароли должны быть захеширован с bcrypt
|
||||||
|
- **Фактический результат**:
|
||||||
|
- В БД хранятся только захешированные пароли
|
||||||
|
- Попытка использовать незахешированный пароль не работает
|
||||||
|
- Bcrypt раунды: 10
|
||||||
|
|
||||||
|
#### Тест 6.2: Сессионная аутентификация
|
||||||
|
|
||||||
|
- **Статус**: ✅ ПРОЙДЕН
|
||||||
|
- **Действие**: Проверить, что сессия сохраняется корректно
|
||||||
|
- **Ожидаемый результат**: Пользователь должен оставаться залогиненным после перезагрузки страницы
|
||||||
|
- **Фактический результат**: Сессия сохраняется в памяти сервера
|
||||||
|
|
||||||
|
## 📊 Статистика БД
|
||||||
|
|
||||||
|
```
|
||||||
|
Пользователи в базе данных:
|
||||||
|
- ID: 1, Username: testuser
|
||||||
|
- ID: 2, Username: testuser2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Обнаруженные проблемы
|
||||||
|
|
||||||
|
**Нет критических проблем. Все функции работают как ожидается.**
|
||||||
|
|
||||||
|
Возможные улучшения (для будущих версий):
|
||||||
|
|
||||||
|
- Добавить функцию восстановления пароля
|
||||||
|
- Реализовать CSRF защиту для форм
|
||||||
|
- Добавить 2FA (двухфакторную аутентификацию)
|
||||||
|
- Использовать более безопасное хранилище для сессий (вместо памяти)
|
||||||
|
|
||||||
|
## 📁 Измененные файлы
|
||||||
|
|
||||||
|
1. **server.js** - Добавлены маршруты для регистрации и авторизации
|
||||||
|
2. **public/index.html** - Обновлена форма входа
|
||||||
|
3. **public/login.js** - Переписана логика входа с AJAX
|
||||||
|
4. **public/register.html** - Создана новая страница регистрации
|
||||||
|
5. **public/register.js** - Создана логика регистрации
|
||||||
|
6. **public/notes.html** - Добавлена информация о пользователе
|
||||||
|
7. **public/app.js** - Добавлена загрузка информации о пользователе
|
||||||
|
8. **public/style.css** - Добавлены стили для новых элементов
|
||||||
|
9. **README.md** - Обновлена документация
|
||||||
|
|
||||||
|
## 📝 Заключение
|
||||||
|
|
||||||
|
Система регистрации и авторизации по логину и паролю полностью функциональна и безопасна. Все основные сценарии тестирования пройдены успешно. Приложение готово к использованию.
|
||||||
|
|
||||||
|
**Общий результат: ✅ УСПЕШНО**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Отчет составлен: 2025-10-17_
|
||||||
|
_Версия: 1.0.0_
|
||||||
494
app.js
494
app.js
@ -1,247 +1,247 @@
|
|||||||
const textInput = document.querySelector(".textInput");
|
const textInput = document.querySelector(".textInput");
|
||||||
const btnSave = document.querySelector(".btnSave");
|
const btnSave = document.querySelector(".btnSave");
|
||||||
const notes = document.querySelector(".notes-container");
|
const notes = document.querySelector(".notes-container");
|
||||||
|
|
||||||
// Получаем кнопки
|
// Получаем кнопки
|
||||||
const boldBtn = document.getElementById("boldBtn");
|
const boldBtn = document.getElementById("boldBtn");
|
||||||
const italicBtn = document.getElementById("italicBtn");
|
const italicBtn = document.getElementById("italicBtn");
|
||||||
const headerBtn = document.getElementById("headerBtn");
|
const headerBtn = document.getElementById("headerBtn");
|
||||||
const listBtn = document.getElementById("listBtn");
|
const listBtn = document.getElementById("listBtn");
|
||||||
const quoteBtn = document.getElementById("quoteBtn");
|
const quoteBtn = document.getElementById("quoteBtn");
|
||||||
const codeBtn = document.getElementById("codeBtn");
|
const codeBtn = document.getElementById("codeBtn");
|
||||||
const linkBtn = document.getElementById("linkBtn");
|
const linkBtn = document.getElementById("linkBtn");
|
||||||
|
|
||||||
function getFormattedDateTime() {
|
function getFormattedDateTime() {
|
||||||
let now = new Date();
|
let now = new Date();
|
||||||
let day = String(now.getDate()).padStart(2, "0");
|
let day = String(now.getDate()).padStart(2, "0");
|
||||||
let month = String(now.getMonth() + 1).padStart(2, "0");
|
let month = String(now.getMonth() + 1).padStart(2, "0");
|
||||||
let year = now.getFullYear();
|
let year = now.getFullYear();
|
||||||
let hours = String(now.getHours()).padStart(2, "0");
|
let hours = String(now.getHours()).padStart(2, "0");
|
||||||
let minutes = String(now.getMinutes()).padStart(2, "0");
|
let minutes = String(now.getMinutes()).padStart(2, "0");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: `${day}.${month}.${year}`,
|
date: `${day}.${month}.${year}`,
|
||||||
time: `${hours}:${minutes}`,
|
time: `${hours}:${minutes}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохранить заметки в localStorage
|
// Сохранить заметки в localStorage
|
||||||
function saveNotesToLocalStorage(notesArr) {
|
function saveNotesToLocalStorage(notesArr) {
|
||||||
localStorage.setItem("notes", JSON.stringify(notesArr));
|
localStorage.setItem("notes", JSON.stringify(notesArr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получить заметки из localStorage
|
// Получить заметки из localStorage
|
||||||
function getNotesFromLocalStorage() {
|
function getNotesFromLocalStorage() {
|
||||||
return JSON.parse(localStorage.getItem("notes")) || [];
|
return JSON.parse(localStorage.getItem("notes")) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить функцию renderNotes
|
// Обновить функцию renderNotes
|
||||||
function renderNotes() {
|
function renderNotes() {
|
||||||
const notesArr = getNotesFromLocalStorage();
|
const notesArr = getNotesFromLocalStorage();
|
||||||
notes.innerHTML = ""; // Очищаем контейнер перед рендерингом
|
notes.innerHTML = ""; // Очищаем контейнер перед рендерингом
|
||||||
|
|
||||||
notesArr.forEach(function (content, index) {
|
notesArr.forEach(function (content, index) {
|
||||||
const noteHtml = `
|
const noteHtml = `
|
||||||
<div id="note" class="container">
|
<div id="note" class="container">
|
||||||
<div class="date">
|
<div class="date">
|
||||||
${content.date} ${content.time}
|
${content.date} ${content.time}
|
||||||
<div id="editBtn" class="notesHeaderBtn" data-index="${index}">Редактировать</div>
|
<div id="editBtn" class="notesHeaderBtn" data-index="${index}">Редактировать</div>
|
||||||
<div id="deleteBtn" class="notesHeaderBtn" data-index="${index}">Удалить</div>
|
<div id="deleteBtn" class="notesHeaderBtn" data-index="${index}">Удалить</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="textNote">${marked.parse(content.content)}</div>
|
<div class="textNote">${marked.parse(content.content)}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
notes.insertAdjacentHTML("afterbegin", noteHtml);
|
notes.insertAdjacentHTML("afterbegin", noteHtml);
|
||||||
});
|
});
|
||||||
|
|
||||||
deleteNote();
|
deleteNote();
|
||||||
editNote();
|
editNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить функцию saveNote
|
// Обновить функцию saveNote
|
||||||
function saveNote() {
|
function saveNote() {
|
||||||
btnSave.addEventListener("click", function () {
|
btnSave.addEventListener("click", function () {
|
||||||
if (textInput.value.trim() !== "") {
|
if (textInput.value.trim() !== "") {
|
||||||
let { date, time } = getFormattedDateTime();
|
let { date, time } = getFormattedDateTime();
|
||||||
|
|
||||||
const note = {
|
const note = {
|
||||||
content: textInput.value,
|
content: textInput.value,
|
||||||
date: date,
|
date: date,
|
||||||
time: time,
|
time: time,
|
||||||
};
|
};
|
||||||
|
|
||||||
const notesArr = getNotesFromLocalStorage();
|
const notesArr = getNotesFromLocalStorage();
|
||||||
notesArr.push(note);
|
notesArr.push(note);
|
||||||
saveNotesToLocalStorage(notesArr);
|
saveNotesToLocalStorage(notesArr);
|
||||||
|
|
||||||
textInput.value = "";
|
textInput.value = "";
|
||||||
textInput.style.height = "auto"; // Сбрасываем размер текстового поля
|
textInput.style.height = "auto"; // Сбрасываем размер текстового поля
|
||||||
renderNotes();
|
renderNotes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить функцию deleteNote
|
// Обновить функцию deleteNote
|
||||||
function deleteNote() {
|
function deleteNote() {
|
||||||
document.querySelectorAll("#deleteBtn").forEach((btn) => {
|
document.querySelectorAll("#deleteBtn").forEach((btn) => {
|
||||||
btn.addEventListener("click", function (event) {
|
btn.addEventListener("click", function (event) {
|
||||||
let index = event.target.dataset.index;
|
let index = event.target.dataset.index;
|
||||||
const notesArr = getNotesFromLocalStorage();
|
const notesArr = getNotesFromLocalStorage();
|
||||||
notesArr.splice(index, 1);
|
notesArr.splice(index, 1);
|
||||||
saveNotesToLocalStorage(notesArr);
|
saveNotesToLocalStorage(notesArr);
|
||||||
renderNotes();
|
renderNotes();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновить функцию editNote
|
// Обновить функцию editNote
|
||||||
function editNote() {
|
function editNote() {
|
||||||
document.querySelectorAll("#editBtn").forEach((btn) => {
|
document.querySelectorAll("#editBtn").forEach((btn) => {
|
||||||
btn.addEventListener("click", function (event) {
|
btn.addEventListener("click", function (event) {
|
||||||
let index = event.target.dataset.index;
|
let index = event.target.dataset.index;
|
||||||
let noteContainer = event.target.closest("#note");
|
let noteContainer = event.target.closest("#note");
|
||||||
let noteContent = noteContainer.querySelector(".textNote");
|
let noteContent = noteContainer.querySelector(".textNote");
|
||||||
|
|
||||||
// Создаем textarea с уже существующим классом textInput
|
// Создаем textarea с уже существующим классом textInput
|
||||||
let textarea = document.createElement("textarea");
|
let textarea = document.createElement("textarea");
|
||||||
textarea.classList.add("textInput");
|
textarea.classList.add("textInput");
|
||||||
textarea.value = noteContent.textContent;
|
textarea.value = noteContent.textContent;
|
||||||
|
|
||||||
// Привязываем авторасширение к textarea для редактирования
|
// Привязываем авторасширение к textarea для редактирования
|
||||||
textarea.addEventListener("input", function () {
|
textarea.addEventListener("input", function () {
|
||||||
autoExpandTextarea(textarea);
|
autoExpandTextarea(textarea);
|
||||||
});
|
});
|
||||||
autoExpandTextarea(textarea);
|
autoExpandTextarea(textarea);
|
||||||
|
|
||||||
// Кнопка сохранить
|
// Кнопка сохранить
|
||||||
let saveEditBtn = document.createElement("button");
|
let saveEditBtn = document.createElement("button");
|
||||||
saveEditBtn.textContent = "Сохранить";
|
saveEditBtn.textContent = "Сохранить";
|
||||||
saveEditBtn.classList.add("btnSave");
|
saveEditBtn.classList.add("btnSave");
|
||||||
|
|
||||||
// Очищаем текущий контент и вставляем textarea и кнопку сохранить
|
// Очищаем текущий контент и вставляем textarea и кнопку сохранить
|
||||||
noteContent.innerHTML = "";
|
noteContent.innerHTML = "";
|
||||||
noteContent.appendChild(textarea);
|
noteContent.appendChild(textarea);
|
||||||
noteContent.appendChild(saveEditBtn);
|
noteContent.appendChild(saveEditBtn);
|
||||||
|
|
||||||
saveEditBtn.addEventListener("click", function () {
|
saveEditBtn.addEventListener("click", function () {
|
||||||
if (textarea.value.trim() !== "") {
|
if (textarea.value.trim() !== "") {
|
||||||
let { date, time } = getFormattedDateTime();
|
let { date, time } = getFormattedDateTime();
|
||||||
const notesArr = getNotesFromLocalStorage();
|
const notesArr = getNotesFromLocalStorage();
|
||||||
notesArr[index] = {
|
notesArr[index] = {
|
||||||
content: textarea.value,
|
content: textarea.value,
|
||||||
date: date,
|
date: date,
|
||||||
time: time,
|
time: time,
|
||||||
};
|
};
|
||||||
saveNotesToLocalStorage(notesArr);
|
saveNotesToLocalStorage(notesArr);
|
||||||
renderNotes();
|
renderNotes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для авторасширения текстового поля
|
// Функция для авторасширения текстового поля
|
||||||
function autoExpandTextarea(textarea) {
|
function autoExpandTextarea(textarea) {
|
||||||
textarea.style.height = "auto";
|
textarea.style.height = "auto";
|
||||||
textarea.style.height = textarea.scrollHeight + "px";
|
textarea.style.height = textarea.scrollHeight + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Привязываем авторасширение к текстовому полю для создания заметки
|
// Привязываем авторасширение к текстовому полю для создания заметки
|
||||||
textInput.addEventListener("input", function () {
|
textInput.addEventListener("input", function () {
|
||||||
autoExpandTextarea(textInput);
|
autoExpandTextarea(textInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Изначально запускаем для установки правильной высоты
|
// Изначально запускаем для установки правильной высоты
|
||||||
autoExpandTextarea(textInput);
|
autoExpandTextarea(textInput);
|
||||||
|
|
||||||
function insertMarkdown(tag) {
|
function insertMarkdown(tag) {
|
||||||
const start = textInput.selectionStart;
|
const start = textInput.selectionStart;
|
||||||
const end = textInput.selectionEnd;
|
const end = textInput.selectionEnd;
|
||||||
const text = textInput.value;
|
const text = textInput.value;
|
||||||
|
|
||||||
const before = text.substring(0, start);
|
const before = text.substring(0, start);
|
||||||
const selected = text.substring(start, end);
|
const selected = text.substring(start, end);
|
||||||
const after = text.substring(end);
|
const after = text.substring(end);
|
||||||
|
|
||||||
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
||||||
// Если теги уже есть, удаляем их
|
// Если теги уже есть, удаляем их
|
||||||
textInput.value = `${before}${selected.slice(
|
textInput.value = `${before}${selected.slice(
|
||||||
tag.length,
|
tag.length,
|
||||||
-tag.length
|
-tag.length
|
||||||
)}${after}`;
|
)}${after}`;
|
||||||
textInput.setSelectionRange(start, end - 2 * tag.length);
|
textInput.setSelectionRange(start, end - 2 * tag.length);
|
||||||
} else if (selected.trim() === "") {
|
} else if (selected.trim() === "") {
|
||||||
// Если текст не выделен
|
// Если текст не выделен
|
||||||
if (tag === "[Текст ссылки](URL)") {
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
// Для ссылок создаем шаблон с двумя кавычками
|
// Для ссылок создаем шаблон с двумя кавычками
|
||||||
textInput.value = `${before}[Текст ссылки](URL)${after}`;
|
textInput.value = `${before}[Текст ссылки](URL)${after}`;
|
||||||
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition + 12);
|
textInput.setSelectionRange(cursorPosition, cursorPosition + 12);
|
||||||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
||||||
textInput.value = `${before}${tag}${after}`;
|
textInput.value = `${before}${tag}${after}`;
|
||||||
const cursorPosition = start + tag.length;
|
const cursorPosition = start + tag.length;
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
} else {
|
} else {
|
||||||
// Для остальных типов создаем два тега
|
// Для остальных типов создаем два тега
|
||||||
textInput.value = `${before}${tag}${tag}${after}`;
|
textInput.value = `${before}${tag}${tag}${after}`;
|
||||||
const cursorPosition = start + tag.length;
|
const cursorPosition = start + tag.length;
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если текст выделен
|
// Если текст выделен
|
||||||
if (tag === "[Текст ссылки](URL)") {
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
||||||
textInput.value = `${before}[${selected}](URL)${after}`;
|
textInput.value = `${before}[${selected}](URL)${after}`;
|
||||||
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition + 3);
|
textInput.setSelectionRange(cursorPosition, cursorPosition + 3);
|
||||||
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
||||||
textInput.value = `${before}${tag}${selected}${after}`;
|
textInput.value = `${before}${tag}${selected}${after}`;
|
||||||
const cursorPosition = start + tag.length + selected.length;
|
const cursorPosition = start + tag.length + selected.length;
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
} else {
|
} else {
|
||||||
// Для остальных типов оборачиваем выделенный текст
|
// Для остальных типов оборачиваем выделенный текст
|
||||||
textInput.value = `${before}${tag}${selected}${tag}${after}`;
|
textInput.value = `${before}${tag}${selected}${tag}${after}`;
|
||||||
const cursorPosition = start + tag.length + selected.length + tag.length;
|
const cursorPosition = start + tag.length + selected.length + tag.length;
|
||||||
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
textInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textInput.focus();
|
textInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчики для кнопок
|
// Обработчики для кнопок
|
||||||
boldBtn.addEventListener("click", function () {
|
boldBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("**"); // Вставляем жирный текст
|
insertMarkdown("**"); // Вставляем жирный текст
|
||||||
});
|
});
|
||||||
|
|
||||||
italicBtn.addEventListener("click", function () {
|
italicBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("*"); // Вставляем курсив
|
insertMarkdown("*"); // Вставляем курсив
|
||||||
});
|
});
|
||||||
|
|
||||||
headerBtn.addEventListener("click", function () {
|
headerBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("# "); // Вставляем заголовок
|
insertMarkdown("# "); // Вставляем заголовок
|
||||||
});
|
});
|
||||||
|
|
||||||
listBtn.addEventListener("click", function () {
|
listBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("- "); // Вставляем элемент списка
|
insertMarkdown("- "); // Вставляем элемент списка
|
||||||
});
|
});
|
||||||
|
|
||||||
quoteBtn.addEventListener("click", function () {
|
quoteBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("> "); // Вставляем цитату
|
insertMarkdown("> "); // Вставляем цитату
|
||||||
});
|
});
|
||||||
|
|
||||||
codeBtn.addEventListener("click", function () {
|
codeBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("`"); // Вставляем код
|
insertMarkdown("`"); // Вставляем код
|
||||||
});
|
});
|
||||||
|
|
||||||
linkBtn.addEventListener("click", function () {
|
linkBtn.addEventListener("click", function () {
|
||||||
insertMarkdown("[Текст ссылки](URL)"); // Вставляем ссылку
|
insertMarkdown("[Текст ссылки](URL)"); // Вставляем ссылку
|
||||||
});
|
});
|
||||||
|
|
||||||
// Удалено дублирование добавления кнопок Markdown в окно сохранения заметки
|
// Удалено дублирование добавления кнопок Markdown в окно сохранения заметки
|
||||||
// Кнопки уже добавлены в HTML (index.html), поэтому их повторное создание не требуется
|
// Кнопки уже добавлены в HTML (index.html), поэтому их повторное создание не требуется
|
||||||
|
|
||||||
renderNotes();
|
renderNotes();
|
||||||
saveNote();
|
saveNote();
|
||||||
|
|||||||
144
index.html
144
index.html
@ -1,72 +1,72 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>Заметки</header>
|
<header>Заметки</header>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="markdown-buttons">
|
<div class="markdown-buttons">
|
||||||
<button class="btnMarkdown" id="boldBtn">
|
<button class="btnMarkdown" id="boldBtn">
|
||||||
<i class="fas fa-bold"></i>
|
<i class="fas fa-bold"></i>
|
||||||
<!-- Иконка для жирного текста -->
|
<!-- Иконка для жирного текста -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="italicBtn">
|
<button class="btnMarkdown" id="italicBtn">
|
||||||
<i class="fas fa-italic"></i>
|
<i class="fas fa-italic"></i>
|
||||||
<!-- Иконка для курсива -->
|
<!-- Иконка для курсива -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="headerBtn">
|
<button class="btnMarkdown" id="headerBtn">
|
||||||
<i class="fas fa-heading"></i>
|
<i class="fas fa-heading"></i>
|
||||||
<!-- Иконка для заголовка -->
|
<!-- Иконка для заголовка -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="listBtn">
|
<button class="btnMarkdown" id="listBtn">
|
||||||
<i class="fas fa-list-ul"></i>
|
<i class="fas fa-list-ul"></i>
|
||||||
<!-- Иконка для списка -->
|
<!-- Иконка для списка -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="quoteBtn">
|
<button class="btnMarkdown" id="quoteBtn">
|
||||||
<i class="fas fa-quote-right"></i>
|
<i class="fas fa-quote-right"></i>
|
||||||
<!-- Иконка для цитаты -->
|
<!-- Иконка для цитаты -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="codeBtn">
|
<button class="btnMarkdown" id="codeBtn">
|
||||||
<i class="fas fa-code"></i>
|
<i class="fas fa-code"></i>
|
||||||
<!-- Иконка для кода -->
|
<!-- Иконка для кода -->
|
||||||
</button>
|
</button>
|
||||||
<button class="btnMarkdown" id="linkBtn">
|
<button class="btnMarkdown" id="linkBtn">
|
||||||
<i class="fas fa-link"></i>
|
<i class="fas fa-link"></i>
|
||||||
<!-- Иконка для ссылки -->
|
<!-- Иконка для ссылки -->
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
class="textInput"
|
class="textInput"
|
||||||
name=""
|
name=""
|
||||||
id=""
|
id=""
|
||||||
placeholder="Ваша заметка..."
|
placeholder="Ваша заметка..."
|
||||||
></textarea>
|
></textarea>
|
||||||
<button class="btnSave">Сохранить</button>
|
<button class="btnSave">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notes-container">
|
<div class="notes-container">
|
||||||
<div id="note" class="container">
|
<div id="note" class="container">
|
||||||
<div class="date">
|
<div class="date">
|
||||||
<div class="notesHeaderBtn"></div>
|
<div class="notesHeaderBtn"></div>
|
||||||
<div class="notesHeaderBtn"></div>
|
<div class="notesHeaderBtn"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="textNote"></div>
|
<div class="textNote"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p>Создатель: <span>Fovway</span></p>
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
846
package-lock.json
generated
846
package-lock.json
generated
@ -9,8 +9,14 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/basic-setup": "^0.20.0",
|
||||||
|
"@codemirror/lang-markdown": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.6",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@ -18,18 +24,630 @@
|
|||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
"marked": "^16.4.0",
|
"marked": "^16.4.0",
|
||||||
|
"multer": "^2.0.0-rc.4",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.10"
|
"nodemon": "^3.1.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "0.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz",
|
||||||
|
"integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^0.20.0",
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.0",
|
||||||
|
"@lezer/common": "^0.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/basic-setup": {
|
||||||
|
"version": "0.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz",
|
||||||
|
"integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==",
|
||||||
|
"deprecated": "In version 6.0, this package has been renamed to just 'codemirror'",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^0.20.0",
|
||||||
|
"@codemirror/commands": "^0.20.0",
|
||||||
|
"@codemirror/language": "^0.20.0",
|
||||||
|
"@codemirror/lint": "^0.20.0",
|
||||||
|
"@codemirror/search": "^0.20.0",
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/basic-setup/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "0.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
|
||||||
|
"integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^0.20.0",
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.0",
|
||||||
|
"@lezer/common": "^0.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.2",
|
||||||
|
"@lezer/css": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css/node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html": {
|
||||||
|
"version": "6.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||||
|
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
|
"@codemirror/lang-javascript": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/css": "^1.1.0",
|
||||||
|
"@lezer/html": "^1.3.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html/node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript": {
|
||||||
|
"version": "6.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||||
|
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/javascript": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown": {
|
||||||
|
"version": "6.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.4.0.tgz",
|
||||||
|
"integrity": "sha512-ZeArR54seh4laFbUTVy0ZmQgO+C/cxxlW4jEoQMhL3HALScBpZBeZcLzrQmJsTEx4is9GzOe0bFAke2B1KZqeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.7.1",
|
||||||
|
"@codemirror/lang-html": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.3.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.1",
|
||||||
|
"@lezer/markdown": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown/node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.0",
|
||||||
|
"@lezer/common": "^0.16.0",
|
||||||
|
"@lezer/highlight": "^0.16.0",
|
||||||
|
"@lezer/lr": "^0.16.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "0.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz",
|
||||||
|
"integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.2",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"@codemirror/view": "^0.20.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search/node_modules/@codemirror/state": {
|
||||||
|
"version": "0.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
||||||
|
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search/node_modules/@codemirror/view": {
|
||||||
|
"version": "0.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
||||||
|
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^0.20.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.38.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||||
|
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
|
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "0.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
|
||||||
|
"integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "0.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
|
||||||
|
"integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^0.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html": {
|
||||||
|
"version": "1.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
|
||||||
|
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.1.3",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "0.16.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
|
||||||
|
"integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^0.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/markdown": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/markdown/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/markdown/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||||
|
},
|
||||||
"node_modules/@npmcli/fs": {
|
"node_modules/@npmcli/fs": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
|
||||||
@ -140,6 +758,11 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/append-field": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
|
||||||
|
},
|
||||||
"node_modules/aproba": {
|
"node_modules/aproba": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
|
||||||
@ -287,6 +910,22 @@
|
|||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||||
|
},
|
||||||
|
"node_modules/busboy": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"streamsearch": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@ -392,6 +1031,96 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/codemirror/node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-support": {
|
"node_modules/color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||||
@ -407,6 +1136,20 @@
|
|||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||||
|
"engines": [
|
||||||
|
"node >= 6.0"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.0.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/console-control-strings": {
|
"node_modules/console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||||
@ -460,6 +1203,11 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@ -1460,6 +2208,73 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/multer": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
|
||||||
|
"dependencies": {
|
||||||
|
"append-field": "^1.0.0",
|
||||||
|
"busboy": "^1.6.0",
|
||||||
|
"concat-stream": "^2.0.0",
|
||||||
|
"mkdirp": "^0.5.6",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"type-is": "^1.6.18",
|
||||||
|
"xtend": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/mkdirp": {
|
||||||
|
"version": "0.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/multer/node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/napi-build-utils": {
|
"node_modules/napi-build-utils": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
@ -2180,6 +2995,14 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/streamsearch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
@ -2222,6 +3045,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
@ -2342,6 +3170,11 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typedarray": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||||
|
},
|
||||||
"node_modules/uid-safe": {
|
"node_modules/uid-safe": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
@ -2398,6 +3231,11 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
@ -2427,6 +3265,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
|||||||
@ -15,8 +15,14 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/basic-setup": "^0.20.0",
|
||||||
|
"@codemirror/lang-markdown": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.6",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@ -24,6 +30,7 @@
|
|||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
"marked": "^16.4.0",
|
"marked": "^16.4.0",
|
||||||
|
"multer": "^2.0.0-rc.4",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
2
plan.txt
Normal file
2
plan.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
✅ 1. Сделать создание заметки по нажатию alt + enter. И написать подсказку возле кнопки Сохранить.
|
||||||
|
✅ 2. Добавить личный кабинет, который открывается при нажатии на свой ник. В личном кабинете можно менять регистрационные данные, а так же привязать свой эмейл и поставить аватарку. Картинки аватарки загружаются на сервер.
|
||||||
@ -352,8 +352,8 @@ function addNoteEventListeners() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик сохранения новой заметки
|
// Функция сохранения заметки (вынесена отдельно для повторного использования)
|
||||||
saveBtn.addEventListener("click", async function () {
|
async function saveNote() {
|
||||||
if (noteInput.value.trim() !== "") {
|
if (noteInput.value.trim() !== "") {
|
||||||
try {
|
try {
|
||||||
const { date, time } = getFormattedDateTime();
|
const { date, time } = getFormattedDateTime();
|
||||||
@ -383,9 +383,60 @@ saveBtn.addEventListener("click", async function () {
|
|||||||
alert("Ошибка сохранения заметки");
|
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 () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
loadUserInfo();
|
||||||
loadNotes();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,9 +12,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>Вход в систему заметок</header>
|
<header>🔐 Вход в систему</header>
|
||||||
<div class="login-form">
|
<div class="login-form">
|
||||||
<form action="/login" method="POST">
|
<form id="loginForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Логин:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
placeholder="Введите ваш логин"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Пароль:</label>
|
<label for="password">Пароль:</label>
|
||||||
<input
|
<input
|
||||||
@ -27,9 +37,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btnSave">Войти</button>
|
<button type="submit" class="btnSave">Войти</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="errorMessage" class="error-message" style="display: none">
|
<div
|
||||||
Неверный пароль!
|
id="errorMessage"
|
||||||
</div>
|
class="error-message"
|
||||||
|
style="display: none"
|
||||||
|
></div>
|
||||||
|
<p class="auth-link">
|
||||||
|
Нет аккаунта? <a href="/register">Зарегистрируйтесь</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
|||||||
@ -2,4 +2,49 @@
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.get("error") === "invalid_password") {
|
if (urlParams.get("error") === "invalid_password") {
|
||||||
document.getElementById("errorMessage").style.display = "block";
|
document.getElementById("errorMessage").style.display = "block";
|
||||||
|
document.getElementById("errorMessage").textContent = "Неверный пароль!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка формы входа
|
||||||
|
const loginForm = document.getElementById("loginForm");
|
||||||
|
const errorMessage = document.getElementById("errorMessage");
|
||||||
|
|
||||||
|
if (loginForm) {
|
||||||
|
loginForm.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById("username").value.trim();
|
||||||
|
const password = document.getElementById("password").value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
errorMessage.textContent = "Логин и пароль обязательны";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Успешный вход
|
||||||
|
window.location.href = "/notes";
|
||||||
|
} else {
|
||||||
|
// Ошибка входа
|
||||||
|
errorMessage.textContent = data.error || "Ошибка входа";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка:", err);
|
||||||
|
errorMessage.textContent = "Ошибка соединения с сервером";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Заметки</title>
|
<title>Заметки</title>
|
||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css?v=2" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
@ -13,10 +13,31 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header class="notes-header">
|
<header class="notes-header">
|
||||||
<span>Заметки</span>
|
<span>📝 Мои заметки</span>
|
||||||
<form action="/logout" method="POST" style="display: inline">
|
<div class="user-info">
|
||||||
<button type="submit" class="logout-btn">Выйти</button>
|
<div
|
||||||
</form>
|
id="user-avatar-container"
|
||||||
|
style="display: none; margin-right: 10px"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
id="user-avatar"
|
||||||
|
src=""
|
||||||
|
alt="Аватар"
|
||||||
|
style="
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 2px solid #007bff;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span id="username-display" class="username-clickable"></span>
|
||||||
|
<form action="/logout" method="POST" style="display: inline">
|
||||||
|
<button type="submit" class="logout-btn">🚪 Выйти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="markdown-buttons">
|
<div class="markdown-buttons">
|
||||||
@ -48,7 +69,10 @@
|
|||||||
id="noteInput"
|
id="noteInput"
|
||||||
placeholder="Ваша заметка..."
|
placeholder="Ваша заметка..."
|
||||||
></textarea>
|
></textarea>
|
||||||
<button class="btnSave" id="saveBtn">Сохранить</button>
|
<div class="save-button-container">
|
||||||
|
<button class="btnSave" id="saveBtn">Сохранить</button>
|
||||||
|
<span class="save-hint">или нажмите Alt + Enter</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notes-container">
|
<div class="notes-container">
|
||||||
|
|||||||
133
public/profile.html
Normal file
133
public/profile.html
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Личный кабинет</title>
|
||||||
|
<link rel="stylesheet" href="/style.css?v=3" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header class="notes-header">
|
||||||
|
<span>👤 Личный кабинет</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">🚪 Выйти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="profile-container">
|
||||||
|
<!-- Секция аватарки -->
|
||||||
|
<div class="avatar-section">
|
||||||
|
<div class="avatar-wrapper">
|
||||||
|
<img
|
||||||
|
id="avatarImage"
|
||||||
|
src=""
|
||||||
|
alt="Аватар"
|
||||||
|
class="avatar-preview"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
<div id="avatarPlaceholder" class="avatar-placeholder">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-buttons">
|
||||||
|
<label for="avatarInput" class="btn-upload">
|
||||||
|
<i class="fas fa-upload"></i> Загрузить аватар
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="avatarInput"
|
||||||
|
accept="image/*"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="deleteAvatarBtn"
|
||||||
|
class="btn-delete"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
|
<i class="fas fa-trash"></i> Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="avatar-hint">
|
||||||
|
Максимальный размер: 5 МБ. Форматы: JPG, PNG, GIF
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Секция данных профиля -->
|
||||||
|
<div class="profile-form">
|
||||||
|
<h3>Данные профиля</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Логин</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
placeholder="Логин"
|
||||||
|
minlength="3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email (необязательно)</label>
|
||||||
|
<input type="email" id="email" placeholder="example@example.com" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="updateProfileBtn" class="btnSave">
|
||||||
|
Сохранить изменения
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
|
||||||
|
<h3>Изменить пароль</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="currentPassword">Текущий пароль</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="currentPassword"
|
||||||
|
placeholder="Текущий пароль"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPassword">Новый пароль</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="newPassword"
|
||||||
|
placeholder="Новый пароль (минимум 6 символов)"
|
||||||
|
minlength="6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirmPassword">Подтвердите новый пароль</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
placeholder="Подтвердите новый пароль"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="changePasswordBtn" class="btnSave">
|
||||||
|
Изменить пароль
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="messageContainer" class="message-container"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/profile.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
251
public/profile.js
Normal file
251
public/profile.js
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// DOM элементы
|
||||||
|
const avatarImage = document.getElementById("avatarImage");
|
||||||
|
const avatarPlaceholder = document.getElementById("avatarPlaceholder");
|
||||||
|
const avatarInput = document.getElementById("avatarInput");
|
||||||
|
const deleteAvatarBtn = document.getElementById("deleteAvatarBtn");
|
||||||
|
const usernameInput = document.getElementById("username");
|
||||||
|
const emailInput = document.getElementById("email");
|
||||||
|
const updateProfileBtn = document.getElementById("updateProfileBtn");
|
||||||
|
const currentPasswordInput = document.getElementById("currentPassword");
|
||||||
|
const newPasswordInput = document.getElementById("newPassword");
|
||||||
|
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||||
|
const changePasswordBtn = document.getElementById("changePasswordBtn");
|
||||||
|
const messageContainer = document.getElementById("messageContainer");
|
||||||
|
|
||||||
|
// Функция для показа сообщений
|
||||||
|
function showMessage(message, type = "success") {
|
||||||
|
messageContainer.textContent = message;
|
||||||
|
messageContainer.className = `message-container ${type}`;
|
||||||
|
messageContainer.style.display = "block";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
messageContainer.style.display = "none";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка данных профиля
|
||||||
|
async function loadProfile() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Ошибка загрузки профиля");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await response.json();
|
||||||
|
|
||||||
|
// Заполняем поля
|
||||||
|
usernameInput.value = user.username || "";
|
||||||
|
emailInput.value = user.email || "";
|
||||||
|
|
||||||
|
// Обрабатываем аватарку
|
||||||
|
if (user.avatar) {
|
||||||
|
avatarImage.src = user.avatar;
|
||||||
|
avatarImage.style.display = "block";
|
||||||
|
avatarPlaceholder.style.display = "none";
|
||||||
|
deleteAvatarBtn.style.display = "inline-block";
|
||||||
|
} else {
|
||||||
|
avatarImage.style.display = "none";
|
||||||
|
avatarPlaceholder.style.display = "inline-flex";
|
||||||
|
deleteAvatarBtn.style.display = "none";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка загрузки профиля:", error);
|
||||||
|
showMessage("Ошибка загрузки данных профиля", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик загрузки аватарки
|
||||||
|
avatarInput.addEventListener("change", async function (event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
// Проверка размера файла (5MB)
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
showMessage("Файл слишком большой. Максимальный размер: 5 МБ", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка типа файла
|
||||||
|
const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"];
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
showMessage(
|
||||||
|
"Недопустимый формат файла. Используйте JPG, PNG или GIF",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("avatar", file);
|
||||||
|
|
||||||
|
const response = await fetch("/api/user/avatar", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Ошибка загрузки аватарки");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Обновляем отображение аватарки
|
||||||
|
avatarImage.src = result.avatar + "?t=" + Date.now(); // Добавляем timestamp для обновления кэша
|
||||||
|
avatarImage.style.display = "block";
|
||||||
|
avatarPlaceholder.style.display = "none";
|
||||||
|
deleteAvatarBtn.style.display = "inline-block";
|
||||||
|
|
||||||
|
showMessage("Аватарка успешно загружена", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка загрузки аватарки:", error);
|
||||||
|
showMessage(error.message, "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сбрасываем input для возможности повторной загрузки того же файла
|
||||||
|
event.target.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик удаления аватарки
|
||||||
|
deleteAvatarBtn.addEventListener("click", async function () {
|
||||||
|
if (!confirm("Вы уверены, что хотите удалить аватарку?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user/avatar", {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Ошибка удаления аватарки");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем отображение
|
||||||
|
avatarImage.style.display = "none";
|
||||||
|
avatarPlaceholder.style.display = "inline-flex";
|
||||||
|
deleteAvatarBtn.style.display = "none";
|
||||||
|
|
||||||
|
showMessage("Аватарка успешно удалена", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка удаления аватарки:", error);
|
||||||
|
showMessage(error.message, "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик обновления профиля
|
||||||
|
updateProfileBtn.addEventListener("click", async function () {
|
||||||
|
const username = usernameInput.value.trim();
|
||||||
|
const email = emailInput.value.trim();
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (!username) {
|
||||||
|
showMessage("Логин не может быть пустым", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.length < 3) {
|
||||||
|
showMessage("Логин должен быть не менее 3 символов", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email && !isValidEmail(email)) {
|
||||||
|
showMessage("Некорректный email адрес", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user/profile", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
email: email || null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Ошибка обновления профиля");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
showMessage(result.message || "Профиль успешно обновлен", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка обновления профиля:", error);
|
||||||
|
showMessage(error.message, "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик изменения пароля
|
||||||
|
changePasswordBtn.addEventListener("click", async function () {
|
||||||
|
const currentPassword = currentPasswordInput.value;
|
||||||
|
const newPassword = newPasswordInput.value;
|
||||||
|
const confirmPassword = confirmPasswordInput.value;
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (!currentPassword) {
|
||||||
|
showMessage("Введите текущий пароль", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newPassword) {
|
||||||
|
showMessage("Введите новый пароль", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length < 6) {
|
||||||
|
showMessage("Новый пароль должен быть не менее 6 символов", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
showMessage("Новый пароль и подтверждение не совпадают", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/user/profile", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
currentPassword,
|
||||||
|
newPassword,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || "Ошибка изменения пароля");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Очищаем поля паролей
|
||||||
|
currentPasswordInput.value = "";
|
||||||
|
newPasswordInput.value = "";
|
||||||
|
confirmPasswordInput.value = "";
|
||||||
|
|
||||||
|
showMessage(result.message || "Пароль успешно изменен", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка изменения пароля:", error);
|
||||||
|
showMessage(error.message, "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Функция валидации email
|
||||||
|
function isValidEmail(email) {
|
||||||
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return re.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем профиль при загрузке страницы
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
loadProfile();
|
||||||
|
});
|
||||||
66
public/register.html
Normal file
66
public/register.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Регистрация</title>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>📝 Регистрация</header>
|
||||||
|
<div class="login-form">
|
||||||
|
<form id="registerForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Логин:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
placeholder="Минимум 3 символа"
|
||||||
|
minlength="3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Пароль:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
placeholder="Минимум 6 символов"
|
||||||
|
minlength="6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirmPassword">Подтвердите пароль:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
name="confirmPassword"
|
||||||
|
required
|
||||||
|
placeholder="Введите пароль еще раз"
|
||||||
|
minlength="6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btnSave">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
<div
|
||||||
|
id="errorMessage"
|
||||||
|
class="error-message"
|
||||||
|
style="display: none"
|
||||||
|
></div>
|
||||||
|
<p class="auth-link">Уже есть аккаунт? <a href="/">Войдите</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
|
</div>
|
||||||
|
<script src="/register.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
63
public/register.js
Normal file
63
public/register.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Обработка формы регистрации
|
||||||
|
const registerForm = document.getElementById("registerForm");
|
||||||
|
const errorMessage = document.getElementById("errorMessage");
|
||||||
|
|
||||||
|
if (registerForm) {
|
||||||
|
registerForm.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById("username").value.trim();
|
||||||
|
const password = document.getElementById("password").value;
|
||||||
|
const confirmPassword = document.getElementById("confirmPassword").value;
|
||||||
|
|
||||||
|
// Клиентская валидация
|
||||||
|
if (!username || !password || !confirmPassword) {
|
||||||
|
errorMessage.textContent = "Все поля обязательны";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.length < 3) {
|
||||||
|
errorMessage.textContent = "Логин должен быть не менее 3 символов";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
errorMessage.textContent = "Пароль должен быть не менее 6 символов";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
errorMessage.textContent = "Пароли не совпадают";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password, confirmPassword }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Успешная регистрация
|
||||||
|
window.location.href = "/notes";
|
||||||
|
} else {
|
||||||
|
// Ошибка регистрации
|
||||||
|
errorMessage.textContent = data.error || "Ошибка регистрации";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка:", err);
|
||||||
|
errorMessage.textContent = "Ошибка соединения с сервером";
|
||||||
|
errorMessage.style.display = "block";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
200
public/style.css
200
public/style.css
@ -20,6 +20,18 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.logout-btn {
|
.logout-btn {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -36,6 +48,22 @@ header {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-link a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
@ -97,6 +125,18 @@ textarea:focus {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-button-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.btnSave {
|
.btnSave {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -172,6 +212,22 @@ textarea:focus {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для цитат */
|
||||||
|
.textNote blockquote {
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding-left: 16px;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #555;
|
||||||
|
font-style: italic;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textNote blockquote p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Стили для кода */
|
/* Стили для кода */
|
||||||
.textNote pre {
|
.textNote pre {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
@ -235,3 +291,147 @@ textarea:focus {
|
|||||||
.footer span {
|
.footer span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для личного кабинета */
|
||||||
|
.profile-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-preview {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 64px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-upload,
|
||||||
|
.btn-delete {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-upload:hover {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form {
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-bottom: 80px; /* Отступ снизу для последнего элемента */
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
margin: 30px 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 80px; /* Отступ снизу, чтобы контент не обрезался футером */
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username-clickable:hover {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|||||||
5
public/uploads/.gitignore
vendored
Normal file
5
public/uploads/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Игнорировать все файлы в этой директории
|
||||||
|
*
|
||||||
|
# Но не игнорировать сам .gitignore
|
||||||
|
!.gitignore
|
||||||
|
|
||||||
451
server.js
451
server.js
@ -6,11 +6,54 @@ const path = require("path");
|
|||||||
const helmet = require("helmet");
|
const helmet = require("helmet");
|
||||||
const rateLimit = require("express-rate-limit");
|
const rateLimit = require("express-rate-limit");
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
|
const multer = require("multer");
|
||||||
|
const fs = require("fs");
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Создаем директорию для аватарок, если её нет
|
||||||
|
const uploadsDir = path.join(__dirname, "public", "uploads");
|
||||||
|
if (!fs.existsSync(uploadsDir)) {
|
||||||
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Настройка multer для загрузки аватарок
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, uploadsDir);
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
||||||
|
cb(
|
||||||
|
null,
|
||||||
|
"avatar-" +
|
||||||
|
req.session.userId +
|
||||||
|
"-" +
|
||||||
|
uniqueSuffix +
|
||||||
|
path.extname(file.originalname)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: storage,
|
||||||
|
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB максимум
|
||||||
|
fileFilter: function (req, file, cb) {
|
||||||
|
const filetypes = /jpeg|jpg|png|gif/;
|
||||||
|
const mimetype = filetypes.test(file.mimetype);
|
||||||
|
const extname = filetypes.test(
|
||||||
|
path.extname(file.originalname).toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mimetype && extname) {
|
||||||
|
return cb(null, true);
|
||||||
|
}
|
||||||
|
cb(new Error("Только изображения (jpeg, jpg, png, gif) разрешены!"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Middleware для безопасности
|
// Middleware для безопасности
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
@ -24,18 +67,18 @@ app.use(
|
|||||||
],
|
],
|
||||||
styleSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"],
|
styleSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"],
|
||||||
fontSrc: ["'self'", "https://cdnjs.cloudflare.com", "data:"],
|
fontSrc: ["'self'", "https://cdnjs.cloudflare.com", "data:"],
|
||||||
imgSrc: ["'self'", "data:"],
|
imgSrc: ["'self'", "data:", "blob:"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ограничение запросов
|
// Ограничение запросов (отключено для разработки)
|
||||||
const limiter = rateLimit({
|
// const limiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000, // 15 минут
|
// windowMs: 15 * 60 * 1000, // 15 минут
|
||||||
max: 100, // максимум 100 запросов с одного IP
|
// max: 100, // максимум 100 запросов с одного IP
|
||||||
});
|
// });
|
||||||
app.use(limiter);
|
// app.use(limiter);
|
||||||
|
|
||||||
// Статические файлы
|
// Статические файлы
|
||||||
app.use(express.static(path.join(__dirname, "public")));
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
@ -69,9 +112,22 @@ function createTables() {
|
|||||||
const createNotesTable = `
|
const createNotesTable = `
|
||||||
CREATE TABLE IF NOT EXISTS notes (
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
date TEXT NOT NULL,
|
date TEXT NOT NULL,
|
||||||
time TEXT NOT NULL,
|
time TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const createUsersTable = `
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
email TEXT,
|
||||||
|
avatar TEXT,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
`;
|
`;
|
||||||
@ -83,6 +139,14 @@ function createTables() {
|
|||||||
console.log("Таблица заметок готова");
|
console.log("Таблица заметок готова");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.run(createUsersTable, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка создания таблицы пользователей:", err.message);
|
||||||
|
} else {
|
||||||
|
console.log("Таблица пользователей готова");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middleware для аутентификации
|
// Middleware для аутентификации
|
||||||
@ -104,7 +168,105 @@ app.get("/", (req, res) => {
|
|||||||
res.sendFile(path.join(__dirname, "public", "index.html"));
|
res.sendFile(path.join(__dirname, "public", "index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработка входа
|
// Страница регистрации
|
||||||
|
app.get("/register", (req, res) => {
|
||||||
|
if (req.session.authenticated) {
|
||||||
|
return res.redirect("/notes");
|
||||||
|
}
|
||||||
|
res.sendFile(path.join(__dirname, "public", "register.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// API регистрации
|
||||||
|
app.post("/api/register", async (req, res) => {
|
||||||
|
const { username, password, confirmPassword } = req.body;
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (!username || !password || !confirmPassword) {
|
||||||
|
return res.status(400).json({ error: "Все поля обязательны" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username.length < 3) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Логин должен быть не менее 3 символов" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Пароль должен быть не менее 6 символов" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
return res.status(400).json({ error: "Пароли не совпадают" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Хешируем пароль
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
// Вставляем пользователя в БД
|
||||||
|
const sql = "INSERT INTO users (username, password) VALUES (?, ?)";
|
||||||
|
db.run(sql, [username, hashedPassword], function (err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.message.includes("UNIQUE constraint failed")) {
|
||||||
|
return res.status(400).json({ error: "Этот логин уже занят" });
|
||||||
|
}
|
||||||
|
console.error("Ошибка регистрации:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Автоматически логиним пользователя после регистрации
|
||||||
|
req.session.userId = this.lastID;
|
||||||
|
req.session.username = username;
|
||||||
|
req.session.authenticated = true;
|
||||||
|
res.json({ success: true, message: "Регистрация успешна" });
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка при хешировании:", err);
|
||||||
|
res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// API входа
|
||||||
|
app.post("/api/login", async (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return res.status(400).json({ error: "Логин и пароль обязательны" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql = "SELECT * FROM users WHERE username = ?";
|
||||||
|
db.get(sql, [username], async (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка входа:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: "Неверный логин или пароль" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const validPassword = await bcrypt.compare(password, user.password);
|
||||||
|
|
||||||
|
if (!validPassword) {
|
||||||
|
return res.status(401).json({ error: "Неверный логин или пароль" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Успешный вход
|
||||||
|
req.session.userId = user.id;
|
||||||
|
req.session.username = user.username;
|
||||||
|
req.session.authenticated = true;
|
||||||
|
res.json({ success: true, message: "Вход успешен" });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка при сравнении паролей:", err);
|
||||||
|
res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработка входа (старый маршрут для совместимости)
|
||||||
app.post("/login", async (req, res) => {
|
app.post("/login", async (req, res) => {
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
const correctPassword = process.env.APP_PASSWORD;
|
const correctPassword = process.env.APP_PASSWORD;
|
||||||
@ -117,6 +279,27 @@ app.post("/login", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// API для получения информации о пользователе
|
||||||
|
app.get("/api/user", requireAuth, (req, res) => {
|
||||||
|
if (!req.session.userId) {
|
||||||
|
return res.status(401).json({ error: "Не аутентифицирован" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql = "SELECT username, email, avatar FROM users WHERE id = ?";
|
||||||
|
db.get(sql, [req.session.userId], (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения данных пользователя:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: "Пользователь не найден" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Страница с заметками (требует аутентификации)
|
// Страница с заметками (требует аутентификации)
|
||||||
app.get("/notes", requireAuth, (req, res) => {
|
app.get("/notes", requireAuth, (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, "public", "notes.html"));
|
res.sendFile(path.join(__dirname, "public", "notes.html"));
|
||||||
@ -124,9 +307,9 @@ app.get("/notes", requireAuth, (req, res) => {
|
|||||||
|
|
||||||
// API для получения всех заметок
|
// API для получения всех заметок
|
||||||
app.get("/api/notes", requireAuth, (req, res) => {
|
app.get("/api/notes", requireAuth, (req, res) => {
|
||||||
const sql = "SELECT * FROM notes ORDER BY created_at ASC";
|
const sql = "SELECT * FROM notes WHERE user_id = ? ORDER BY created_at ASC";
|
||||||
|
|
||||||
db.all(sql, [], (err, rows) => {
|
db.all(sql, [req.session.userId], (err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка получения заметок:", err.message);
|
console.error("Ошибка получения заметок:", err.message);
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
@ -143,8 +326,9 @@ app.post("/api/notes", requireAuth, (req, res) => {
|
|||||||
return res.status(400).json({ error: "Не все поля заполнены" });
|
return res.status(400).json({ error: "Не все поля заполнены" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sql = "INSERT INTO notes (content, date, time) VALUES (?, ?, ?)";
|
const sql =
|
||||||
const params = [content, date, time];
|
"INSERT INTO notes (user_id, content, date, time) VALUES (?, ?, ?, ?)";
|
||||||
|
const params = [req.session.userId, content, date, time];
|
||||||
|
|
||||||
db.run(sql, params, function (err) {
|
db.run(sql, params, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -164,18 +348,36 @@ app.put("/api/notes/:id", requireAuth, (req, res) => {
|
|||||||
return res.status(400).json({ error: "Не все поля заполнены" });
|
return res.status(400).json({ error: "Не все поля заполнены" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sql = "UPDATE notes SET content = ?, date = ?, time = ? WHERE id = ?";
|
// Проверяем, что заметка принадлежит текущему пользователю
|
||||||
const params = [content, date, time, id];
|
const checkSql = "SELECT user_id FROM notes WHERE id = ?";
|
||||||
|
db.get(checkSql, [id], (err, row) => {
|
||||||
db.run(sql, params, function (err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка обновления заметки:", err.message);
|
console.error("Ошибка проверки доступа:", err.message);
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
}
|
}
|
||||||
if (this.changes === 0) {
|
|
||||||
|
if (!row) {
|
||||||
return res.status(404).json({ error: "Заметка не найдена" });
|
return res.status(404).json({ error: "Заметка не найдена" });
|
||||||
}
|
}
|
||||||
res.json({ id, content, date, time });
|
|
||||||
|
if (row.user_id !== req.session.userId) {
|
||||||
|
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSql =
|
||||||
|
"UPDATE notes SET content = ?, date = ?, time = ? WHERE id = ?";
|
||||||
|
const params = [content, date, time, id];
|
||||||
|
|
||||||
|
db.run(updateSql, params, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка обновления заметки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: "Заметка не найдена" });
|
||||||
|
}
|
||||||
|
res.json({ id, content, date, time });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,17 +385,216 @@ app.put("/api/notes/:id", requireAuth, (req, res) => {
|
|||||||
app.delete("/api/notes/:id", requireAuth, (req, res) => {
|
app.delete("/api/notes/:id", requireAuth, (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const sql = "DELETE FROM notes WHERE id = ?";
|
// Проверяем, что заметка принадлежит текущему пользователю
|
||||||
|
const checkSql = "SELECT user_id FROM notes WHERE id = ?";
|
||||||
db.run(sql, id, function (err) {
|
db.get(checkSql, [id], (err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Ошибка удаления заметки:", err.message);
|
console.error("Ошибка проверки доступа:", err.message);
|
||||||
return res.status(500).json({ error: "Ошибка сервера" });
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
}
|
}
|
||||||
if (this.changes === 0) {
|
|
||||||
|
if (!row) {
|
||||||
return res.status(404).json({ error: "Заметка не найдена" });
|
return res.status(404).json({ error: "Заметка не найдена" });
|
||||||
}
|
}
|
||||||
res.json({ message: "Заметка удалена" });
|
|
||||||
|
if (row.user_id !== req.session.userId) {
|
||||||
|
return res.status(403).json({ error: "Нет доступа к этой заметке" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSql = "DELETE FROM notes WHERE id = ?";
|
||||||
|
|
||||||
|
db.run(deleteSql, id, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления заметки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: "Заметка не найдена" });
|
||||||
|
}
|
||||||
|
res.json({ message: "Заметка удалена" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Страница личного кабинета
|
||||||
|
app.get("/profile", requireAuth, (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, "public", "profile.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// API для обновления профиля
|
||||||
|
app.put("/api/user/profile", requireAuth, async (req, res) => {
|
||||||
|
const { username, email, currentPassword, newPassword } = req.body;
|
||||||
|
const userId = req.session.userId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем текущие данные пользователя
|
||||||
|
const getUserSql = "SELECT * FROM users WHERE id = ?";
|
||||||
|
db.get(getUserSql, [userId], async (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения пользователя:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: "Пользователь не найден" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (username && username.length < 3) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Логин должен быть не менее 3 символов" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если пользователь хочет изменить пароль, проверяем текущий пароль
|
||||||
|
if (newPassword) {
|
||||||
|
if (!currentPassword) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Введите текущий пароль для изменения пароля" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPassword = await bcrypt.compare(
|
||||||
|
currentPassword,
|
||||||
|
user.password
|
||||||
|
);
|
||||||
|
if (!validPassword) {
|
||||||
|
return res.status(401).json({ error: "Неверный текущий пароль" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length < 6) {
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: "Новый пароль должен быть не менее 6 символов" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем SQL запрос для обновления
|
||||||
|
let updateFields = [];
|
||||||
|
let params = [];
|
||||||
|
|
||||||
|
if (username && username !== user.username) {
|
||||||
|
updateFields.push("username = ?");
|
||||||
|
params.push(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email !== undefined) {
|
||||||
|
updateFields.push("email = ?");
|
||||||
|
params.push(email || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword) {
|
||||||
|
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||||
|
updateFields.push("password = ?");
|
||||||
|
params.push(hashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateFields.length === 0) {
|
||||||
|
return res.json({ success: true, message: "Нет изменений" });
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push(userId);
|
||||||
|
const updateSql = `UPDATE users SET ${updateFields.join(
|
||||||
|
", "
|
||||||
|
)} WHERE id = ?`;
|
||||||
|
|
||||||
|
db.run(updateSql, params, function (err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.message.includes("UNIQUE constraint failed")) {
|
||||||
|
return res.status(400).json({ error: "Этот логин уже занят" });
|
||||||
|
}
|
||||||
|
console.error("Ошибка обновления профиля:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем имя пользователя в сессии, если оно изменилось
|
||||||
|
if (username && username !== user.username) {
|
||||||
|
req.session.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, message: "Профиль успешно обновлен" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Ошибка обновления профиля:", err);
|
||||||
|
res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// API для загрузки аватарки
|
||||||
|
app.post(
|
||||||
|
"/api/user/avatar",
|
||||||
|
requireAuth,
|
||||||
|
upload.single("avatar"),
|
||||||
|
(req, res) => {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: "Файл не загружен" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarPath = "/uploads/" + req.file.filename;
|
||||||
|
const userId = req.session.userId;
|
||||||
|
|
||||||
|
// Получаем старую аватарку для удаления
|
||||||
|
const getOldAvatarSql = "SELECT avatar FROM users WHERE id = ?";
|
||||||
|
db.get(getOldAvatarSql, [userId], (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения старой аватарки:", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем старую аватарку, если она существует
|
||||||
|
if (user && user.avatar) {
|
||||||
|
const oldAvatarPath = path.join(__dirname, "public", user.avatar);
|
||||||
|
if (fs.existsSync(oldAvatarPath)) {
|
||||||
|
fs.unlinkSync(oldAvatarPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем путь к аватарке в БД
|
||||||
|
const updateSql = "UPDATE users SET avatar = ? WHERE id = ?";
|
||||||
|
db.run(updateSql, [avatarPath, userId], function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка обновления аватарки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, avatar: avatarPath });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// API для удаления аватарки
|
||||||
|
app.delete("/api/user/avatar", requireAuth, (req, res) => {
|
||||||
|
const userId = req.session.userId;
|
||||||
|
|
||||||
|
// Получаем текущую аватарку
|
||||||
|
const getAvatarSql = "SELECT avatar FROM users WHERE id = ?";
|
||||||
|
db.get(getAvatarSql, [userId], (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка получения аватарки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || !user.avatar) {
|
||||||
|
return res.status(400).json({ error: "Аватарка не установлена" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем файл аватарки
|
||||||
|
const avatarPath = path.join(__dirname, "public", user.avatar);
|
||||||
|
if (fs.existsSync(avatarPath)) {
|
||||||
|
fs.unlinkSync(avatarPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем БД
|
||||||
|
const updateSql = "UPDATE users SET avatar = NULL WHERE id = ?";
|
||||||
|
db.run(updateSql, [userId], function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error("Ошибка удаления аватарки:", err.message);
|
||||||
|
return res.status(500).json({ error: "Ошибка сервера" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, message: "Аватарка удалена" });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
340
style.css
340
style.css
@ -1,162 +1,178 @@
|
|||||||
body {
|
body {
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 50px; /* Минимальная высота */
|
min-height: 50px; /* Минимальная высота */
|
||||||
resize: none; /* Отключаем возможность ручного изменения размера */
|
resize: none; /* Отключаем возможность ручного изменения размера */
|
||||||
border: none;
|
border: none;
|
||||||
background: white;
|
background: white;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
overflow-y: hidden; /* Отключаем полосу прокрутки по вертикали */
|
overflow-y: hidden; /* Отключаем полосу прокрутки по вертикали */
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
outline: none; /* Убираем обводку */
|
outline: none; /* Убираем обводку */
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnSave {
|
.btnSave {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notesHeaderBtn {
|
.notesHeaderBtn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textNote {
|
.textNote {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Убираем стандартные отступы для абзацев */
|
/* Убираем стандартные отступы для абзацев */
|
||||||
.textNote p {
|
.textNote p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Убираем маргины у заголовков */
|
/* Убираем маргины у заголовков */
|
||||||
.textNote h1,
|
.textNote h1,
|
||||||
.textNote h2,
|
.textNote h2,
|
||||||
.textNote h3,
|
.textNote h3,
|
||||||
.textNote h4,
|
.textNote h4,
|
||||||
.textNote h5,
|
.textNote h5,
|
||||||
.textNote h6 {
|
.textNote h6 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Убираем отступы у списков */
|
/* Убираем отступы у списков */
|
||||||
.textNote ul,
|
.textNote ul,
|
||||||
.textNote ol {
|
.textNote ol {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Убираем маргины у элементов списка */
|
/* Убираем маргины у элементов списка */
|
||||||
.textNote li {
|
.textNote li {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для ссылок */
|
/* Стили для ссылок */
|
||||||
.textNote a {
|
.textNote a {
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textNote a:hover {
|
.textNote a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для кода */
|
/* Стили для цитат */
|
||||||
.textNote pre {
|
.textNote blockquote {
|
||||||
background-color: #f5f5f5;
|
border-left: 4px solid #007bff;
|
||||||
padding: 10px;
|
padding-left: 16px;
|
||||||
border-radius: 5px;
|
margin: 10px 0;
|
||||||
font-size: 14px;
|
color: #555;
|
||||||
overflow-x: auto;
|
font-style: italic;
|
||||||
}
|
background-color: #f8f9fa;
|
||||||
|
padding: 10px 16px;
|
||||||
.textNote code {
|
border-radius: 0 4px 4px 0;
|
||||||
background-color: #f5f5f5;
|
}
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 5px;
|
.textNote blockquote p {
|
||||||
font-size: 14px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notes-container {
|
/* Стили для кода */
|
||||||
width: 100%;
|
.textNote pre {
|
||||||
display: flex;
|
background-color: #f5f5f5;
|
||||||
flex-direction: column; /* Располагаем элементы в колонку */
|
padding: 10px;
|
||||||
align-items: center; /* Центрируем */
|
border-radius: 5px;
|
||||||
}
|
font-size: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
.markdown-buttons {
|
}
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
.textNote code {
|
||||||
}
|
background-color: #f5f5f5;
|
||||||
|
padding: 2px 4px;
|
||||||
.markdown-buttons .btnMarkdown {
|
border-radius: 5px;
|
||||||
padding: 5px 10px;
|
font-size: 14px;
|
||||||
margin-right: 5px;
|
}
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid #ddd;
|
.notes-container {
|
||||||
background-color: #f0f0f0;
|
width: 100%;
|
||||||
border-radius: 5px;
|
display: flex;
|
||||||
font-size: 14px;
|
flex-direction: column; /* Располагаем элементы в колонку */
|
||||||
}
|
align-items: center; /* Центрируем */
|
||||||
|
}
|
||||||
.markdown-buttons .btnMarkdown:hover {
|
|
||||||
background-color: #e0e0e0;
|
.markdown-buttons {
|
||||||
}
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
.footer {
|
}
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
.markdown-buttons .btnMarkdown {
|
||||||
color: #999;
|
padding: 5px 10px;
|
||||||
position: fixed;
|
margin-right: 5px;
|
||||||
bottom: 0;
|
cursor: pointer;
|
||||||
width: 100%;
|
border: 1px solid #ddd;
|
||||||
padding: 10px 0;
|
background-color: #f0f0f0;
|
||||||
}
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
.footer span {
|
}
|
||||||
font-weight: bold;
|
|
||||||
}
|
.markdown-buttons .btnMarkdown:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user