feat(flashcards): клик-для-редактирования вместо дубля поле+превью
Карточка по умолчанию показывает ОТРИСОВАННЫЙ текст (формулы KaTeX красиво), клик → textarea с сырым LaTeX для правки, blur → сохранение + переотрисовка. Никакого дублирования (как в Anki). Кнопка «ƒₓ Формула» синхронизирует отрисовку после вставки (модалка снимает фокус с поля → fcEndEdit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+42
-22
@@ -139,9 +139,12 @@
|
|||||||
.card-textarea { width: 100%; border: none; outline: none; resize: none; background: transparent;
|
.card-textarea { width: 100%; border: none; outline: none; resize: none; background: transparent;
|
||||||
font-family: 'Manrope', sans-serif; font-size: .88rem; color: var(--text);
|
font-family: 'Manrope', sans-serif; font-size: .88rem; color: var(--text);
|
||||||
min-height: 48px; line-height: 1.5; padding: 0; }
|
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;
|
/* Карточка по умолчанию — отрисованный текст (с KaTeX). Клик → редактирование. */
|
||||||
color: var(--text); background: rgba(155,93,229,.05); border: 1px solid rgba(155,93,229,.14);
|
.card-display { min-height: 48px; line-height: 1.5; font-size: .88rem; color: var(--text);
|
||||||
border-radius: 8px; overflow-x: auto; }
|
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-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;
|
.card-act-btn { padding: 0 14px; height: 100%; flex: 1; border: none; background: none;
|
||||||
cursor: pointer; color: var(--text-3); transition: .15s; display: flex;
|
cursor: pointer; color: var(--text-3); transition: .15s; display: flex;
|
||||||
@@ -828,11 +831,10 @@ function renderCardList() {
|
|||||||
<span class="card-side-lbl">Вопрос</span>
|
<span class="card-side-lbl">Вопрос</span>
|
||||||
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
|
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
|
||||||
</div>
|
</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')"
|
onpaste="onCardPaste(event,${c.id},'front')"
|
||||||
oninput="fcMathPreview(this)"
|
onblur="fcEndEdit(this)">${esc(c.front)}</textarea>
|
||||||
onchange="saveCard(${c.id},'front',this.value)">${esc(c.front)}</textarea>
|
|
||||||
<div class="fc-math-preview"></div>
|
|
||||||
${imgRowHtml(c, 'front')}
|
${imgRowHtml(c, 'front')}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-divider"></div>
|
<div class="card-divider"></div>
|
||||||
@@ -841,11 +843,10 @@ function renderCardList() {
|
|||||||
<span class="card-side-lbl">Ответ</span>
|
<span class="card-side-lbl">Ответ</span>
|
||||||
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
|
<button class="fx-mini" title="Вставить формулу (KaTeX)" onclick="openFormula(this)">ƒₓ</button>
|
||||||
</div>
|
</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')"
|
onpaste="onCardPaste(event,${c.id},'back')"
|
||||||
oninput="fcMathPreview(this)"
|
onblur="fcEndEdit(this)">${esc(c.back)}</textarea>
|
||||||
onchange="saveCard(${c.id},'back',this.value)">${esc(c.back)}</textarea>
|
|
||||||
<div class="fc-math-preview"></div>
|
|
||||||
${imgRowHtml(c, 'back')}
|
${imgRowHtml(c, 'back')}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@@ -855,20 +856,37 @@ function renderCardList() {
|
|||||||
</div>
|
</div>
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
|
|
||||||
// Превью формул KaTeX под каждым полем (textarea сырой LaTeX не рендерит)
|
// Отрисовать карточки (KaTeX). Правка — по клику (textarea), как в Anki.
|
||||||
list.querySelectorAll('.card-textarea').forEach(fcMathPreview);
|
list.querySelectorAll('.card-display').forEach(fcRenderDisplay);
|
||||||
if (!q) bindCardDrag();
|
if (!q) bindCardDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Рендер KaTeX-превью под полем карточки, если в тексте есть формула. */
|
/* Показать отрисованный текст карточки (или плейсхолдер, если пусто). */
|
||||||
function fcMathPreview(ta) {
|
function fcRenderDisplay(disp) {
|
||||||
const side = ta.closest('.card-side');
|
const side = disp.closest('.card-side');
|
||||||
const prev = side && side.querySelector('.fc-math-preview');
|
const ta = side && side.querySelector('.card-textarea');
|
||||||
if (!prev) return;
|
const text = ta ? ta.value : '';
|
||||||
const txt = ta.value || '';
|
if (text && text.trim()) { disp.innerHTML = mathHtmlFC(text); disp.classList.remove('fc-empty'); }
|
||||||
if (!/\$|\\\(|\\\[/.test(txt)) { prev.style.display = 'none'; prev.innerHTML = ''; return; }
|
else { disp.textContent = disp.dataset.ph || ''; disp.classList.add('fc-empty'); }
|
||||||
prev.innerHTML = mathHtmlFC(txt);
|
}
|
||||||
prev.style.display = 'block';
|
/* Клик по отрисованной карточке → редактирование (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 карточек (только без активного фильтра) ── */
|
/* ── drag-reorder карточек (только без активного фильтра) ── */
|
||||||
@@ -1411,6 +1429,8 @@ function fxInsert() {
|
|||||||
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
ta.focus();
|
ta.focus();
|
||||||
|
// Поле карточки скрылось при открытии модалки (blur) — сохранить и переотрисовать.
|
||||||
|
if (ta.classList && ta.classList.contains('card-textarea')) fcEndEdit(ta);
|
||||||
}
|
}
|
||||||
closeModal('modal-formula');
|
closeModal('modal-formula');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user