Jul 22 - Update - add Rich-text editor, draft/publish, drag-to-reorder

This commit is contained in:
2026-07-22 17:13:26 -04:00
parent a39fa091fa
commit 69b97d51fe
15 changed files with 1295 additions and 23 deletions
+64 -3
View File
@@ -4,12 +4,12 @@ import re
from functools import wraps
from flask import (
Blueprint, current_app, flash, redirect, render_template,
Blueprint, abort, current_app, flash, jsonify, redirect, render_template,
request, session, url_for,
)
from werkzeug.security import check_password_hash
from app import db, log_action, AuditLog, Section, Topic
from app import db, log_action, sanitize_html, AuditLog, Section, Topic
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
@@ -171,13 +171,14 @@ def topic_form(topic_id=None):
topic.section_id = section_id
topic.slug = slug
topic.title = title
topic.body_html = f.get("body_html", "").strip() or None
topic.body_html = sanitize_html(f.get("body_html", ""))
topic.link_url = f.get("link_url", "").strip() or None
topic.link_label = f.get("link_label", "").strip() or None
topic.media_type = media_type
topic.media_url = f.get("media_url", "").strip() or None
topic.media_caption = f.get("media_caption", "").strip() or None
topic.sort_order = _int(f.get("sort_order"), 0)
topic.is_published = f.get("is_published") == "1"
if is_new:
db.session.add(topic)
@@ -207,6 +208,66 @@ def topic_delete(topic_id):
return redirect(url_for("admin.dashboard"))
@admin_bp.route("/topic/<int:topic_id>/toggle", methods=["POST"])
@login_required
def topic_toggle(topic_id):
topic = Topic.query.get_or_404(topic_id)
topic.is_published = not topic.is_published
db.session.commit()
state = "published" if topic.is_published else "unpublished (draft)"
log_action(session.get("admin"), "update", "topic", topic.id,
f"{state}: {topic.title}")
flash(f"'{topic.title}' is now {state}.", "ok")
return redirect(url_for("admin.dashboard"))
# ------------------------------------------------------------------ reorder
@admin_bp.route("/reorder", methods=["POST"])
@login_required
def reorder():
"""Persist drag-and-drop order. JSON body:
{"type":"topic","section_id":N,"order":[id,...]} reorder within a section
{"type":"section","order":[id,...]} reorder sections
Sort values are rewritten to 10,20,30,... in the given order."""
data = request.get_json(silent=True) or {}
kind = data.get("type")
order = data.get("order") or []
if kind not in ("topic", "section") or not isinstance(order, list):
abort(400)
ids = []
for v in order:
iv = _int(v, None)
if iv is None:
abort(400)
ids.append(iv)
if kind == "topic":
section_id = _int(data.get("section_id"), None)
if section_id is None:
abort(400)
# Only reorder topics that actually belong to this section.
rows = {t.id: t for t in Topic.query.filter(
Topic.section_id == section_id, Topic.id.in_(ids)
).all()}
for i, tid in enumerate(ids):
if tid in rows:
rows[tid].sort_order = (i + 1) * 10
detail = f"reordered {len(rows)} topics in section {section_id}"
entity = "topic"
else:
rows = {s.id: s for s in Section.query.filter(Section.id.in_(ids)).all()}
for i, sid in enumerate(ids):
if sid in rows:
rows[sid].sort_order = (i + 1) * 10
detail = f"reordered {len(rows)} sections"
entity = "section"
db.session.commit()
log_action(session.get("admin"), "update", entity, None, detail)
return jsonify(ok=True)
# ------------------------------------------------------------------ sections
@admin_bp.route("/section/new", methods=["GET", "POST"])
@admin_bp.route("/section/<int:section_id>", methods=["GET", "POST"])