feat(materials): Фаза 1 — правка, переименование, создание заметки
- PATCH /api/materials/:id (title, body) с проверкой владельца (@public-by-design) + LS.updateMaterial. - /my-materials: кнопка «+ Заметка» (личный блокнот с нуля), «Изменить» на карточках (заголовок; для заметок — и текст) через LS.modal. - Добавлен план развития «Мои материалы»: plans/my-materials/PLAN.md (6 фаз). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
<div class="mm-main">
|
||||
<div class="mm-head">
|
||||
<span class="mm-title">Мои материалы</span>
|
||||
<button class="mm-btn" style="margin-left:auto" onclick="createNote()"><i data-lucide="plus"></i> Заметка</button>
|
||||
</div>
|
||||
<div class="mm-sub">Сохранённые с уроков: страницы доски, заметки и вложения. Хранятся у вас и не пропадают, даже если урок удалят.</div>
|
||||
<div class="mm-grid" id="mm-grid"><div class="mm-empty">Загрузка…</div></div>
|
||||
@@ -61,11 +62,13 @@
|
||||
}
|
||||
|
||||
const KIND_LABEL = { board: 'Доска', note: 'Заметка', link: 'Ссылка', image: 'Изображение' };
|
||||
let _mats = [];
|
||||
|
||||
function card(m) {
|
||||
const kind = KIND_LABEL[m.kind] || m.kind;
|
||||
const meta = `${esc(m.source_title || '')}${m.source_title ? ' · ' : ''}${fmtDate(m.created_at)}`;
|
||||
const del = `<button class="mm-btn danger" onclick="delMaterial(${m.id})"><i data-lucide="trash-2"></i> Удалить</button>`;
|
||||
const del = `<button class="mm-btn danger" onclick="delMaterial(${m.id})"><i data-lucide="trash-2"></i> Удалить</button>`;
|
||||
const edit = `<button class="mm-btn" onclick="editMaterial(${m.id})" title="Изменить"><i data-lucide="pencil"></i></button>`;
|
||||
if (m.kind === 'board' || m.kind === 'image') {
|
||||
return `<div class="mm-card">
|
||||
<span class="mm-kind">${kind}</span>
|
||||
@@ -76,7 +79,7 @@
|
||||
<div class="mm-card-actions">
|
||||
<a class="mm-btn" href="${esc(m.url)}" target="_blank" rel="noopener"><i data-lucide="external-link"></i> Открыть</a>
|
||||
<a class="mm-btn" href="${esc(m.url)}" download><i data-lucide="download"></i></a>
|
||||
${del}
|
||||
${edit}${del}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -88,7 +91,7 @@
|
||||
<div class="mm-card-body">
|
||||
<div class="mm-card-title">${esc(m.title || kind)}</div>
|
||||
<div class="mm-card-meta">${meta}</div>
|
||||
<div class="mm-card-actions">${del}</div>
|
||||
<div class="mm-card-actions">${edit}${del}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -108,6 +111,7 @@
|
||||
const grid = document.getElementById('mm-grid');
|
||||
try {
|
||||
const { materials } = await LS.listMaterials();
|
||||
_mats = materials || [];
|
||||
if (!materials || !materials.length) {
|
||||
grid.innerHTML = `<div class="mm-empty" style="grid-column:1/-1">
|
||||
<i data-lucide="folder-open"></i>
|
||||
@@ -130,6 +134,45 @@
|
||||
}
|
||||
window.delMaterial = delMaterial;
|
||||
|
||||
const FLD = 'padding:9px 12px;border:1px solid var(--border);border-radius:9px;font:inherit;width:100%;box-sizing:border-box';
|
||||
function createNote() {
|
||||
const content = `<div style="display:flex;flex-direction:column;gap:10px">
|
||||
<input id="mm-nt-title" placeholder="Заголовок (необязательно)" style="${FLD}" />
|
||||
<textarea id="mm-nt-body" rows="7" placeholder="Текст заметки…" style="${FLD};resize:vertical"></textarea>
|
||||
</div>`;
|
||||
const m = LS.modal({ title: 'Новая заметка', content, size: 'sm', actions: [
|
||||
{ label: 'Отмена', onClick: () => m.close() },
|
||||
{ label: 'Создать', primary: true, onClick: async () => {
|
||||
const title = m.body.querySelector('#mm-nt-title').value.trim();
|
||||
const text = m.body.querySelector('#mm-nt-body').value.trim();
|
||||
if (!text && !title) { LS.toast('Введите текст заметки', 'warn'); return; }
|
||||
try { await LS.saveMaterial({ kind: 'note', title, body: text }); m.close(); load(); }
|
||||
catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
|
||||
} },
|
||||
] });
|
||||
}
|
||||
window.createNote = createNote;
|
||||
|
||||
function editMaterial(id) {
|
||||
const mt = _mats.find(x => x.id === id);
|
||||
if (!mt) return;
|
||||
const isNote = mt.kind === 'note';
|
||||
const content = `<div style="display:flex;flex-direction:column;gap:10px">
|
||||
<input id="mm-ed-title" value="${esc(mt.title || '')}" placeholder="Заголовок" style="${FLD}" />
|
||||
${isNote ? `<textarea id="mm-ed-body" rows="7" style="${FLD};resize:vertical">${esc(mt.body || '')}</textarea>` : ''}
|
||||
</div>`;
|
||||
const m = LS.modal({ title: 'Изменить', content, size: 'sm', actions: [
|
||||
{ label: 'Отмена', onClick: () => m.close() },
|
||||
{ label: 'Сохранить', primary: true, onClick: async () => {
|
||||
const data = { title: m.body.querySelector('#mm-ed-title').value.trim() };
|
||||
if (isNote) data.body = m.body.querySelector('#mm-ed-body').value;
|
||||
try { await LS.updateMaterial(id, data); m.close(); load(); }
|
||||
catch (e) { LS.toast(e.message || 'Ошибка', 'error'); }
|
||||
} },
|
||||
] });
|
||||
}
|
||||
window.editMaterial = editMaterial;
|
||||
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user