Learn pages: icon actions (eye/pencil/hand/trash), centered accordion arrows; toasts bottom-center in B3 style

This commit is contained in:
2026-10-02 17:31:06 +03:00
parent 2ccdbc832f
commit aee1672058
6 changed files with 62 additions and 71 deletions

View File

@@ -228,7 +228,7 @@ tr:last-child td { border-bottom: none; }
.alert-warning { background: #fef3c7; color: #92400e; } .alert-warning { background: #fef3c7; color: #92400e; }
.list-pad { padding-left: 1.25rem; } .list-pad { padding-left: 1.25rem; }
.import-card { border-color: rgba(16,185,129,0.4); } .import-card { border-color: var(--c-badge-ok-border); }
.mini-alert { padding: 0.5rem 0.75rem; border-radius: var(--radius-xs); margin-bottom: 0.5rem; } .mini-alert { padding: 0.5rem 0.75rem; border-radius: var(--radius-xs); margin-bottom: 0.5rem; }
/* Theme toggle */ /* Theme toggle */
.theme-btn { padding: 0.25rem 0.5rem; font-size: 0.75rem; } .theme-btn { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
@@ -258,29 +258,32 @@ tr:last-child td { border-bottom: none; }
.toast { position: fixed; top: 1rem; right: 1rem; z-index: 50; width: 20rem; } .toast { position: fixed; top: 1rem; right: 1rem; z-index: 50; width: 20rem; }
/* Всплывающие уведомления (flash notice/error) вместо статичных алертов */ /* Всплывающие уведомления (flash notice/error) вместо статичных алертов */
/* Уведомления: внизу по центру, в стиле Б3 (поверх .notice) */
.toast-host { .toast-host {
position: fixed; top: 1rem; right: 1rem; z-index: 100; position: fixed; left: 50%; transform: translateX(-50%); bottom: 1.5rem; z-index: 100;
display: flex; flex-direction: column; gap: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; align-items: stretch;
width: min(24rem, calc(100vw - 2rem)); pointer-events: none; width: min(34rem, calc(100vw - 2rem)); pointer-events: none;
} }
.toast-item { .toast-item {
pointer-events: auto; display: flex; align-items: flex-start; gap: 0.5rem; pointer-events: auto; display: flex; align-items: flex-start; gap: 0.75rem;
padding: 0.7rem 0.85rem; border-radius: var(--radius); border: 1px solid var(--c-border); padding: 0.75rem 1rem; border-radius: var(--radius); border: 1px solid var(--c-border);
border-left-width: 4px; background: var(--c-surface); color: var(--c-text); border-left: 3px solid var(--c-accent); background: var(--c-surface); color: var(--c-text);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); font-size: 0.8125rem;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
animation: toast-in 0.18s ease-out; animation: toast-in 0.18s ease-out;
} }
.toast-item .toast-text { flex: 1 1 auto; min-width: 0; white-space: pre-line; overflow-wrap: anywhere; } .toast-item .toast-text { flex: 1 1 auto; min-width: 0; white-space: pre-line; overflow-wrap: anywhere; }
.toast-item.toast-success { border-left-color: #16a34a; } .toast-item .toast-text .code { margin: 0 0.1rem; }
.toast-item.toast-error { border-left-color: #dc2626; } .toast-item.toast-success { border-left-color: var(--c-tone-pos); }
.toast-item.toast-warning { border-left-color: #d97706; } .toast-item.toast-error { border-left-color: var(--c-tone-neg); }
.toast-item.toast-warning { border-left-color: var(--c-badge-warn-text); }
.toast-close { .toast-close {
flex: 0 0 auto; border: none; background: none; cursor: pointer; flex: 0 0 auto; border: none; background: none; cursor: pointer;
color: var(--c-text-muted); font-size: 1.05rem; line-height: 1; padding: 0 0.1rem; color: var(--c-text-muted); font-size: 1rem; line-height: 1; padding: 0 0.1rem;
} }
.toast-close:hover { color: var(--c-text); } .toast-close:hover { color: var(--c-text); }
@keyframes toast-in { @keyframes toast-in {
from { opacity: 0; transform: translateY(-6px); } from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; } to { opacity: 1; transform: none; }
} }
@@ -494,7 +497,7 @@ dialog.modal.modal-lg .modal-body { max-height: 88vh; min-height: 62vh; }
} }
.sub-toggle:hover { color: var(--c-text); } .sub-toggle:hover { color: var(--c-text); }
.sub-arrow { .sub-arrow {
flex: 0 0 auto; width: 8px; height: 8px; display: block; flex: 0 0 auto; width: 8px; height: 8px;
border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
transform: rotate(-135deg); transition: transform 0.2s; transform: rotate(-135deg); transition: transform 0.2s;
} }
@@ -614,7 +617,9 @@ th.th-filterable { padding-bottom: 0.35rem; }
cursor: grab; user-select: none; color: var(--c-text-muted); cursor: grab; user-select: none; color: var(--c-text-muted);
font-size: 1rem; line-height: 1; padding: 0.15rem 0; border-radius: var(--radius-sm); font-size: 1rem; line-height: 1; padding: 0.15rem 0; border-radius: var(--radius-sm);
} }
.drag-handle svg { width: 1rem; height: 1rem; display: block; margin: 0 auto; }
.drag-handle:hover { background: var(--c-bg); color: var(--c-text); } .drag-handle:hover { background: var(--c-bg); color: var(--c-text); }
.mini-btn.danger:hover { color: var(--c-accent); border-color: #dcb9be; background: var(--c-accent-soft); }
.drag-handle:active { cursor: grabbing; } .drag-handle:active { cursor: grabbing; }
.dragging { opacity: 0.45; } .dragging { opacity: 0.45; }
.drop-before { box-shadow: 0 -3px 0 0 var(--c-text); } .drop-before { box-shadow: 0 -3px 0 0 var(--c-text); }

View File

@@ -307,7 +307,7 @@
<div th:if="${createdStudent}" class="card import-card toast"> <div th:if="${createdStudent}" class="card import-card">
<div class="card-body"> <div class="card-body">
<h2 class="section-title">Студент добавлен</h2> <h2 class="section-title">Студент добавлен</h2>
<p class="text-sm" th:text="${createdStudent.fullName}">—</p> <p class="text-sm" th:text="${createdStudent.fullName}">—</p>
@@ -317,7 +317,7 @@
</div> </div>
<!-- Итог импорта группы: одноразовые пароли новых студентов показываются здесь один раз. --> <!-- Итог импорта группы: одноразовые пароли новых студентов показываются здесь один раз. -->
<div th:if="${importResult}" class="card import-card toast"> <div th:if="${importResult}" class="card import-card">
<div class="card-body"> <div class="card-body">
<h2 class="section-title"> <h2 class="section-title">
Группа <span th:text="${importResult.groupName}">—</span> импортирована Группа <span th:text="${importResult.groupName}">—</span> импортирована

View File

@@ -52,9 +52,8 @@
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}"> <input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><svg class="cell-ico" 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></button>
th:classappend="${article.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><svg class="cell-ico" 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></button>
th:text="${article.published} ? 'Скрыть от студентов' : 'Опубликовать'">Скрыть</button>
</form> </form>
<a class="btn btn-secondary" <a class="btn btn-secondary"
th:href="@{/learn/{c}(c=${article.chapterSlug})}">К главе</a> th:href="@{/learn/{c}(c=${article.chapterSlug})}">К главе</a>
@@ -62,7 +61,7 @@
onsubmit="return confirm('Удалить статью?')"> onsubmit="return confirm('Удалить статью?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}"> <input type="hidden" name="back" th:value="${backParent}">
<button class="btn btn-danger" type="submit">Удалить статью</button> <button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>

View File

@@ -16,26 +16,25 @@
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4"> <div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
<button class="btn btn-primary" type="button" onclick="openModal('modal-new-section')">+ Раздел</button> <button class="btn btn-primary" type="button" onclick="openModal('modal-new-section')">+ Раздел</button>
<button class="btn btn-secondary" type="button" onclick="openModal('modal-rename-chapter')">Переименовать главу</button> <button class="mini-btn" type="button" onclick="openModal('modal-rename-chapter')" title="Переименовать главу" aria-label="Переименовать главу"><svg class="cell-ico" 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></button>
<form class="inline-form" th:action="@{/admin/content/chapters/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${chapter.id}"> <input type="hidden" name="id" th:value="${chapter.id}">
<input type="hidden" name="published" th:value="${!chapter.published}"> <input type="hidden" name="published" th:value="${!chapter.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><svg class="cell-ico" 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></button>
th:classappend="${chapter.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><svg class="cell-ico" 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></button>
th:text="${chapter.published} ? 'Скрыть главу' : 'Опубликовать главу'">Скрыть</button>
</form> </form>
<form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post"
onsubmit="return confirm('Удалить главу со всеми разделами и статьями?')"> onsubmit="return confirm('Удалить главу со всеми разделами и статьями?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}"> <input type="hidden" name="back" th:value="${backParent}">
<button class="btn btn-danger" type="submit">Удалить главу</button> <button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
<div th:if="${currentUser != null and currentUser.admin}" class="card card-body text-sm text-muted mb-4"> <div th:if="${currentUser != null and currentUser.admin}" class="card card-body text-sm text-muted mb-4">
Перетаскивайте за значок ⠿ слева, чтобы менять порядок разделов и переносить статьи Перетаскивайте за значок руки слева, чтобы менять порядок разделов и переносить статьи
между разделами этой главы. Порядок сохраняется сразу. между разделами этой главы. Порядок сохраняется сразу.
</div> </div>
<div th:if="${currentUser != null and currentUser.admin}" id="sort-status" class="sort-status" hidden> <div th:if="${currentUser != null and currentUser.admin}" id="sort-status" class="sort-status" hidden>
@@ -52,7 +51,7 @@
<div class="sub-head sort-row"> <div class="sub-head sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='section',data-drag-id=${section.id}" th:attr="data-drag-kind='section',data-drag-id=${section.id}"
title="Перетащите мышью: изменить порядок разделов">⠿</span> title="Перетащите мышью: изменить порядок разделов"><svg class="cell-ico" 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></span>
<button type="button" class="sub-toggle" aria-expanded="false" onclick="toggleSection(this)" <button type="button" class="sub-toggle" aria-expanded="false" onclick="toggleSection(this)"
title="Свернуть или развернуть раздел"> title="Свернуть или развернуть раздел">
<span class="sub-arrow" aria-hidden="true"></span> <span class="sub-arrow" aria-hidden="true"></span>
@@ -62,24 +61,21 @@
th:text="${section.title}">Раздел</a> th:text="${section.title}">Раздел</a>
<span th:if="${!section.published}" class="badge badge-warn">скрыто</span> <span th:if="${!section.published}" class="badge badge-warn">скрыто</span>
<div th:if="${currentUser != null and currentUser.admin}" class="sort-actions"> <div th:if="${currentUser != null and currentUser.admin}" class="sort-actions">
<button class="btn btn-secondary" type="button" <button class="mini-btn" type="button" th:data-modal="|modal-rename-section-${section.id}|" onclick="openModalData(this)" title="Переименовать раздел" aria-label="Переименовать раздел"><svg class="cell-ico" 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></button>
th:data-modal="|modal-rename-section-${section.id}|" onclick="openModalData(this)">Переименовать</button>
<form class="inline-form" th:action="@{/admin/content/sections/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}"> <input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}"> <input type="hidden" name="published" th:value="${!section.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><svg class="cell-ico" 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></button>
th:classappend="${section.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><svg class="cell-ico" 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></button>
th:text="${section.published} ? 'Скрыть' : 'Опубликовать'">Скрыть</button>
</form> </form>
<button class="btn btn-secondary" type="button" <button class="mini-btn" type="button" th:data-modal="|modal-move-${section.id}|" onclick="openModalData(this)" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><svg class="cell-ico" 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></button>
th:data-modal="|modal-move-${section.id}|" onclick="openModalData(this)">Перенести статьи и удалить</button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post"
onsubmit="return confirm('Удалить раздел вместе со статьями?')"> onsubmit="return confirm('Удалить раздел вместе со статьями?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn btn-danger" type="submit">Удалить</button> <button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
</div> </div>
@@ -89,7 +85,7 @@
<li th:each="article : ${section.articles}" th:attr="data-article-id=${article.id}"> <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" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}" th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите мышью: изменить порядок или перенести в другой раздел">⠿</span> title="Перетащите мышью: изменить порядок или перенести в другой раздел"><svg class="cell-ico" 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></span>
<a class="sort-title" <a class="sort-title"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}" th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">Статья</a> th:text="${article.title}">Статья</a>
@@ -102,9 +98,8 @@
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}"> <input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><svg class="cell-ico" 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></button>
th:classappend="${article.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><svg class="cell-ico" 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></button>
th:text="${article.published} ? 'Скрыть' : 'Опубликовать'">Скрыть</button>
</form> </form>
</div> </div>
</li> </li>

View File

@@ -24,7 +24,7 @@
<div class="sort-row"> <div class="sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='chapter',data-drag-id=${chapter.id}" th:attr="data-drag-kind='chapter',data-drag-id=${chapter.id}"
title="Перетащите мышью: изменить порядок глав">⠿</span> title="Перетащите мышью: изменить порядок глав"><svg class="cell-ico" 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></span>
<h2 class="section-title sort-title m-0" th:text="${chapter.title}">Глава</h2> <h2 class="section-title sort-title m-0" th:text="${chapter.title}">Глава</h2>
<span th:if="${!chapter.published}" class="badge badge-warn">скрыто</span> <span th:if="${!chapter.published}" class="badge badge-warn">скрыто</span>
<div th:if="${currentUser != null and currentUser.admin}" class="sort-actions"> <div th:if="${currentUser != null and currentUser.admin}" class="sort-actions">
@@ -32,16 +32,14 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${chapter.id}"> <input type="hidden" name="id" th:value="${chapter.id}">
<input type="hidden" name="published" th:value="${!chapter.published}"> <input type="hidden" name="published" th:value="${!chapter.published}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><svg class="cell-ico" 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></button>
th:classappend="${chapter.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${chapter.published}" title="Опубликовать главу" aria-label="Опубликовать главу"><svg class="cell-ico" 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></button>
th:text="${chapter.published} ? 'Скрыть главу' : 'Опубликовать главу'">Скрыть главу</button>
</form> </form>
<button class="btn btn-secondary" type="button" <button class="mini-btn" type="button" th:data-modal="|modal-rename-chapter-${chapter.id}|" onclick="openModalData(this)" title="Переименовать главу" aria-label="Переименовать главу"><svg class="cell-ico" 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></button>
th:data-modal="|modal-rename-chapter-${chapter.id}|" onclick="openModalData(this)">Переименовать</button>
<form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/chapters/{id}/delete(id=${chapter.id})}" method="post"
onsubmit="return confirm('Удалить главу со всеми разделами и статьями?')"> onsubmit="return confirm('Удалить главу со всеми разделами и статьями?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="btn btn-danger" type="submit">Удалить главу</button> <button class="mini-btn danger" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
</div> </div>
@@ -54,7 +52,7 @@
<div class="sub-head sort-row"> <div class="sub-head sort-row">
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='section',data-drag-id=${section.id}" th:attr="data-drag-kind='section',data-drag-id=${section.id}"
title="Перетащите мышью: изменить порядок или перенести раздел">⠿</span> title="Перетащите мышью: изменить порядок или перенести раздел"><svg class="cell-ico" 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></span>
<button type="button" class="sub-toggle" aria-expanded="false" onclick="toggleSection(this)" <button type="button" class="sub-toggle" aria-expanded="false" onclick="toggleSection(this)"
title="Свернуть или развернуть раздел"> title="Свернуть или развернуть раздел">
<span class="sub-arrow" aria-hidden="true"></span> <span class="sub-arrow" aria-hidden="true"></span>
@@ -68,18 +66,15 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}"> <input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}"> <input type="hidden" name="published" th:value="${!section.published}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><svg class="cell-ico" 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></button>
th:classappend="${section.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><svg class="cell-ico" 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></button>
th:text="${section.published} ? 'Скрыть раздел' : 'Опубликовать раздел'">Скрыть раздел</button>
</form> </form>
<button class="btn btn-secondary" type="button" <button class="mini-btn" type="button" th:data-modal="|modal-rename-section-${section.id}|" onclick="openModalData(this)" title="Переименовать раздел" aria-label="Переименовать раздел"><svg class="cell-ico" 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></button>
th:data-modal="|modal-rename-section-${section.id}|" onclick="openModalData(this)">Переименовать</button> <button class="mini-btn" type="button" th:data-modal="|modal-move-${section.id}|" onclick="openModalData(this)" title="Перенести статьи в другой раздел" aria-label="Перенести статьи в другой раздел"><svg class="cell-ico" 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></button>
<button class="btn btn-secondary" type="button"
th:data-modal="|modal-move-${section.id}|" onclick="openModalData(this)">Перенести статьи</button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post"
onsubmit="return confirm('Удалить раздел со всеми статьями?')"> onsubmit="return confirm('Удалить раздел со всеми статьями?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="btn btn-danger" type="submit">Удалить раздел</button> <button class="mini-btn danger" type="submit" title="Удалить раздел со всеми статьями" aria-label="Удалить раздел со всеми статьями"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
</div> </div>
@@ -89,7 +84,7 @@
<li th:each="article : ${section.articles}" th:attr="data-article-id=${article.id}"> <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" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}" th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите мышью: изменить порядок или перенести статью">⠿</span> title="Перетащите мышью: изменить порядок или перенести статью"><svg class="cell-ico" 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></span>
<a class="sort-title" <a class="sort-title"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}" th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">Статья</a> th:text="${article.title}">Статья</a>
@@ -99,16 +94,15 @@
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}"> <input type="hidden" name="published" th:value="${!article.published}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><svg class="cell-ico" 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></button>
th:classappend="${article.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><svg class="cell-ico" 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></button>
th:text="${article.published} ? 'Скрыть' : 'Опубликовать'">Скрыть</button>
</form> </form>
<a class="btn btn-secondary" <a class="btn btn-secondary"
th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}">Править</a> th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug}, edit=1)}">Править</a>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post"
onsubmit="return confirm('Удалить статью?')"> onsubmit="return confirm('Удалить статью?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<button class="btn btn-danger" type="submit">✕</button> <button class="mini-btn danger" type="submit" title="Удалить статью" aria-label="Удалить статью"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
</li> </li>

View File

@@ -18,27 +18,26 @@
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4"> <div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
<button class="btn btn-primary" type="button" onclick="openModal('modal-new-article')">+ Статья</button> <button class="btn btn-primary" type="button" onclick="openModal('modal-new-article')">+ Статья</button>
<button class="btn btn-secondary" type="button" onclick="openModal('modal-rename-section')">Переименовать раздел</button> <button class="mini-btn" type="button" onclick="openModal('modal-rename-section')" title="Переименовать раздел" aria-label="Переименовать раздел"><svg class="cell-ico" 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></button>
<form class="inline-form" th:action="@{/admin/content/sections/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${section.id}"> <input type="hidden" name="id" th:value="${section.id}">
<input type="hidden" name="published" th:value="${!section.published}"> <input type="hidden" name="published" th:value="${!section.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><svg class="cell-ico" 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></button>
th:classappend="${section.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${section.published}" title="Опубликовать раздел" aria-label="Опубликовать раздел"><svg class="cell-ico" 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></button>
th:text="${section.published} ? 'Скрыть раздел' : 'Опубликовать раздел'">Скрыть</button>
</form> </form>
<button class="btn btn-secondary" type="button" onclick="openModal('modal-move-section')">Перенести статьи и удалить</button> <button class="mini-btn" type="button" onclick="openModal('modal-move-section')" title="Перенести статьи и удалить" aria-label="Перенести статьи и удалить"><svg class="cell-ico" 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></button>
<form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post"
onsubmit="return confirm('Удалить раздел вместе со статьями?')"> onsubmit="return confirm('Удалить раздел вместе со статьями?')">
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
<input type="hidden" name="back" th:value="${backParent}"> <input type="hidden" name="back" th:value="${backParent}">
<button class="btn btn-danger" type="submit">Удалить раздел</button> <button class="mini-btn danger" type="submit" title="Удалить раздел вместе со статьями" aria-label="Удалить раздел вместе со статьями"><svg class="cell-ico" 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></button>
</form> </form>
</div> </div>
<div th:if="${currentUser != null and currentUser.admin}" class="card card-body text-sm text-muted mb-4"> <div th:if="${currentUser != null and currentUser.admin}" class="card card-body text-sm text-muted mb-4">
Перетаскивайте статьи за значок ⠿, чтобы менять их порядок. Порядок сохраняется сразу. Перетаскивайте статьи за значок руки, чтобы менять их порядок. Порядок сохраняется сразу.
</div> </div>
<div th:if="${currentUser != null and currentUser.admin}" id="sort-status" class="sort-status" hidden> <div th:if="${currentUser != null and currentUser.admin}" id="sort-status" class="sort-status" hidden>
<span id="sort-status-text">Изменения не сохранены…</span> <span id="sort-status-text">Изменения не сохранены…</span>
@@ -52,7 +51,7 @@
<td> <td>
<span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true" <span th:if="${currentUser != null and currentUser.admin}" class="drag-handle" draggable="true"
th:attr="data-drag-kind='article',data-drag-id=${article.id}" th:attr="data-drag-kind='article',data-drag-id=${article.id}"
title="Перетащите, чтобы изменить порядок статей">⠿</span> title="Перетащите, чтобы изменить порядок статей"><svg class="cell-ico" 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></span>
<a th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}" <a th:href="@{/learn/{c}/{s}/{a}(c=${chapter.slug}, s=${section.slug}, a=${article.slug})}"
th:text="${article.title}">—</a> th:text="${article.title}">—</a>
<span th:if="${!article.published}" class="badge badge-warn ml-1">скрыто</span> <span th:if="${!article.published}" class="badge badge-warn ml-1">скрыто</span>
@@ -65,9 +64,8 @@
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
<input type="hidden" name="published" th:value="${!article.published}"> <input type="hidden" name="published" th:value="${!article.published}">
<input type="hidden" name="back" th:value="${back}"> <input type="hidden" name="back" th:value="${back}">
<button class="btn" type="submit" <button class="mini-btn" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><svg class="cell-ico" 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></button>
th:classappend="${article.published} ? 'btn-secondary' : 'btn-primary'" <button class="mini-btn" type="submit" th:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><svg class="cell-ico" 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></button>
th:text="${article.published} ? 'Скрыть' : 'Опубликовать'">Скрыть</button>
</form> </form>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" <form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post"
onsubmit="return confirm('Удалить статью?')"> onsubmit="return confirm('Удалить статью?')">