Rework on Bootstrap 5: standard navbar+offcanvas (fixed empty menu), Bootstrap Icons, complete B3 theme via BS vars, row/col grids

This commit is contained in:
2026-10-03 00:05:52 +03:00
parent 03b64586b2
commit f65a60462e
39 changed files with 229 additions and 311 deletions

View File

@@ -81,37 +81,18 @@ a:not(.btn):hover { color: var(--c-accent-hover); }
/* Layout */
.page { display: flex; flex-direction: column; min-height: 100vh; }
.page-header { position: sticky; top: 0; z-index: 10; border-bottom: 1px solid var(--c-border); background: var(--c-surface); }
.page-header-inner { max-width: 1200px; margin: 0 auto; display: flex; align-items: center; gap: 0.5rem 1.75rem; flex-wrap: wrap; padding: 0.875rem 1.75rem; }
@media (max-width: 640px) {
.page-header-inner { padding: 0.5rem 0.875rem; gap: 0.5rem 1rem; }
.nav { gap: 0.5rem 0.75rem; flex-wrap: wrap; }
.user { gap: 0.5rem; }
.nav-user { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
}
.page-main { flex: 1; width: 100%; max-width: 1200px; margin: 0 auto; padding: 2.5rem 1.75rem 3.5rem; }
@media (max-width: 720px) { .page-main { padding: 1.5rem 0.875rem 2.5rem; } }
.page-footer { text-align: center; color: var(--c-text-muted); font-size: 0.75rem; padding: 1.5rem; }
/* Nav */
.nav { display: flex; align-items: center; gap: 1.125rem; flex: 1 1 auto; flex-wrap: wrap; font-size: 0.84375rem; }
.nav-link { color: var(--c-accent); text-decoration: none; font-weight: 500; }
.nav-link:hover { color: var(--c-accent-hover); }
.nav-link.active { color: var(--c-accent); font-weight: 700; }
.nav-sep { color: var(--c-text-muted); }
.logo { font-size: 1rem; font-weight: 700; color: var(--c-text); text-decoration: none; letter-spacing: -0.01em; }
.logo b { color: var(--c-accent); }
.user { display: flex; align-items: center; gap: 0.75rem; font-size: 0.84375rem; margin-left: auto; }
.nav-user { font-weight: 500; color: var(--c-text); }
.nav-user:hover { color: var(--c-accent); }
.nav-user.defense-active { color: var(--c-accent); }
.nav-user.defense-active::before { content: ""; display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--c-accent); margin-right: 0.4rem; vertical-align: middle; }
.score-big { display: inline-block; margin-left: 0.5rem; font-size: 1.75rem; font-weight: 700; line-height: 1; color: var(--c-tone-pos); font-variant-numeric: tabular-nums; vertical-align: middle; }
.variant-name { font-weight: 600; }
.variant-name b { font-variant-numeric: tabular-nums; }
p { margin-top: 0; }
.theme-btn { display: inline-flex; align-items: center; justify-content: center; padding: 0.3rem 0.5rem; }
.theme-icon { width: 1.05rem; height: 1.05rem; display: block; }
.breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.breadcrumb .nav-sep { user-select: none; }
.check-list { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.25rem; max-height: 12rem; overflow-y: auto; }
@@ -148,35 +129,51 @@ p { margin-top: 0; }
.hint { margin-top: 0.25rem; font-size: 0.75rem; color: var(--c-text-muted); }
/* ---- Icons: SVG-файлы из /icons, подключаются CSS-масками (цвет = currentColor) ---- */
.ico {
display: inline-block; flex: 0 0 auto;
background-color: currentColor;
-webkit-mask-position: center; mask-position: center;
-webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
-webkit-mask-size: contain; mask-size: contain;
/* ===== B3-тема поверх Bootstrap: navbar, offcanvas, кнопки, ссылки ===== */
.b3-navbar { background: var(--c-surface); border-bottom: 1px solid var(--c-border); }
.b3-navbar .container, .page-main.container { max-width: 1200px; }
.b3-navbar .navbar-brand { color: var(--c-text); font-weight: 700; font-size: 1rem; }
.b3-navbar .navbar-brand b { color: var(--c-accent); }
.b3-navbar .navbar-toggler { border: 1px solid var(--c-border-strong); color: var(--c-accent); padding: 0.25rem 0.5rem; line-height: 1; }
.b3-navbar .navbar-toggler:focus { box-shadow: none; }
.nav-link { --bs-nav-link-color: var(--c-accent); --bs-nav-link-hover-color: var(--c-accent-hover); font-weight: 500; font-size: 0.875rem; }
.nav-link.active { color: var(--c-accent) !important; font-weight: 700; }
.navbar-close { border: 0; background: none; color: var(--c-text-muted); font-size: 1.1rem; line-height: 1; }
.navbar-close:hover { color: var(--c-text); }
#mobileNav { --bs-offcanvas-width: 17rem; --bs-offcanvas-bg: var(--c-surface); --bs-offcanvas-color: var(--c-text); --bs-offcanvas-border-color: var(--c-border); }
#mobileNav .offcanvas-header { border-bottom: 1px solid var(--c-border); }
#mobileNav .navbar-nav { gap: 0.25rem; }
#mobileNav .nav-link { padding: 0.5rem 0.25rem; font-size: 1rem; }
@media (max-width: 767.98px) {
#mobileNav .offcanvas-body { display: flex; flex-direction: column; justify-content: space-between; gap: 1rem; }
}
.ico-eye-open { -webkit-mask-image: url('../icons/eye-open.svg'); mask-image: url('../icons/eye-open.svg'); }
.ico-eye-closed { -webkit-mask-image: url('../icons/eye-closed.svg'); mask-image: url('../icons/eye-closed.svg'); }
.ico-pencil { -webkit-mask-image: url('../icons/pencil.svg'); mask-image: url('../icons/pencil.svg'); }
.ico-hand { -webkit-mask-image: url('../icons/hand.svg'); mask-image: url('../icons/hand.svg'); }
.ico-trash { -webkit-mask-image: url('../icons/trash.svg'); mask-image: url('../icons/trash.svg'); }
.ico-menu { -webkit-mask-image: url('../icons/menu.svg'); mask-image: url('../icons/menu.svg'); }
.ico-chevron-down { -webkit-mask-image: url('../icons/chevron-down.svg'); mask-image: url('../icons/chevron-down.svg'); }
.ico-theme { -webkit-mask-image: url('../icons/theme.svg'); mask-image: url('../icons/theme.svg'); }
.ico-md-bold { -webkit-mask-image: url('../icons/md-bold.svg'); mask-image: url('../icons/md-bold.svg'); }
.ico-md-italic { -webkit-mask-image: url('../icons/md-italic.svg'); mask-image: url('../icons/md-italic.svg'); }
.ico-md-h2 { -webkit-mask-image: url('../icons/md-h2.svg'); mask-image: url('../icons/md-h2.svg'); }
.ico-md-ul { -webkit-mask-image: url('../icons/md-ul.svg'); mask-image: url('../icons/md-ul.svg'); }
.ico-md-ol { -webkit-mask-image: url('../icons/md-ol.svg'); mask-image: url('../icons/md-ol.svg'); }
.ico-md-quote { -webkit-mask-image: url('../icons/md-quote.svg'); mask-image: url('../icons/md-quote.svg'); }
.ico-md-link { -webkit-mask-image: url('../icons/md-link.svg'); mask-image: url('../icons/md-link.svg'); }
.ico-md-code { -webkit-mask-image: url('../icons/md-code.svg'); mask-image: url('../icons/md-code.svg'); }
.ico-md-codeblock { -webkit-mask-image: url('../icons/md-codeblock.svg'); mask-image: url('../icons/md-codeblock.svg'); }
.ico-md-table { -webkit-mask-image: url('../icons/md-table.svg'); mask-image: url('../icons/md-table.svg'); }
.ico-md-figure { -webkit-mask-image: url('../icons/md-figure.svg'); mask-image: url('../icons/md-figure.svg'); }
.ico-md-video { -webkit-mask-image: url('../icons/md-video.svg'); mask-image: url('../icons/md-video.svg'); }
.ico-md-spoiler { -webkit-mask-image: url('../icons/md-spoiler.svg'); mask-image: url('../icons/md-spoiler.svg'); }
.nav-user { font-weight: 500; color: var(--c-accent); text-decoration: none; }
.nav-user:hover { color: var(--c-accent-hover); }
.nav-user.defense-active::before { content: ""; display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--c-accent); margin-right: 0.4rem; vertical-align: middle; }
.page-main { padding: 2.5rem 1.75rem 3.5rem; }
@media (max-width: 720px) { .page-main { padding: 1.5rem 0.875rem 2.5rem; } }
/* Кнопки: B3-палитра через переменные Bootstrap */
.btn {
--bs-btn-bg: var(--c-accent); --bs-btn-border-color: var(--c-accent); --bs-btn-color: #ffffff;
--bs-btn-hover-bg: var(--c-accent-hover); --bs-btn-hover-border-color: var(--c-accent-hover); --bs-btn-hover-color: #ffffff;
--bs-btn-active-bg: #6a1d2c; --bs-btn-active-border-color: #6a1d2c; --bs-btn-active-color: #ffffff;
--bs-btn-disabled-bg: var(--c-accent); --bs-btn-disabled-border-color: var(--c-accent); --bs-btn-disabled-color: #ffffff;
--bs-btn-font-size: 0.8125rem; --bs-btn-padding-y: 0.5rem; --bs-btn-padding-x: 1rem;
--bs-btn-border-radius: var(--radius);
}
.btn-primary { --bs-btn-bg: var(--c-accent); --bs-btn-border-color: var(--c-accent); --bs-btn-color: #ffffff;
--bs-btn-hover-bg: var(--c-accent-hover); --bs-btn-hover-border-color: var(--c-accent-hover); --bs-btn-hover-color: #ffffff;
--bs-btn-active-bg: #6a1d2c; --bs-btn-active-border-color: #6a1d2c; --bs-btn-active-color: #ffffff; }
.btn-secondary { --bs-btn-bg: var(--c-surface); --bs-btn-border-color: var(--c-border-strong); --bs-btn-color: var(--c-text);
--bs-btn-hover-bg: var(--c-bg); --bs-btn-hover-border-color: var(--c-border-strong); --bs-btn-hover-color: var(--c-text);
--bs-btn-active-bg: var(--c-bg); --bs-btn-active-border-color: var(--c-border-strong); --bs-btn-active-color: var(--c-text); }
.btn-danger { --bs-btn-bg: var(--c-surface); --bs-btn-border-color: #dcb9be; --bs-btn-color: var(--c-accent);
--bs-btn-hover-bg: var(--c-accent-soft); --bs-btn-hover-border-color: var(--c-accent); --bs-btn-hover-color: var(--c-accent);
--bs-btn-active-bg: var(--c-accent-soft); --bs-btn-active-border-color: var(--c-accent); --bs-btn-active-color: var(--c-accent); }
.btn-sm { --bs-btn-padding-y: 0.25rem; --bs-btn-padding-x: 0.6rem; --bs-btn-font-size: 0.75rem; }
a { --bs-link-color: var(--c-accent); --bs-link-hover-color: var(--c-accent-hover); text-decoration: none; }
/* Cards */
.card { border-radius: var(--radius-lg); background: var(--c-surface); border: 1px solid var(--c-border); }
@@ -497,7 +494,7 @@ dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
background: none; cursor: pointer; color: var(--c-text-muted);
}
.sub-toggle:hover { color: var(--c-text); }
.sub-arrow .ico { width: 1rem; height: 1rem; }
.sub-arrow i { font-size: 1rem; line-height: 1; }
.sub-arrow { transform: rotate(0deg); transition: transform 0.2s; }
.sub-acc.collapsed .sub-arrow { transform: rotate(0deg); }
.sub-acc:not(.collapsed) .sub-arrow { transform: rotate(180deg); }
@@ -588,9 +585,7 @@ dialog.dlg.dlg-lg .dlg-body { max-height: 88vh; min-height: 62vh; }
.actions-col .inline-form { display: block; width: 100%; }
.actions-col .btn { display: block; width: 100%; text-align: center; }
/* Иконки — SVG-файлы из /icons через CSS-маски: цвет наследуется от текста */
.cell-ico {
width: 0.95rem; height: 0.95rem; display: block;
}
.cell-ico { font-size: 0.95rem; line-height: 1; }
/* tablekit: сортируемые таблицы и поиск */
th.th-sortable { cursor: pointer; user-select: none; }
@@ -671,9 +666,7 @@ th.th-filterable { padding-bottom: 0.35rem; }
.md-btn:hover { background: var(--c-surface); border-color: var(--c-border); }
.md-btn:active { transform: translateY(1px); }
.md-btn:focus-visible { outline: 2px solid var(--c-text-link); outline-offset: 1px; }
.md-ico {
width: 1.15rem; height: 1.15rem;
}
.md-ico { font-size: 1.15rem; line-height: 1; }
.md-sep { width: 1px; align-self: stretch; margin: 0.2rem 0.3rem; background: var(--c-border); }
.md-editor-head {
display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
@@ -746,19 +739,4 @@ th.th-filterable { padding-bottom: 0.35rem; }
.sort-row .sort-title { flex: 1 1 auto; }
}
/* ---- Мобильное меню: бургер в хэдере, сайдбар справа (BS offcanvas) ---- */
.nav-burger { 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; }
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>

Before

Width:  |  Height:  |  Size: 199 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/><line x1="3" y1="3" x2="21" y2="21"/></svg>

Before

Width:  |  Height:  |  Size: 288 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>

Before

Width:  |  Height:  |  Size: 251 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"/><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></svg>

Before

Width:  |  Height:  |  Size: 421 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 5h5.2a3.4 3.4 0 0 1 0 6.8H7z"/><path d="M7 11.8h6a3.6 3.6 0 0 1 0 7.2H7z"/></svg>

Before

Width:  |  Height:  |  Size: 153 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 8l-4 4 4 4M15 8l4 4-4 4"/></svg>

Before

Width:  |  Height:  |  Size: 104 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M9.5 10l-2 2 2 2M14.5 10l2 2-2 2"/></svg>

Before

Width:  |  Height:  |  Size: 159 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.5"/><path d="M4 17l4.5-4.5 3 3 3-3L20 17"/></svg>

Before

Width:  |  Height:  |  Size: 188 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 6v12M11 6v12M4 12h7"/><text x="14.5" y="18.2" font-size="10" font-weight="700" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">2</text></svg>

Before

Width:  |  Height:  |  Size: 235 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11 5h6M7 19h6M15 5l-4 14"/></svg>

Before

Width:  |  Height:  |  Size: 103 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.5 13.5a4 4 0 0 1 0-5.66l2.12-2.12a4 4 0 0 1 5.66 5.66l-1.2 1.2"/><path d="M13.5 10.5a4 4 0 0 1 0 5.66l-2.12 2.12a4 4 0 0 1-5.66-5.66l1.2-1.2"/></svg>

Before

Width:  |  Height:  |  Size: 222 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11 6h9M11 12h9M11 18h9"/><text x="3" y="8.4" font-size="7.5" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">1</text><text x="3" y="14.4" font-size="7.5" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">2</text><text x="3" y="20.4" font-size="7.5" fill="currentColor" stroke="none" font-family="system-ui, sans-serif">3</text></svg>

Before

Width:  |  Height:  |  Size: 445 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 6v12" stroke-width="3"/><path d="M10 8h9M10 12h9M10 16h6"/></svg>

Before

Width:  |  Height:  |  Size: 137 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 3l18 18"/><path d="M10.6 6.2A9.7 9.7 0 0 1 12 6c5 0 9 6 9 6a15.4 15.4 0 0 1-3.5 4M6.4 8.1A15.3 15.3 0 0 0 3 12s4 6 9 6a9.6 9.6 0 0 0 3.1-.5"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/></svg>

Before

Width:  |  Height:  |  Size: 258 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 9.7h18M9 9.7V19M15 9.7V19M3 14.3h18"/></svg>

Before

Width:  |  Height:  |  Size: 165 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 6h11M9 12h11M9 18h11"/><circle cx="4.6" cy="6" r="1.4" fill="currentColor" stroke="none"/><circle cx="4.6" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="4.6" cy="18" r="1.4" fill="currentColor" stroke="none"/></svg>

Before

Width:  |  Height:  |  Size: 304 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M10 9.5l5 2.5-5 2.5z" fill="currentColor" stroke="none"/></svg>

Before

Width:  |  Height:  |  Size: 181 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>

Before

Width:  |  Height:  |  Size: 199 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 222 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="5" fill="none" stroke="currentColor" stroke-width="1.6"/><g stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><line x1="12" y1="1.4" x2="12" y2="3.2"/><line x1="12" y1="20.8" x2="12" y2="22.6"/><line x1="1.4" y1="12" x2="3.2" y2="12"/><line x1="20.8" y1="12" x2="22.6" y2="12"/><line x1="4.5" y1="4.5" x2="5.8" y2="5.8"/><line x1="18.2" y1="18.2" x2="19.5" y2="19.5"/><line x1="4.5" y1="19.5" x2="5.8" y2="18.2"/><line x1="18.2" y1="5.8" x2="19.5" y2="4.5"/></g></svg>

Before

Width:  |  Height:  |  Size: 732 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>

Before

Width:  |  Height:  |  Size: 344 B

View File

@@ -84,7 +84,7 @@
/* Активный пункт меню */
function initNavActive() {
var path = window.location.pathname;
var links = [].slice.call(document.querySelectorAll('#top-nav .nav-link, #mobile-nav .nav-link'));
var links = [].slice.call(document.querySelectorAll('.b3-navbar .nav-link'));
var bestHref = null;
var bestLen = -1;
links.forEach(function (a) {
@@ -241,34 +241,6 @@
else if (kind === 'comment-add' && typeof window.csRow === 'function') window.csRow('', 'neutral');
});
/* Мобильное меню */
function openMobileNav() {
var n = document.getElementById('mobile-nav');
var o = document.getElementById('mobile-nav-overlay');
var b = document.getElementById('nav-burger');
if (n) n.classList.add('show');
if (o) o.hidden = false;
if (b) b.setAttribute('aria-expanded', 'true');
document.documentElement.classList.add('nav-open');
}
function closeMobileNav() {
var n = document.getElementById('mobile-nav');
var o = document.getElementById('mobile-nav-overlay');
var b = document.getElementById('nav-burger');
if (n) n.classList.remove('show');
if (o) o.hidden = true;
if (b) b.setAttribute('aria-expanded', 'false');
document.documentElement.classList.remove('nav-open');
}
document.addEventListener('click', function (e) {
if (e.target.closest('#nav-burger')) { openMobileNav(); return; }
if (e.target.closest('#mobile-nav-overlay') || e.target.closest('.mobile-nav-close')) { closeMobileNav(); return; }
if (e.target.closest('.mobile-nav a')) { closeMobileNav(); return; }
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeMobileNav();
});
/* Изменение полей: автоотправка форм (data-auto-submit),
подтверждение занятия занятого варианта (select.js-variant-select) */
document.addEventListener('change', function (e) {

File diff suppressed because one or more lines are too long

View File

@@ -5,18 +5,18 @@
<div id="content">
<h1 class="page-title">Панель преподавателя</h1>
<div class="grid grid-stats">
<div class="card card-body"><p class="stat-title">Группы</p><p class="stat-value" th:text="${groupCount}">0</p></div>
<div class="card card-body"><p class="stat-title">Студенты</p><p class="stat-value" th:text="${studentCount}">0</p></div>
<div class="card card-body"><p class="stat-title">Лабораторные</p><p class="stat-value" th:text="${labCount}">0</p>
<p class="hint" th:if="${labCount > 0}">опубликовано: <span th:text="${publishedLabs}">0</span></p></div>
<div class="card card-body"><p class="stat-title">Средний балл</p>
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-3">
<div class="col"><div class="card card-body h-100"><p class="stat-title">Группы</p><p class="stat-value" th:text="${groupCount}">0</p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Студенты</p><p class="stat-value" th:text="${studentCount}">0</p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Лабораторные</p><p class="stat-value" th:text="${labCount}">0</p>
<p class="hint" th:if="${labCount > 0}">опубликовано: <span th:text="${publishedLabs}">0</span></p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Средний балл</p>
<p class="stat-value" th:if="${avgGrade != null}" th:text="${#numbers.formatDecimal(avgGrade, 1, 2)}">—</p>
<p class="stat-value text-muted" th:if="${avgGrade == null}">—</p></div>
<p class="stat-value text-muted" th:if="${avgGrade == null}">—</p></div></div>
</div>
<div class="grid grid-2 mt-8">
<div>
<div class="row g-4 mt-4">
<div class="col-lg-6">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title m-0">Лабораторные</h2>
<a th:href="@{/admin/labs}" class="text-sm">Все лабораторные →</a>
@@ -33,7 +33,7 @@
</table>
</div>
</div>
<div>
<div class="col-lg-6">
<div class="flex items-center justify-between mb-4">
<h2 class="section-title m-0">Группы</h2>
<a th:href="@{/admin/groups}" class="text-sm">Все группы →</a>

View File

@@ -49,7 +49,7 @@
<button class="mini-btn" type="button"
th:attr="data-url=@{/admin/groups/{id}/assign-modal(id=${group.id}, labId=${lab.id})},data-lab-name=|ЛР ${lab.number}|" data-action="assign"
title="Выдача вариантов: уровни, варианты, автокраспределение по группе">
<span class="cell-ico ico ico-menu" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-list" aria-hidden="true"></i>
</button>
<form class="inline-form" th:with="pub=${labPublication[lab.id]}, pc=${pub != null ? pub.publishedCount : 0}, all=${pub != null and pub.allPublished}"
th:action="@{/admin/groups/{id}/publish(id=${group.id})}" method="post">
@@ -61,8 +61,8 @@
th:attr="data-lab=${lab.id},data-count=${pc},data-total=${pub != null ? pub.total : 0},data-all=${all}"
th:classappend="${all} ? 'is-all' : (${pc > 0} ? 'is-partial' : 'is-empty')"
th:title="${pub == null} ? '' : (${all} ? 'Оценки опубликованы всей группе — нажмите, чтобы скрыть у всех' : (${pc > 0} ? 'Опубликовано ' + ${pc} + ' из ' + ${pub.total} + ' — нажмите, чтобы опубликовать всем' : 'Показать оценки и комментарии всей группе'))">
<span class="eye-open ico ico-eye-open" aria-hidden="true"></span>
<span class="eye-closed ico ico-eye-closed" aria-hidden="true"></span>
<i class="eye-open ico bi bi-eye" aria-hidden="true"></i>
<i class="eye-closed ico bi bi-eye-slash" aria-hidden="true"></i>
<span class="mini-count js-pub-count" th:classappend="(${all} or ${pc == 0}) ? 'is-hidden' : ''" th:text="${pc}"></span>
</button>
</form>
@@ -278,20 +278,20 @@
</form>
<form class="form-stack mt-4" th:action="@{/admin/groups/{gid}/students/create(gid=${group.id})}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div class="grid grid-2">
<div><label class="label" for="stNo">Студенческий номер (логин)</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="stNo">Студенческий номер (логин)</label>
<input class="input" id="stNo" name="studentNo" required></div>
<div><label class="label" for="stBasis">Основа обучения</label>
<div class="col-md-6"><label class="label" for="stBasis">Основа обучения</label>
<input class="input" id="stBasis" name="basis" placeholder="бюджет"></div>
</div>
</div>
<div><label class="label" for="stLast">Фамилия</label>
<input class="input" id="stLast" name="lastName" required></div>
<div class="grid grid-2">
<div><label class="label" for="stFirst">Имя</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="stFirst">Имя</label>
<input class="input" id="stFirst" name="firstName"></div>
<div><label class="label" for="stMiddle">Отчество</label>
<div class="col-md-6"><label class="label" for="stMiddle">Отчество</label>
<input class="input" id="stMiddle" name="middleName"></div>
</div>
</div>
<p class="hint">Новому студенту пароль будет сгенерирован автоматически.</p>
<button class="btn btn-primary" type="submit">Создать нового</button>
</form>

View File

@@ -33,25 +33,25 @@
<h2 class="section-title m-0">Условие лабораторной</h2>
<button class="mini-btn" type="button" data-action="lab-edit"
title="Редактировать параметры лабораторной" aria-label="Редактировать параметры лабораторной">
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i>
</button>
</div>
<div class="card card-body md-body mb-6" id="lab-description-view" th:utext="${descriptionHtml}"></div>
<form id="lab-edit-form" hidden class="card card-body form-stack mb-6" method="post"
th:action="@{/admin/labs/{id}/edit(id=${lab.id})}">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div class="grid grid-2">
<div><label class="label" for="l-title">Название</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="l-title">Название</label>
<input class="input" id="l-title" name="title" th:value="${lab.title}" required></div>
<div><label class="label" for="l-points">Максимум баллов</label>
<div class="col-md-6"><label class="label" for="l-points">Максимум баллов</label>
<input class="input" id="l-points" name="pointsMax" type="number" step="0.1" min="0"
th:value="${lab.pointsMax}" required></div>
</div>
<div class="grid grid-2">
<div><label class="label" for="l-deadline">Срок сдачи (время — МСК)</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="l-deadline">Срок сдачи (время — МСК)</label>
<input class="input" id="l-deadline" name="deadlineAt" type="datetime-local"
th:value="${deadlineLocal}"></div>
<div><label class="label" for="l-defense">Дата защиты (время — МСК)</label>
<div class="col-md-6"><label class="label" for="l-defense">Дата защиты (время — МСК)</label>
<input class="input" id="l-defense" name="defenseAt" type="datetime-local" th:value="${defenseLocal}">
</div>
</div>
@@ -82,7 +82,7 @@
<div class="flex items-center gap-2">
<button class="mini-btn" type="button" th:attr="data-modal=|modal-level-rename-${level.id}|"
title="Переименовать уровень" aria-label="Переименовать уровень">
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i>
</button>
<form class="inline-form"
th:action="@{/admin/labs/{labId}/levels/{levelId}/delete(labId=${lab.id}, levelId=${level.id})}"
@@ -90,7 +90,7 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="mini-btn danger" type="submit" title="Удалить уровень"
aria-label="Удалить уровень">
<span class="cell-ico ico ico-trash" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-trash" aria-hidden="true"></i>
</button>
</form>
<button class="btn btn-sm" type="button" th:attr="data-modal=|modal-variant-add-${level.id}|">+
@@ -108,7 +108,7 @@
<div class="flex gap-2">
<button class="mini-btn" type="button" th:attr="data-modal=|modal-variant-edit-${v.id}|"
title="Редактировать вариант" aria-label="Редактировать вариант">
<span class="cell-ico ico ico-pencil" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i>
</button>
<form class="inline-form"
th:action="@{/admin/labs/{labId}/variants/{variantId}/delete(labId=${lab.id}, variantId=${v.id})}"
@@ -116,7 +116,7 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="mini-btn danger" type="submit" title="Удалить вариант"
aria-label="Удалить вариант">
<span class="cell-ico ico ico-trash" aria-hidden="true"></span>
<i class="cell-ico ico bi bi-trash" aria-hidden="true"></i>
</button>
</form>
</div>
@@ -154,12 +154,12 @@
method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="labLevelId" th:value="${level.id}">
<div class="grid grid-2">
<div><label class="label" for="|va-number-${level.id}|">Номер</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="|va-number-${level.id}|">Номер</label>
<input class="input" th:id="|va-number-${level.id}|" name="number" required></div>
<div><label class="label" for="|va-title-${level.id}|">Название</label>
<div class="col-md-6"><label class="label" for="|va-title-${level.id}|">Название</label>
<input class="input" th:id="|va-title-${level.id}|" name="title"></div>
</div>
</div>
<div><label class="label" for="|va-statement-${level.id}|">Условие (markdown)</label>
<textarea class="input" th:id="|va-statement-${level.id}|" name="statementMd"
rows="8"></textarea></div>
@@ -179,14 +179,14 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="variantId" th:value="${v.id}">
<input type="hidden" name="labLevelId" th:value="${level.id}">
<div class="grid grid-2">
<div><label class="label" for="|ve-number-${v.id}|">Номер</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="|ve-number-${v.id}|">Номер</label>
<input class="input" th:id="|ve-number-${v.id}|" name="number" th:value="${v.number}"
required></div>
<div><label class="label" for="|ve-title-${v.id}|">Название</label>
<div class="col-md-6"><label class="label" for="|ve-title-${v.id}|">Название</label>
<input class="input" th:id="|ve-title-${v.id}|" name="title" th:value="${v.title}">
</div>
</div>
</div>
<div><label class="label" for="|ve-statement-${v.id}|">Условие (markdown)</label>
<textarea class="input" th:id="|ve-statement-${v.id}|" name="statementMd" rows="10"
th:text="${v.statementMd}"></textarea></div>

View File

@@ -79,20 +79,20 @@
</div>
<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>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="labNumber">Номер</label>
<input class="input" id="labNumber" name="number" type="number" min="1" required></div>
<div><label class="label" for="labPoints">Максимум баллов</label>
<div class="col-md-6"><label class="label" for="labPoints">Максимум баллов</label>
<input class="input" id="labPoints" name="pointsMax" type="number" step="0.1" min="0" value="5" required></div>
</div>
</div>
<div><label class="label" for="labTitle">Название</label>
<input class="input" id="labTitle" name="title" required></div>
<div class="grid grid-2">
<div><label class="label" for="labDeadline">Срок сдачи</label>
<div class="row g-3">
<div class="col-md-6"><label class="label" for="labDeadline">Срок сдачи</label>
<input class="input" id="labDeadline" name="deadlineAt" type="datetime-local"></div>
<div><label class="label" for="labDefense">Дата защиты</label>
<div class="col-md-6"><label class="label" for="labDefense">Дата защиты</label>
<input class="input" id="labDefense" name="defenseAt" type="datetime-local"></div>
</div>
</div>
<div><label class="label" for="labStatus">Статус</label>
<select class="input" id="labStatus" name="status">
<option value="draft">draft</option>

View File

@@ -16,84 +16,59 @@
<link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg(v='2')}">
<!-- v=4: сброс кэша стилей после редизайна; при изменении css поднимать версию -->
<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>
<link rel="stylesheet" th:href="@{/css/bootstrap-icons.min.css}">
<link rel="stylesheet" th:href="@{/css/app.css(v='18')}">
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='18')}">
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='18')}" disabled>
<script th:src="@{/js/highlight.min.js(v='18')}" defer></script>
<script th:src="@{/js/table-scroll.js(v='18')}" defer></script>
<script th:src="@{/js/tablekit.js(v='18')}" defer></script>
<script th:src="@{/js/bootstrap.bundle.min.js}" defer></script>
<script th:src="@{/js/theme-init.js(v='18')}"></script>
<script th:src="@{/js/app.js(v='18')}" defer></script>
<script th:src="@{/js/ui-state.js(v='18')}" defer></script>
<script th:src="@{/js/md-content.js(v='18')}" defer></script>
<script th:src="@{/js/submit-guard.js(v='18')}" defer></script>
</head>
<body>
<div class="page">
<header class="page-header">
<div class="page-header-inner">
<a th:href="@{/}" class="logo">LMS<b>.</b></a>
<nav class="nav" id="top-nav">
<th:block th:if="${currentUser != null}">
<a th:if="${currentUser.admin}" th:href="@{/admin/groups}" class="nav-link">Группы</a>
<a th:if="${currentUser.admin}" th:href="@{/admin/users}" class="nav-link">Пользователи</a>
<a th:if="${currentUser.admin}" th:href="@{/admin/labs}" class="nav-link">Лабораторные</a>
<a th:unless="${currentUser.admin}" th:href="@{/student/labs}" class="nav-link">Лабораторные</a>
<a th:href="@{/change-password}" class="nav-link">Смена пароля</a>
</th:block>
</nav>
<button type="button" class="btn btn-secondary theme-btn"
aria-label="Сменить тему" title="Сменить тему">
<span class="theme-icon ico ico-theme" aria-hidden="true"></span>
<nav class="navbar b3-navbar navbar-expand-md sticky-top">
<div class="container">
<a th:href="@{/}" class="navbar-brand">LMS<b>.</b></a>
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#mobileNav"
aria-controls="mobileNav" aria-label="Меню">
<i class="bi bi-list"></i>
</button>
<button type="button" id="nav-burger" class="btn btn-sm nav-burger"
aria-label="Меню" aria-expanded="false">
<span class="ico ico-menu"></span>
</button>
<div class="user">
<th:block th:if="${currentUser != null}">
<a th:href="@{/admin}" th:if="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
<a th:href="@{/student}" th:unless="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
</th:block>
<a th:if="${currentUser == null}" th:href="@{/login}" class="btn btn-sm">Войти</a>
<form th:if="${currentUser != null}" th:action="@{/logout}" method="post">
<button class="btn btn-sm" type="submit">Выйти</button>
<input type="hidden" name="_csrf" th:value="${csrfToken}">
</form>
<div class="offcanvas offcanvas-end" tabindex="-1" id="mobileNav" aria-labelledby="mobileNavLabel">
<div class="offcanvas-header">
<span class="navbar-brand" id="mobileNavLabel">LMS<b>.</b></span>
<button type="button" class="navbar-close" data-bs-dismiss="offcanvas" aria-label="Закрыть">✕</button>
</div>
<div class="offcanvas-body">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/groups}">Группы</a></li>
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/users}">Пользователи</a></li>
<li class="nav-item" th:if="${currentUser != null and currentUser.admin}"><a class="nav-link" th:href="@{/admin/labs}">Лабораторные</a></li>
<li class="nav-item" th:if="${currentUser != null and !currentUser.admin}"><a class="nav-link" th:href="@{/student/labs}">Лабораторные</a></li>
<li class="nav-item" th:if="${currentUser != null}"><a class="nav-link" th:href="@{/change-password}">Смена пароля</a></li>
</ul>
<div class="d-flex align-items-center gap-2">
<a th:href="@{/admin}" th:if="${currentUser != null and currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
<a th:href="@{/student}" th:if="${currentUser != null and !currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
<button type="button" class="btn btn-secondary btn-sm theme-btn" aria-label="Сменить тему" title="Сменить тему">
<i class="bi bi-circle-half"></i>
</button>
<a th:if="${currentUser == null}" th:href="@{/login}" class="btn btn-sm">Войти</a>
<form th:if="${currentUser != null}" th:action="@{/logout}" method="post" class="m-0">
<button class="btn btn-sm" type="submit">Выйти</button>
<input type="hidden" name="_csrf" th:value="${csrfToken}">
</form>
</div>
</div>
</div>
</div>
</header>
<div id="mobile-nav-overlay" class="mobile-nav-overlay" hidden></div>
<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="dlg-close mobile-nav-close" type="button" aria-label="Закрыть меню">✕</button>
</div>
<div class="mobile-nav-links nav">
<th:block th:if="${currentUser != null}">
<a th:if="${currentUser.admin}" th:href="@{/admin/groups}" class="nav-link">Группы</a>
<a th:if="${currentUser.admin}" th:href="@{/admin/users}" class="nav-link">Пользователи</a>
<a th:if="${currentUser.admin}" th:href="@{/admin/labs}" class="nav-link">Лабораторные</a>
<a th:unless="${currentUser.admin}" th:href="@{/student/labs}" class="nav-link">Лабораторные</a>
<a th:href="@{/change-password}" class="nav-link">Смена пароля</a>
</th:block>
</div>
<div class="mobile-nav-user">
<th:block th:if="${currentUser != null}">
<a th:href="@{/admin}" th:if="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
<a th:href="@{/student}" th:unless="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
</th:block>
<button type="button" class="btn btn-secondary btn-sm theme-btn" aria-label="Сменить тему" title="Сменить тему">
<span class="ico ico-theme"></span>
</button>
<form th:if="${currentUser != null}" th:action="@{/logout}" method="post">
<button class="btn btn-secondary btn-sm" type="submit">Выйти</button>
<input type="hidden" name="_csrf" th:value="${csrfToken}">
</form>
</div>
</nav>
<main class="page-main">
<main class="container page-main">
<th:block th:replace="${content}" />
</main>
<footer class="page-footer">

View File

@@ -5,52 +5,52 @@
<div th:fragment="toolbar(target)" class="md-toolbar" th:attr="data-md-target=${target}"
role="toolbar" aria-label="Форматирование текста">
<button type="button" class="md-btn" data-md="bold" title="Полужирный" aria-label="Полужирный">
<span class="md-ico ico ico-md-bold" aria-hidden="true"></span>
<i class="md-ico ico bi bi-type-bold" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="italic" title="Курсив" aria-label="Курсив">
<span class="md-ico ico ico-md-italic" aria-hidden="true"></span>
<i class="md-ico ico bi bi-type-italic" aria-hidden="true"></i>
</button>
<span class="md-sep"></span>
<button type="button" class="md-btn" data-md="h2" title="Заголовок раздела" aria-label="Заголовок раздела">
<span class="md-ico ico ico-md-h2" aria-hidden="true"></span>
<i class="md-ico ico bi bi-type-h2" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="ul" title="Маркированный список" aria-label="Маркированный список">
<span class="md-ico ico ico-md-ul" aria-hidden="true"></span>
<i class="md-ico ico bi bi-list-ul" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="ol" title="Нумерованный список" aria-label="Нумерованный список">
<span class="md-ico ico ico-md-ol" aria-hidden="true"></span>
<i class="md-ico ico bi bi-list-ol" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="quote" title="Цитата" aria-label="Цитата">
<span class="md-ico ico ico-md-quote" aria-hidden="true"></span>
<i class="md-ico ico bi bi-quote" aria-hidden="true"></i>
</button>
<span class="md-sep"></span>
<button type="button" class="md-btn" data-md="link" title="Ссылка" aria-label="Ссылка">
<span class="md-ico ico ico-md-link" aria-hidden="true"></span>
<i class="md-ico ico bi bi-link-45deg" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="code" title="Код в строке" aria-label="Код в строке">
<span class="md-ico ico ico-md-code" aria-hidden="true"></span>
<i class="md-ico ico bi bi-code" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="codeblock" title="Блок кода" aria-label="Блок кода">
<span class="md-ico ico ico-md-codeblock" aria-hidden="true"></span>
<i class="md-ico ico bi bi-code-square" aria-hidden="true"></i>
</button>
<span class="md-sep"></span>
<button type="button" class="md-btn" data-md="table" title="Таблица" aria-label="Таблица">
<span class="md-ico ico ico-md-table" aria-hidden="true"></span>
<i class="md-ico ico bi bi-table" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="figure" title="Картинка с подписью" aria-label="Картинка с подписью">
<span class="md-ico ico ico-md-figure" aria-hidden="true"></span>
<i class="md-ico ico bi bi-image" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="video" title="Видео" aria-label="Видео">
<span class="md-ico ico ico-md-video" aria-hidden="true"></span>
<i class="md-ico ico bi bi-camera-video" aria-hidden="true"></i>
</button>
<button type="button" class="md-btn" data-md="spoiler" title="Спойлер (скрытый текст)" aria-label="Спойлер">
<span class="md-ico ico ico-md-spoiler" aria-hidden="true"></span>
<i class="md-ico ico bi bi-eye-slash" aria-hidden="true"></i>
</button>
</div>
</body>

View File

@@ -46,21 +46,21 @@
</h1>
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
<button class="mini-btn" type="button" data-action="article-edit" title="Править статью" aria-label="Править статью"><span class="ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" data-action="article-edit" title="Править статью" aria-label="Править статью"><i class="ico bi bi-pencil" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<a class="btn btn-secondary"
th:href="@{/learn/{c}(c=${article.chapterSlug})}">К главе</a>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" data-confirm="Удалить статью?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}">
<button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>

View File

@@ -16,19 +16,19 @@
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
<button class="btn btn-primary" type="button" data-modal="modal-new-section">+ Раздел</button>
<button class="mini-btn" type="button" data-modal="modal-rename-chapter" title="Переименовать главу" aria-label="Переименовать главу"><span class="cell-ico ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" data-modal="modal-rename-chapter" title="Переименовать главу" aria-label="Переименовать главу"><i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/chapters/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${chapter.id}">
<input type="hidden" name="published" th:value="${!chapter.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post" data-confirm="Удалить главу со всеми разделами и статьями?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}">
<button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
@@ -50,30 +50,30 @@
<div class="sub-head sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='section',data-drag-id=${section.id}"
title="Перетащите мышью: изменить порядок разделов">⠿</span>
title="Перетащите мышью: изменить порядок разделов"><i class="bi bi-grip-vertical"></i></span>
<button type="button" class="sub-toggle" aria-expanded="false"
title="Свернуть или развернуть раздел">
<span class="sub-arrow" aria-hidden="true"><span class="cell-ico ico ico-chevron-down" aria-hidden="true"></span></span>
<span class="sub-arrow" aria-hidden="true"><i class="cell-ico ico bi bi-chevron-down" aria-hidden="true"></i></span>
</button>
<a class="group-title sort-title m-0"
th:href="@{/learn/{c}/{s}(c=${chapter.slug}, s=${section.slug})}"
th:text="${section.title}">Раздел</a>
<span th:if="${!section.published}" class="badge badge-warn">скрыто</span>
<div th:if="${currentUser != null and currentUser.admin}" class="sort-actions">
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-section-${section.id}|" title="Переименовать раздел" aria-label="Переименовать раздел"><span class="cell-ico ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-section-${section.id}|" title="Переименовать раздел" aria-label="Переименовать раздел"><i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/sections/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-move-${section.id}|" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><span class="cell-ico ico ico-hand" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-move-${section.id}|" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><i class="cell-ico ico bi bi-arrow-left-right" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" data-confirm="Удалить раздел вместе со статьями?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
@@ -83,21 +83,21 @@
<li th:each="article : ${section.articles}" th:attr="data-article-id=${article.id}">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите мышью: изменить порядок или перенести в другой раздел">⠿</span>
title="Перетащите мышью: изменить порядок или перенести в другой раздел"><i class="bi bi-grip-vertical"></i></span>
<a class="sort-title"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">Статья</a>
<span th:if="${!article.published}" class="badge badge-warn">скрыто</span>
<div th:if="${currentUser != null and currentUser.admin}" class="sort-actions">
<a class="mini-btn" title="Править статью" aria-label="Править статью"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><i class="ico bi bi-pencil" aria-hidden="true"></i></a>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
</div>
</li>

View File

@@ -24,7 +24,7 @@
<div class="sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='chapter',data-drag-id=${chapter.id}"
title="Перетащите мышью: изменить порядок глав">⠿</span>
title="Перетащите мышью: изменить порядок глав"><i class="bi bi-grip-vertical"></i></span>
<h2 class="section-title sort-title m-0" th:text="${chapter.title}">Глава</h2>
<span th:if="${!chapter.published}" class="badge badge-warn">скрыто</span>
<div th:if="${currentUser != null and currentUser.admin}" class="sort-actions">
@@ -32,13 +32,13 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${chapter.id}">
<input type="hidden" name="published" th:value="${!chapter.published}">
<button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-chapter-${chapter.id}|" title="Переименовать главу" aria-label="Переименовать главу"><span class="cell-ico ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-chapter-${chapter.id}|" title="Переименовать главу" aria-label="Переименовать главу"><i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post" data-confirm="Удалить главу со всеми разделами и статьями?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
@@ -51,10 +51,10 @@
<div class="sub-head sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='section',data-drag-id=${section.id}"
title="Перетащите мышью: изменить порядок или перенести раздел">⠿</span>
title="Перетащите мышью: изменить порядок или перенести раздел"><i class="bi bi-grip-vertical"></i></span>
<button type="button" class="sub-toggle" aria-expanded="false"
title="Свернуть или развернуть раздел">
<span class="sub-arrow" aria-hidden="true"><span class="cell-ico ico ico-chevron-down" aria-hidden="true"></span></span>
<span class="sub-arrow" aria-hidden="true"><i class="cell-ico ico bi bi-chevron-down" aria-hidden="true"></i></span>
</button>
<a class="group-title sort-title m-0"
th:href="@{/learn/{c}/{s}(c=${chapter.slug}, s=${section.slug})}"
@@ -65,14 +65,14 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}">
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-section-${section.id}|" title="Переименовать раздел" aria-label="Переименовать раздел"><span class="cell-ico ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-move-${section.id}|" title="Перенести статьи в другой раздел" aria-label="Перенести статьи в другой раздел"><span class="cell-ico ico ico-hand" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-rename-section-${section.id}|" title="Переименовать раздел" aria-label="Переименовать раздел"><i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i></button>
<button class="mini-btn" type="button" th:attr="data-modal=|modal-move-${section.id}|" title="Перенести статьи в другой раздел" aria-label="Перенести статьи в другой раздел"><i class="cell-ico ico bi bi-arrow-left-right" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" data-confirm="Удалить раздел со всеми статьями?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="mini-btn danger" type="submit" title="Удалить раздел со всеми статьями" aria-label="Удалить раздел со всеми статьями"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить раздел со всеми статьями" aria-label="Удалить раздел со всеми статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
@@ -82,7 +82,7 @@
<li th:each="article : ${section.articles}" th:attr="data-article-id=${article.id}">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите мышью: изменить порядок или перенести статью">⠿</span>
title="Перетащите мышью: изменить порядок или перенести статью"><i class="bi bi-grip-vertical"></i></span>
<a class="sort-title"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">Статья</a>
@@ -92,14 +92,14 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}">
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<a class="mini-btn" title="Править статью" aria-label="Править статью"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><i class="ico bi bi-pencil" aria-hidden="true"></i></a>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" data-confirm="Удалить статью?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</li>

View File

@@ -18,20 +18,20 @@
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
<button class="btn btn-primary" type="button" data-modal="modal-new-article">+ Статья</button>
<button class="mini-btn" type="button" data-modal="modal-rename-section" title="Переименовать раздел" aria-label="Переименовать раздел"><span class="cell-ico ico ico-pencil" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" data-modal="modal-rename-section" title="Переименовать раздел" aria-label="Переименовать раздел"><i class="cell-ico ico bi bi-pencil" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/sections/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<button class="mini-btn" type="button" data-modal="modal-move-section" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><span class="cell-ico ico ico-hand" aria-hidden="true"></span></button>
<button class="mini-btn" type="button" data-modal="modal-move-section" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><i class="cell-ico ico bi bi-arrow-left-right" aria-hidden="true"></i></button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" data-confirm="Удалить раздел вместе со статьями?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}">
<button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
<button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
@@ -50,21 +50,21 @@
<td>
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите, чтобы изменить порядок статей">⠿</span>
title="Перетащите, чтобы изменить порядок статей"><i class="bi bi-grip-vertical"></i></span>
<a th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">—</a>
<span th:if="${!article.published}" class="badge badge-warn ml-1">скрыто</span>
</td>
<td class="text-right actions-cell">
<a class="mini-btn" title="Править статью" aria-label="Править статью"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}"><i class="ico bi bi-pencil" aria-hidden="true"></i></a>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}">
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><span class="cell-ico ico ico-eye-open" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
<button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><i class="cell-ico ico bi bi-eye-slash" aria-hidden="true"></i></button>
</form>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" data-confirm="Удалить статью?">
<input type="hidden" name="_csrf" th:value="${csrfToken}">

View File

@@ -5,19 +5,19 @@
<div id="content">
<h1 class="page-title">Здравствуйте, <span th:text="${currentUser.name}">студент</span></h1>
<div class="grid grid-stats">
<div class="card card-body"><p class="stat-title">Сдано работ</p>
<p class="stat-value" th:text="${submittedCount}">0</p></div>
<div class="card card-body"><p class="stat-title">Средний балл</p>
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-3">
<div class="col"><div class="card card-body h-100"><p class="stat-title">Сдано работ</p>
<p class="stat-value" th:text="${submittedCount}">0</p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Средний балл</p>
<p class="stat-value" th:if="${average != null}" th:text="${average}">—</p>
<p class="stat-value text-muted" th:if="${average == null}">—</p></div>
<div class="card card-body"><p class="stat-title">Лучшая оценка</p>
<p class="stat-value text-muted" th:if="${average == null}">—</p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Лучшая оценка</p>
<p class="stat-value" th:if="${best != null}" th:text="${best.toPlainString()}">—</p>
<p class="stat-value text-muted" th:if="${best == null}">—</p></div>
<div class="card card-body"><p class="stat-title">Слабое место</p>
<p class="stat-value text-muted" th:if="${best == null}">—</p></div></div>
<div class="col"><div class="card card-body h-100"><p class="stat-title">Слабое место</p>
<p class="stat-value" th:if="${worst != null}" th:text="${worst.toPlainString()}">—</p>
<p class="stat-value text-muted" th:if="${worst == null}">—</p></div>
</div>
<p class="stat-value text-muted" th:if="${worst == null}">—</p></div></div>
</div>
<div class="flex flex-wrap gap-2 mt-8">
<a class="btn btn-primary" th:href="@{/student/labs}">Лабораторные</a>
@@ -25,8 +25,8 @@
</div>
<h2 class="section-title mt-8">Мои лабораторные</h2>
<div class="grid grid-2">
<div th:each="c : ${cards}" class="card">
<div class="row row-cols-1 row-cols-lg-2 g-4">
<div th:each="c : ${cards}" class="col"><div class="card h-100">
<div class="card-body">
<h3 class="section-title">
<a th:href="@{/student/labs/{id}(id=${c.id})}" th:text="|Лабораторная работа ${c.number}|">ЛР</a>
@@ -40,6 +40,7 @@
</div>
</div>
</div>
</div>
<div th:if="${#lists.isEmpty(cards)}" class="card card-body text-muted">Лабораторных пока нет.</div>
</div>
</div>

View File

@@ -4,8 +4,8 @@
<body>
<div id="content">
<h1 class="page-title">Лабораторные работы</h1>
<div class="grid grid-2">
<div th:each="c : ${cards}" class="card">
<div class="row row-cols-1 row-cols-lg-2 g-4">
<div th:each="c : ${cards}" class="col"><div class="card h-100">
<div class="card-body">
<h2 class="section-title">
<a th:href="@{/student/labs/{id}(id=${c.id})}" th:text="|Лабораторная работа ${c.number}. ${c.title}|">ЛР</a>
@@ -23,6 +23,7 @@
</p>
</div>
</div>
</div>
<div th:if="${#lists.isEmpty(cards)}" class="card card-body text-muted">Опубликованных лабораторных пока нет.</div>
</div>
</div>