feat(flashcards): глобальный quick-add FAB + виджет «повтори карточку»
Backend: - POST /api/flashcards/quick — добавить карточку из любой точки; колода по выбору или автоколода «Быстрые карточки» (создаётся при первом обращении) - GET /api/flashcards/random — случайная карточка из всего пула пользователя Frontend: - /js/flashcard-fab.js — плавающая кнопка «запомнить» на всех страницах (учебник, лаборатория, симуляция…). Поповер: вопрос/ответ/колода, Ctrl+Enter. Гейт по фиче-флагу flashcards; исключены classroom/login/error/сама /flashcards. Загружается лениво из sidebar.js (на 45 страницах с шапкой). - dashboard: виджет #w-flashcard в колонке прогресса — флип-карта (вопрос↔ответ), кнопка «Другая», счётчик пула, CTA при пустом пуле; слушает событие flashcard:added для авто-обновления. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -241,8 +241,64 @@ function getStats(req, res) {
|
||||
res.json({ decks_count, cards_count, due_count, reviewed_today });
|
||||
}
|
||||
|
||||
/* ── POST /api/flashcards/quick — быстрое добавление из любой точки ──────
|
||||
Кладёт карточку в указанную колоду (deckId) либо в личную колоду
|
||||
«Быстрые карточки» (создаётся при первом обращении). */
|
||||
const QUICK_DECK_TITLE = 'Быстрые карточки';
|
||||
function quickAdd(req, res) {
|
||||
const uid = req.user.id;
|
||||
const front = stripTags((req.body.front || '').slice(0, 5000)).trim();
|
||||
const back = stripTags((req.body.back || '').slice(0, 5000)).trim();
|
||||
if (!front) return res.status(400).json({ error: 'Лицевая сторона обязательна' });
|
||||
|
||||
let deck = null;
|
||||
const deckId = Number(req.body.deckId) || 0;
|
||||
if (deckId) {
|
||||
deck = db.prepare(`SELECT id, title, color FROM flashcard_decks WHERE id = ? AND user_id = ?`)
|
||||
.get(deckId, uid);
|
||||
}
|
||||
if (!deck) {
|
||||
deck = db.prepare(`SELECT id, title, color FROM flashcard_decks WHERE user_id = ? AND title = ? ORDER BY id LIMIT 1`)
|
||||
.get(uid, QUICK_DECK_TITLE);
|
||||
if (!deck) {
|
||||
const r = db.prepare(
|
||||
`INSERT INTO flashcard_decks (user_id, title, description, color) VALUES (?,?,?,?)`
|
||||
).run(uid, QUICK_DECK_TITLE, 'Карточки, добавленные на лету', '#9B5DE5');
|
||||
deck = { id: r.lastInsertRowid, title: QUICK_DECK_TITLE, color: '#9B5DE5', created: true };
|
||||
}
|
||||
}
|
||||
|
||||
const maxIdx = db.prepare(`SELECT MAX(order_idx) AS m FROM flashcard_cards WHERE deck_id = ?`)
|
||||
.get(deck.id)?.m ?? -1;
|
||||
const r = db.prepare(`INSERT INTO flashcard_cards (deck_id, front, back, order_idx) VALUES (?,?,?,?)`)
|
||||
.run(deck.id, front, back, maxIdx + 1);
|
||||
res.json({ id: r.lastInsertRowid, deck_id: deck.id, deck_title: deck.title, deck_color: deck.color, front, back });
|
||||
}
|
||||
|
||||
/* ── GET /api/flashcards/random — случайная карточка из всего пула ───────
|
||||
Для дашборд-виджета «повтори карточку». */
|
||||
function getRandom(req, res) {
|
||||
const uid = req.user.id;
|
||||
const total = db.prepare(`
|
||||
SELECT COUNT(*) AS n FROM flashcard_cards c
|
||||
JOIN flashcard_decks d ON d.id = c.deck_id WHERE d.user_id = ?
|
||||
`).get(uid).n;
|
||||
if (!total) return res.json({ card: null, total: 0 });
|
||||
|
||||
const card = db.prepare(`
|
||||
SELECT c.id, c.front, c.back, c.deck_id,
|
||||
d.title AS deck_title, d.color AS deck_color
|
||||
FROM flashcard_cards c
|
||||
JOIN flashcard_decks d ON d.id = c.deck_id
|
||||
WHERE d.user_id = ?
|
||||
ORDER BY RANDOM() LIMIT 1
|
||||
`).get(uid);
|
||||
res.json({ card, total });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listDecks, createDeck, updateDeck, deleteDeck,
|
||||
getCards, addCard, addCardsBulk, updateCard, deleteCard,
|
||||
getStudySession, submitReview, getStats,
|
||||
quickAdd, getRandom,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user