feat: frontend, sqlite integration

This commit is contained in:
2025-12-26 21:05:11 +03:00
parent 869bc60b60
commit ca4d4cd685
8 changed files with 1254 additions and 224 deletions

View File

@@ -0,0 +1,493 @@
/* Основные стили для приложения заметок */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--success-color: #4cc9f0;
--danger-color: #f72585;
--warning-color: #ff9e00;
--light-color: #f8f9fa;
--dark-color: #212529;
--gray-color: #6c757d;
--light-gray: #e9ecef;
--border-color: #dee2e6;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-hover: 0 6px 12px rgba(0, 0, 0, 0.15);
--border-radius: 8px;
--transition: all 0.3s ease;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--dark-color);
background-color: #f5f7fb;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
overflow: hidden;
}
/* Шапка */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
}
.logo {
display: flex;
align-items: center;
gap: 15px;
}
.logo i {
font-size: 2.5rem;
}
.logo h1 {
font-size: 1.8rem;
font-weight: 600;
color: var(--light-color);
}
.stats {
display: flex;
align-items: center;
gap: 20px;
}
#notes-count {
font-size: 1.1rem;
background-color: rgba(255, 255, 255, 0.2);
padding: 8px 15px;
border-radius: 50px;
}
/* Кнопки */
.btn {
padding: 10px 20px;
border: none;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-hover);
}
.btn:active {
transform: translateY(0);
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--secondary-color);
}
.btn-secondary {
background-color: var(--light-gray);
color: var(--dark-color);
}
.btn-secondary:hover {
background-color: #d1d5db;
}
.btn-success {
background-color: var(--success-color);
color: white;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-warning {
background-color: var(--warning-color);
color: white;
}
.btn-refresh {
background-color: white;
color: var(--primary-color);
}
.btn-refresh:hover {
background-color: var(--light-gray);
}
/* Основной контент */
.main-content {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
padding: 30px;
}
@media (min-width: 992px) {
.main-content {
grid-template-columns: 1fr 1fr;
}
}
.note-form-section, .notes-section {
background-color: white;
padding: 25px;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
}
.note-form-section h2, .notes-section h2 {
margin-bottom: 20px;
color: var(--primary-color);
padding-bottom: 10px;
border-bottom: 2px solid var(--light-gray);
}
/* Форма */
.note-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 600;
color: var(--dark-color);
}
.form-group input,
.form-group textarea {
padding: 12px 15px;
border: 2px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 1rem;
transition: var(--transition);
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
}
.form-actions {
display: flex;
gap: 15px;
margin-top: 10px;
}
/* Фильтры и поиск */
.filters {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
}
@media (min-width: 768px) {
.filters {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
.search-box {
position: relative;
flex-grow: 1;
}
.search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--gray-color);
}
.search-box input {
width: 100%;
padding: 12px 15px 12px 45px;
border: 2px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 1rem;
}
.search-box input:focus {
outline: none;
border-color: var(--primary-color);
}
.sort-options select {
padding: 12px 15px;
border: 2px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 1rem;
background-color: white;
cursor: pointer;
min-width: 200px;
}
/* Контейнер заметок */
.notes-container {
display: flex;
flex-direction: column;
gap: 20px;
}
/* Карточка заметки */
.note-card {
background-color: white;
border-radius: var(--border-radius);
padding: 20px;
box-shadow: var(--shadow);
border-left: 5px solid var(--primary-color);
transition: var(--transition);
display: flex;
flex-direction: column;
gap: 15px;
}
.note-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-hover);
}
.note-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 10px;
}
.note-title {
font-size: 1.3rem;
font-weight: 600;
color: var(--dark-color);
margin: 0;
word-break: break-word;
}
.note-actions {
display: flex;
gap: 10px;
}
.note-action-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
padding: 5px;
border-radius: 4px;
transition: var(--transition);
color: var(--gray-color);
}
.note-action-btn:hover {
background-color: var(--light-gray);
color: var(--dark-color);
}
.note-action-btn.edit:hover {
color: var(--primary-color);
}
.note-action-btn.delete:hover {
color: var(--danger-color);
}
.note-text {
color: var(--gray-color);
line-height: 1.5;
word-break: break-word;
flex-grow: 1;
}
.note-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 15px;
border-top: 1px solid var(--light-gray);
font-size: 0.9rem;
color: var(--gray-color);
}
.note-author {
font-weight: 600;
color: var(--primary-color);
}
.note-date {
text-align: right;
}
/* Состояния */
.loading {
text-align: center;
padding: 40px;
color: var(--gray-color);
}
.loading i {
font-size: 3rem;
margin-bottom: 15px;
color: var(--primary-color);
}
.empty-state {
text-align: center;
padding: 50px 20px;
color: var(--gray-color);
display: none;
}
.empty-state i {
font-size: 4rem;
margin-bottom: 15px;
color: var(--light-gray);
}
.empty-state h3 {
margin-bottom: 10px;
color: var(--dark-color);
}
/* Подвал */
.footer {
text-align: center;
padding: 20px;
background-color: var(--light-gray);
color: var(--gray-color);
border-top: 1px solid var(--border-color);
}
#backend-status {
font-weight: 600;
padding: 4px 10px;
border-radius: 4px;
}
.backend-connected {
background-color: rgba(76, 201, 240, 0.2);
color: #0c5460;
}
.backend-disconnected {
background-color: rgba(247, 37, 133, 0.2);
color: #721c24;
}
.api-info {
font-size: 0.9rem;
margin-top: 5px;
}
/* Модальное окно */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-content {
background-color: white;
padding: 30px;
border-radius: var(--border-radius);
max-width: 500px;
width: 90%;
box-shadow: var(--shadow-hover);
}
.modal-content h3 {
margin-bottom: 15px;
color: var(--danger-color);
}
.modal-content p {
margin-bottom: 25px;
line-height: 1.6;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 15px;
}
/* Уведомления */
.notification {
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 25px;
border-radius: var(--border-radius);
background-color: var(--primary-color);
color: white;
box-shadow: var(--shadow);
transform: translateY(100px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
z-index: 1001;
}
.notification.show {
transform: translateY(0);
opacity: 1;
}
.notification.success {
background-color: var(--success-color);
}
.notification.error {
background-color: var(--danger-color);
}
.notification.warning {
background-color: var(--warning-color);
}

View File

@@ -1,12 +1,136 @@
<!DOCTYPE html>
<html lang="ru" >
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notes App - Управление заметками</title>
<link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<h1>Привет с фронта!</h1>
<p>Вот мы и встретились... снова.</p>
<div class="container">
<!-- Шапка приложения -->
<header class="header">
<div class="logo">
<i class="fas fa-sticky-note"></i>
<h1>Мои Заметки</h1>
</div>
<div class="stats">
<span id="notes-count">0 заметок</span>
<button class="btn btn-refresh" id="refresh-btn">
<i class="fas fa-sync-alt"></i> Обновить
</button>
</div>
</header>
<!-- Основной контент -->
<main class="main-content">
<!-- Форма для создания/редактирования заметки -->
<section class="note-form-section">
<h2 id="form-title">Создать новую заметку</h2>
<form id="note-form" class="note-form">
<input type="hidden" id="note-id">
<div class="form-group">
<label for="title">Заголовок</label>
<input type="text" id="title" required
placeholder="Введите заголовок заметки...">
</div>
<div class="form-group">
<label for="text">Текст заметки</label>
<textarea id="text" rows="4" required
placeholder="Введите текст заметки..."></textarea>
</div>
<div class="form-group">
<label for="author">Автор</label>
<input type="text" id="author" required
placeholder="Ваше имя">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="save-btn">
<i class="fas fa-save"></i> Сохранить заметку
</button>
<button type="button" class="btn btn-secondary" id="clear-btn">
<i class="fas fa-times"></i> Очистить форму
</button>
</div>
</form>
</section>
<!-- Список заметок -->
<section class="notes-section">
<h2>Все заметки</h2>
<!-- Фильтры и поиск -->
<div class="filters">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="search-input"
placeholder="Поиск по заметкам...">
</div>
<div class="sort-options">
<select id="sort-select">
<option value="newest">Сначала новые</option>
<option value="oldest">Сначала старые</option>
<option value="title_asc">По заголовку (А-Я)</option>
<option value="title_desc">По заголовку (Я-А)</option>
</select>
</div>
</div>
<!-- Состояние загрузки -->
<div id="loading" class="loading">
<i class="fas fa-spinner fa-spin"></i>
<p>Загрузка заметок...</p>
</div>
<!-- Контейнер для заметок -->
<div id="notes-container" class="notes-container">
<!-- Заметки будут вставляться сюда динамически -->
</div>
<!-- Сообщение если заметок нет -->
<div id="empty-state" class="empty-state">
<i class="fas fa-clipboard"></i>
<h3>Нет заметок</h3>
<p>Создайте свою первую заметку, используя форму выше.</p>
</div>
</section>
</main>
<!-- Подвал -->
<footer class="footer">
<p>Notes App &copy; 2025 | Backend: <span id="backend-status">Не подключен</span></p>
<p class="api-info" id="api-info"></p>
</footer>
</div>
<!-- Модальное окно подтверждения удаления -->
<div id="delete-modal" class="modal">
<div class="modal-content">
<h3>Подтверждение удаления</h3>
<p>Вы уверены, что хотите удалить заметку "<span id="delete-note-title"></span>"?</p>
<div class="modal-actions">
<button id="confirm-delete" class="btn btn-danger">
<i class="fas fa-trash"></i> Удалить
</button>
<button id="cancel-delete" class="btn btn-secondary">
<i class="fas fa-times"></i> Отмена
</button>
</div>
</div>
</div>
<!-- Уведомления -->
<div id="notification" class="notification">
<span id="notification-text"></span>
</div>
<!-- Подключаем JavaScript -->
<script src="/js/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,485 @@
// Основной JavaScript файл для приложения заметок (упрощённая версия)
// Глобальные переменные состояния
var backendUrl = null;
var notes = [];
var noteToDelete = null;
// DOM элементы
var noteForm, noteIdInput, titleInput, textInput, authorInput;
var saveBtn, clearBtn, formTitle;
var notesContainer, loadingElement, emptyState, notesCount;
var refreshBtn, searchInput, sortSelect;
var deleteModal, deleteNoteTitle, confirmDeleteBtn, cancelDeleteBtn;
var notification, notificationText, backendStatus, apiInfo;
// Инициализация приложения после загрузки DOM
document.addEventListener('DOMContentLoaded', function() {
initializeElements();
setupEventListeners();
initApp();
console.log('Notes App инициализирован');
});
// Инициализация DOM элементов
function initializeElements() {
// Форма
noteForm = document.getElementById('note-form');
noteIdInput = document.getElementById('note-id');
titleInput = document.getElementById('title');
textInput = document.getElementById('text');
authorInput = document.getElementById('author');
saveBtn = document.getElementById('save-btn');
clearBtn = document.getElementById('clear-btn');
formTitle = document.getElementById('form-title');
// Контейнеры и состояние
notesContainer = document.getElementById('notes-container');
loadingElement = document.getElementById('loading');
emptyState = document.getElementById('empty-state');
notesCount = document.getElementById('notes-count');
// Кнопки и фильтры
refreshBtn = document.getElementById('refresh-btn');
searchInput = document.getElementById('search-input');
sortSelect = document.getElementById('sort-select');
// Модальное окно
deleteModal = document.getElementById('delete-modal');
deleteNoteTitle = document.getElementById('delete-note-title');
confirmDeleteBtn = document.getElementById('confirm-delete');
cancelDeleteBtn = document.getElementById('cancel-delete');
// Уведомления
notification = document.getElementById('notification');
notificationText = document.getElementById('notification-text');
// Статус
backendStatus = document.getElementById('backend-status');
apiInfo = document.getElementById('api-info');
}
// Основная инициализация приложения
async function initApp() {
try {
await loadConfig();
await loadNotes();
} catch (error) {
console.error('Ошибка инициализации приложения:', error);
showNotification('Не удалось инициализировать приложение', 'error');
}
}
// Загрузка конфигурации
async function loadConfig() {
try {
var response = await fetch('/config.json');
if (!response.ok) {
throw new Error('HTTP ' + response.status + ': ' + response.statusText);
}
var config = await response.json();
backendUrl = config.backendUrl;
if (!backendUrl) {
throw new Error('URL бэкенда не указан в конфигурации');
}
apiInfo.textContent = 'API: ' + backendUrl;
await checkBackendConnection();
return true;
} catch (error) {
console.error('Ошибка загрузки конфигурации:', error);
showNotification('Ошибка загрузки конфигурации: ' + error.message, 'error');
throw error;
}
}
// Проверка соединения с бэкендом
async function checkBackendConnection() {
try {
var response = await fetch(backendUrl + '/');
if (response.ok) {
backendStatus.textContent = 'Подключен';
backendStatus.className = 'backend-connected';
return true;
} else {
backendStatus.textContent = 'Ошибка подключения';
backendStatus.className = 'backend-disconnected';
return false;
}
} catch (error) {
backendStatus.textContent = 'Не подключен';
backendStatus.className = 'backend-disconnected';
return false;
}
}
// Настройка обработчиков событий
function setupEventListeners() {
noteForm.addEventListener('submit', handleFormSubmit);
clearBtn.addEventListener('click', clearForm);
refreshBtn.addEventListener('click', loadNotes);
searchInput.addEventListener('input', filterNotes);
sortSelect.addEventListener('change', sortNotes);
confirmDeleteBtn.addEventListener('click', deleteNote);
cancelDeleteBtn.addEventListener('click', closeDeleteModal);
deleteModal.addEventListener('click', function(e) {
if (e.target === deleteModal) {
closeDeleteModal();
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && deleteModal.style.display === 'flex') {
closeDeleteModal();
}
});
}
// Загрузка заметок с сервера
async function loadNotes() {
if (!backendUrl) {
showNotification('Конфигурация не загружена. Пожалуйста, обновите страницу.', 'error');
showLoading(false);
return;
}
showLoading(true);
try {
var response = await fetch(backendUrl + '/notes');
if (!response.ok) {
throw new Error('HTTP ошибка! статус: ' + response.status);
}
var result = await response.json();
if (result.success) {
notes = result.data;
renderNotes();
updateNotesCount();
showNotification('Загружено ' + result.count + ' заметок', 'success');
} else {
throw new Error(result.message || 'Не удалось загрузить заметки');
}
} catch (error) {
console.error('Ошибка загрузки заметок:', error);
showNotification('Ошибка загрузки: ' + error.message, 'error');
renderNotes();
} finally {
showLoading(false);
checkBackendConnection();
}
}
// Отображение списка заметок
function renderNotes() {
if (!notes || notes.length === 0) {
notesContainer.innerHTML = '';
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
var filteredNotes = filterNotesBySearch();
filteredNotes = sortNotesList(filteredNotes);
var notesHTML = filteredNotes.map(function(note) {
return createNoteHTML(note);
}).join('');
notesContainer.innerHTML = notesHTML;
addNoteActionListeners();
}
// Создание HTML для одной заметки
function createNoteHTML(note) {
var date = new Date(note.createdAt);
var formattedDate = date.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
var shortText = note.text.length > 100
? note.text.substring(0, 100) + '...'
: note.text;
return `
<div class="note-card" data-id="${note.id}">
<div class="note-header">
<h3 class="note-title">${escapeHtml(note.title)}</h3>
<div class="note-actions">
<button class="note-action-btn edit" title="Редактировать">
<i class="fas fa-edit"></i>
</button>
<button class="note-action-btn delete" title="Удалить">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<p class="note-text">${escapeHtml(shortText)}</p>
<div class="note-footer">
<div class="note-author">
<i class="fas fa-user"></i> ${escapeHtml(note.author)}
</div>
<div class="note-date" title="${date.toLocaleString('ru-RU')}">
<i class="far fa-calendar"></i> ${formattedDate}
</div>
</div>
</div>
`;
}
// Добавление обработчиков для кнопок действий в заметках
function addNoteActionListeners() {
var editButtons = document.querySelectorAll('.note-action-btn.edit');
editButtons.forEach(function(btn) {
btn.addEventListener('click', function(e) {
var noteCard = e.target.closest('.note-card');
var noteId = parseInt(noteCard.dataset.id);
editNote(noteId);
});
});
var deleteButtons = document.querySelectorAll('.note-action-btn.delete');
deleteButtons.forEach(function(btn) {
btn.addEventListener('click', function(e) {
var noteCard = e.target.closest('.note-card');
var noteId = parseInt(noteCard.dataset.id);
var noteTitle = noteCard.querySelector('.note-title').textContent;
openDeleteModal(noteId, noteTitle);
});
});
}
// Редактирование заметки
function editNote(noteId) {
var note = notes.find(function(n) {
return n.id === noteId;
});
if (!note) return;
noteIdInput.value = note.id;
titleInput.value = note.title;
textInput.value = note.text;
authorInput.value = note.author;
formTitle.textContent = 'Редактировать заметку';
saveBtn.innerHTML = '<i class="fas fa-save"></i> Обновить заметку';
titleInput.scrollIntoView({ behavior: 'smooth' });
titleInput.focus();
showNotification('Редактирование заметки: "' + note.title + '"', 'success');
}
// Открытие модального окна удаления
function openDeleteModal(noteId, noteTitle) {
noteToDelete = noteId;
deleteNoteTitle.textContent = noteTitle;
deleteModal.style.display = 'flex';
}
// Закрытие модального окна удаления
function closeDeleteModal() {
deleteModal.style.display = 'none';
noteToDelete = null;
}
// Удаление заметки
async function deleteNote() {
if (!noteToDelete) return;
try {
var response = await fetch(backendUrl + '/notes/' + noteToDelete, {
method: 'DELETE'
});
var result = await response.json();
if (result.success) {
showNotification('Заметка удалена: "' + result.data.title + '"', 'success');
loadNotes();
closeDeleteModal();
} else {
throw new Error(result.message || 'Не удалось удалить заметку');
}
} catch (error) {
console.error('Ошибка удаления заметки:', error);
showNotification('Ошибка удаления: ' + error.message, 'error');
closeDeleteModal();
}
}
// Обработка отправки формы
async function handleFormSubmit(e) {
e.preventDefault();
if (!backendUrl) {
showNotification('Конфигурация не загружена. Пожалуйста, обновите страницу.', 'error');
return;
}
var noteData = {
title: titleInput.value.trim(),
text: textInput.value.trim(),
author: authorInput.value.trim()
};
if (!noteData.title || !noteData.text || !noteData.author) {
showNotification('Пожалуйста, заполните все поля', 'warning');
return;
}
var noteId = noteIdInput.value;
var isEditing = !!noteId;
try {
var response, result;
if (isEditing) {
response = await fetch(backendUrl + '/notes/' + noteId, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(noteData)
});
result = await response.json();
if (result.success) {
showNotification('Заметка обновлена: "' + noteData.title + '"', 'success');
} else {
throw new Error(result.message || 'Не удалось обновить заметку');
}
} else {
response = await fetch(backendUrl + '/notes', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(noteData)
});
result = await response.json();
if (result.success) {
showNotification('Заметка создана: "' + noteData.title + '"', 'success');
} else {
throw new Error(result.message || 'Не удалось создать заметку');
}
}
clearForm();
loadNotes();
} catch (error) {
console.error('Ошибка сохранения заметки:', error);
showNotification('Ошибка сохранения: ' + error.message, 'error');
}
}
// Очистка формы
function clearForm() {
noteIdInput.value = '';
titleInput.value = '';
textInput.value = '';
authorInput.value = '';
formTitle.textContent = 'Создать новую заметку';
saveBtn.innerHTML = '<i class="fas fa-save"></i> Сохранить заметку';
titleInput.focus();
showNotification('Форма очищена', 'success');
}
// Фильтрация заметок по поисковому запросу
function filterNotesBySearch() {
var searchTerm = searchInput.value.toLowerCase().trim();
if (!searchTerm) {
return notes.slice();
}
return notes.filter(function(note) {
return note.title.toLowerCase().includes(searchTerm) ||
note.text.toLowerCase().includes(searchTerm) ||
note.author.toLowerCase().includes(searchTerm);
});
}
// Сортировка заметок
function sortNotesList(notesArray) {
var sortValue = sortSelect.value;
return notesArray.slice().sort(function(a, b) {
switch (sortValue) {
case 'newest':
return new Date(b.createdAt) - new Date(a.createdAt);
case 'oldest':
return new Date(a.createdAt) - new Date(b.createdAt);
case 'title_asc':
return a.title.localeCompare(b.title);
case 'title_desc':
return b.title.localeCompare(a.title);
default:
return 0;
}
});
}
// Обработчики для фильтрации и сортировки
function filterNotes() {
renderNotes();
}
function sortNotes() {
renderNotes();
}
// Обновление счетчика заметок
function updateNotesCount() {
var count = notes.length;
notesCount.textContent = count + ' ' + getRussianPlural(count, 'заметка', 'заметки', 'заметок');
}
// Вспомогательные функции
function showLoading(show) {
loadingElement.style.display = show ? 'block' : 'none';
notesContainer.style.display = show ? 'none' : 'grid';
}
function showNotification(message, type) {
notificationText.textContent = message;
notification.className = 'notification show ' + type;
setTimeout(function() {
notification.classList.remove('show');
}, 3000);
}
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getRussianPlural(number, one, few, many) {
var n = Math.abs(number) % 100;
var n1 = n % 10;
if (n > 10 && n < 20) return many;
if (n1 > 1 && n1 < 5) return few;
if (n1 === 1) return one;
return many;
}

View File

@@ -1,7 +0,0 @@
// Какой-то скрипт на JS, просто чтобы был
function displayMessage() {
document.getElementById('message').innerText = 'Hello World';
}
window.onload = displayMessage;