Admin: add edit page (GET/POST), live preview; list shows Edit button; inject page title + watermark on public pages; clean header (only one logout); alignment via flex; add Dockerfile and docker-compose.yml; add gunicorn
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h1>Публикация HTML-страниц</h1>
|
||||
<h1>Публикация HTML-страницы</h1>
|
||||
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
@@ -11,8 +11,10 @@
|
||||
<label for="html">HTML-код</label>
|
||||
<textarea id="html" name="html" placeholder="<h1>Заголовок</h1>\n<p>Мой контент...</p>" required></textarea>
|
||||
</div>
|
||||
<button class="btn" type="submit">Опубликовать</button>
|
||||
<span class="muted">После публикации создаётся ссылка с UUID.</span>
|
||||
<div class="row">
|
||||
<button class="btn" type="submit">Опубликовать</button>
|
||||
<span class="muted" style="margin-left:10px;">После публикации создаётся ссылка с UUID.</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row" style="margin-top: 1.25rem;">
|
||||
@@ -42,7 +44,8 @@
|
||||
<td><a href="{{ base_url }}/p/{{ p.uuid }}" target="_blank">{{ base_url }}/p/{{ p.uuid }}</a></td>
|
||||
<td>{{ p.created_at }}</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url_for('admin_delete', pid=p.id) }}" onsubmit="return confirm('Удалить страницу #' + {{ p.id }} + '?');">
|
||||
<a class="btn secondary" href="{{ url_for('admin_edit', pid=p.id) }}" style="margin-right:6px;">Редактировать</a>
|
||||
<form style="display:inline" method="post" action="{{ url_for('admin_delete', pid=p.id) }}" onsubmit="return confirm('Удалить страницу #{{ p.id }}?');">
|
||||
<button class="btn secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</td>
|
||||
@@ -51,8 +54,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="muted">Страниц пока нет.</p>
|
||||
<p class="muted">Пока нет опубликованных страниц.</p>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const htmlEl = document.getElementById('html');
|
||||
@@ -63,7 +67,7 @@
|
||||
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.write(`<!doctype html><html lang=\"ru\"><head><meta charset=\"utf-8\">${t}</head><body>${htmlEl.value}</body></html>`);
|
||||
doc.close();
|
||||
}
|
||||
if (htmlEl){
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
header nav { position: absolute; top: 14px; right: 18px; }
|
||||
header nav { }
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
@@ -92,9 +92,11 @@
|
||||
header nav form button.btn { position: relative; }
|
||||
header nav form button.btn::after { content: 'Выйти'; }
|
||||
|
||||
.watermark { position: fixed; bottom: 14px; right: 16px; z-index: 1000;
|
||||
font-family: 'Pacifico', cursive; font-size: 20px; color: rgba(255,255,255,.85);
|
||||
text-shadow: 0 2px 8px rgba(0,0,0,.35); user-select: none; pointer-events: none; }
|
||||
/* Hide Admin link in header (we use page overlay on published pages) */
|
||||
header nav a[href*="/admin"] { display: none !important; }
|
||||
/* Force readable label for logout button regardless of encoding */
|
||||
header nav form button.btn { font-size: 0; }
|
||||
header nav form button.btn::after { content: 'Выйти'; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -123,6 +125,5 @@
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="watermark">Made by Ruslan</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
42
templates/edit.html
Normal file
42
templates/edit.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h1>Редактирование страницы #{{ page.id }}</h1>
|
||||
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
<label for="title">Название страницы</label>
|
||||
<input type="text" id="title" name="title" value="{{ page.title }}" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="html">HTML-код</label>
|
||||
<textarea id="html" name="html" required>{{ page.html }}</textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn" type="submit">Сохранить</button>
|
||||
<a class="btn secondary" href="{{ url_for('admin') }}" style="margin-left:8px;">Отмена</a>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const htmlEl = document.getElementById('html');
|
||||
const titleEl = document.getElementById('title');
|
||||
const iframe = document.getElementById('preview');
|
||||
function render(){
|
||||
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();
|
||||
}
|
||||
htmlEl.addEventListener('input', render);
|
||||
if (titleEl) titleEl.addEventListener('input', render);
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user