Files
LMS/web/src/main/resources/templates/learn/index.html

244 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/base :: layout(~{::title}, ~{::#content})}">
<head><title>Материалы — LMS</title></head>
<body>
<div id="content" th:attr="data-csrf=${csrfToken},data-sort-scope='full'">
<div class="row g-3 align-items-center mb-4">
<div class="col-12 col-md">
<h1 class="page-title m-0">Учебные материалы</h1>
</div>
<div class="col-12 col-md-auto" th:if="${currentUser != null and currentUser.admin}">
<div class="row row-cols-3 row-cols-md-auto g-2">
<div class="col"><button class="btn btn-secondary w-100" type="button" data-modal="modal-import">Импорт</button></div>
<div class="col"><button class="btn primary w-100" type="button" data-modal="modal-new-chapter">+ Глава</button></div>
<div class="col"><button class="btn btn-primary w-100" type="button" data-modal="modal-new-article">+ Статья</button></div>
</div>
</div>
</div>
<div th:if="${currentUser != null and currentUser.admin}" id="sort-status" class="sort-status" hidden>
<span id="sort-status-text">Изменения не сохранены…</span>
</div>
<div th:if="${tree == null || #lists.isEmpty(tree.chapters)}" class="card card-body text-muted">
Материалы ещё не добавлены.
</div>
<div th:each="chapter, chStat : ${tree?.chapters}" class="card card-body chapter-card mb-4"
th:attr="data-chapter-id=${chapter.id}">
<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="Перетащите мышью: изменить порядок глав"><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">
<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}">
<button class="btn btn-secondary btn-sm p-2" type="submit" th:if="${chapter.published}" title="Скрыть главу" aria-label="Скрыть главу"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="btn btn-secondary btn-sm p-2" 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="btn btn-secondary btn-sm p-2" 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="btn btn-danger btn-sm p-2" type="submit" title="Удалить главу со всеми разделами и статьями" aria-label="Удалить главу со всеми разделами и статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
<div data-section-list>
<p th:if="${#lists.isEmpty(chapter.sections)}" class="text-sm text-muted mt-2 mb-0">Разделов пока нет</p>
<div th:each="section : ${chapter.sections}" class="sub-acc collapsed"
th:attr="data-section-id=${section.id},data-default-open=${chStat.last}">
<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="Перетащите мышью: изменить порядок или перенести раздел"><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"><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">
<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}">
<button class="btn btn-secondary btn-sm p-2" type="submit" th:if="${section.published}" title="Скрыть раздел" aria-label="Скрыть раздел"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="btn btn-secondary btn-sm p-2" 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="btn btn-secondary btn-sm p-2" 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="btn btn-secondary btn-sm p-2" 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="btn btn-danger btn-sm p-2" type="submit" title="Удалить раздел со всеми статьями" aria-label="Удалить раздел со всеми статьями"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</div>
<div class="section-body">
<ul class="sort-list text-sm" data-article-list>
<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="Перетащите мышью: изменить порядок или перенести статью"><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">
<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}">
<button class="btn btn-secondary btn-sm p-2" type="submit" th:if="${article.published}" title="Скрыть статью" aria-label="Скрыть статью"><i class="cell-ico ico bi bi-eye" aria-hidden="true"></i></button>
<button class="btn btn-secondary btn-sm p-2" 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 btn-sm p-2" title="Править статью" aria-label="Править статью"
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="btn btn-danger btn-sm p-2" type="submit" title="Удалить статью" aria-label="Удалить статью"><i class="cell-ico ico bi bi-trash" aria-hidden="true"></i></button>
</form>
</div>
</li>
<li th:if="${#lists.isEmpty(section.articles)}" class="text-muted">Статей пока нет</li>
</ul>
</div>
<th:block th:if="${currentUser != null and currentUser.admin}">
<dialog th:id="|modal-rename-section-${section.id}|" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Переименовать раздел</h2>
<button class="dlg-close" type="button" aria-label="Закрыть"
th:attr="data-modal=|modal-rename-section-${section.id}|">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/sections/{id}/rename(id=${section.id})}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div><label class="label">Название</label>
<input class="input" name="title" th:value="${section.title}" required></div>
<button class="btn btn-primary" type="submit">Сохранить</button>
</form>
</div>
</dialog>
<dialog th:id="|modal-move-${section.id}|" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Перенести статьи из раздела</h2>
<button class="dlg-close" type="button" aria-label="Закрыть"
th:attr="data-modal=|modal-move-${section.id}|">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/sections/{id}/delete(id=${section.id})}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<p class="text-sm text-muted">Выберите раздел-приёмник: статьи перенесутся туда, а текущий раздел будет удалён.</p>
<div><label class="label">Перенести в раздел</label>
<select class="input" name="moveTo">
<option th:each="pair : ${sectionChoices}"
th:if="${pair['id'] != section.id}"
th:value="${pair['id']}" th:text="${pair['label']}">—</option>
</select></div>
<button class="btn btn-primary" type="submit">Перенести и удалить раздел</button>
</form>
</div>
</dialog>
</th:block>
</div>
</div>
<th:block th:if="${currentUser != null and currentUser.admin}">
<dialog th:id="|modal-rename-chapter-${chapter.id}|" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Переименовать главу</h2>
<button class="dlg-close" type="button" aria-label="Закрыть"
th:attr="data-modal=|modal-rename-chapter-${chapter.id}|">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/chapters/{id}/rename(id=${chapter.id})}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div><label class="label">Название</label>
<input class="input" name="title" th:value="${chapter.title}" required></div>
<button class="btn btn-primary" type="submit">Сохранить</button>
</form>
</div>
</dialog>
</th:block>
</div>
<dialog id="modal-import" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Импорт материалов (zip)</h2>
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/import}" method="post" enctype="multipart/form-data">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<p class="text-sm text-muted">
Структура: <code class="code">chapters/01-глава/01-раздел/01-статья.md</code>, картинки в <code class="code">assets/</code>.
</p>
<div><label class="label" for="file">Архив</label>
<input class="input" id="file" name="file" type="file" accept=".zip" required></div>
<label class="tone-label">
<input type="checkbox" name="sync" value="true">
Удалять разделы и статьи, которых нет в архиве (синхронизация)
</label>
<p class="hint">
Без этой галочки импорт <b>только добавляет и обновляет</b> материалы из архива —
статьи, отредактированные прямо в приложении, не затрагиваются.
</p>
<button class="btn btn-primary" type="submit">Импортировать</button>
</form>
</div>
</dialog>
<dialog id="modal-new-chapter" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Новая глава</h2>
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/chapters/create}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div><label class="label" for="chTitle">Название</label>
<input class="input" id="chTitle" name="title" required></div>
<button class="btn btn-primary" type="submit">Создать</button>
</form>
</div>
</dialog>
<dialog id="modal-new-article" class="dlg">
<div class="dlg-header">
<h2 class="dlg-title">Новая статья</h2>
<button class="dlg-close" type="button" aria-label="Закрыть">✕</button>
</div>
<div class="dlg-body">
<form class="form-stack" th:action="@{/admin/content/articles/create}" method="post">
<input type="hidden" name="_csrf" th:value="${csrfToken}">
<div><label class="label" for="naChapter">Существующая глава и раздел</label>
<select class="input" id="naChapter" name="sectionId">
<option th:each="pair : ${sectionChoices}"
th:value="${pair['id']}" th:text="${pair['label']}">—</option>
</select></div>
<div><label class="label" for="naNewSection">…или новый раздел в выбранной главе</label>
<input class="input" id="naNewSection" name="newSectionTitle" placeholder="Новый раздел"></div>
<div><label class="label" for="naTitle">Заголовок статьи</label>
<input class="input" id="naTitle" name="title" required></div>
<div><label class="label" for="naBody">Текст (markdown)</label>
<div th:replace="~{fragments/md-toolbar :: toolbar('#naBody')}"></div>
<textarea class="input md-editor-body" id="naBody" name="bodyMd" rows="18"></textarea></div>
<button class="btn btn-primary" type="submit">Создать</button>
</form>
</div>
</dialog>
<script th:src="@{/js/mdeditor.js(v='33')}" defer></script>
<script th:src="@{/js/content-sort.js(v='33')}" defer></script>
</div>
</body>
</html>