mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 15:37:52 +08:00
188 lines
7.7 KiB
JavaScript
188 lines
7.7 KiB
JavaScript
/* SPDX-License-Identifier: Apache-2.0 */
|
|
(function() {
|
|
'use strict';
|
|
|
|
function translate(message) {
|
|
var russian = {
|
|
'Table': 'Таблица', 'Actions': 'Действия',
|
|
'Ping destination': 'Адрес для пинга',
|
|
'Traceroute destination': 'Адрес для трассировки',
|
|
'DNS destination': 'Адрес для DNS-запроса'
|
|
};
|
|
return /^ru(?:-|$)/i.test(document.documentElement.lang) && russian[message] ||
|
|
(typeof window._ === 'function' ? window._(message) : message);
|
|
}
|
|
|
|
function cells(row) {
|
|
return Array.from(row.children).filter(function(cell) {
|
|
return cell.matches('td, th, .td, .th');
|
|
});
|
|
}
|
|
|
|
function prepareTable(table) {
|
|
var rows = Array.from(table.querySelectorAll('tr, .tr')).filter(function(row) {
|
|
return row.closest('.table') === table;
|
|
});
|
|
var heading = rows.find(function(row) {
|
|
return row.matches('.table-titles, .cbi-section-table-titles') ||
|
|
cells(row).some(function(cell) { return cell.matches('th, .th'); });
|
|
});
|
|
var labels = heading ? cells(heading).map(function(cell) { return cell.textContent.trim(); }) : [];
|
|
var diagnostics = /(?:^|-)admin-network-diagnostics$/.test(document.body.getAttribute('data-page') || '');
|
|
|
|
if (diagnostics) {
|
|
table.classList.add('rmm-diagnostics');
|
|
var destinations = [ 'Ping destination', 'Traceroute destination', 'DNS destination' ];
|
|
rows.forEach(function(row) {
|
|
cells(row).forEach(function(cell, index) {
|
|
var input = cell.querySelector('input[type="text"]');
|
|
if (!input || index >= destinations.length || cell.querySelector('.rmm-diag-label'))
|
|
return;
|
|
var label = document.createElement('label');
|
|
label.className = 'rmm-diag-label';
|
|
label.textContent = translate(destinations[index]);
|
|
if (!input.id)
|
|
input.id = 'rmm-diag-destination-' + index;
|
|
label.htmlFor = input.id;
|
|
cell.insertBefore(label, input);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (heading) {
|
|
table.classList.add('rmm-records');
|
|
heading.classList.add('rmm-table-head');
|
|
rows.forEach(function(row) {
|
|
if (row === heading || row.matches('.cbi-section-table-descr, .placeholder'))
|
|
return;
|
|
var values = cells(row);
|
|
if (values.length !== labels.length || values.some(function(cell) {
|
|
return Number(cell.getAttribute('colspan') || 1) > 1;
|
|
}))
|
|
return;
|
|
row.classList.add('rmm-record-row');
|
|
values.forEach(function(cell, index) {
|
|
// Own label attribute leaves LuCI's data-title and widget hooks intact.
|
|
var label = cell.getAttribute('data-title') || labels[index];
|
|
if (!label && cell.matches('.cbi-section-actions'))
|
|
label = translate('Actions');
|
|
if (cell.getAttribute('data-rmm-label') !== label)
|
|
cell.setAttribute('data-rmm-label', label);
|
|
});
|
|
});
|
|
}
|
|
else if (rows.length && rows.every(function(row) { return cells(row).length === 2; })) {
|
|
table.classList.add('rmm-key-values');
|
|
}
|
|
|
|
else if (rows.length && rows.every(function(row) { return cells(row).length >= 3 && !cells(row).some(function(cell) { return Number(cell.getAttribute('colspan') || 1) > 1; }); })) {
|
|
// Native interface/Wi-Fi summaries have no column heading.
|
|
table.classList.add('rmm-status-table');
|
|
}
|
|
|
|
// Form widgets need visible overflow for their native dropdowns and tooltips.
|
|
// Only read-only tables get a local scroll wrapper; nodes/events are retained.
|
|
if (table.querySelector('input, select, textarea, .cbi-dropdown, .cbi-tooltip-container'))
|
|
return;
|
|
if (!table.parentElement.classList.contains('rmm-table-scroll')) {
|
|
var wrapper = document.createElement('div');
|
|
wrapper.className = 'rmm-table-scroll';
|
|
wrapper.setAttribute('role', 'region');
|
|
wrapper.setAttribute('aria-label', translate('Table'));
|
|
wrapper.tabIndex = 0;
|
|
table.parentElement.insertBefore(wrapper, table);
|
|
wrapper.appendChild(table);
|
|
}
|
|
}
|
|
|
|
function positionDropdowns() {
|
|
var width = document.documentElement.clientWidth || window.innerWidth;
|
|
var height = window.innerHeight;
|
|
var reserved = parseFloat(window.getComputedStyle(document.body).paddingBottom) || 0;
|
|
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(dropdown) {
|
|
var list = dropdown.querySelector(':scope > ul.dropdown');
|
|
if (!list) return;
|
|
var anchor = dropdown.getBoundingClientRect();
|
|
var panelWidth = Math.min(Math.max(anchor.width, list.scrollWidth), Math.max(0, width - 24));
|
|
var below = Math.max(0, height - reserved - anchor.bottom - 12);
|
|
var above = Math.max(0, anchor.top - 12);
|
|
var useAbove = below < Math.min(list.scrollHeight, 180) && above > below;
|
|
var available = useAbove ? above : below;
|
|
var panelHeight = Math.min(list.scrollHeight, available);
|
|
dropdown.style.setProperty('--rmm-dropdown-left', Math.max(12, Math.min(anchor.left, width - panelWidth - 12)) + 'px');
|
|
dropdown.style.setProperty('--rmm-dropdown-top', (useAbove ? anchor.top - panelHeight : anchor.bottom) + 'px');
|
|
dropdown.style.setProperty('--rmm-dropdown-width', panelWidth + 'px');
|
|
dropdown.style.setProperty('--rmm-dropdown-height', available + 'px');
|
|
list.classList.add('rmm-dropdown-panel');
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
var scheduled = false;
|
|
var previousFocus = document.activeElement;
|
|
var modalWasOpen = false;
|
|
|
|
function refresh() {
|
|
scheduled = false;
|
|
document.querySelectorAll('#view .table, #modal_overlay .table').forEach(prepareTable);
|
|
positionDropdowns();
|
|
var overlay = document.getElementById('modal_overlay');
|
|
var modal = overlay && overlay.querySelector('.modal:not(.login)');
|
|
var open = !!modal && document.body.classList.contains('modal-overlay-active');
|
|
if (open) {
|
|
modal.setAttribute('role', 'dialog');
|
|
modal.setAttribute('aria-modal', 'true');
|
|
var title = modal.querySelector('h4');
|
|
if (title) {
|
|
if (!title.id) title.id = 'rmm-dialog-title';
|
|
modal.setAttribute('aria-labelledby', title.id);
|
|
}
|
|
}
|
|
if (modalWasOpen && !open && previousFocus && previousFocus.isConnected &&
|
|
(overlay?.contains(document.activeElement) || document.activeElement === document.body))
|
|
previousFocus.focus();
|
|
modalWasOpen = open;
|
|
}
|
|
|
|
function schedule() {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
window.requestAnimationFrame(refresh);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('focusin', function(event) {
|
|
if (!document.body.classList.contains('modal-overlay-active'))
|
|
previousFocus = event.target;
|
|
});
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key !== 'Tab' || !document.body.classList.contains('modal-overlay-active'))
|
|
return;
|
|
var overlay = document.getElementById('modal_overlay');
|
|
var modal = overlay && overlay.querySelector('.modal:not(.login)');
|
|
if (!modal) return;
|
|
var controls = Array.from(modal.querySelectorAll('a[href], button, input:not([type="hidden"]), select, textarea, [tabindex]')).filter(function(control) {
|
|
return !control.disabled && control.tabIndex >= 0 && control.getClientRects().length;
|
|
});
|
|
if (!controls.length) return;
|
|
var index = controls.indexOf(document.activeElement);
|
|
if (index < 0 || (event.shiftKey ? index === 0 : index === controls.length - 1)) {
|
|
event.preventDefault();
|
|
controls[event.shiftKey ? controls.length - 1 : 0].focus();
|
|
}
|
|
});
|
|
new MutationObserver(schedule).observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
new MutationObserver(schedule).observe(document.body, { attributes: true, attributeFilter: [ 'class' ] });
|
|
new MutationObserver(schedule).observe(document.body, { attributes: true, subtree: true, attributeFilter: [ 'open' ] });
|
|
window.addEventListener('resize', schedule);
|
|
window.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
refresh();
|
|
}
|
|
|
|
if (document.readyState === 'loading')
|
|
document.addEventListener('DOMContentLoaded', init, { once: true });
|
|
else
|
|
init();
|
|
})();
|