feat: inline editor, fix whitespace, fix filters UX

This commit is contained in:
Your Name
2026-07-19 00:14:32 +05:00
parent 89a7ff7723
commit 2d58dc9057
2 changed files with 76 additions and 171 deletions
@@ -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"> <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 %} {% 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"> <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 %} /> <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.label }}">{{ item.label }}</span> <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> <span class="badge badge-neutral bg-transparent border-none">{{ item.count }}</span>
</label> </label>
{% endfor %} {% endfor %}
@@ -68,7 +68,7 @@
{% for item in facets.categories %} {% 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"> <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 %} /> <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> <span class="badge badge-neutral bg-transparent border-none">{{ item.count }}</span>
</label> </label>
{% endfor %} {% endfor %}
+51 -146
View File
@@ -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"> <div class="flex flex-col xl:flex-row">
<!-- Left: Meta Info & Status --> <!-- Left: Meta Info & Status -->
@@ -39,14 +40,6 @@
<span class="text-app-textMuted">AI Оценка:</span> <span class="text-app-textMuted">AI Оценка:</span>
<span class="font-mono text-white font-medium">{{ p.qualification_score or "?" }}/10</span> <span class="font-mono text-white font-medium">{{ p.qualification_score or "?" }}/10</span>
</div> </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> </div>
{% if p.publication_platforms %} {% if p.publication_platforms %}
@@ -69,18 +62,39 @@
{% endif %} {% endif %}
</div> </div>
<!-- Right: Content & Actions --> <!-- Right: Edit Area & Spoilers -->
<div class="flex-1 flex flex-col min-w-0"> <div class="flex-1 flex flex-col min-w-0 p-5">
<!-- Content Preview --> <!-- Edit Area -->
<div class="p-5 flex-1 text-sm text-app-textMain leading-relaxed whitespace-pre-wrap font-sans"> <label class="block text-xs font-bold text-app-textMuted mb-2 uppercase tracking-wider">Финальный текст</label>
{{ p.publication_preview_text }} <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> </div>
<!-- Media Thumbnails -->
{% if p.media_items %} {% if p.media_items %}
<div class="px-5 pb-4"> <div class="mt-4 flex flex-wrap gap-2">
<div class="flex flex-wrap gap-2">
{% for m in p.media_items %} {% for m in p.media_items %}
{% if m.type == "photo" and m.url %} {% 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"> <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 %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endif %} {% endif %}
<!-- Original / Notes Details (Details block) --> <!-- Original / AI Spoiler -->
<div class="px-5 pb-5"> <div class="mt-4">
<details class="group/details"> <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"> <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> <i data-lucide="chevron-right" class="w-4 h-4 transition-transform group-open/details:rotate-90"></i>
ПОСМОТРЕТЬ ИСХОДНИК И ЗАМЕТКИ ПОСМОТРЕТЬ ИСХОДНИК И AI-ПРЕДЛОЖЕНИЕ
</summary> </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 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> <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 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> <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> <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"> <div class="mt-4">
<span class="font-bold block mb-1 uppercase tracking-wider text-xs">Заметка редактора:</span> <label class="block text-xs font-bold text-app-textMuted mb-1 uppercase tracking-wider">Заметка редактора (внутренняя)</label>
{{ p.editor_notes }} <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> </div>
{% endif %}
</div> </div>
</div> </div>
</details> </details>
</div> </div>
<!-- Actions --> <!-- 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> <div>
<!-- Если статус Ожидает (не принят и не отклонен), показываем быстрые кнопки -->
{% if p.editorial_status in ['pending', 'ai_review', 'reviewing'] %} {% if p.editorial_status in ['pending', 'ai_review', 'reviewing'] %}
<div class="flex gap-2"> <button class="btn btn-ghost btn-sm text-app-error hover:bg-app-errorBg hover:text-app-error" type="button"
<form hx-post="/editor/{{ p.id }}/accept" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML" class="m-0"> hx-post="/editor/{{ p.id }}/reject" hx-target="#post-container-{{ p.id }}" hx-swap="outerHTML"
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}"> hx-vals='{"csrf_token": "{{ user.csrf_token }}"}'
<button class="btn btn-primary btn-sm" type="submit"> onclick="return confirm('Отклонить пост?')">
<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('Отклонить пост?')">
<i data-lucide="x" class="w-4 h-4"></i> В мусор <i data-lucide="x" class="w-4 h-4"></i> В мусор
</button> </button>
</form>
</div>
{% endif %} {% endif %}
</div> </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>
&bull; {{ 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"> <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> Сохранить как черновик <i data-lucide="save" class="w-4 h-4"></i> Сохранить как черновик
</button> </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> Сохранить и Опубликовать <i data-lucide="send" class="w-4 h-4"></i> Сохранить и Опубликовать
</button> </button>
</div> </div>
</div> </div>
</form>
</div> </div>
<form method="dialog" class="modal-backdrop bg-black/60 backdrop-blur-sm">
<button>Закрыть</button>
</form>
</dialog>
</div> </div>
</form>