feat(access): Фаза 2c (часть) — массовые операции в матрице доступа
Клик по названию контента в матрице открывает/закрывает его сразу ВСЕМ классам; клик по имени класса (заголовок столбца) — открывает/закрывает ВЕСЬ контент этому классу. Массовое закрытие спрашивает подтверждение; перерисовывается только tbody. Использует существующий accSetRule (без новых эндпоинтов). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -396,7 +396,10 @@
|
||||
/* ════════ режим «Матрица» (класс × контент одним экраном) ════════ */
|
||||
function matrixHeadCells(classes) {
|
||||
return classes.map(c =>
|
||||
`<th style="padding:6px 8px;font-size:11.5px;font-weight:600;color:var(--text-3);white-space:nowrap;border-bottom:1px solid var(--border)">${esc(c.name)}</th>`).join('');
|
||||
`<th style="padding:6px 8px;border-bottom:1px solid var(--border)">
|
||||
<button onclick="accMxColBulk(${c.id})" title="Открыть/закрыть весь контент классу «${esc(c.name)}»"
|
||||
style="border:none;background:transparent;cursor:pointer;font-family:inherit;font-size:11.5px;font-weight:600;color:var(--text-3);white-space:nowrap;padding:0">${esc(c.name)}</button>
|
||||
</th>`).join('');
|
||||
}
|
||||
function matrixBody() {
|
||||
const classes = _matrix.classes || [];
|
||||
@@ -410,7 +413,10 @@
|
||||
return `<td style="text-align:center;border-bottom:1px solid var(--border-soft,#f0f0f0)">
|
||||
<input type="checkbox" ${open ? 'checked' : ''} onchange="accMx('${type}','${esc(ref)}',${c.id},this.checked)" title="${esc(c.name)} · ${esc(it.title)}"></td>`;
|
||||
}).join('');
|
||||
return `<tr><th scope="row" style="text-align:left;padding:6px 10px;font-size:13px;font-weight:500;color:var(--text-1);white-space:nowrap;position:sticky;left:0;background:var(--card,#fff);border-bottom:1px solid var(--border-soft,#f0f0f0)">${esc(it.title)}</th>${cells}</tr>`;
|
||||
return `<tr><th scope="row" style="text-align:left;padding:6px 10px;white-space:nowrap;position:sticky;left:0;background:var(--card,#fff);border-bottom:1px solid var(--border-soft,#f0f0f0)">
|
||||
<button onclick="accMxRowBulk('${type}','${esc(ref)}')" title="Открыть/закрыть «${esc(it.title)}» всем классам"
|
||||
style="border:none;background:transparent;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:var(--text-1);text-align:left;padding:0">${esc(it.title)}</button>
|
||||
</th>${cells}</tr>`;
|
||||
}).join('');
|
||||
if (!rows) return '';
|
||||
return `<tr><th colspan="${classes.length + 1}" style="text-align:left;padding:10px 10px 4px;font-size:12px;font-weight:700;color:var(--text-3)">${TYPE_LABEL[type] || type}</th></tr>${rows}`;
|
||||
@@ -453,6 +459,40 @@
|
||||
}
|
||||
function mxSearch(v) { _mSearch = v; const b = document.getElementById('acc-mx-body'); if (b) b.innerHTML = matrixBody(); }
|
||||
|
||||
function mxRepaint() { const b = document.getElementById('acc-mx-body'); if (b) b.innerHTML = matrixBody(); }
|
||||
function mxApply(o, type, ref, open) {
|
||||
const arr = o[type] || (o[type] = []);
|
||||
const i = arr.indexOf(ref);
|
||||
if (open && i < 0) arr.push(ref);
|
||||
if (!open && i >= 0) arr.splice(i, 1);
|
||||
}
|
||||
/* строка матрицы: открыть/закрыть один контент всем классам */
|
||||
async function mxRowBulk(type, ref) {
|
||||
const classes = _matrix.classes || [];
|
||||
const allOpen = classes.length && classes.every(c => ((_matrix.open[c.id] || {})[type] || []).includes(ref));
|
||||
const open = !allOpen;
|
||||
if (!open && !confirm(`Закрыть «${contentTitle(type, ref)}» у всех классов?`)) return;
|
||||
try {
|
||||
await Promise.all(classes.map(c => LS.accessSetRule(type, ref, 'class', c.id, open ? 1 : null)));
|
||||
classes.forEach(c => mxApply(_matrix.open[c.id] || (_matrix.open[c.id] = {}), type, ref, open));
|
||||
mxRepaint();
|
||||
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); _matrix = null; renderMatrix(); }
|
||||
}
|
||||
/* столбец матрицы: открыть/закрыть весь контент одному классу */
|
||||
async function mxColBulk(classId) {
|
||||
const items = CONTENT_TYPES.flatMap(t => itemsOf(t).map(it => [t, it[keyName(t)]]));
|
||||
const o = _matrix.open[classId] || (_matrix.open[classId] = {});
|
||||
const allOpen = items.length && items.every(([t, ref]) => (o[t] || []).includes(ref));
|
||||
const open = !allOpen;
|
||||
const cls = (_matrix.classes.find(c => c.id === classId) || {}).name || ('#' + classId);
|
||||
if (!open && !confirm(`Закрыть весь контент у класса «${cls}»?`)) return;
|
||||
try {
|
||||
await Promise.all(items.map(([t, ref]) => LS.accessSetRule(t, ref, 'class', classId, open ? 1 : null)));
|
||||
items.forEach(([t, ref]) => mxApply(o, t, ref, open));
|
||||
mxRepaint();
|
||||
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); _matrix = null; renderMatrix(); }
|
||||
}
|
||||
|
||||
/* ── режим ── */
|
||||
function setMode(m) {
|
||||
if (m === _mode) return;
|
||||
@@ -472,6 +512,8 @@
|
||||
window.accClassBulk = classBulk;
|
||||
window.accMx = mxToggle;
|
||||
window.accMxSearch = mxSearch;
|
||||
window.accMxRowBulk = mxRowBulk;
|
||||
window.accMxColBulk = mxColBulk;
|
||||
window.accLeftSearch = leftSearch;
|
||||
|
||||
window.AdminSections = window.AdminSections || {};
|
||||
|
||||
Reference in New Issue
Block a user