Files
TestingWebApp/flask_app/app/templates/tests/editor.html
T
2026-04-29 21:06:17 +05:00

418 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ content.test.title }} — редактор{% endblock %}
{% block content %}
<div id="editor-root"
class="space-y-4 sm:space-y-5 pb-24 {% if ui_variant == 'legacy' %}test-detail-page test-detail-page--with-fixed-actions{% endif %}"
data-test-id="{{ test_id }}"
data-initial='{{ content | tojson | safe }}'>
<section class="cabinet-brick cabinet-brick--hero hero-brick">
<a href="{{ url_for('tests.tests_list_page') }}" class="link-back">К тестам</a>
<textarea id="test-title" maxlength="200" rows="1" placeholder="Название теста"
class="hero-brick__title font-headline"></textarea>
<textarea id="test-title" maxlength="200" rows="1" placeholder="Название теста"
class="hero-brick__title font-headline"></textarea>
<textarea id="test-description" rows="2" placeholder="Краткое описание (необязательно)"
class="hero-brick__desc"></textarea>
<div class="hero-brick__meta-row">
<span>Автор: <b id="intro-author">Вы</b></span>
<span class="hero-brick__sep">·</span>
<span>Обновлён: <span id="intro-updated"></span></span>
<span class="hero-brick__sep">·</span>
<span>Версия <span id="intro-version"></span></span>
</div>
<div class="hero-brick__divider"></div>
<div class="hero-brick__meta-row">
<span>Порог зачёта: <b id="threshold-mirror"></b>%</span>
<span class="hero-brick__sep">·</span>
<span>Вопросов: <b id="q-count">0</b></span>
<span class="hero-brick__sep">·</span>
<span id="chain-active-display">Активна в каталоге</span>
</div>
</section>
{# ── Версии ───────────────────────────────────────────────────── #}
<details class="cabinet-disclosure cabinet-brick">
<summary class="cabinet-disclosure__summary">
<span class="cabinet-disclosure__summary-text">
<span class="cabinet-disclosure__summary-title font-headline">Версии</span>
<span class="cabinet-disclosure__summary-sub">История изменений; можно переключить активную версию</span>
</span>
</summary>
<div class="cabinet-disclosure__body">
<ul id="versions-list" class="version-card-list"></ul>
</div>
</details>
<details class="cabinet-disclosure cabinet-brick">
<summary class="cabinet-disclosure__summary">
<span class="cabinet-disclosure__summary-text">
<span class="cabinet-disclosure__summary-title font-headline">Параметры теста</span>
<span class="cabinet-disclosure__summary-sub">Порог, таймер, режим показа результата и подсказок</span>
</span>
</summary>
<div class="cabinet-disclosure__body">
<div class="settings-grid">
<label class="settings-row">
<span class="settings-row__label">Проходной балл, %</span>
<input id="test-threshold" type="number" min="0" max="100" step="1" inputmode="numeric"
class="settings-row__input" />
</label>
<label class="settings-row">
<span class="settings-row__label">
Таймер, минут
<span class="settings-row__hint">0 или пусто — без ограничения</span>
</span>
<input id="test-time-limit" type="number" min="0" max="600" step="1" inputmode="numeric"
class="settings-row__input" placeholder="—" />
</label>
<fieldset class="settings-row settings-row--block">
<legend class="settings-row__label">Когда показывать результат</legend>
<label class="settings-radio">
<input type="radio" name="result-mode" value="end" />
<span>В конце теста <span class="settings-row__hint">(подсказок не будет)</span></span>
</label>
<label class="settings-radio">
<input type="radio" name="result-mode" value="immediate" />
<span>Сразу после ответа <span class="settings-row__hint">(с ИИ-подсказкой)</span></span>
</label>
</fieldset>
<label class="settings-row settings-row--toggle" id="test-hints-row" style="display:none;">
<span class="settings-row__label">
Показывать подсказку после ответа
<span class="settings-row__hint">Краткое объяснение во всплывающем окне</span>
</span>
<input id="test-hints-enabled" type="checkbox" />
</label>
<div class="settings-row settings-row--block" style="padding-top:0.75rem; border-top:1px solid var(--outline-variant); margin-top:0.25rem;">
<span class="settings-row__label">Видимость в каталоге</span>
<p class="settings-row__hint" style="margin-bottom:0.5rem;">Скрытые тесты не показываются в общем списке; ссылку по-прежнему можно открыть.</p>
<button id="btn-toggle-visibility" class="btn btn-ghost btn--sm" type="button">Скрыть из списка</button>
</div>
</div>
</div>
</details>
<details class="cabinet-disclosure cabinet-brick" open>
<summary class="cabinet-disclosure__summary">
<span class="cabinet-disclosure__summary-text">
<span class="cabinet-disclosure__summary-title font-headline">Вопросы</span>
<span class="cabinet-disclosure__summary-sub">Тексты, варианты и при необходимости загрузка из файла</span>
</span>
</summary>
<div class="cabinet-disclosure__body">
<section class="rounded-2xl bg-brand-50/60 border border-brand-100 p-4 sm:p-5 test-detail-ai-panel">
{# ── Создать шаблон ──────────────────────────────────────── #}
<div class="question-editor-block question-editor-block--first">
<h3 class="test-detail-subsection__title">Структура теста</h3>
<p class="muted text-xs mb-3">
Укажите количество вопросов и вариантов — создайте шаблон и заполните его вручную или через ИИ.
</p>
<div class="flex flex-wrap items-end gap-3">
<label class="block">
<span class="form-label">Вопросов</span>
<input id="ai-q-count" type="number" min="1" max="30" step="1" value="7"
class="form-input" style="width:90px;" />
</label>
<label class="block">
<span class="form-label">Вариантов</span>
<input id="ai-o-count" type="number" min="2" max="8" step="1" value="3"
class="form-input" style="width:90px;" />
</label>
<button id="create-template"
class="btn btn-ghost" type="button" style="min-height:43px;">
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">grid_view</span>
Создать шаблон
</button>
</div>
</div>
{# ── Заполнить через ИИ по теме ──────────────────────────── #}
<div class="question-editor-block">
<h3 class="test-detail-subsection__title">Заполнить через ИИ</h3>
<label class="block">
<span class="form-label">Тема / промпт</span>
<textarea id="ai-topic" rows="1" class="form-input"
placeholder="Например: охрана труда на производстве"
style="resize:none; overflow:hidden; font-family:inherit;"></textarea>
</label>
<div class="mt-2">
<button id="ai-generate-test"
class="btn btn-ghost" type="button" style="min-height:43px;">
Сгенерировать вопросы (ИИ)
</button>
</div>
</div>
{# ── Проверить и улучшить ─────────────────────────────────── #}
<div class="question-editor-block">
<h3 class="test-detail-subsection__title">Проверить и улучшить</h3>
<div class="flex flex-wrap gap-2">
<button id="ai-check"
class="btn btn-ghost" type="button" style="min-height:43px;">
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">fact_check</span>
Проверить тест
</button>
<button id="ai-improve"
class="btn btn-ghost" type="button" style="min-height:43px;">
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">auto_fix_high</span>
Предложить улучшение
</button>
</div>
</div>
{# ── Документ в вопросы ──────────────────────────────────── #}
<div class="question-editor-block test-detail-subsection test-detail-subsection--import">
<h3 class="test-detail-subsection__title">Документ в вопросы</h3>
<label id="ai-import-dropzone"
class="import-dropzone mt-2 flex flex-col w-full items-center justify-center gap-1
px-4 py-5 rounded-xl bg-white border-2 border-dashed border-ink-300/70
hover:border-brand-400 hover:bg-brand-50/40 cursor-pointer transition-colors">
<span class="material-symbols-outlined text-2xl text-brand-400">upload_file</span>
<span id="ai-import-dropzone-label" class="text-sm font-medium text-ink-700">Перетащите файл сюда или нажмите</span>
<span class="text-xs text-ink-400">PDF, DOCX, TXT, MD · до 16 МБ</span>
<input id="ai-import-file" type="file" accept=".pdf,.docx,.txt,.md" class="hidden" />
</label>
<label class="block mt-3">
<span class="form-label">Пожелания по содержанию <span class="text-ink-400 font-normal">(необязательно)</span></span>
<textarea id="doc-user-hint" rows="1"
class="form-input mt-1"
placeholder="Например: акцент на разделе 3, не делать вопросы про даты"
style="resize:none; overflow:hidden; font-family:inherit;"></textarea>
</label>
<button id="doc-generate-btn"
class="btn btn-ghost mt-2" type="button" style="min-height:43px;">
<span class="material-symbols-outlined text-base" style="vertical-align:-3px;">auto_awesome</span>
Сгенерировать из документа
</button>
</div>
{# ── Модалка результата импорта документа ─────────────────── #}
<dialog id="import-modal" class="save-modal">
<div class="save-modal__inner" style="max-width:480px; width:100%;">
<h3 id="import-modal-title" class="font-headline text-base font-semibold mb-2"></h3>
<div id="import-modal-body" class="text-sm text-ink-600 mb-4 max-h-64 overflow-y-auto"></div>
<div id="import-modal-actions" class="flex gap-2 justify-end flex-wrap"></div>
</div>
</dialog>
<p id="ai-status" class="mt-3 text-sm text-ink-500 min-h-[1.25rem]"></p>
</section>
{# ── 3. Вопросы ─────────────────────────────────────────────── #}
<section class="mt-1">
<div class="flex items-center gap-2">
<h2 class="font-semibold text-ink-900">Вопросы (<span id="q-count-mirror">0</span>)</h2>
</div>
<ol id="questions" class="mt-3 space-y-4"></ol>
<div class="mt-3 flex justify-center">
<button id="add-question"
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 min-h-10
btn btn-ghost question-editor__add-question">
<span class="material-symbols-outlined text-base">add</span>
<span>Добавить вопрос</span>
</button>
</div>
{# Кнопка «Сохранить» под вопросами #}
<div class="mt-5 flex items-center gap-3">
<button id="save-draft-inline"
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white font-medium min-h-11 btn btn-primary">
<span class="material-symbols-outlined text-base">save</span>
Сохранить
</button>
<button id="btn-cancel-inline"
class="inline-flex px-3 py-2 rounded-lg text-ink-700 hover:bg-ink-100 text-sm btn btn-ghost">
Отмена
</button>
<p id="save-status-inline" class="text-xs text-ink-500"></p>
</div>
</section>
</div>
</details>
{# Прохождения перенесены на /stats. Назначения перенесены на /assignments #}
</div>
{# ── Sticky-footer: «Цепочка активна» + «Сохранить» ────────────── #}
<div class="fixed bottom-0 inset-x-0 z-30 bg-white/95 backdrop-blur border-t border-ink-300/60
pb-[env(safe-area-inset-bottom)]">
<div class="mx-auto {% if ui_variant == 'legacy' %}max-w-2xl{% else %}max-w-6xl{% endif %} px-4 py-3
flex items-center justify-between gap-3">
<div id="intro-fork-banner" class="callout callout--warning text-xs sm:text-sm"
data-fork-risk="{{ '1' if content.test.hasForkRisk else '0' }}"
style="display:none; margin:0; padding:0.4rem 0.6rem; flex:1 1 0; min-width:0; white-space:normal; word-break:break-word; line-height:1.25;">
При сохранении будет создана новая версия теста.
</div>
<div class="flex items-center gap-2 ml-auto shrink-0">
<button id="btn-cancel"
class="inline-flex px-3 py-2 rounded-lg text-ink-700 hover:bg-ink-100 text-sm btn btn-ghost">
Отмена
</button>
<button id="save-draft"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white font-medium min-h-11 btn btn-primary">
<span class="material-symbols-outlined text-base">save</span>
Сохранить
</button>
</div>
</div>
<p id="save-status" class="mx-auto {% if ui_variant == 'legacy' %}max-w-2xl{% else %}max-w-6xl{% endif %} px-4 pb-2 text-xs text-ink-500"></p>
</div>
{# ── Шаблон вопроса ─────────────────────────────────────────────── #}
<template id="tpl-question">
<li class="relative rounded-xl bg-white border border-ink-300/60 p-4 sm:p-5 q-item" draggable="true">
{# Оверлей загрузки AI #}
<div class="q-ai-overlay hidden absolute inset-0 rounded-xl z-10
bg-white/80 backdrop-blur-[2px] flex flex-col items-center justify-center gap-2">
<span class="q-ai-spinner inline-block w-7 h-7 rounded-full
border-[3px] border-brand-200 border-t-brand-600 animate-spin"></span>
<span class="text-xs text-ink-500 font-medium">Генерирую…</span>
</div>
{# Шапка карточки вопроса #}
<div class="flex items-center justify-between gap-2">
<span class="inline-flex items-center gap-1">
<button class="q-drag p-2 rounded hover:bg-ink-100 min-w-10 min-h-10 cursor-grab"
title="Перетащить" aria-label="Перетащить" type="button">
<span class="material-symbols-outlined text-base">drag_indicator</span>
</button>
<span class="inline-flex items-center px-2 py-0.5 rounded-md
bg-brand-50 text-brand-700 text-xs font-medium q-num">Вопрос #</span>
</span>
<div class="flex items-center gap-0.5">
<button class="q-clear p-2 rounded hover:bg-ink-100 min-w-10 min-h-10 text-ink-400"
title="Очистить вопрос" aria-label="Очистить вопрос" type="button">
<span class="material-symbols-outlined text-base">backspace</span>
</button>
<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>
</button>
<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>
</button>
<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>
</button>
</div>
</div>
<div class="mt-2 relative">
<textarea class="q-text w-full rounded-lg border border-ink-300 px-3 py-2
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20"
rows="1" placeholder="Формулировка вопроса" maxlength="500"
style="resize:none; overflow:hidden; font-family:inherit;"></textarea>
<span class="q-text-counter absolute bottom-1.5 right-2 text-xs text-ink-400 pointer-events-none select-none"></span>
</div>
<div class="mt-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-sm">
<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>
</label>
<button class="q-ai btn btn-ghost btn--sm q-ai-btn" style="font-size:0.75rem; padding:0.3rem 0.7rem;">
<span class="material-symbols-outlined q-ai-icon" style="font-size:0.9rem; vertical-align:-2px;">auto_fix_high</span>
<span class="q-ai-label">Сгенерировать</span>
</button>
</div>
<p class="mt-4 mb-2 text-xs text-ink-400 font-medium">Отметьте правильные варианты</p>
<ul class="q-options space-y-2"></ul>
<div class="mt-3 flex items-center gap-3">
<button class="q-add-option inline-flex items-center gap-1 px-2 py-2 rounded
text-sm text-brand-700 hover:bg-brand-50 min-h-10 btn btn-ghost btn--sm">
<span class="material-symbols-outlined text-base">add</span>
<span class="q-add-option-label">Добавить вариант</span>
</button>
<span class="q-options-count text-xs text-ink-400"></span>
</div>
</li>
</template>
{# ── Шаблон варианта ────────────────────────────────────────────── #}
<template id="tpl-option">
<li class="flex items-start gap-2 opt-item">
{# Чекбокс «Правильный» — выровнен по первой строке textarea #}
<label class="shrink-0 w-10 inline-flex items-center justify-center cursor-pointer
rounded hover:bg-ink-100 pt-1.5" style="min-height:2.5rem;" title="Правильный ответ">
<input type="checkbox"
class="opt-correct w-5 h-5 rounded border-ink-300 text-brand-600 focus:ring-brand-500" />
</label>
<textarea rows="1"
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"
placeholder="Вариант ответа"
style="resize:none; overflow:hidden; font-family:inherit; line-height:1.55;"></textarea>
<button class="opt-delete shrink-0 w-10 inline-flex items-center justify-center
rounded hover:bg-red-50 text-red-600 pt-1.5"
style="min-height:2.5rem;"
title="Удалить" aria-label="Удалить вариант">
<span class="material-symbols-outlined text-base">close</span>
</button>
</li>
</template>
{# ── Модалка успешного сохранения (компактная, сверху) ──────────── #}
<dialog id="save-modal" class="save-modal">
<div class="save-modal__inner">
<h3 class="text-base font-semibold mb-1" id="save-modal-title">Сохранено</h3>
<p id="save-modal-msg" class="text-sm text-ink-700">Изменения сохранены.</p>
<div class="mt-4 flex items-center justify-end gap-2">
<button id="save-modal-stay" type="button"
class="px-3 py-2 rounded-lg bg-ink-100 hover:bg-ink-200 text-sm btn btn-ghost">
К редактору
</button>
<button id="save-modal-go" type="button"
class="px-3 py-2 rounded-lg bg-brand-600 hover:bg-brand-700 text-white text-sm btn btn-primary">
К каталогу
</button>
</div>
</div>
</dialog>
{# ── Модалка результата 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 %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/editor.js') }}"></script>
{% endblock %}