feat(materials): серверные миниатюры (sharp) + пагинация рендера списка

Миниатюры: uploadPersonalFile генерирует webp ≤480px (sharp), возвращает {url, thumbUrl}; колонка thumb_url (мигр.074); грид рисует <img> на миниатюре, просмотр/скачивание/аннотация — полный url. Ссылочная чистка матчит url И thumb_url; share копирует thumb; квота учитывает файл+миниатюру. Сейверы board-clip/material-save/textbook-clip/draw пробрасывают thumbUrl.

Пагинация: клиент рендерит PAGE_SIZE=60 карточек + «Показать ещё» (сброс на смену фильтра), сохраняя клиентский поиск/сортировку над полным списком.

Тесты: materials.test.js 16→19. План V2 выполнен.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-13 14:40:23 +03:00
parent abe84b9f90
commit 786419ce01
9 changed files with 179 additions and 70 deletions
+13 -7
View File
@@ -27,12 +27,17 @@
4. **`backend/tests/materials.test.js`** — CRUD, владелец (403/404), коллекции, share-копия + роль/owner,
валидация URL, лимит числа, ссылочная чистка (прямой вызов хелпера на временном файле).
## Фаза 2 — Производительность ✅ (частично)
## Фаза 2 — Производительность ✅
-`GET /api/materials` отдаёт **обрезанный** `body` (первые 1000 симв.) + флаг `body_trunc`; полный текст —
ленивый `GET /api/materials/:id` (`getOne`, owner-only). Клиент `ensureFullBody()` подгружает перед
просмотром/правкой/флешкартой (иначе правка сохранила бы усечённый текст).
- Пагинация/keyset — отложено (клиент пока фильтрует в памяти; включить при росте объёмов).
- ⬜ Серверные миниатюры `board/image` — отложено (нужна обработка картинок; пока `loading=lazy`).
- Пагинация рендера: клиент держит весь список (поиск/фильтр/сортировка в памяти), но в DOM рисует
`PAGE_SIZE=60` карточек + «Показать ещё»; `_shown` сбрасывается на смену фильтра. Снимает стоимость
рендера тысяч узлов, не ломая клиентский поиск (keyset на сервере не нужен на текущих объёмах).
- ✅ Серверные миниатюры `board/image`: `uploadPersonalFile` (sharp → webp ≤480px) возвращает `{url, thumbUrl}`;
колонка `thumb_url` (мигр. **074**); грид рисует `<img src=thumb_url||url>`, просмотр/скачивание/аннотация —
полный `url`. Чистится по ссылкам (releaseFileForUrl теперь матчит url **и** thumb_url); share копирует thumb;
квота считает файл+миниатюру. Клиентские сейверы (board-clip/material-save/textbook-clip/draw) пробрасывают `thumbUrl`.
## Фаза 3 — Доводка заложенных фич ✅
- ✅ UI тегов: ввод в модалках создания/правки + чипы на карточке (клик → фильтр) + пилюля активного фильтра.
@@ -45,10 +50,11 @@
- ✅ Множественный выбор (чекбокс на карточке) + панель массовых действий (переместить/удалить, reuse per-item API).
- ✅ Живое превью KaTeX в редакторе заметки (oninput → `mmPreview``mathHtml`).
### Статус
Сделано и проверено: **Ф1 целиком** (16 backend-тестов), **Ф2/Ф3/Ф4 ✅** (headless-смоук `my-materials.html`:
синтаксис + рендер карточек с deep-link/тегами/чекбоксом + фильтр по тегу + bulk-bar + тинт папки).
Осталось ⬜ (инфра, отложено): пагинация/keyset списка, серверные миниатюры board/image.
### Статус — ПЛАН V2 ВЫПОЛНЕН
**Ф1–Ф4 ✅.** Backend: 19 тестов `materials.test.js` (CRUD/владелец/коллекции/share/URL-allowlist/квота/
ссылочная чистка url+thumb/round-trip thumb_url). Frontend: headless-смоук `my-materials.html` (синтаксис +
deep-link/теги/чекбокс/bulk/тинт папки + `<img>` на thumb_url + пагинация «Показать ещё»). sharp-пайплайн и
client-сейверы (board-clip/material-save/textbook-clip) проверены. Открытого из плана не осталось.
---