Bootstrap 5.3 as CSS base + B3 theme layer; dialogs isolated as .dlg; offcanvas sidebar; icons via SVG assets + masks
This commit is contained in:
@@ -16,6 +16,14 @@ body {
|
||||
Цвет несёт смысл: акцент — действия и фокус, зелёный/янтарный — статусы защиты,
|
||||
винный — деструктивные действия и негативная тональность. */
|
||||
:root {
|
||||
--bs-body-font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--bs-body-bg: #fafafa;
|
||||
--bs-body-color: #131316;
|
||||
--bs-border-color: #e6e6e9;
|
||||
--bs-border-radius: 0.5rem;
|
||||
--bs-border-radius-lg: 0.625rem;
|
||||
--bs-link-color: #7a2333;
|
||||
--bs-link-hover-color: #9d2b3c;
|
||||
--c-bg: #fafafa;
|
||||
--c-surface: #ffffff;
|
||||
--c-text: #131316;
|
||||
@@ -281,30 +289,30 @@ tr:last-child td { border-bottom: none; }
|
||||
.confirm-text { margin: 0; font-size: 0.875rem; white-space: pre-line; overflow-wrap: anywhere; }
|
||||
|
||||
/* Modals */
|
||||
dialog.modal {
|
||||
dialog.dlg {
|
||||
border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: 0;
|
||||
background: var(--c-surface); color: var(--c-text);
|
||||
width: min(34rem, calc(100vw - 2rem));
|
||||
box-shadow: 0 16px 48px rgba(19, 19, 22, 0.28);
|
||||
}
|
||||
dialog.modal::backdrop { background: rgba(19, 19, 22, 0.5); }
|
||||
dialog.dlg::backdrop { background: rgba(19, 19, 22, 0.5); }
|
||||
/* Пока открыта модалка — страница не прокручивается, позиция скролла сохраняется;
|
||||
прокрутка остаётся внутри .modal-body */
|
||||
html.modal-open body {
|
||||
position: fixed; left: 0; right: 0; width: 100%; top: var(--modal-top, 0);
|
||||
padding-right: var(--modal-scrollbar, 0);
|
||||
}
|
||||
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; border-bottom: 1px solid var(--c-border); }
|
||||
.modal-title { margin: 0; font-size: 1.0625rem; font-weight: 600; }
|
||||
.modal-close { border: none; background: none; cursor: pointer; font-size: 1.25rem; color: var(--c-text-muted); padding: 0 0.25rem; }
|
||||
.modal-close:hover { color: var(--c-text); }
|
||||
.modal-body { padding: 1.5rem; max-height: 70vh; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
.dlg-header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; border-bottom: 1px solid var(--c-border); }
|
||||
.dlg-title { margin: 0; font-size: 1.0625rem; font-weight: 600; }
|
||||
.dlg-close { border: none; background: none; cursor: pointer; font-size: 1.25rem; color: var(--c-text-muted); padding: 0 0.25rem; }
|
||||
.dlg-close:hover { color: var(--c-text); }
|
||||
.dlg-body { padding: 1.5rem; max-height: 70vh; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
/* Кнопки действий — внизу справа; при увеличенной модалке пустота снизу не остаётся */
|
||||
.modal-body > form.form-stack:last-child { flex: 1 1 auto; }
|
||||
.modal-body > form.form-stack:last-child > button:last-child,
|
||||
.modal-body > form.form-stack:last-child > .flex:last-child,
|
||||
.modal-body.form-stack > button:last-child,
|
||||
.modal-body.form-stack > .flex:last-child,
|
||||
.dlg-body > form.form-stack:last-child { flex: 1 1 auto; }
|
||||
.dlg-body > form.form-stack:last-child > button:last-child,
|
||||
.dlg-body > form.form-stack:last-child > .flex:last-child,
|
||||
.dlg-body.form-stack > button:last-child,
|
||||
.dlg-body.form-stack > .flex:last-child,
|
||||
.modal-body > button:last-child,
|
||||
.modal-body > .flex:last-child {
|
||||
margin-top: auto;
|
||||
@@ -462,8 +470,8 @@ code.code { padding: 0.125rem 0.5rem; border-radius: var(--radius-xs); font-fami
|
||||
.js-pub-count.is-hidden { display: none; }
|
||||
|
||||
/* Увеличенный диалог (оценка + комментарии) */
|
||||
dialog.modal.modal-lg { width: min(72rem, calc(100vw - 2rem)); }
|
||||
dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; }
|
||||
dialog.dlg.dlg-lg { width: min(72rem, calc(100vw - 2rem)); }
|
||||
dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
|
||||
.mini-btn svg, .cell-btn svg { width: 1rem; height: 1rem; }
|
||||
|
||||
.chip { display: inline-block; margin: 2px 4px 2px 0; padding: 2px 8px; border-radius: var(--radius-sm); font-size: 11.5px; line-height: 1.5; }
|
||||
@@ -738,28 +746,19 @@ th.th-filterable { padding-bottom: 0.35rem; }
|
||||
.sort-row .sort-title { flex: 1 1 auto; }
|
||||
}
|
||||
|
||||
/* ---- Мобильное меню: бургер в хэдере, ссылки в выезжающем сайдбаре справа ---- */
|
||||
/* ---- Мобильное меню: бургер в хэдере, сайдбар справа (BS offcanvas) ---- */
|
||||
.nav-burger { display: none; }
|
||||
.mobile-nav { display: none; }
|
||||
.mobile-nav-overlay { display: none; }
|
||||
#mobile-nav { --bs-offcanvas-width: 17rem; --bs-offcanvas-bg: var(--c-surface); --bs-offcanvas-border-color: var(--c-border); --bs-offcanvas-zindex: 120; }
|
||||
#mobile-nav .mobile-nav-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
|
||||
#mobile-nav .mobile-nav-links { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
#mobile-nav .nav-link { padding: 0.5rem 0.25rem; font-size: 1rem; }
|
||||
#mobile-nav .mobile-nav-user { display: flex; flex-direction: column; gap: 0.75rem; margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--c-border); }
|
||||
.mobile-nav-overlay { position: fixed; inset: 0; z-index: 1040; background: rgba(0, 0, 0, 0.45); }
|
||||
html.nav-open, html.nav-open body { overflow: hidden; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.nav { display: none; }
|
||||
.page-header .user { display: none; }
|
||||
.page-header .theme-btn { margin-left: auto; }
|
||||
.nav-burger { display: inline-flex; }
|
||||
.mobile-nav {
|
||||
display: flex; flex-direction: column; gap: 1rem;
|
||||
position: fixed; top: 0; right: 0; bottom: 0; left: auto; width: 17rem; max-width: 86vw;
|
||||
z-index: 120; background: var(--c-surface);
|
||||
border-right: none; border-left: 1px solid var(--c-border);
|
||||
padding: 1rem; overflow-y: auto;
|
||||
transform: translateX(100%); transition: transform 0.2s;
|
||||
}
|
||||
.mobile-nav.open { transform: none; }
|
||||
.mobile-nav-overlay { display: block; position: fixed; inset: 0; z-index: 110; background: rgba(0, 0, 0, 0.45); }
|
||||
.mobile-nav-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
.mobile-nav-links { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.mobile-nav-links .nav-link { padding: 0.5rem 0.25rem; font-size: 1rem; }
|
||||
.mobile-nav-user { display: flex; flex-direction: column; gap: 0.75rem; margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--c-border); }
|
||||
}
|
||||
html.nav-open, html.nav-open body { overflow: hidden; }
|
||||
|
||||
6
web/src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
6
web/src/main/resources/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -166,7 +166,7 @@ function attachCommentAutocomplete(input) {
|
||||
/* Если снизу внутри модалки мало места — раскрываем список вверх, чтобы он не обрезался. */
|
||||
function positionList() {
|
||||
var wrap = input.parentNode;
|
||||
var scrollParent = input.closest('.modal-body') || document.documentElement;
|
||||
var scrollParent = input.closest('.dlg-body') || document.documentElement;
|
||||
var wr = wrap.getBoundingClientRect();
|
||||
var sr = scrollParent.getBoundingClientRect();
|
||||
var below = sr.bottom - wr.bottom;
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
document.addEventListener('click', function (e) {
|
||||
var el = e.target;
|
||||
|
||||
var close = el.closest('.modal-close');
|
||||
var close = el.closest('.dlg-close');
|
||||
if (close) {
|
||||
var dlg = close.closest('dialog');
|
||||
if (dlg) dlg.close();
|
||||
@@ -246,7 +246,7 @@
|
||||
var n = document.getElementById('mobile-nav');
|
||||
var o = document.getElementById('mobile-nav-overlay');
|
||||
var b = document.getElementById('nav-burger');
|
||||
if (n) n.classList.add('open');
|
||||
if (n) n.classList.add('show');
|
||||
if (o) o.hidden = false;
|
||||
if (b) b.setAttribute('aria-expanded', 'true');
|
||||
document.documentElement.classList.add('nav-open');
|
||||
@@ -255,7 +255,7 @@
|
||||
var n = document.getElementById('mobile-nav');
|
||||
var o = document.getElementById('mobile-nav-overlay');
|
||||
var b = document.getElementById('nav-burger');
|
||||
if (n) n.classList.remove('open');
|
||||
if (n) n.classList.remove('show');
|
||||
if (o) o.hidden = true;
|
||||
if (b) b.setAttribute('aria-expanded', 'false');
|
||||
document.documentElement.classList.remove('nav-open');
|
||||
|
||||
@@ -148,12 +148,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Диалог: оценка + комментарии -->
|
||||
<dialog id="modal-grade" class="modal modal-lg">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="grade-title">Оценка и комментарии</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-grade" class="dlg dlg-lg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title" id="grade-title">Оценка и комментарии</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" id="grade-form" th:action="@{/admin/groups/{id}/grades(id=${group.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="studentId" id="grade-student">
|
||||
@@ -173,12 +173,12 @@
|
||||
|
||||
<!-- Диалог: индивидуальное задание (защита) -->
|
||||
<!-- Диалог: назначение варианта -->
|
||||
<dialog id="modal-variant" class="modal modal-lg">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Назначить вариант</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-variant" class="dlg dlg-lg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Назначить вариант</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body form-stack">
|
||||
<div class="dlg-body form-stack">
|
||||
<p class="text-sm text-muted" id="variant-who">—</p>
|
||||
<div><label class="label" for="variant-level">Уровень</label>
|
||||
<select class="input" id="variant-level"></select></div>
|
||||
@@ -209,12 +209,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-defense" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Индивидуальное задание</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-defense" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Индивидуальное задание</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" id="defense-form" th:action="@{/admin/defenses/save}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="groupId" th:value="${group.id}">
|
||||
@@ -257,12 +257,12 @@
|
||||
|
||||
|
||||
<!-- Диалог добавления студента -->
|
||||
<dialog id="modal-add-student" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Добавить студента</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-add-student" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Добавить студента</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" id="enroll-form" method="post"
|
||||
th:action="@{/admin/groups/{gid}/students/enroll(gid=${group.id})}">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
@@ -336,12 +336,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Большая модалка «Выдача вариантов»: содержимое подгружается фрагментом по лабе -->
|
||||
<dialog id="modal-lab-assign" class="modal modal-lg">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="assign-modal-title">Выдача вариантов</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-lab-assign" class="dlg dlg-lg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title" id="assign-modal-title">Выдача вариантов</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="assign-modal-body"></div>
|
||||
<div class="dlg-body" id="assign-modal-body"></div>
|
||||
</dialog>
|
||||
|
||||
<script th:src="@{/js/admin-group.js(v='15')}" defer></script>
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-create-group" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая группа</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-create-group" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новая группа</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/groups}" method="post">
|
||||
<div><label class="label" for="name">Название</label>
|
||||
<input class="input" id="name" name="name" placeholder="ПИ-11БО" required autofocus></div>
|
||||
@@ -42,12 +42,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-import-group" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Импорт студентов</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-import-group" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Импорт студентов</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/groups/import}" method="post" enctype="multipart/form-data">
|
||||
<p class="text-sm text-muted">Файл .xls, .xlsx или .csv со столбцами: студенческий номер, ФИО, основа обучения. Логин — студенческий номер, пароль сгенерируется и покажется после импорта.</p>
|
||||
<div><label class="label" for="file">Файл со списком</label>
|
||||
|
||||
@@ -128,12 +128,12 @@
|
||||
</div>
|
||||
|
||||
<div th:each="level : ${lab.levels}" th:remove="tag">
|
||||
<dialog th:id="|modal-level-rename-${level.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать уровень</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog th:id="|modal-level-rename-${level.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать уровень</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/{labId}/levels/save(labId=${lab.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="levelId" th:value="${level.id}">
|
||||
@@ -144,12 +144,12 @@
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog th:id="|modal-variant-add-${level.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" th:text="|Новый вариант — ${level.title}|">Новый вариант</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog th:id="|modal-variant-add-${level.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title" th:text="|Новый вариант — ${level.title}|">Новый вариант</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/{labId}/variants/save(labId=${lab.id})}"
|
||||
method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
@@ -168,12 +168,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
<th:block th:each="v : ${level.variants}">
|
||||
<dialog th:id="|modal-variant-edit-${v.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" th:text="|Вариант ${v.number}|">Вариант</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog th:id="|modal-variant-edit-${v.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title" th:text="|Вариант ${v.number}|">Вариант</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/{labId}/variants/save(labId=${lab.id})}"
|
||||
method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
|
||||
@@ -49,12 +49,12 @@
|
||||
</div>
|
||||
|
||||
|
||||
<dialog id="modal-import-lab" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Импорт лабораторной</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-import-lab" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Импорт лабораторной</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/import}" method="post" enctype="multipart/form-data">
|
||||
<div><label class="label" for="labFile">Файл</label>
|
||||
<input class="input" id="labFile" type="file" name="file" accept=".zip" required></div>
|
||||
@@ -72,12 +72,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-create-lab" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая лабораторная</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-create-lab" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новая лабораторная</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/create}" method="post">
|
||||
<div class="grid grid-2">
|
||||
<div><label class="label" for="labNumber">Номер</label>
|
||||
|
||||
@@ -15,17 +15,18 @@
|
||||
</noscript>
|
||||
<link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg(v='2')}">
|
||||
<!-- v=4: сброс кэша стилей после редизайна; при изменении css поднимать версию -->
|
||||
<link rel="stylesheet" th:href="@{/css/app.css(v='15')}">
|
||||
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='15')}">
|
||||
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='15')}" disabled>
|
||||
<script th:src="@{/js/highlight.min.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/table-scroll.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/tablekit.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/theme-init.js(v='15')}"></script>
|
||||
<script th:src="@{/js/app.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/ui-state.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/md-content.js(v='15')}" defer></script>
|
||||
<script th:src="@{/js/submit-guard.js(v='15')}" defer></script>
|
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/css/app.css(v='17')}">
|
||||
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='17')}">
|
||||
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='17')}" disabled>
|
||||
<script th:src="@{/js/highlight.min.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/table-scroll.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/tablekit.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/theme-init.js(v='17')}"></script>
|
||||
<script th:src="@{/js/app.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/ui-state.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/md-content.js(v='17')}" defer></script>
|
||||
<script th:src="@{/js/submit-guard.js(v='17')}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
@@ -64,10 +65,10 @@
|
||||
</header>
|
||||
|
||||
<div id="mobile-nav-overlay" class="mobile-nav-overlay" hidden></div>
|
||||
<nav id="mobile-nav" class="mobile-nav" aria-label="Меню">
|
||||
<nav id="mobile-nav" class="mobile-nav offcanvas offcanvas-end" aria-label="Меню" tabindex="-1">
|
||||
<div class="mobile-nav-head">
|
||||
<span class="logo">LMS<b>.</b></span>
|
||||
<button class="modal-close mobile-nav-close" type="button" aria-label="Закрыть меню">✕</button>
|
||||
<button class="dlg-close mobile-nav-close" type="button" aria-label="Закрыть меню">✕</button>
|
||||
</div>
|
||||
<div class="mobile-nav-links nav">
|
||||
<th:block th:if="${currentUser != null}">
|
||||
@@ -116,12 +117,12 @@
|
||||
<button class="toast-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<dialog id="modal-confirm" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="confirm-title">Подтверждение</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-confirm" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title" id="confirm-title">Подтверждение</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<p class="confirm-text" id="confirm-text">—</p>
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<button class="btn btn-secondary" type="button" id="confirm-cancel">Отмена</button>
|
||||
|
||||
@@ -106,13 +106,13 @@
|
||||
</div>
|
||||
|
||||
<th:block th:if="${currentUser != null and currentUser.admin}">
|
||||
<dialog th:id="|modal-rename-section-${section.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать раздел</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть"
|
||||
<dialog th:id="|modal-rename-section-${section.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать раздел</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть"
|
||||
th:attr="data-modal=|modal-rename-section-${section.id}|">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/rename(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${back}">
|
||||
@@ -122,13 +122,13 @@
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog th:id="|modal-move-${section.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Перенести статьи из раздела</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть"
|
||||
<dialog th:id="|modal-move-${section.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Перенести статьи из раздела</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть"
|
||||
th:attr="data-modal=|modal-move-${section.id}|">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${back}">
|
||||
@@ -150,12 +150,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-rename-chapter" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать главу</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-rename-chapter" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать главу</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/chapters/{id}/rename(id=${chapter.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${back}">
|
||||
@@ -166,12 +166,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-new-section" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новый раздел в главе</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-new-section" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новый раздел в главе</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/create}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="chapterId" th:value="${chapter.id}">
|
||||
|
||||
@@ -108,13 +108,13 @@
|
||||
</div>
|
||||
|
||||
<th:block th:if="${currentUser != null and currentUser.admin}">
|
||||
<dialog th:id="|modal-rename-section-${section.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать раздел</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть"
|
||||
<dialog th:id="|modal-rename-section-${section.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать раздел</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть"
|
||||
th:attr="data-modal=|modal-rename-section-${section.id}|">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/rename(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<div><label class="label">Название</label>
|
||||
@@ -123,13 +123,13 @@
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog th:id="|modal-move-${section.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Перенести статьи из раздела</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть"
|
||||
<dialog th:id="|modal-move-${section.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Перенести статьи из раздела</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть"
|
||||
th:attr="data-modal=|modal-move-${section.id}|">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<p class="text-sm text-muted">Выберите раздел-приёмник: статьи перенесутся туда, а текущий раздел будет удалён.</p>
|
||||
@@ -148,13 +148,13 @@
|
||||
</div>
|
||||
|
||||
<th:block th:if="${currentUser != null and currentUser.admin}">
|
||||
<dialog th:id="|modal-rename-chapter-${chapter.id}|" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать главу</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть"
|
||||
<dialog th:id="|modal-rename-chapter-${chapter.id}|" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать главу</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть"
|
||||
th:attr="data-modal=|modal-rename-chapter-${chapter.id}|">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/chapters/{id}/rename(id=${chapter.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<div><label class="label">Название</label>
|
||||
@@ -166,12 +166,12 @@
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-import" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Импорт материалов (zip)</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-import" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Импорт материалов (zip)</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/import}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<p class="text-sm text-muted">
|
||||
@@ -192,12 +192,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-new-chapter" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая глава</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-new-chapter" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новая глава</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/chapters/create}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<div><label class="label" for="chTitle">Название</label>
|
||||
@@ -207,12 +207,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-new-article" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая статья</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-new-article" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новая статья</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/articles/create}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<div><label class="label" for="naChapter">Существующая глава и раздел</label>
|
||||
|
||||
@@ -78,12 +78,12 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-rename-section" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Переименовать раздел</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-rename-section" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Переименовать раздел</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/rename(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${back}">
|
||||
@@ -94,12 +94,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-move-section" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Перенести статьи и удалить раздел</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-move-section" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Перенести статьи и удалить раздел</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${backParent}">
|
||||
@@ -118,12 +118,12 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-new-article" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая статья в разделе</h2>
|
||||
<button class="modal-close" type="button" aria-label="Закрыть">✕</button>
|
||||
<dialog id="modal-new-article" class="dlg">
|
||||
<div class="dlg-header">
|
||||
<h2 class="dlg-title">Новая статья в разделе</h2>
|
||||
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="dlg-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/articles/create}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="sectionId" th:value="${section.id}">
|
||||
|
||||
Reference in New Issue
Block a user