From e793b4ec096099ba813646e395b9d7badd2c9cdd Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Thu, 4 Jun 2026 12:23:19 +0300 Subject: [PATCH] =?UTF-8?q?feat(materials):=20=D0=A4=D0=B0=D0=B7=D0=B0=205?= =?UTF-8?q?=20=E2=80=94=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D1=84=D0=BB=D0=B5=D1=88=D0=BA=D0=B0=D1=80=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кнопка «В флешкарты» на карточке-заметке: выбор колоды (или новая «Из материалов») → создание карточки (front=заголовок, back=текст) через существующий API флешкард. Хелперы fcListDecks/fcCreateDeck/fcAddCard в js/api.js. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/my-materials.html | 36 +++++++++++++++++++++++++++++++++++- js/api.js | 4 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/frontend/my-materials.html b/frontend/my-materials.html index 9be5232..186b8cc 100644 --- a/frontend/my-materials.html +++ b/frontend/my-materials.html @@ -108,6 +108,8 @@ const edit = ``; const ann = (m.kind === 'board' || m.kind === 'image') ? `` : ''; + const fc = (m.kind === 'note') + ? `` : ''; const mv = moveSelect(m); if (m.kind === 'board' || m.kind === 'image') { return `
@@ -132,7 +134,7 @@
${esc(m.title || kind)}
${meta}
-
${mv}${edit}${del}
+
${mv}${fc}${edit}${del}
`; } @@ -351,6 +353,38 @@ } window.annotate = annotate; + /* ── Заметка → флешкарта ── */ + async function toFlashcard(id) { + const mt = _mats.find(x => x.id === id); + if (!mt) return; + let decks = []; + try { const d = await LS.fcListDecks(); decks = d.decks || []; } catch (e) {} + const opts = [''] + .concat(decks.map(d => ``)).join(''); + const front = (mt.title || '').trim() || (mt.body || '').slice(0, 80); + const back = (mt.body || '').trim() || (mt.title || ''); + const content = `
+ + + + + + +
`; + const m = LS.modal({ title: 'В флешкарты', content, size: 'sm', actions: [ + { label: 'Отмена', onClick: () => m.close() }, + { label: 'Создать карточку', primary: true, onClick: async () => { + try { + let deckId = m.body.querySelector('#fc-deck').value; + if (deckId === '__new') { const nd = await LS.fcCreateDeck({ title: 'Из материалов' }); deckId = nd.id; } + await LS.fcAddCard(deckId, { front: m.body.querySelector('#fc-front').value, back: m.body.querySelector('#fc-back').value }); + m.close(); LS.toast('Карточка добавлена в флешкарты', 'success'); + } catch (e) { LS.toast(e.message || 'Ошибка', 'error'); } + } }, + ] }); + } + window.toFlashcard = toFlashcard; + load(); diff --git a/js/api.js b/js/api.js index 718eadd..564eb7d 100644 --- a/js/api.js +++ b/js/api.js @@ -1050,6 +1050,7 @@ window.LS = { crAdminGetAllHistory, crAdminGetTeachersList, listMaterials, saveMaterial, updateMaterial, deleteMaterial, createMaterialCollection, updateMaterialCollection, deleteMaterialCollection, + fcListDecks, fcCreateDeck, fcAddCard, escapeHtml, esc, parseDate, fmtRelTime, safeHref, initPage, @@ -1251,6 +1252,9 @@ async function deleteMaterial(id) { return req('DELETE', `/materials/${id}`) async function createMaterialCollection(d) { return req('POST', '/materials/collections', d); } async function updateMaterialCollection(id,d){ return req('PATCH', `/materials/collections/${id}`, d); } async function deleteMaterialCollection(id) { return req('DELETE', `/materials/collections/${id}`); } +async function fcListDecks() { return req('GET', '/flashcards/decks'); } +async function fcCreateDeck(d) { return req('POST', '/flashcards/decks', d); } +async function fcAddCard(deckId, d) { return req('POST', `/flashcards/decks/${deckId}/cards`, d); } async function deleteFile(id) { return req('DELETE', `/files/${id}`); } async function getFileAccess(id) { return req('GET', `/files/${id}/access`); } async function assignFile(id, data) { return req('POST', `/files/${id}/assign`, data); }