feat(materials): просмотр материала в модалке-лайтбоксе

Клик по картинке/доске больше не открывает новую вкладку, а показывает
материал в окне на странице (LS.modal size lg): изображение, кнопки
«Скачать» и «В новой вкладке». Кнопка действия «Открыть» заменена на
«Просмотр» (иконка eye). Ссылки по-прежнему открываются внешне.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-04 14:37:50 +03:00
parent 53e996e2e0
commit bdc8075c3d
+34 -2
View File
@@ -51,6 +51,9 @@
.mm-card-link-url { font-size: 0.7rem; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .mm-card-link-url { font-size: 0.7rem; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mm-btn.primary { background: var(--violet); border-color: var(--violet); color: #fff; } .mm-btn.primary { background: var(--violet); border-color: var(--violet); color: #fff; }
.mm-btn.primary:hover { background: #7c3aed; border-color: #7c3aed; color: #fff; } .mm-btn.primary:hover { background: #7c3aed; border-color: #7c3aed; color: #fff; }
.mm-viewer { display: flex; align-items: center; justify-content: center; background: #f1f5f9; border-radius: 10px; padding: 8px; }
.mm-viewer img { max-width: 100%; max-height: 68vh; object-fit: contain; border-radius: 6px; }
.mm-viewer-note { white-space: pre-wrap; word-break: break-word; line-height: 1.6; font-size: 0.9rem; color: var(--text-2); }
.mm-empty { padding: 60px 20px; text-align: center; color: var(--text-3); } .mm-empty { padding: 60px 20px; text-align: center; color: var(--text-3); }
.mm-empty svg { width: 38px; height: 38px; opacity: 0.4; margin-bottom: 12px; } .mm-empty svg { width: 38px; height: 38px; opacity: 0.4; margin-bottom: 12px; }
@media (max-width: 640px) { .mm-grid { grid-template-columns: 1fr 1fr; } .mm-main { padding: 18px 14px; } } @media (max-width: 640px) { .mm-grid { grid-template-columns: 1fr 1fr; } .mm-main { padding: 18px 14px; } }
@@ -143,14 +146,14 @@
if (m.kind === 'board' || m.kind === 'image') { if (m.kind === 'board' || m.kind === 'image') {
return `<div class="mm-card"> return `<div class="mm-card">
<a class="mm-card-media" href="${esc(m.url)}" target="_blank" rel="noopener"><img src="${esc(m.url)}" alt="" loading="lazy"/></a> <a class="mm-card-media" href="${esc(m.url)}" onclick="openViewer(${m.id});return false;"><img src="${esc(m.url)}" alt="" loading="lazy"/></a>
<div class="mm-card-body"> <div class="mm-card-body">
${chip} ${chip}
<div class="mm-card-title">${esc(m.title || kind)}</div> <div class="mm-card-title">${esc(m.title || kind)}</div>
<div class="mm-card-meta">${meta}</div> <div class="mm-card-meta">${meta}</div>
<div class="mm-card-actions"> <div class="mm-card-actions">
${mv} ${mv}
<a class="mm-btn" href="${esc(m.url)}" target="_blank" rel="noopener" title="Открыть"><i data-lucide="external-link"></i></a> <button class="mm-btn" onclick="openViewer(${m.id})" title="Просмотр"><i data-lucide="eye"></i></button>
<a class="mm-btn" href="${esc(m.url)}" download title="Скачать"><i data-lucide="download"></i></a> <a class="mm-btn" href="${esc(m.url)}" download title="Скачать"><i data-lucide="download"></i></a>
${ann}${sh}${edit}${del} ${ann}${sh}${edit}${del}
</div> </div>
@@ -310,6 +313,35 @@
} }
window.editMaterial = editMaterial; window.editMaterial = editMaterial;
/* ── Просмотр материала в модалке (лайтбокс) ── */
function openViewer(id) {
const mt = _mats.find(x => x.id === id);
if (!mt) return false;
const kind = KIND_LABEL[mt.kind] || mt.kind;
let body;
if (mt.kind === 'image' || mt.kind === 'board') {
body = `<div class="mm-viewer"><img src="${esc(mt.url)}" alt="${esc(mt.title || '')}" /></div>`;
} else if (mt.kind === 'note') {
body = `<div class="mm-viewer-note">${esc(mt.body || '')}</div>`;
} else {
body = `<div class="mm-viewer-note"><a href="${esc(mt.url)}" target="_blank" rel="noopener" style="color:var(--violet)">${esc(mt.url)}</a></div>`;
}
const actions = [];
if (mt.url && (mt.kind === 'image' || mt.kind === 'board')) {
actions.push({ label: 'Скачать', onClick: () => {
const ext = (String(mt.url).match(/\.(png|jpe?g|gif|webp)(?:$|\?)/i) || [])[1] || 'png';
const name = (mt.title || 'material').slice(0, 60).replace(/[\\/:*?"<>|]/g, '_') + '.' + ext;
const a = document.createElement('a'); a.href = mt.url; a.download = name;
document.body.appendChild(a); a.click(); a.remove();
} });
}
if (mt.url) actions.push({ label: 'В новой вкладке', onClick: () => window.open(mt.url, '_blank', 'noopener') });
actions.push({ label: 'Закрыть', primary: true, onClick: () => m.close() });
const m = LS.modal({ title: mt.title || kind, content: body, size: 'lg', actions });
return false;
}
window.openViewer = openViewer;
/* ── Collection CRUD ── */ /* ── Collection CRUD ── */
function createCollection() { function createCollection() {
const content = `<input id="mm-col-name" placeholder="Название папки" style="${FLD}" />`; const content = `<input id="mm-col-name" placeholder="Название папки" style="${FLD}" />`;