feat(flashcards): клик-для-редактирования вместо дубля поле+превью

Карточка по умолчанию показывает ОТРИСОВАННЫЙ текст (формулы KaTeX красиво),
клик → textarea с сырым LaTeX для правки, blur → сохранение + переотрисовка.
Никакого дублирования (как в Anki). Кнопка «ƒₓ Формула» синхронизирует
отрисовку после вставки (модалка снимает фокус с поля → fcEndEdit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 23:19:43 +03:00
parent 39aa283daf
commit cd9f2d5efa
+42 -22
View File
@@ -139,9 +139,12 @@
.card-textarea { width: 100%; border: none; outline: none; resize: none; background: transparent;
font-family: 'Manrope', sans-serif; font-size: .88rem; color: var(--text);
min-height: 48px; line-height: 1.5; padding: 0; }
.fc-math-preview { display: none; margin-top: 6px; padding: 6px 9px; font-size: .85rem; line-height: 1.5;
color: var(--text); background: rgba(155,93,229,.05); border: 1px solid rgba(155,93,229,.14);
border-radius: 8px; overflow-x: auto; }
/* Карточка по умолчанию — отрисованный текст (с KaTeX). Клик → редактирование. */
.card-display { min-height: 48px; line-height: 1.5; font-size: .88rem; color: var(--text);
cursor: text; padding: 2px 4px; margin: 0 -4px; border-radius: 7px;
transition: background .12s; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
.card-display:hover { background: rgba(155,93,229,.06); }
.card-display.fc-empty { color: var(--text-3); font-style: italic; }
.card-actions { display: flex; flex-direction: column; gap: 0; border-left: 1px solid var(--border); }
.card-act-btn { padding: 0 14px; height: 100%; flex: 1; border: none; background: none;
cursor: pointer; color: var(--text-3); transition: .15s; display: flex;
@@ -828,11 +831,10 @@ function renderCardList() {
<span class="card-side-lbl">Вопрос</span>
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
</div>
<textarea class="card-textarea" rows="2"
<div class="card-display" data-ph="Вопрос…" onclick="fcStartEdit(this)"></div>
<textarea class="card-textarea" rows="2" style="display:none" data-cid="${c.id}" data-side="front"
onpaste="onCardPaste(event,${c.id},'front')"
oninput="fcMathPreview(this)"
onchange="saveCard(${c.id},'front',this.value)">${esc(c.front)}</textarea>
<div class="fc-math-preview"></div>
onblur="fcEndEdit(this)">${esc(c.front)}</textarea>
${imgRowHtml(c, 'front')}
</div>
<div class="card-divider"></div>
@@ -841,11 +843,10 @@ function renderCardList() {
<span class="card-side-lbl">Ответ</span>
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
</div>
<textarea class="card-textarea" rows="2"
<div class="card-display" data-ph="Ответ…" onclick="fcStartEdit(this)"></div>
<textarea class="card-textarea" rows="2" style="display:none" data-cid="${c.id}" data-side="back"
onpaste="onCardPaste(event,${c.id},'back')"
oninput="fcMathPreview(this)"
onchange="saveCard(${c.id},'back',this.value)">${esc(c.back)}</textarea>
<div class="fc-math-preview"></div>
onblur="fcEndEdit(this)">${esc(c.back)}</textarea>
${imgRowHtml(c, 'back')}
</div>
<div class="card-actions">
@@ -855,20 +856,37 @@ function renderCardList() {
</div>
</div>`).join('');
// Превью формул KaTeX под каждым полем (textarea сырой LaTeX не рендерит)
list.querySelectorAll('.card-textarea').forEach(fcMathPreview);
// Отрисовать карточки (KaTeX). Правка — по клику (textarea), как в Anki.
list.querySelectorAll('.card-display').forEach(fcRenderDisplay);
if (!q) bindCardDrag();
}
/* Рендер KaTeX-превью под полем карточки, если в тексте есть формула. */
function fcMathPreview(ta) {
const side = ta.closest('.card-side');
const prev = side && side.querySelector('.fc-math-preview');
if (!prev) return;
const txt = ta.value || '';
if (!/\$|\\\(|\\\[/.test(txt)) { prev.style.display = 'none'; prev.innerHTML = ''; return; }
prev.innerHTML = mathHtmlFC(txt);
prev.style.display = 'block';
/* Показать отрисованный текст карточки (или плейсхолдер, если пусто). */
function fcRenderDisplay(disp) {
const side = disp.closest('.card-side');
const ta = side && side.querySelector('.card-textarea');
const text = ta ? ta.value : '';
if (text && text.trim()) { disp.innerHTML = mathHtmlFC(text); disp.classList.remove('fc-empty'); }
else { disp.textContent = disp.dataset.ph || ''; disp.classList.add('fc-empty'); }
}
/* Клик по отрисованной карточке → редактирование (textarea с сырым LaTeX). */
function fcStartEdit(disp) {
const side = disp.closest('.card-side');
const ta = side && side.querySelector('.card-textarea');
if (!ta) return;
disp.style.display = 'none';
ta.style.display = '';
ta.focus();
const v = ta.value; try { ta.setSelectionRange(v.length, v.length); } catch (e) {}
}
/* Конец редактирования (blur / после вставки формулы): сохранить + отрисовать. */
function fcEndEdit(ta) {
const id = +ta.dataset.cid, side = ta.dataset.side;
if (id && side) saveCard(id, side, ta.value);
const sideEl = ta.closest('.card-side');
const disp = sideEl && sideEl.querySelector('.card-display');
ta.style.display = 'none';
if (disp) { disp.style.display = ''; fcRenderDisplay(disp); }
}
/* ── drag-reorder карточек (только без активного фильтра) ── */
@@ -1411,6 +1429,8 @@ function fxInsert() {
ta.dispatchEvent(new Event('input', { bubbles: true }));
ta.dispatchEvent(new Event('change', { bubbles: true }));
ta.focus();
// Поле карточки скрылось при открытии модалки (blur) — сохранить и переотрисовать.
if (ta.classList && ta.classList.contains('card-textarea')) fcEndEdit(ta);
}
closeModal('modal-formula');
}