- Реализованы API для сохранения и получения AI настроек пользователя, включая OpenAI API ключ, базовый URL и модель. - Добавлена возможность окончательного удаления всех архивных заметок с подтверждением пароля. - Внедрена функция улучшения текста через AI, с обработкой запросов к OpenAI API. - Обновлены интерфейсы для работы с AI настройками и добавлены уведомления для улучшения пользовательского опыта.
308 lines
10 KiB
HTML
308 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||
/>
|
||
<title>Вход в систему заметок</title>
|
||
|
||
<script>
|
||
try {
|
||
if (localStorage.getItem("isAuthenticated") === "true") {
|
||
// Используем replace, чтобы не оставлять страницу логина в истории
|
||
window.location.replace("/notes");
|
||
}
|
||
} catch (e) {}
|
||
</script>
|
||
|
||
<!-- Предотвращение мерцания темы -->
|
||
<script>
|
||
(function () {
|
||
try {
|
||
const savedTheme = localStorage.getItem("theme");
|
||
const systemPrefersDark = window.matchMedia(
|
||
"(prefers-color-scheme: dark)"
|
||
).matches;
|
||
const theme = savedTheme || (systemPrefersDark ? "dark" : "light");
|
||
|
||
if (theme === "dark") {
|
||
document.documentElement.setAttribute("data-theme", "dark");
|
||
}
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
|
||
<!-- PWA Meta Tags -->
|
||
<meta
|
||
name="description"
|
||
content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря"
|
||
/>
|
||
<meta name="theme-color" content="#007bff" />
|
||
<meta name="mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta
|
||
name="apple-mobile-web-app-status-bar-style"
|
||
content="black-translucent"
|
||
/>
|
||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||
<meta name="apple-touch-fullscreen" content="yes" />
|
||
<meta name="msapplication-TileColor" content="#007bff" />
|
||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||
<meta name="application-name" content="NoteJS" />
|
||
<meta name="format-detection" content="telephone=no" />
|
||
|
||
<!-- Icons -->
|
||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||
<link
|
||
rel="icon"
|
||
type="image/png"
|
||
sizes="32x32"
|
||
href="/icons/icon-32x32.png"
|
||
/>
|
||
<link
|
||
rel="icon"
|
||
type="image/png"
|
||
sizes="16x16"
|
||
href="/icons/icon-16x16.png"
|
||
/>
|
||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="114x114"
|
||
href="/icons/icon-128x128.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="120x120"
|
||
href="/icons/icon-128x128.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="144x144"
|
||
href="/icons/icon-144x144.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="152x152"
|
||
href="/icons/icon-152x152.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="180x180"
|
||
href="/icons/icon-192x192.png"
|
||
/>
|
||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||
|
||
<!-- Manifest -->
|
||
<link rel="manifest" href="/manifest.json" />
|
||
|
||
<!-- Styles -->
|
||
<link rel="stylesheet" href="/style.css" />
|
||
|
||
<!-- Scripts -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
"
|
||
>
|
||
<span
|
||
><span class="iconify" data-icon="mdi:login"></span> Вход в
|
||
систему</span
|
||
>
|
||
<button
|
||
id="theme-toggle-btn"
|
||
class="theme-toggle-btn"
|
||
title="Переключить тему"
|
||
>
|
||
<span class="iconify" data-icon="mdi:theme-light-dark"></span>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<div class="login-form">
|
||
<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">
|
||
<label for="password">Пароль:</label>
|
||
<input
|
||
type="password"
|
||
id="password"
|
||
name="password"
|
||
required
|
||
placeholder="Введите пароль"
|
||
/>
|
||
</div>
|
||
<button type="submit" class="btnSave">Войти</button>
|
||
</form>
|
||
<div
|
||
id="errorMessage"
|
||
class="error-message"
|
||
style="display: none"
|
||
></div>
|
||
<p class="auth-link">
|
||
Нет аккаунта? <a href="/register">Зарегистрируйтесь</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="footer">
|
||
<p>Создатель: <span>Fovway</span></p>
|
||
</div>
|
||
<script src="/login.js"></script>
|
||
|
||
<!-- PWA Service Worker Registration -->
|
||
<script>
|
||
// Универсальная функция для модальных окон подтверждения
|
||
function showConfirmModal(title, message, options = {}) {
|
||
return new Promise((resolve) => {
|
||
// Создаем модальное окно
|
||
const modal = document.createElement("div");
|
||
modal.className = "modal";
|
||
modal.style.display = "block";
|
||
|
||
// Создаем содержимое модального окна
|
||
const modalContent = document.createElement("div");
|
||
modalContent.className = "modal-content";
|
||
|
||
// Создаем заголовок
|
||
const modalHeader = document.createElement("div");
|
||
modalHeader.className = "modal-header";
|
||
modalHeader.innerHTML = `
|
||
<h3>${title}</h3>
|
||
<span class="modal-close">×</span>
|
||
`;
|
||
|
||
// Создаем тело модального окна
|
||
const modalBody = document.createElement("div");
|
||
modalBody.className = "modal-body";
|
||
modalBody.innerHTML = `<p>${message}</p>`;
|
||
|
||
// Создаем футер с кнопками
|
||
const modalFooter = document.createElement("div");
|
||
modalFooter.className = "modal-footer";
|
||
modalFooter.innerHTML = `
|
||
<button id="confirmBtn" class="${
|
||
options.confirmType === "danger" ? "btn-danger" : "btn-primary"
|
||
}" style="margin-right: 10px">
|
||
${options.confirmText || "OK"}
|
||
</button>
|
||
<button id="cancelBtn" class="btn-secondary">
|
||
${options.cancelText || "Отмена"}
|
||
</button>
|
||
`;
|
||
|
||
// Собираем модальное окно
|
||
modalContent.appendChild(modalHeader);
|
||
modalContent.appendChild(modalBody);
|
||
modalContent.appendChild(modalFooter);
|
||
modal.appendChild(modalContent);
|
||
|
||
// Добавляем на страницу
|
||
document.body.appendChild(modal);
|
||
|
||
// Функция закрытия
|
||
function closeModal() {
|
||
modal.style.display = "none";
|
||
if (modal.parentNode) {
|
||
modal.parentNode.removeChild(modal);
|
||
}
|
||
}
|
||
|
||
// Обработчики событий
|
||
const closeBtn = modalHeader.querySelector(".modal-close");
|
||
const cancelBtn = modalFooter.querySelector("#cancelBtn");
|
||
const confirmBtn = modalFooter.querySelector("#confirmBtn");
|
||
|
||
closeBtn.addEventListener("click", () => {
|
||
closeModal();
|
||
resolve(false);
|
||
});
|
||
|
||
cancelBtn.addEventListener("click", () => {
|
||
closeModal();
|
||
resolve(false);
|
||
});
|
||
|
||
confirmBtn.addEventListener("click", () => {
|
||
closeModal();
|
||
resolve(true);
|
||
});
|
||
|
||
// Закрытие при клике вне модального окна
|
||
modal.addEventListener("click", (e) => {
|
||
if (e.target === modal) {
|
||
closeModal();
|
||
resolve(false);
|
||
}
|
||
});
|
||
|
||
// Закрытие по Escape
|
||
const handleEscape = (e) => {
|
||
if (e.key === "Escape") {
|
||
closeModal();
|
||
resolve(false);
|
||
document.removeEventListener("keydown", handleEscape);
|
||
}
|
||
};
|
||
document.addEventListener("keydown", handleEscape);
|
||
});
|
||
}
|
||
|
||
if ("serviceWorker" in navigator) {
|
||
window.addEventListener("load", () => {
|
||
navigator.serviceWorker
|
||
.register("/sw.js")
|
||
.then((registration) => {
|
||
console.log("SW зарегистрирован успешно:", registration.scope);
|
||
|
||
// Проверяем обновления
|
||
registration.addEventListener("updatefound", () => {
|
||
const newWorker = registration.installing;
|
||
newWorker.addEventListener("statechange", async () => {
|
||
if (
|
||
newWorker.state === "installed" &&
|
||
navigator.serviceWorker.controller
|
||
) {
|
||
// Новый контент доступен, можно показать уведомление
|
||
const confirmed = await showConfirmModal(
|
||
"Обновление приложения",
|
||
"Доступна новая версия приложения. Обновить?",
|
||
{ confirmText: "Обновить" }
|
||
);
|
||
if (confirmed) {
|
||
newWorker.postMessage({ type: "SKIP_WAITING" });
|
||
window.location.reload();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.log("Ошибка регистрации SW:", error);
|
||
});
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|