Admin: add title field, live preview, and delete; DB migration for title; show title in list; keep Admin button on public pages

This commit is contained in:
2025-09-03 15:06:07 +03:00
parent 85eaf6a99a
commit aa5cb82295
2 changed files with 62 additions and 5 deletions

29
app.py
View File

@@ -53,11 +53,19 @@ def create_app():
CREATE TABLE IF NOT EXISTS pages ( CREATE TABLE IF NOT EXISTS pages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE, uuid TEXT NOT NULL UNIQUE,
title TEXT NOT NULL DEFAULT '',
html TEXT NOT NULL, html TEXT NOT NULL,
created_at TEXT NOT NULL created_at TEXT NOT NULL
); );
""" """
) )
# Migration: add 'title' column for older DB versions
try:
cols = {row[1] for row in db.execute("PRAGMA table_info(pages)").fetchall()}
if "title" not in cols:
db.execute("ALTER TABLE pages ADD COLUMN title TEXT NOT NULL DEFAULT ''")
except Exception:
pass
db.commit() db.commit()
@app.before_request @app.before_request
@@ -120,29 +128,42 @@ def create_app():
login_required() login_required()
db = get_db() db = get_db()
if request.method == "POST": if request.method == "POST":
title = request.form.get("title", "").strip()
html = request.form.get("html", "").strip() html = request.form.get("html", "").strip()
if not html: if not html:
flash("HTML не может быть пустым.", "error") flash("HTML не может быть пустым.", "error")
else: else:
uid = uuid_lib.uuid4().hex uid = uuid_lib.uuid4().hex
db.execute( db.execute(
"INSERT INTO pages (uuid, html, created_at) VALUES (?, ?, ?)", "INSERT INTO pages (uuid, title, html, created_at) VALUES (?, ?, ?, ?)",
(uid, html, datetime.utcnow().isoformat(timespec="seconds")), (uid, title, html, datetime.utcnow().isoformat(timespec="seconds")),
) )
db.commit() db.commit()
flash("Страница опубликована.", "success") flash("Страница опубликована.", "success")
return redirect(url_for("admin")) return redirect(url_for("admin"))
pages = db.execute( pages = db.execute(
"SELECT id, uuid, created_at FROM pages ORDER BY id DESC" "SELECT id, uuid, title, created_at FROM pages ORDER BY id DESC"
).fetchall() ).fetchall()
base_url = request.host_url.rstrip("/") base_url = request.host_url.rstrip("/")
return render_template("admin.html", pages=pages, base_url=base_url) return render_template("admin.html", pages=pages, base_url=base_url)
@app.route("/admin/delete/<int:pid>", methods=["POST"])
def admin_delete(pid: int):
login_required()
db = get_db()
cur = db.execute("DELETE FROM pages WHERE id = ?", (pid,))
db.commit()
if cur.rowcount:
flash("Страница удалена.", "success")
else:
flash("Страница не найдена.", "error")
return redirect(url_for("admin"))
@app.route("/p/<string:uid>") @app.route("/p/<string:uid>")
def view_page(uid: str): def view_page(uid: str):
db = get_db() db = get_db()
row = db.execute("SELECT html FROM pages WHERE uuid = ?", (uid,)).fetchone() row = db.execute("SELECT html, title FROM pages WHERE uuid = ?", (uid,)).fetchone()
if row is None: if row is None:
abort(404) abort(404)
# Show a floating admin button for authenticated users, otherwise serve raw HTML # Show a floating admin button for authenticated users, otherwise serve raw HTML

View File

@@ -3,6 +3,10 @@
<h1>Публикация HTML-страниц</h1> <h1>Публикация HTML-страниц</h1>
<form method="post"> <form method="post">
<div class="row">
<label for="title">Название страницы</label>
<input type="text" id="title" name="title" placeholder="Например: Презентация" />
</div>
<div class="row"> <div class="row">
<label for="html">HTML-код</label> <label for="html">HTML-код</label>
<textarea id="html" name="html" placeholder="<h1>Заголовок</h1>\n<p>Мой контент...</p>" required></textarea> <textarea id="html" name="html" placeholder="<h1>Заголовок</h1>\n<p>Мой контент...</p>" required></textarea>
@@ -11,24 +15,37 @@
<span class="muted">После публикации создаётся ссылка с UUID.</span> <span class="muted">После публикации создаётся ссылка с UUID.</span>
</form> </form>
<div class="row" style="margin-top: 1.25rem;">
<div class="muted" style="margin-bottom: .5rem;">Моментальный предпросмотр</div>
<iframe id="preview" style="width:100%; height:320px; border:1px solid rgba(255,255,255,.08); border-radius:12px; background:#fff;"></iframe>
</div>
<h2 style="margin-top:2rem;">Опубликованные страницы</h2> <h2 style="margin-top:2rem;">Опубликованные страницы</h2>
{% if pages %} {% if pages %}
<table> <table>
<thead> <thead>
<tr> <tr>
<th>ID</th> <th>ID</th>
<th>Название</th>
<th>UUID</th> <th>UUID</th>
<th>Ссылка</th> <th>Ссылка</th>
<th>Создано</th> <th>Создано</th>
<th>Действия</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for p in pages %} {% for p in pages %}
<tr> <tr>
<td>{{ p.id }}</td> <td>{{ p.id }}</td>
<td>{{ p.title or "" }}</td>
<td><code>{{ p.uuid }}</code></td> <td><code>{{ p.uuid }}</code></td>
<td><a href="{{ base_url }}/p/{{ p.uuid }}" target="_blank">{{ base_url }}/p/{{ p.uuid }}</a></td> <td><a href="{{ base_url }}/p/{{ p.uuid }}" target="_blank">{{ base_url }}/p/{{ p.uuid }}</a></td>
<td>{{ p.created_at }}</td> <td>{{ p.created_at }}</td>
<td>
<form method="post" action="{{ url_for('admin_delete', pid=p.id) }}" onsubmit="return confirm('Удалить страницу #' + {{ p.id }} + '?');">
<button class="btn secondary" type="submit">Удалить</button>
</form>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -36,5 +53,24 @@
{% else %} {% else %}
<p class="muted">Страниц пока нет.</p> <p class="muted">Страниц пока нет.</p>
{% endif %} {% endif %}
<script>
(function(){
const htmlEl = document.getElementById('html');
const titleEl = document.getElementById('title');
const iframe = document.getElementById('preview');
function render(){
if(!iframe || !htmlEl) return;
const doc = iframe.contentDocument || iframe.contentWindow.document;
const t = (titleEl && titleEl.value) ? `<title>${titleEl.value}</title>` : '';
doc.open();
doc.write(`<!doctype html><html lang="ru"><head><meta charset="utf-8">${t}</head><body>${htmlEl.value}</body></html>`);
doc.close();
}
if (htmlEl){
htmlEl.addEventListener('input', render);
if (titleEl) titleEl.addEventListener('input', render);
render();
}
})();
</script>
{% endblock %} {% endblock %}