Mobile: burger primary right, theme in header, sidebar from right, learn rows wrap with actions below; pencil for all edit actions

This commit is contained in:
2026-10-02 23:12:32 +03:00
parent 1c5c5651c7
commit 24b72a2525
9 changed files with 46 additions and 37 deletions

View File

@@ -731,20 +731,29 @@ th.th-filterable { padding-bottom: 0.35rem; }
.offscreen { position: fixed; top: -1000px; left: 0; } .offscreen { position: fixed; top: -1000px; left: 0; }
/* ---- Мобильное меню: бургер в хэдере, ссылки в выезжающем сайдбаре ---- */ /* Материалы на мобильных: действия переносятся под заголовок */
@media (max-width: 720px) {
.sort-row, .sort-list > li { flex-wrap: wrap; row-gap: 0.35rem; }
.sort-row .sort-actions, .sort-list > li .sort-actions { margin-left: 2.4rem; width: calc(100% - 2.4rem); justify-content: flex-start; }
.sort-row .sort-title { flex: 1 1 auto; }
}
/* ---- Мобильное меню: бургер в хэдере, ссылки в выезжающем сайдбаре справа ---- */
.nav-burger { display: none; } .nav-burger { display: none; }
.mobile-nav { display: none; } .mobile-nav { display: none; }
.mobile-nav-overlay { display: none; } .mobile-nav-overlay { display: none; }
@media (max-width: 720px) { @media (max-width: 720px) {
.nav { display: none; } .nav { display: none; }
.page-header .user { display: none; } .page-header .user { display: none; }
.page-header .theme-btn { margin-left: auto; }
.nav-burger { display: inline-flex; } .nav-burger { display: inline-flex; }
.mobile-nav { .mobile-nav {
display: flex; flex-direction: column; gap: 1rem; display: flex; flex-direction: column; gap: 1rem;
position: fixed; top: 0; left: 0; bottom: 0; width: 17rem; max-width: 86vw; position: fixed; top: 0; right: 0; bottom: 0; left: auto; width: 17rem; max-width: 86vw;
z-index: 120; background: var(--c-surface); border-right: 1px solid var(--c-border); z-index: 120; background: var(--c-surface);
border-right: none; border-left: 1px solid var(--c-border);
padding: 1rem; overflow-y: auto; padding: 1rem; overflow-y: auto;
transform: translateX(-100%); transition: transform 0.2s; transform: translateX(100%); transition: transform 0.2s;
} }
.mobile-nav.open { transform: none; } .mobile-nav.open { transform: none; }
.mobile-nav-overlay { display: block; position: fixed; inset: 0; z-index: 110; background: rgba(0, 0, 0, 0.45); } .mobile-nav-overlay { display: block; position: fixed; inset: 0; z-index: 110; background: rgba(0, 0, 0, 0.45); }

View File

@@ -344,7 +344,7 @@
<div class="modal-body" id="assign-modal-body"></div> <div class="modal-body" id="assign-modal-body"></div>
</dialog> </dialog>
<script th:src="@{/js/admin-group.js(v='14')}" defer></script> <script th:src="@{/js/admin-group.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -196,7 +196,7 @@
</dialog> </dialog>
</th:block> </th:block>
</div> </div>
<script th:src="@{/js/admin-lab.js(v='14')}" defer></script> <script th:src="@{/js/admin-lab.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -15,17 +15,17 @@
</noscript> </noscript>
<link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg(v='2')}"> <link rel="icon" type="image/svg+xml" th:href="@{/favicon.svg(v='2')}">
<!-- v=4: сброс кэша стилей после редизайна; при изменении css поднимать версию --> <!-- v=4: сброс кэша стилей после редизайна; при изменении css поднимать версию -->
<link rel="stylesheet" th:href="@{/css/app.css(v='14')}"> <link rel="stylesheet" th:href="@{/css/app.css(v='15')}">
<link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='14')}"> <link id="hljs-light" rel="stylesheet" th:href="@{/css/hljs-github.css(v='15')}">
<link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='14')}" disabled> <link id="hljs-dark" rel="stylesheet" th:href="@{/css/hljs-github-dark.css(v='15')}" disabled>
<script th:src="@{/js/highlight.min.js(v='14')}" defer></script> <script th:src="@{/js/highlight.min.js(v='15')}" defer></script>
<script th:src="@{/js/table-scroll.js(v='14')}" defer></script> <script th:src="@{/js/table-scroll.js(v='15')}" defer></script>
<script th:src="@{/js/tablekit.js(v='14')}" defer></script> <script th:src="@{/js/tablekit.js(v='15')}" defer></script>
<script th:src="@{/js/theme-init.js(v='14')}"></script> <script th:src="@{/js/theme-init.js(v='15')}"></script>
<script th:src="@{/js/app.js(v='14')}" defer></script> <script th:src="@{/js/app.js(v='15')}" defer></script>
<script th:src="@{/js/ui-state.js(v='14')}" defer></script> <script th:src="@{/js/ui-state.js(v='15')}" defer></script>
<script th:src="@{/js/md-content.js(v='14')}" defer></script> <script th:src="@{/js/md-content.js(v='15')}" defer></script>
<script th:src="@{/js/submit-guard.js(v='14')}" defer></script> <script th:src="@{/js/submit-guard.js(v='15')}" defer></script>
</head> </head>
<body> <body>
<div class="page"> <div class="page">
@@ -41,7 +41,11 @@
<a th:href="@{/change-password}" class="nav-link">Смена пароля</a> <a th:href="@{/change-password}" class="nav-link">Смена пароля</a>
</th:block> </th:block>
</nav> </nav>
<button type="button" id="nav-burger" class="btn btn-secondary btn-sm nav-burger" <button type="button" class="btn btn-secondary theme-btn"
aria-label="Сменить тему" title="Сменить тему">
<span class="theme-icon ico ico-theme" aria-hidden="true"></span>
</button>
<button type="button" id="nav-burger" class="btn btn-sm nav-burger"
aria-label="Меню" aria-expanded="false"> aria-label="Меню" aria-expanded="false">
<span class="ico ico-menu"></span> <span class="ico ico-menu"></span>
</button> </button>
@@ -50,13 +54,9 @@
<a th:href="@{/admin}" th:if="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a> <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> <a th:href="@{/student}" th:unless="${currentUser.admin}" class="nav-user" th:text="${currentUser.name}" title="Личный кабинет">—</a>
</th:block> </th:block>
<button type="button" class="btn btn-secondary theme-btn" <a th:if="${currentUser == null}" th:href="@{/login}" class="btn btn-sm">Войти</a>
aria-label="Сменить тему" title="Сменить тему">
<span class="theme-icon ico ico-theme" aria-hidden="true"></span>
</button>
<a th:if="${currentUser == null}" th:href="@{/login}" class="btn">Войти</a>
<form th:if="${currentUser != null}" th:action="@{/logout}" method="post"> <form th:if="${currentUser != null}" th:action="@{/logout}" method="post">
<button class="btn" type="submit">Выйти</button> <button class="btn btn-sm" type="submit">Выйти</button>
<input type="hidden" name="_csrf" th:value="${csrfToken}"> <input type="hidden" name="_csrf" th:value="${csrfToken}">
</form> </form>
</div> </div>

View File

@@ -46,7 +46,7 @@
</h1> </h1>
<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" data-action="article-edit">Править</button> <button class="mini-btn" type="button" data-action="article-edit" title="Править статью" aria-label="Править статью"><span class="ico ico-pencil" aria-hidden="true"></span></button>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
@@ -124,7 +124,7 @@
</div> </div>
<script th:src="@{/js/mdeditor.js(v='14')}" defer></script> <script th:src="@{/js/mdeditor.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -89,8 +89,8 @@
th:text="${article.title}">Статья</a> th:text="${article.title}">Статья</a>
<span th:if="${!article.published}" class="badge badge-warn">скрыто</span> <span th:if="${!article.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">
<a class="btn btn-secondary" <a class="mini-btn" title="Править статью" aria-label="Править статью"
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)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
@@ -183,7 +183,7 @@
</div> </div>
</dialog> </dialog>
<script th:src="@{/js/content-sort.js(v='14')}" defer></script> <script th:src="@{/js/content-sort.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -95,8 +95,8 @@
<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: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:unless="${article.published}" title="Опубликовать статью" aria-label="Опубликовать статью"><span class="cell-ico ico ico-eye-closed" aria-hidden="true"></span></button>
</form> </form>
<a class="btn btn-secondary" <a class="mini-btn" title="Править статью" aria-label="Править статью"
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)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post" data-confirm="Удалить статью?"> <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="_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="Удалить статью"><span class="cell-ico ico ico-trash" aria-hidden="true"></span></button>
@@ -232,8 +232,8 @@
</div> </div>
</dialog> </dialog>
<script th:src="@{/js/mdeditor.js(v='14')}" defer></script> <script th:src="@{/js/mdeditor.js(v='15')}" defer></script>
<script th:src="@{/js/content-sort.js(v='14')}" defer></script> <script th:src="@{/js/content-sort.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -56,8 +56,8 @@
<span th:if="${!article.published}" class="badge badge-warn ml-1">скрыто</span> <span th:if="${!article.published}" class="badge badge-warn ml-1">скрыто</span>
</td> </td>
<td class="text-right actions-cell"> <td class="text-right actions-cell">
<a class="btn btn-secondary" <a class="mini-btn" title="Править статью" aria-label="Править статью"
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)}"><span class="ico ico-pencil" aria-hidden="true"></span></a>
<form class="inline-form" th:action="@{/admin/content/articles/toggle-published}" method="post"> <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="_csrf" th:value="${csrfToken}">
<input type="hidden" name="id" th:value="${article.id}"> <input type="hidden" name="id" th:value="${article.id}">
@@ -143,7 +143,7 @@
</div> </div>
</dialog> </dialog>
<script th:src="@{/js/content-sort.js(v='14')}" defer></script> <script th:src="@{/js/content-sort.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -107,7 +107,7 @@
<p class="text-sm text-muted">Ожидайте преподавателя с проверкой.</p> <p class="text-sm text-muted">Ожидайте преподавателя с проверкой.</p>
</div> </div>
</div> </div>
<script th:src="@{/js/defense-timer.js(v='14')}" defer></script> <script th:src="@{/js/defense-timer.js(v='15')}" defer></script>
</div> </div>
</body> </body>
</html> </html>