Learn UI: prev/next navigation, sidebar outline, editing on every page; merge import; defense date
- article page: ← / → navigation across the whole material tree (including between sections), sticky sidebar outline with the current article highlighted - editing on chapter, section and article pages: rename, publish/hide, delete, create section/article, move articles while deleting a section; forms return to the page they were opened from (validated internal `back` parameter) - article page opens the edit dialog directly via ?edit=1 (used by "Править" links on section/chapter pages) - import: optional sync flag — with it stale sections/articles of archived chapters are deleted (previous behaviour), without it import only adds and updates, so material edited in the UI is not overwritten - student area: lab page and lab cards now show the defense date next to the submission deadline - fix SpEL crash on the student lab page (`and` with nullable strings)
This commit is contained in:
@@ -3,21 +3,127 @@
|
||||
<head><title th:text="${article.title} + ' — LMS'">Статья</title></head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<nav class="text-sm mb-4 breadcrumb">
|
||||
<a th:href="@{/learn}" class="nav-link">Материалы</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<a th:href="@{/learn/{c}(c=${article.chapterSlug})}" class="nav-link" th:text="${article.chapterTitle}">Глава</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<a th:href="@{/learn/{c}/{s}(c=${article.chapterSlug}, s=${article.sectionSlug})}" class="nav-link"
|
||||
th:text="${article.sectionTitle}">Раздел</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<span th:text="${article.title}">Статья</span>
|
||||
</nav>
|
||||
<h1 class="page-title">
|
||||
<span th:text="${article.title}">Статья</span>
|
||||
<span th:if="${!article.published}" class="badge badge-warn ml-2">скрыто от студентов</span>
|
||||
</h1>
|
||||
<div class="card card-body md-body" th:utext="${article.bodyHtml}"></div>
|
||||
<div class="learn-layout">
|
||||
<div class="learn-main">
|
||||
<nav class="text-sm mb-4 breadcrumb">
|
||||
<a th:href="@{/learn}" class="nav-link">Материалы</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<a th:href="@{/learn/{c}(c=${article.chapterSlug})}" class="nav-link" th:text="${article.chapterTitle}">Глава</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<a th:href="@{/learn/{c}/{s}(c=${article.chapterSlug}, s=${article.sectionSlug})}" class="nav-link"
|
||||
th:text="${article.sectionTitle}">Раздел</a>
|
||||
<span class="nav-sep">/</span>
|
||||
<span th:text="${article.title}">Статья</span>
|
||||
</nav>
|
||||
|
||||
<h1 class="page-title">
|
||||
<span th:text="${article.title}">Статья</span>
|
||||
<span th:if="${!article.published}" class="badge badge-warn ml-2">скрыто от студентов</span>
|
||||
</h1>
|
||||
|
||||
<div th:if="${currentUser != null and currentUser.admin}" class="flex flex-wrap gap-2 mb-4">
|
||||
<button class="btn btn-primary btn-sm" type="button" onclick="openModal('modal-edit-article')">Править</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="btn btn-sm" type="submit"
|
||||
th:classappend="${article.published} ? 'btn-secondary' : 'btn-primary'"
|
||||
th:text="${article.published} ? 'Скрыть от студентов' : 'Опубликовать'">Скрыть</button>
|
||||
</form>
|
||||
<a class="btn btn-secondary btn-sm"
|
||||
th:href="@{/learn/{c}(c=${article.chapterSlug})}">К главе</a>
|
||||
<form class="inline-form" th:action="@{/admin/content/articles/{id}/delete(id=${article.id})}" method="post"
|
||||
onsubmit="return confirm('Удалить статью?')">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${backParent}">
|
||||
<button class="btn btn-danger btn-sm" type="submit">Удалить статью</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<nav class="article-nav mb-4" th:if="${prevArticle != null or nextArticle != null}">
|
||||
<a th:if="${prevArticle != null}" class="btn btn-secondary btn-sm"
|
||||
th:href="@{/learn/{c}/{s}/{a}(c=${prevArticle.chapterSlug}, s=${prevArticle.sectionSlug}, a=${prevArticle.slug})}"
|
||||
th:title="|${prevArticle.chapterTitle} / ${prevArticle.sectionTitle}|">
|
||||
← <span th:text="${prevArticle.title}">Предыдущая</span>
|
||||
</a>
|
||||
<span th:if="${prevArticle == null}"></span>
|
||||
<a th:if="${nextArticle != null}" class="btn btn-secondary btn-sm ml-auto"
|
||||
th:href="@{/learn/{c}/{s}/{a}(c=${nextArticle.chapterSlug}, s=${nextArticle.sectionSlug}, a=${nextArticle.slug})}"
|
||||
th:title="|${nextArticle.chapterTitle} / ${nextArticle.sectionTitle}|">
|
||||
<span th:text="${nextArticle.title}">Следующая</span> →
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="card card-body md-body" th:utext="${article.bodyHtml}"></div>
|
||||
|
||||
<nav class="article-nav mt-6" th:if="${prevArticle != null or nextArticle != null}">
|
||||
<a th:if="${prevArticle != null}" class="btn btn-secondary"
|
||||
th:href="@{/learn/{c}/{s}/{a}(c=${prevArticle.chapterSlug}, s=${prevArticle.sectionSlug}, a=${prevArticle.slug})}">
|
||||
← <span th:text="${prevArticle.title}">Предыдущая</span>
|
||||
</a>
|
||||
<span th:if="${prevArticle == null}"></span>
|
||||
<a th:if="${nextArticle != null}" class="btn btn-primary ml-auto"
|
||||
th:href="@{/learn/{c}/{s}/{a}(c=${nextArticle.chapterSlug}, s=${nextArticle.sectionSlug}, a=${nextArticle.slug})}">
|
||||
<span th:text="${nextArticle.title}">Следующая</span> →
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<aside class="learn-side">
|
||||
<div class="card card-body">
|
||||
<p class="text-sm text-muted mb-2">Содержание</p>
|
||||
<div th:each="c : ${tree.chapters}">
|
||||
<div class="side-chapter">
|
||||
<a th:href="@{/learn/{c}(c=${c.slug})}" th:text="${c.title}">Глава</a>
|
||||
<span th:if="${!c.published}" class="badge badge-warn">скрыто</span>
|
||||
</div>
|
||||
<div th:each="s : ${c.sections}" class="side-section">
|
||||
<a th:href="@{/learn/{c}/{s}(c=${c.slug}, s=${s.slug})}" th:text="${s.title}">Раздел</a>
|
||||
<span th:if="${!s.published}" class="badge badge-warn">скрыто</span>
|
||||
<ul class="side-articles">
|
||||
<li th:each="a : ${s.articles}">
|
||||
<a th:href="@{/learn/{c}/{s}/{a2}(c=${c.slug}, s=${s.slug}, a2=${a.slug})}"
|
||||
th:text="${a.title}"
|
||||
th:classappend="${a.slug == article.slug and s.slug == article.sectionSlug and c.slug == article.chapterSlug} ? 'side-current'">Статья</a>
|
||||
<span th:if="${!a.published}" class="badge badge-warn">скрыто</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-edit-article" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Правка статьи</h2>
|
||||
<button class="modal-close" type="button" onclick="closeModal('modal-edit-article')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-stack" th:action="@{/admin/content/articles/{id}/save(id=${article.id})}" method="post">
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
<input type="hidden" name="back" th:value="${back}">
|
||||
<div><label class="label" for="artTitle">Заголовок</label>
|
||||
<input class="input" id="artTitle" name="title" th:value="${article.title}" required></div>
|
||||
<div><label class="label" for="artBody">Текст (markdown)</label>
|
||||
<textarea class="input" id="artBody" name="bodyMd" rows="18" th:text="${article.bodyMd}"></textarea></div>
|
||||
<p class="hint">
|
||||
Картинка с подписью: <code class="code">:::figure assets/имя.svg | Рис. 1. Подпись</code>.
|
||||
Видео: <code class="code">:::video assets/имя.mp4 | Подпись</code>.
|
||||
Решение под спойлером: <code class="code">:::spoiler Решение … :::</code>.
|
||||
</p>
|
||||
<button class="btn btn-primary" type="submit">Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script th:if="${currentUser != null and currentUser.admin}">
|
||||
if (window.location.search.indexOf('edit=1') >= 0) {
|
||||
document.addEventListener('DOMContentLoaded', function () { openModal('modal-edit-article'); });
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user