Jul 22 - Update - add Rich-text editor, draft/publish, drag-to-reorder
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<header class="page-head">
|
||||
<div>
|
||||
<h1>Content</h1>
|
||||
<p class="muted">Edit the sections and topics shown on the public site.</p>
|
||||
<p class="muted">Drag the handles to reorder. Toggle Publish to show or hide on the public site.</p>
|
||||
</div>
|
||||
<div class="page-head__actions">
|
||||
<a class="btn btn--ghost" href="{{ url_for('admin.section_form') }}">+ Section</a>
|
||||
@@ -16,11 +16,13 @@
|
||||
<div class="empty">No sections yet. Start by adding one.</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="sections">
|
||||
{% for section in sections %}
|
||||
<section class="sec-card">
|
||||
<section class="sec-card" data-section-id="{{ section.id }}">
|
||||
<div class="sec-card__head">
|
||||
<div class="sec-card__meta">
|
||||
<span class="pill">§{{ '%02d' % section.num }}</span>
|
||||
<button type="button" class="drag-handle sec-drag" title="Drag to reorder sections" aria-label="Reorder section">⠇</button>
|
||||
<span class="pill">§{{ '%02d' % section.num }}</span>
|
||||
<h2>{{ section.title }}</h2>
|
||||
{% if section.subtitle %}<span class="muted">{{ section.subtitle }}</span>{% endif %}
|
||||
</div>
|
||||
@@ -28,7 +30,7 @@
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.section_form', section_id=section.id) }}">Edit</a>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.topic_form') }}?section={{ section.id }}">+ Topic</a>
|
||||
<form method="post" action="{{ url_for('admin.section_delete', section_id=section.id) }}"
|
||||
onsubmit="return confirm('Delete section “{{ section.title }}” and ALL its topics?');">
|
||||
onsubmit="return confirm('Delete section “{{ section.title }}” and ALL its topics?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--danger btn--sm" type="submit">Delete</button>
|
||||
</form>
|
||||
@@ -36,19 +38,25 @@
|
||||
</div>
|
||||
|
||||
{% if section.topics %}
|
||||
<ul class="topic-rows">
|
||||
<ul class="topic-rows" data-section-id="{{ section.id }}">
|
||||
{% for topic in section.topics %}
|
||||
<li class="topic-row">
|
||||
<li class="topic-row {{ 'is-draft' if not topic.is_published }}" data-topic-id="{{ topic.id }}">
|
||||
<div class="topic-row__main">
|
||||
<button type="button" class="drag-handle row-drag" title="Drag to reorder" aria-label="Reorder topic">⠇</button>
|
||||
<span class="topic-row__order">{{ topic.sort_order }}</span>
|
||||
<a class="topic-row__title" href="{{ url_for('admin.topic_form', topic_id=topic.id) }}">{{ topic.title | safe }}</a>
|
||||
{% if not topic.is_published %}<span class="chip chip--draft">draft</span>{% endif %}
|
||||
{% if topic.media_type and topic.media_type != 'none' %}<span class="chip">{{ topic.media_type }}</span>{% endif %}
|
||||
{% if topic.link_url %}<span class="chip chip--link">link</span>{% endif %}
|
||||
</div>
|
||||
<div class="topic-row__actions">
|
||||
<form method="post" action="{{ url_for('admin.topic_toggle', topic_id=topic.id) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--ghost btn--sm" type="submit">{{ 'Unpublish' if topic.is_published else 'Publish' }}</button>
|
||||
</form>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('admin.topic_form', topic_id=topic.id) }}">Edit</a>
|
||||
<form method="post" action="{{ url_for('admin.topic_delete', topic_id=topic.id) }}"
|
||||
onsubmit="return confirm('Delete topic “{{ topic.title }}”?');">
|
||||
onsubmit="return confirm('Delete topic “{{ topic.title }}”?');">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button class="btn btn--danger btn--sm" type="submit">Delete</button>
|
||||
</form>
|
||||
@@ -61,4 +69,51 @@
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='vendor/Sortable.min.js') }}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var token = document.querySelector('meta[name="csrf-token"]').content;
|
||||
|
||||
function post(payload, rows) {
|
||||
fetch('{{ url_for('admin.reorder') }}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': token },
|
||||
body: JSON.stringify(payload)
|
||||
}).then(function (r) {
|
||||
if (!r.ok) { console.error('reorder failed', r.status); return; }
|
||||
if (rows) rows.forEach(function (el, i) {
|
||||
var n = el.querySelector('.topic-row__order');
|
||||
if (n) n.textContent = (i + 1) * 10;
|
||||
});
|
||||
}).catch(function (e) { console.error(e); });
|
||||
}
|
||||
|
||||
var secWrap = document.getElementById('sections');
|
||||
if (secWrap && window.Sortable) {
|
||||
Sortable.create(secWrap, {
|
||||
handle: '.sec-drag', animation: 150, draggable: '.sec-card',
|
||||
onEnd: function () {
|
||||
var order = Array.from(secWrap.querySelectorAll(':scope > .sec-card'))
|
||||
.map(function (el) { return parseInt(el.dataset.sectionId, 10); });
|
||||
post({ type: 'section', order: order });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.topic-rows').forEach(function (ul) {
|
||||
Sortable.create(ul, {
|
||||
handle: '.row-drag', animation: 150, draggable: '.topic-row',
|
||||
onEnd: function () {
|
||||
var lis = Array.from(ul.querySelectorAll(':scope > .topic-row'));
|
||||
var order = lis.map(function (el) { return parseInt(el.dataset.topicId, 10); });
|
||||
post({ type: 'topic', section_id: parseInt(ul.dataset.sectionId, 10), order: order }, lis);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user