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
+3 -1
View File
@@ -39,14 +39,16 @@
if (btn) btn.disabled = true;
try {
let url = o.url;
let thumbUrl = o.thumbUrl || null;
if (o.blob) {
const fd = new FormData();
fd.append('file', o.blob, o.name || 'image.png');
const up = await LS.uploadMaterialFile(fd);
url = up.url;
thumbUrl = up.thumbUrl || null;
}
if (!url) throw new Error('Нет изображения');
await LS.saveMaterial({ kind: 'image', title: o.title || '', url: url, sourceTitle: o.sourceTitle || null });
await LS.saveMaterial({ kind: 'image', title: o.title || '', url: url, thumbUrl: thumbUrl, sourceTitle: o.sourceTitle || null });
ok();
} catch (e) { err(e); } finally { if (btn) btn.disabled = false; }
}