786419ce01
Миниатюры: 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>
58 lines
2.5 KiB
JavaScript
58 lines
2.5 KiB
JavaScript
'use strict';
|
|
/* material-save.js — универсальная кнопка «В мои материалы».
|
|
* Тонкий слой поверх LS.saveMaterial / LS.uploadFile: сохраняет заметку,
|
|
* ссылку или изображение в личную коллекцию ученика из любой части платформы
|
|
* (учебник, экзамен, лаборатория, чат и т.п.).
|
|
*
|
|
* MaterialSave.note({ title, body, sourceTitle }, btn?)
|
|
* MaterialSave.link({ title, url, sourceTitle }, btn?)
|
|
* MaterialSave.image({ title, url|blob, name?, sourceTitle }, btn?)
|
|
*
|
|
* Требует window.LS (api.js). Показывает тост; кнопку (если передана) блокирует на время.
|
|
*/
|
|
(function () {
|
|
function ok() { if (window.LS && LS.toast) LS.toast('Сохранено в «Мои материалы»', 'success'); }
|
|
function err(e) { if (window.LS && LS.toast) LS.toast((e && e.message) || 'Ошибка сохранения', 'error'); }
|
|
|
|
async function note(o, btn) {
|
|
o = o || {};
|
|
if (!String(o.body || '').trim() && !String(o.title || '').trim()) { err({ message: 'Пустая заметка' }); return; }
|
|
if (btn) btn.disabled = true;
|
|
try {
|
|
await LS.saveMaterial({ kind: 'note', title: o.title || '', body: o.body || '', sourceTitle: o.sourceTitle || null });
|
|
ok();
|
|
} catch (e) { err(e); } finally { if (btn) btn.disabled = false; }
|
|
}
|
|
|
|
async function link(o, btn) {
|
|
o = o || {};
|
|
if (!o.url) { err({ message: 'Нет ссылки' }); return; }
|
|
if (btn) btn.disabled = true;
|
|
try {
|
|
await LS.saveMaterial({ kind: 'link', title: o.title || '', url: o.url, sourceTitle: o.sourceTitle || null });
|
|
ok();
|
|
} catch (e) { err(e); } finally { if (btn) btn.disabled = false; }
|
|
}
|
|
|
|
async function image(o, btn) {
|
|
o = o || {};
|
|
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, thumbUrl: thumbUrl, sourceTitle: o.sourceTitle || null });
|
|
ok();
|
|
} catch (e) { err(e); } finally { if (btn) btn.disabled = false; }
|
|
}
|
|
|
|
window.MaterialSave = { note: note, link: link, image: image };
|
|
})();
|