feat(flashcards): фаза 1 полировки — хоткеи, поиск, drag-reorder, честные интервалы

- study: хоткеи Space/стрелки=флип, 1-4/←→=оценка
- превью интервалов = точная копия серверного SM-2 (было враньё «<1 мин»)
- поиск/фильтр карточек внутри колоды
- drag-reorder карточек + endpoint PUT /decks/:id/reorder (requireOwnership)
- flashcard_decks добавлен в ALLOWED_TABLES requireOwnership
- эмодзи в empty-state → inline SVG .ic
- deleteCard: нативный confirm() → LS.confirm

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-31 09:53:03 +03:00
parent 1dcc4cbf6e
commit 29301ff87d
4 changed files with 209 additions and 28 deletions
+25 -1
View File
@@ -126,6 +126,30 @@ function addCardsBulk(req, res) {
res.json({ inserted });
}
/* ── PUT /api/flashcards/decks/:id/reorder ─────────────────────────────────
body: { order: [cardId, …] } — переписывает order_idx по позиции в массиве.
Принимаются только карточки, реально принадлежащие колоде владельца. */
function reorderCards(req, res) {
const uid = req.user.id;
const deck = db.prepare(`SELECT id FROM flashcard_decks WHERE id = ? AND user_id = ?`)
.get(req.params.id, uid);
if (!deck) return res.status(404).json({ error: 'Not found' });
const { order } = req.body;
if (!Array.isArray(order) || !order.length)
return res.status(400).json({ error: 'order[] required' });
const owned = new Set(
db.prepare(`SELECT id FROM flashcard_cards WHERE deck_id = ?`).all(deck.id).map(r => r.id)
);
const stmt = db.prepare(`UPDATE flashcard_cards SET order_idx = ? WHERE id = ? AND deck_id = ?`);
const run = db.transaction(() => {
let idx = 0;
order.forEach(id => { if (owned.has(Number(id))) stmt.run(idx++, Number(id), deck.id); });
});
run();
res.json({ ok: true });
}
/* ── PUT /api/flashcards/cards/:id ─────────────────────────────────────── */
function updateCard(req, res) {
const uid = req.user.id;
@@ -298,7 +322,7 @@ function getRandom(req, res) {
module.exports = {
listDecks, createDeck, updateDeck, deleteDeck,
getCards, addCard, addCardsBulk, updateCard, deleteCard,
getCards, addCard, addCardsBulk, updateCard, deleteCard, reorderCards,
getStudySession, submitReview, getStats,
quickAdd, getRandom,
};