feat: inline editor, fix whitespace, fix filters UX
This commit is contained in:
@@ -53,8 +53,8 @@
|
||||
<div class="bg-app-bg border border-app-border rounded-lg p-2 max-h-48 overflow-y-auto space-y-1">
|
||||
{% for item in facets.sources %}
|
||||
<label class="flex items-center gap-2 px-2 py-1 hover:bg-app-surfaceHover rounded cursor-pointer transition-colors group">
|
||||
<input type="checkbox" name="source_id" value="{{ item.value }}" class="checkbox" {% if item.value|string in source_ids %}checked{% endif %} />
|
||||
<span class="text-sm text-app-textMain flex-1 truncate group-hover:text-white" title="{{ item.label }}">{{ item.label }}</span>
|
||||
<input type="checkbox" name="source_id" value="{{ item.id }}" class="checkbox" {% if item.id|string in source_ids %}checked{% endif %} />
|
||||
<span class="text-sm text-app-textMain flex-1 truncate group-hover:text-white" title="{{ item.name }}">{{ item.name }}</span>
|
||||
<span class="badge badge-neutral bg-transparent border-none">{{ item.count }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
@@ -68,7 +68,7 @@
|
||||
{% for item in facets.categories %}
|
||||
<label class="flex items-center gap-2 px-2 py-1 hover:bg-app-surfaceHover rounded cursor-pointer transition-colors group">
|
||||
<input type="checkbox" name="category" value="{{ item.value }}" class="checkbox" {% if item.value in categories_selected %}checked{% endif %} />
|
||||
<span class="text-sm text-app-textMain flex-1 group-hover:text-white">{{ item.value or "Без категории" }}</span>
|
||||
<span class="text-sm text-app-textMain flex-1 truncate group-hover:text-white" title="{{ item.value or 'Без категории' }}">{{ item.value or "Без категории" }}</span>
|
||||
<span class="badge badge-neutral bg-transparent border-none">{{ item.count }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="post-container-{{ p.id }}" class="card flex flex-col group relative transition-colors border border-app-border {% if p.editorial_status == 'accepted' %}bg-app-successBg/10 border-app-success/20{% elif p.editorial_status == 'rejected' %}bg-app-errorBg/10 opacity-75{% else %}hover:border-app-borderFocus hover:shadow-glow{% endif %}">
|
||||
<form hx-post="/editor/{{ p.id }}/save" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" hx-encoding="multipart/form-data" id="post-container-{{ p.id }}" class="card flex flex-col group relative transition-colors border border-app-border {% if p.editorial_status == 'accepted' %}bg-app-successBg/10 border-app-success/20{% elif p.editorial_status == 'rejected' %}bg-app-errorBg/10 opacity-75{% else %}hover:border-app-borderFocus hover:shadow-glow{% endif %}">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
|
||||
<div class="flex flex-col xl:flex-row">
|
||||
<!-- Left: Meta Info & Status -->
|
||||
@@ -39,14 +40,6 @@
|
||||
<span class="text-app-textMuted">AI Оценка:</span>
|
||||
<span class="font-mono text-white font-medium">{{ p.qualification_score or "?" }}/10</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-app-textMuted">Категория:</span>
|
||||
<span class="truncate ml-2 font-medium text-white" title="{{ p.review_category }}">{{ p.review_category or "—" }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-app-textMuted">Тег:</span>
|
||||
<span class="font-mono text-blue-400 font-medium">#{{ p.review_source_tag or p.source_tag or "none" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if p.publication_platforms %}
|
||||
@@ -69,18 +62,39 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Right: Content & Actions -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<!-- Right: Edit Area & Spoilers -->
|
||||
<div class="flex-1 flex flex-col min-w-0 p-5">
|
||||
|
||||
<!-- Content Preview -->
|
||||
<div class="p-5 flex-1 text-sm text-app-textMain leading-relaxed whitespace-pre-wrap font-sans">
|
||||
{{ p.publication_preview_text }}
|
||||
<!-- Edit Area -->
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-2 uppercase tracking-wider">Финальный текст</label>
|
||||
<textarea name="final_text" class="textarea w-full font-sans text-sm leading-relaxed resize-y p-3 bg-app-bg border border-app-border min-h-[150px]" placeholder="Напишите идеальный пост здесь...">{{ p.review_text }}</textarea>
|
||||
|
||||
<!-- Meta Inputs row -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Категория</label>
|
||||
<select name="final_category" class="select select-sm w-full bg-app-bg">
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.name }}" data-tag="{{ category.tag }}" {% if p.review_category == category.name %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
{% set category_names = categories | map(attribute="name") | list %}
|
||||
{% if p.review_category and p.review_category not in category_names %}
|
||||
<option value="{{ p.review_category }}" selected>{{ p.review_category }} · старая</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Тег источника</label>
|
||||
<input type="text" name="final_source_tag" value="{{ p.review_source_tag or p.source_tag or '' }}" class="input input-sm w-full bg-app-bg">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Доп. Медиа</label>
|
||||
<input type="file" name="media_files" class="block w-full text-xs text-app-textMuted file:mr-2 file:py-1 file:px-2 file:rounded file:border-0 file:text-xs file:font-semibold file:bg-app-border file:text-white hover:file:bg-app-borderFocus cursor-pointer border border-app-border bg-app-bg rounded-md" accept="image/*,video/*,.pdf,.doc,.docx" multiple>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Thumbnails -->
|
||||
{% if p.media_items %}
|
||||
<div class="px-5 pb-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
{% for m in p.media_items %}
|
||||
{% if m.type == "photo" and m.url %}
|
||||
<a href="{{ m.url }}" target="_blank" class="block w-16 h-16 rounded-lg overflow-hidden border border-app-border hover:border-app-primary transition-colors">
|
||||
@@ -93,15 +107,14 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Original / Notes Details (Details block) -->
|
||||
<div class="px-5 pb-5">
|
||||
<!-- Original / AI Spoiler -->
|
||||
<div class="mt-4">
|
||||
<details class="group/details">
|
||||
<summary class="flex items-center gap-2 text-xs font-semibold tracking-wide text-app-textMuted hover:text-white cursor-pointer select-none">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4 transition-transform group-open/details:rotate-90"></i>
|
||||
ПОСМОТРЕТЬ ИСХОДНИК И ЗАМЕТКИ
|
||||
ПОСМОТРЕТЬ ИСХОДНИК И AI-ПРЕДЛОЖЕНИЕ
|
||||
</summary>
|
||||
<div class="mt-3 grid grid-cols-1 xl:grid-cols-2 gap-4 bg-app-bg/50 rounded-lg p-4 border border-app-border text-sm">
|
||||
<div>
|
||||
@@ -109,152 +122,44 @@
|
||||
<div class="whitespace-pre-wrap text-app-textMain/80 font-mono text-xs leading-relaxed max-h-[300px] overflow-y-auto">{{ p.raw_text }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-app-textMuted font-bold mb-2 uppercase tracking-wider">AI Заметки / Резолюция</div>
|
||||
<div class="text-xs text-app-primary font-bold mb-2 uppercase tracking-wider">AI-Предложение</div>
|
||||
<div class="whitespace-pre-wrap text-app-textMain/90 font-mono text-xs leading-relaxed max-h-[300px] overflow-y-auto">{{ p.rewritten_text }}</div>
|
||||
|
||||
<div class="text-xs text-app-textMuted font-bold mt-4 mb-2 uppercase tracking-wider">AI Заметки / Резолюция</div>
|
||||
<div class="text-app-textMain/80 whitespace-pre-wrap italic">{{ p.rewrite_notes or p.qualification_reason or "—" }}</div>
|
||||
{% if p.editor_notes %}
|
||||
<div class="mt-4 pt-4 border-t border-app-border text-app-warning">
|
||||
<span class="font-bold block mb-1 uppercase tracking-wider text-xs">Заметка редактора:</span>
|
||||
{{ p.editor_notes }}
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1 uppercase tracking-wider">Заметка редактора (внутренняя)</label>
|
||||
<input type="text" name="editor_notes" value="{{ p.editor_notes or '' }}" class="input input-sm w-full text-app-warning placeholder:text-app-warning/50 bg-app-bg">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="bg-app-bg border-t border-app-border p-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="mt-5 pt-4 border-t border-app-border flex flex-wrap items-center justify-between gap-3">
|
||||
<!-- Delete Button -->
|
||||
<div>
|
||||
<!-- Если статус Ожидает (не принят и не отклонен), показываем быстрые кнопки -->
|
||||
{% if p.editorial_status in ['pending', 'ai_review', 'reviewing'] %}
|
||||
<div class="flex gap-2">
|
||||
<form hx-post="/editor/{{ p.id }}/accept" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" class="m-0">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<button class="btn btn-primary btn-sm" type="submit">
|
||||
<i data-lucide="check" class="w-4 h-4"></i> Принять сразу
|
||||
</button>
|
||||
</form>
|
||||
<form hx-post="/editor/{{ p.id }}/reject" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" class="m-0">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
<button class="btn btn-ghost btn-sm text-app-error hover:bg-app-errorBg hover:text-app-error" type="submit" onclick="return confirm('Отклонить пост?')">
|
||||
<button class="btn btn-ghost btn-sm text-app-error hover:bg-app-errorBg hover:text-app-error" type="button"
|
||||
hx-post="/editor/{{ p.id }}/reject" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML"
|
||||
hx-vals='{"csrf_token": "{{ user.csrf_token }}"}'
|
||||
onclick="return confirm('Отклонить пост?')">
|
||||
<i data-lucide="x" class="w-4 h-4"></i> В мусор
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-surface btn-sm" onclick="document.getElementById('editModal{{ p.id }}').showModal()">
|
||||
<i data-lucide="edit" class="w-4 h-4"></i>
|
||||
{% if p.editorial_status == 'accepted' or p.editorial_status == 'rejected' %}Редактировать снова{% else %}Открыть редактор{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for Editing -->
|
||||
<dialog id="editModal{{ p.id }}" class="modal">
|
||||
<div class="bg-app-surface w-11/12 max-w-6xl rounded-xl shadow-2xl border border-app-borderFocus flex flex-col max-h-[90vh]">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="flex justify-between items-center p-4 border-b border-app-border">
|
||||
<div>
|
||||
<h3 class="font-bold text-white text-lg">Редактирование поста #{{ p.id }}</h3>
|
||||
<div class="text-sm text-app-textMuted mt-1">
|
||||
<a href="{{ p.original_url }}" target="_blank" class="text-blue-400 hover:underline">{{ p.source_name }}</a>
|
||||
• {{ p.posted_at_fmt or p.created_at_fmt }}
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-ghost btn-icon rounded-full text-app-textMuted hover:text-white"><i data-lucide="x" class="w-5 h-5"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- HTMX form -->
|
||||
<form hx-post="/editor/{{ p.id }}/save" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" hx-encoding="multipart/form-data" class="flex-1 overflow-hidden flex flex-col">
|
||||
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-6 flex flex-col lg:flex-row gap-6 bg-app-bg">
|
||||
<!-- Left Column: Context -->
|
||||
<div class="w-full lg:w-5/12 flex flex-col gap-4">
|
||||
|
||||
<!-- Original Text -->
|
||||
<div class="bg-app-surface border border-app-border rounded-lg p-3 flex flex-col max-h-64">
|
||||
<span class="text-xs font-bold text-app-textMuted uppercase tracking-wider mb-2">Оригинал:</span>
|
||||
<div class="overflow-y-auto text-xs font-mono text-app-textMain/70 whitespace-pre-wrap leading-relaxed pr-2">
|
||||
{{ p.raw_text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Rewrite -->
|
||||
<div class="bg-app-surface border border-app-border rounded-lg p-3 flex flex-col max-h-64">
|
||||
<span class="text-xs font-bold text-blue-400 uppercase tracking-wider mb-2">AI-Предложение:</span>
|
||||
<div class="overflow-y-auto text-xs font-mono text-app-textMain/90 whitespace-pre-wrap leading-relaxed pr-2">
|
||||
{{ p.rewritten_text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Meta Inputs -->
|
||||
<div class="bg-app-surface border border-app-border rounded-lg p-4 space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Категория</label>
|
||||
<select name="final_category" class="select select-sm w-full">
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.name }}" data-tag="{{ category.tag }}" {% if p.review_category == category.name %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
{% set category_names = categories | map(attribute="name") | list %}
|
||||
{% if p.review_category and p.review_category not in category_names %}
|
||||
<option value="{{ p.review_category }}" selected>{{ p.review_category }} · старая</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Тег источника</label>
|
||||
<input type="text" name="final_source_tag" value="{{ p.review_source_tag or p.source_tag or '' }}" class="input input-sm w-full">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Заметка редактора</label>
|
||||
<input type="text" name="editor_notes" value="{{ p.editor_notes or '' }}" class="input input-sm w-full text-app-warning placeholder:text-app-warning/50">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-app-textMuted mb-1">Доп. Медиа</label>
|
||||
<input type="file" name="media_files" class="block w-full text-sm text-app-textMuted file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-app-border file:text-white hover:file:bg-app-borderFocus cursor-pointer border border-app-border rounded-md" accept="image/*,video/*,.pdf,.doc,.docx" multiple>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Edit Area -->
|
||||
<div class="w-full lg:w-7/12 flex flex-col">
|
||||
<label class="block text-sm font-bold text-white mb-2 flex items-center justify-between">
|
||||
<span>Финальный текст публикации</span>
|
||||
</label>
|
||||
<textarea name="final_text" class="textarea flex-1 w-full font-sans text-[15px] leading-relaxed resize-none p-4" placeholder="Напишите идеальный пост здесь...">{{ p.review_text }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Footer -->
|
||||
<div class="p-4 bg-app-surface border-t border-app-border flex flex-wrap items-center justify-between gap-4">
|
||||
<form method="dialog" class="m-0">
|
||||
<button class="btn btn-ghost" type="button" onclick="document.getElementById('editModal{{ p.id }}').close()">Отмена</button>
|
||||
</form>
|
||||
<div class="flex gap-2">
|
||||
<button type="submit" name="action" value="save" class="btn btn-surface" onclick="document.getElementById('editModal{{ p.id }}').close()">
|
||||
<button type="submit" name="action" value="save" class="btn btn-surface btn-sm">
|
||||
<i data-lucide="save" class="w-4 h-4"></i> Сохранить как черновик
|
||||
</button>
|
||||
<button type="submit" name="action" value="accept" class="btn btn-primary" onclick="document.getElementById('editModal{{ p.id }}').close()">
|
||||
<button type="submit" name="action" value="accept" class="btn btn-primary btn-sm">
|
||||
<i data-lucide="send" class="w-4 h-4"></i> Сохранить и Опубликовать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop bg-black/60 backdrop-blur-sm">
|
||||
<button>Закрыть</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user