@ -3,187 +3,236 @@
{% block content %}
{% block content %}
< div id = "editor-root"
< div id = "editor-root"
class="space-y-4 sm:space-y-5 pb-24"
data-test-id="{{ test_id }}"
data-test-id="{{ test_id }}"
data-initial='{{ content | tojson | safe }}'>
data-initial='{{ content | tojson | safe }}'>
<!-- Шапка: название/описание/проходной балл -->
< section class = "rounded-2xl bg-white shadow-sm border border-ink-300/60 p-5" >
{# ── 1. Шапка теста ─────────────────────────────────────────── #}
< div class = "flex items-start justify-between gap-3 flex-wrap" >
< section class = "rounded-2xl bg-white shadow-sm border border-ink-300/60 p-4 sm:p-5" >
< div class = "flex-1 min-w-[260px]" >
< h2 class = "text-xs font-medium text-ink-500 uppercase tracking-wide" > Тест< / h2 >
< label class = "block" >
< span class = "text-xs font-medium text-ink-500 uppercase" > Название< / span >
< label class = "mt-2 block" >
< input id = "test-title" type = "text" maxlength = "200"
< span class = "sr-only" > Название< / span >
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-2 text-lg font-semibold
< input id = "test-title" type = "text" maxlength = "200" placeholder = "Название теста"
class="w-full rounded-lg border border-ink-300 px-3 py-3 text-lg font-semibold
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
< / label >
< / label >
< label class = "block mt-3" >
< span class = "text-xs font-medium text-ink-500 uppercase" > Описание< / span >
< label class = "mt-3 block" >
< textarea id = "test-description" rows = "2"
< span class = "text-xs font-medium text-ink-500" > Описание< / span >
< textarea id = "test-description" rows = "2" placeholder = "Краткое описание (необязательно)"
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-2
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-2
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20">< / textarea >
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20">< / textarea >
< / label >
< / label >
< / div >
< div class = "w-44" >
< label class = "mt-3 flex items-center justify-between gap-3" >
< label class = "block" >
< span class = "text-xs font-medium text-ink-500" > Проходной балл, %< / span >
< span class = "text-xs font-medium text-ink-500 uppercase" > Проходной балл, %< / span >
< input id = "test-threshold" type = "number" min = "0" max = "100" step = "1"
< input id = "test-threshold" type = "number" min = "0" max = "100" step = "1"
class="mt-1 w-full rounded-lg border border-ink-300 px-3 py-2
inputmode="numeric"
class="w-24 text-right rounded-lg border border-ink-300 px-3 py-2
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
< / label >
< / label >
< / div >
< / div >
< / section >
< / section >
<!-- AI - панель -->
{# ── 2. AI-помощник ─────────────────────────────────────────── #}
< section class = "mt-4 rounded-2xl bg-brand-50/60 border border-brand-100 p-4" >
< section class = "rounded-2xl bg-brand-50/60 border border-brand-100 p-4 sm:p-5 " >
< div class = "flex items-center gap-2" >
< div class = "flex items-center gap-2" >
< span class = "material-symbols-outlined text-brand-600" > auto_awesome< / span >
< span class = "material-symbols-outlined text-brand-600" > auto_awesome< / span >
< h2 class = "font-semibold text-brand-700" > AI-помощник< / h2 >
< h2 class = "font-semibold text-brand-700" > AI-помощник< / h2 >
< / div >
< / div >
< p class = "mt-1 text-sm text-ink-700" >
Сгенерировать вопросы по текущей сетке (число вопросов и вариантов берётся из таблицы ниже).
{# Группа A — генерация. Главные действия. На sm+ — в одну строку. #}
< / p >
< div class = "mt-3" >
< div class = "mt-3 flex flex-wrap gap-2 items-center" >
< p class = "text-xs font-medium text-ink-500 uppercase tracking-wide" > Создать вопросы< / p >
< button id = "ai-generate-test"
< div class = "mt-2 grid grid-cols-1 sm:grid-cols-2 gap-2" >
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white text-sm">
< span class = "material-symbols-outlined text-base" > stars< / span >
Сгенерировать по сетке
< / button >
< button id = "ai-generate-by-title"
< button id = "ai-generate-by-title"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg
class="inline-flex items-center justify-center gap-2 px-3 py-3 rounded-lg
bg-white border border-brand-300/60 text-brand-700 hover:bg-brand-50 text-sm ">
bg-brand-600 hover:bg-brand-700 text-white text-sm font-medium min-h-11">
< span class = "material-symbols-outlined text-base" > edit_note< / span >
< span class = "material-symbols-outlined text-base" > edit_note< / span >
Сгенерировать по названию
По названию
< / button >
< button id = "ai-generate-test"
class="inline-flex items-center justify-center gap-2 px-3 py-3 rounded-lg
bg-white border border-brand-300/60 text-brand-700 hover:bg-brand-50
text-sm font-medium min-h-11">
< span class = "material-symbols-outlined text-base" > stars< / span >
По текущей сетке
< / button >
< / button >
< / div >
< / div >
{# Группа B — анализ существующего. #}
< div class = "mt-4" >
< p class = "text-xs font-medium text-ink-500 uppercase tracking-wide" > Улучшить существующее< / p >
< div class = "mt-2 grid grid-cols-2 gap-2" >
< button id = "ai-check"
< button id = "ai-check"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg
class="inline-flex items-center justify-center gap-2 px-3 py-3 rounded-lg
bg-white border border-ink-300/60 hover:border-brand-300 text-sm">
bg-white border border-ink-300/60 hover:border-brand-300
text-sm min-h-11">
< span class = "material-symbols-outlined text-base" > fact_check< / span >
< span class = "material-symbols-outlined text-base" > fact_check< / span >
Проверить тест
Проверить
< / button >
< / button >
< button id = "ai-improve"
< button id = "ai-improve"
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg
class="inline-flex items-center justify-center gap-2 px-3 py-3 rounded-lg
bg-white border border-ink-300/60 hover:border-brand-300 text-sm">
bg-white border border-ink-300/60 hover:border-brand-300
text-sm min-h-11">
< span class = "material-symbols-outlined text-base" > tune< / span >
< span class = "material-symbols-outlined text-base" > tune< / span >
Улучшить тест
Улучшить
< / button >
< / button >
< label class = "inline-flex items-center gap-2 px-3 py-2 rounded-lg
< / div >
bg-white border border-ink-300/60 hover:border-brand-300 text-sm cursor-pointer">
< / div >
{# Группа C — импорт. #}
< div class = "mt-4" >
< p class = "text-xs font-medium text-ink-500 uppercase tracking-wide" > Импортировать< / p >
< label class = "mt-2 inline-flex w-full items-center justify-center gap-2 px-3 py-3
rounded-lg bg-white border border-ink-300/60 hover:border-brand-300
text-sm cursor-pointer min-h-11">
< span class = "material-symbols-outlined text-base text-brand-600" > upload_file< / span >
< span class = "material-symbols-outlined text-base text-brand-600" > upload_file< / span >
< span > Импорт документа< / span >
< span > Загрузить документ (PDF, DOCX, TXT, MD) < / span >
< input id = "ai-import-file" type = "file" accept = ".pdf,.docx,.txt,.md" class = "hidden" / >
< input id = "ai-import-file" type = "file" accept = ".pdf,.docx,.txt,.md" class = "hidden" / >
< / label >
< / label >
< span id = "ai-status" class = "text-sm text-ink-500" > < / span >
< p class = "mt-1.5 text-xs text-ink-500" >
< / div >
До 16 МБ. AI извлечёт текст и предложит черновик теста.
< p class = "mt-2 text-xs text-ink-500" >
Поддерживаются PDF, DOCX, TXT, MD (до 16 МБ). AI извлечёт текст и предложит черновик теста.
< / p >
< / p >
< / div >
< p id = "ai-status" class = "mt-3 text-sm text-ink-500 min-h-[1.25rem]" > < / p >
< / section >
< / section >
<!-- Список вопросов -->
{# ── 3. Вопросы ─────────────────────────────────────────────── #}
< section class = "mt-4" >
< section >
< div class = "flex items-center justify-between gap-2 px-1" >
< div class = "flex items-center justify-between gap-2 px-1" >
< h2 class = "font-semibold" > Вопросы (< span id = "q-count" > 0< / span > )< / h2 >
< h2 class = "font-semibold" > Вопросы (< span id = "q-count" > 0< / span > )< / h2 >
< button id = "add-question"
< button id = "add-question"
class="inline-flex items-center gap-1 px-3 py-1.5 rounded-lg
class="inline-flex items-center gap-1 px-3 py-2 rounded-lg
bg-white border border-ink-300/60 hover:border-brand-300 text-sm">
bg-white border border-ink-300/60 hover:border-brand-300 text-sm min-h-10 ">
< span class = "material-symbols-outlined text-base" > add< / span >
< span class = "material-symbols-outlined text-base" > add< / span >
Добавить вопрос
< span class = "hidden sm:inline" > Добавить вопрос< / span >
< span class = "sm:hidden" > Добавить< / span >
< / button >
< / button >
< / div >
< / div >
< ol id = "questions" class = "mt-3 space-y-3" > < / ol >
< ol id = "questions" class = "mt-3 space-y-3" > < / ol >
< / section >
< / section >
< / div >
<!-- Footer: сохранение / активность цепочки -->
{# ── Sticky-footer: «Цепочка активна» + «Сохранить» ────────────── #}
< section class = "sticky bottom-0 z-20 mt-6 -mx-4 px-4 py-3
< div class = "fixed bottom-0 inset-x-0 z-30 bg-white / 95 backdrop-blur border-t border-ink-300 / 60
bg-white/90 backdrop-blur border-t border-ink-300/60
pb-[env(safe-area-inset-bottom)]">
flex items-center justify-between gap-2 flex-wrap">
< div class = "mx-auto max-w-6xl px-4 py-3
< label class = "inline-flex items-center gap-2 text-sm" >
flex items-center justify-between gap-3">
< label class = "inline-flex items-center gap-2 text-sm min-w-0" >
< input id = "chain-active" type = "checkbox"
< input id = "chain-active" type = "checkbox"
class="rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
class="rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
< span > Цепочка активна (виден в каталоге) < / span >
< span class = "truncate" > Цепочка активна< / span >
< / label >
< / label >
< div class = "flex items-center gap-2" >
< div class = "flex items-center gap-2 shrink-0" >
< span id = "save-status" class = "text-sm text-ink-500" > < / span >
< a href = "{{ url_for('tests.tests_list_page') }}"
< a href = "{{ url_for('tests.tests_list_page') }}"
class="px-3 py-2 rounded-lg text-ink-700 hover:bg-ink-100 text-sm">К каталогу< / a >
class="hidden sm:inline-flex px-3 py-2 rounded-lg text-ink-700 hover:bg-ink-100 text-sm">
К каталогу
< / a >
< button id = "save-draft"
< button id = "save-draft"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white">
bg-brand-600 hover:bg-brand-700 text-white font-medium min-h-11 ">
< span class = "material-symbols-outlined text-base" > save< / span >
< span class = "material-symbols-outlined text-base" > save< / span >
Сохранить
Сохранить
< / button >
< / button >
< / div >
< / div >
< / section >
< / div >
< p id = "save-status" class = "mx-auto max-w-6xl px-4 pb-2 text-xs text-ink-500" > < / p >
< / div >
< / div >
<!-- Шаблон вопроса -->
{# ── Шаблон вопроса ─────────────────────────────────────────────── #}
< template id = "tpl-question" >
< template id = "tpl-question" >
< li class = "rounded-xl bg-white border border-ink-300/60 p-4 q-item" >
< li class = "rounded-xl bg-white border border-ink-300/60 p-3 sm:p-4 q-item" >
< div class = "flex items-start justify-between gap-2" >
{# Шапка карточки вопроса: номер слева, кнопки справа. #}
< span class = "text-xs uppercase tracking-wide text-ink-500 q-num" > Вопрос #< / span >
< div class = "flex items-center justify-between gap-2" >
< div class = "flex items-center gap-1" >
< span class = "inline-flex items-center px-2 py-0 . 5 rounded-md
< button class = "q-up p-1 rounded hover:bg-ink-100" title = "Выше" >
bg-brand-50 text-brand-700 text-xs font-medium q-num">Вопрос #< / span >
< div class = "flex items-center gap-0.5" >
< button class = "q-up p-2 rounded hover:bg-ink-100 min-w-10 min-h-10"
title="Выше" aria-label="Поднять выше">
< span class = "material-symbols-outlined text-base" > arrow_upward< / span >
< span class = "material-symbols-outlined text-base" > arrow_upward< / span >
< / button >
< / button >
< button class = "q-down p-1 rounded hover:bg-ink-100" title = "Ниже" >
< button class = "q-down p-2 rounded hover:bg-ink-100 min-w-10 min-h-10"
title="Ниже" aria-label="Опустить ниже">
< span class = "material-symbols-outlined text-base" > arrow_downward< / span >
< span class = "material-symbols-outlined text-base" > arrow_downward< / span >
< / button >
< / button >
< button class = "q-delete p-1 rounded hover:bg-red-50 text-red-600" title = "Удалить" >
< button class = "q-delete p-2 rounded hover:bg-red-50 text-red-600 min-w-10 min-h-10"
title="Удалить" aria-label="Удалить вопрос">
< span class = "material-symbols-outlined text-base" > delete< / span >
< span class = "material-symbols-outlined text-base" > delete< / span >
< / button >
< / button >
< / div >
< / div >
< / div >
< / div >
< textarea class = "q-text mt-2 w-full rounded-lg border border-ink-300 px-3 py-2
< textarea class = "q-text mt-3 w-full rounded-lg border border-ink-300 px-3 py-2
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
rows="2" placeholder="Формулировка вопроса">< / textarea >
rows="2" placeholder="Формулировка вопроса">< / textarea >
< div class = "mt-2 flex items-center justify-between gap-2 flex-wrap text-sm" >
{# Тип ответа + AI — две полные строки на мобиле, в строку на sm+. #}
< label class = "inline-flex items-center gap-2" >
< div class = "mt-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-sm" >
< input type = "checkbox" class = "q-multi rounded border-ink-300 text-brand-600 focus:ring-brand-500" / >
< label class = "inline-flex items-center gap-2 min-h-9" >
< input type = "checkbox"
class="q-multi rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
< span > Несколько правильных ответов< / span >
< span > Несколько правильных ответов< / span >
< / label >
< / label >
< button class = "q-ai inline-flex items-center gap-1 px-2 py-1 rounded-lg
< button class = "q-ai inline-flex items-center justify-center gap-1 px-2 . 5 py-2 rounded-lg
bg-brand-50 hover:bg-brand-100 text-brand-700">
bg-brand-50 hover:bg-brand-100 text-brand-700 text-sm min-h-10 ">
< span class = "material-symbols-outlined text-base" > auto_awesome< / span >
< span class = "material-symbols-outlined text-base" > auto_awesome< / span >
AI: вопрос/переформулировать
AI: вопрос/переформулировать
< / button >
< / button >
< / div >
< / div >
< ul class = "q-options mt-3 space-y-2" > < / ul >
< ul class = "q-options mt-3 space-y-2" > < / ul >
< div class = "mt-2" >
< button class = "q-add-option mt-2 inline-flex items-center gap-1 px-2 py-2 rounded
< button class = "q-add-option inline-flex items-center gap-1 text-sm text-brand-700 hover:underline" >
text-sm text-brand-700 hover:bg-brand-50 min-h-10">
< span class = "material-symbols-outlined text-base" > add< / span > Добавить вариант
< span class = "material-symbols-outlined text-base" > add< / span >
Добавить вариант
< / button >
< / button >
< / div >
< / li >
< / li >
< / template >
< / template >
<!-- Модалка результата AI - проверки/улучшения -->
{# ── Шаблон варианта ────────────────────────────────────────────── #}
< dialog id = "ai-modal" class = "rounded-2xl p-0 max-w-3xl w-full backdrop:bg-black/40" >
< div class = "p-5" >
< div class = "flex items-center justify-between gap-3" >
< h3 id = "ai-modal-title" class = "text-lg font-semibold" > AI< / h3 >
< button id = "ai-modal-close" class = "p-1 rounded hover:bg-ink-100" >
< span class = "material-symbols-outlined" > close< / span >
< / button >
< / div >
< div id = "ai-modal-body" class = "mt-3 max-h-[70vh] overflow-y-auto" > < / div >
< div id = "ai-modal-actions" class = "mt-4 flex items-center justify-end gap-2" > < / div >
< / div >
< / dialog >
< template id = "tpl-option" >
< template id = "tpl-option" >
< li class = "flex items-center gap-2 opt-item" >
< li class = "flex items-center gap-2 opt-item" >
< input type = "checkbox" class = "opt-correct rounded border-ink-300 text-brand-600 focus:ring-brand-500" / >
{# Чекбокс «Правильный» — обёрнут в большой tap-target. #}
< input type = "text" class = "opt-text flex-1 rounded-lg border border-ink-300 px-3 py-1 . 5
< label class = "inline-flex items-center justify-center w-10 h-10 shrink-0 cursor-pointer
rounded hover:bg-ink-100" title="Правильный ответ">
< input type = "checkbox"
class="opt-correct w-5 h-5 rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
< / label >
< input type = "text"
class="opt-text flex-1 min-w-0 rounded-lg border border-ink-300 px-3 py-2
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
placeholder="Вариант ответа" />
placeholder="Вариант ответа" />
< button class = "opt-delete p-1 rounded hover:bg-red-50 text-red-600" title = "Удалить" >
< button class = "opt-delete shrink-0 w-10 h-10 inline-flex items-center justify-center
rounded hover:bg-red-50 text-red-600"
title="Удалить" aria-label="Удалить вариант">
< span class = "material-symbols-outlined text-base" > close< / span >
< span class = "material-symbols-outlined text-base" > close< / span >
< / button >
< / button >
< / li >
< / li >
< / template >
< / template >
{# ── Модалка результата AI-проверки/улучшения (fullscreen на мобиле) ── #}
< dialog id = "ai-modal"
class="m-0 p-0 w-full h-full sm:h-auto sm:max-w-3xl sm:w-full sm:max-h-[90vh]
sm:rounded-2xl sm:m-auto bg-white backdrop:bg-black/50">
< div class = "flex flex-col h-full sm:max-h-[90vh]" >
< div class = "flex items-center justify-between gap-3 px-4 sm:px-5 py-3 border-b border-ink-300/60" >
< h3 id = "ai-modal-title" class = "text-lg font-semibold truncate" > AI< / h3 >
< button id = "ai-modal-close"
class="p-2 rounded hover:bg-ink-100 min-w-10 min-h-10"
aria-label="Закрыть">
< span class = "material-symbols-outlined" > close< / span >
< / button >
< / div >
< div id = "ai-modal-body" class = "flex-1 overflow-y-auto px-4 sm:px-5 py-4" > < / div >
< div id = "ai-modal-actions"
class="px-4 sm:px-5 py-3 border-t border-ink-300/60
flex items-center justify-end gap-2 flex-wrap
pb-[max(env(safe-area-inset-bottom),0.75rem)]">< / div >
< / div >
< / dialog >
{% endblock %}
{% endblock %}
{% block scripts %}
{% block scripts %}