feat(admin): Einstellungsseite überarbeitet, auch für das Handy

Verbindung: Probleme vor der Anleitung. Tools: Suche, Filter, Alle ein-/ausklappen, Zurücksetzen je Gruppe.
Divi-Reiter in Klartext mit Statussymbolen. Rollenhinweise, Hilfe-Tab, Reiter als Raster und Karten auf dem Handy,
Kontrast, Tippflächen, einheitliche Du-Anrede. Bestätigungen über data-lit-mcp-confirm statt Inline-onclick.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-05 16:36:16 +01:00
co-authored by Claude Sonnet 5.5
parent 165e61d7fe
commit 96fa58188b
16 changed files with 865 additions and 84 deletions
+206 -2
View File
@@ -1,5 +1,5 @@
/* Einstellungen → LIT MCP: copy the server URL, live group feedback and collapsible tool
groups, filter paging, and a question before a role save that locks people out. */
/* Einstellungen → LIT MCP: copy the server URL, live group feedback, search and reset for the
collapsible tool groups, filter paging, and questions before a destructive or locking-out save. */
( function () {
'use strict';
@@ -119,6 +119,206 @@
}
}
function setGroupOpen( group, open ) {
var toggle = group.querySelector( '.handlediv' );
group.classList.toggle( 'closed', ! open );
if ( toggle ) {
toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
}
}
function saveGroupState() {
if ( window.postboxes && window.pagenow ) {
window.postboxes.save_state( window.pagenow );
}
}
function markAllUserSet( groups ) {
groups.forEach( function ( group ) {
group.dataset.litUserSet = '1';
} );
}
// Open or close every group; with `save`, remember it like a click on a single toggle would.
function setAllGroups( groups, open, save ) {
groups.forEach( function ( group ) {
setGroupOpen( group, open );
} );
if ( save ) {
saveGroupState();
}
}
// Tools: search by name, "only changed", open/close all. Rows and empty groups hide instead of vanish.
function setUpToolFilter() {
var groups = document.querySelector( '.lit-mcp-groups' );
var search = document.querySelector( '.lit-mcp-tool-search' );
var only = document.querySelector( '.lit-mcp-only-changed' );
var status = document.querySelector( '.lit-mcp-filter-status' );
var collapse = document.querySelector( '.lit-mcp-collapse-all' );
var expand = document.querySelector( '.lit-mcp-expand-all' );
var closedBefore = null;
var all;
if ( ! groups || ! search || ! only || ! collapse || ! expand ) {
return;
}
all = groups.querySelectorAll( '.lit-mcp-group' );
function countText( shown ) {
var text = window.litMcpAdmin;
if ( ! text ) {
return '';
}
return shown === 1 ? text.toolShown : text.toolsShown.replace( '%d', shown );
}
// The filter is cleared: forget the temporary state and, if the user changed groups meanwhile, save it now.
function finishFilter() {
var changed = false;
closedBefore = null;
all.forEach( function ( group ) {
changed = changed || !! group.dataset.litUserSet;
delete group.dataset.litUserSet;
} );
if ( changed ) {
saveGroupState();
}
}
function isFiltering() {
return search.value.trim() !== '' || only.checked;
}
function apply() {
var term = search.value.trim().toLowerCase();
var filtering = isFiltering();
var shown = 0;
/*
* A filter opens the groups with hits; clearing it puts every group back as the user had it.
* A group the user opens or closes themselves while filtering keeps that choice.
*/
if ( filtering && closedBefore === null ) {
closedBefore = Array.prototype.map.call( all, function ( group ) {
return group.classList.contains( 'closed' );
} );
}
all.forEach( function ( group, index ) {
var visible = 0;
group.querySelectorAll( 'tbody tr' ).forEach( function ( row ) {
var select = row.querySelector( '.lit-mcp-tool-mode' );
var cell = row.querySelector( '.column-tool' );
var name = cell ? cell.textContent.toLowerCase() : '';
var match = ( term === '' || name.indexOf( term ) !== -1 ) &&
( ! only.checked || ( select && select.value !== '' ) );
row.hidden = ! match;
visible += match ? 1 : 0;
} );
group.hidden = visible === 0;
if ( filtering && visible > 0 && ! group.dataset.litUserSet ) {
setGroupOpen( group, true );
} else if ( ! filtering && closedBefore && ! group.dataset.litUserSet ) {
setGroupOpen( group, ! closedBefore[ index ] );
}
shown += visible;
} );
if ( ! filtering && closedBefore ) {
finishFilter();
}
if ( status ) {
status.textContent = filtering ? countText( shown ) : '';
}
}
// Enter in the search field would submit the whole settings form, half-done changes included.
search.addEventListener( 'keydown', function ( event ) {
if ( event.key === 'Enter' ) {
event.preventDefault();
}
} );
search.addEventListener( 'input', apply );
only.addEventListener( 'change', apply );
// While filtering, nothing is saved: the hidden groups would be stored as hidden boxes.
// "User set" only means something while a filter is on; outside of it a stale flag would stop the next filter.
collapse.addEventListener( 'click', function () {
if ( isFiltering() ) {
markAllUserSet( all );
}
setAllGroups( all, false, ! isFiltering() );
} );
expand.addEventListener( 'click', function () {
if ( isFiltering() ) {
markAllUserSet( all );
}
setAllGroups( all, true, ! isFiltering() );
} );
groups.addEventListener( 'click', function ( event ) {
var group = event.target.closest( '.handlediv, .hndle' ) && event.target.closest( '.lit-mcp-group' );
if ( group && isFiltering() ) {
group.dataset.litUserSet = '1';
}
} );
// The browser may restore the search text after a reload; make the list match it.
apply();
}
// Tools: "Auf Standard zurücksetzen" clears a group's mode, its "nur lesen" switch and every tool's own setting.
function setUpGroupReset() {
document.querySelectorAll( '.lit-mcp-group' ).forEach( function ( group ) {
var button = group.querySelector( '.lit-mcp-group-reset' );
if ( ! button ) {
return;
}
button.addEventListener( 'click', function () {
var readonly = group.querySelector( '.lit-mcp-group-readonly' );
var mode = group.querySelector( '.lit-mcp-group-mode' );
// Order matters: unlock the rows first, then fall back to the default.
change( readonly, function () {
readonly.checked = false;
} );
change( mode, function () {
mode.value = '';
} );
group.querySelectorAll( '.lit-mcp-tool-mode' ).forEach( function ( select ) {
change( select, function () {
select.value = '';
} );
} );
if ( window.wp && window.wp.a11y && window.litMcpAdmin ) {
window.wp.a11y.speak( window.litMcpAdmin.groupReset );
}
} );
} );
}
function change( field, apply ) {
if ( ! field ) {
return;
}
apply();
field.dispatchEvent( new Event( 'change', { bubbles: true } ) );
}
// A button with data-lit-mcp-confirm asks first (revoke a connection, clear the log).
function setUpConfirmButtons() {
document.addEventListener( 'click', function ( event ) {
var button = event.target.closest( '[data-lit-mcp-confirm]' );
if ( button && ! window.confirm( button.dataset.litMcpConfirm ) ) {
event.preventDefault();
}
} );
}
// Protokoll: a changed filter starts again on page 1.
function setUpFilterPaging() {
document.querySelectorAll( '.lit-mcp-log-filters select, .lit-mcp-log-filters input' ).forEach(
@@ -162,9 +362,13 @@
}
document.addEventListener( 'DOMContentLoaded', function () {
// First: the confirmations guard destructive buttons and must not depend on the rest starting up.
setUpConfirmButtons();
setUpCopyButton();
setUpGroupToggles();
setUpLiveGroupFeedback();
setUpToolFilter();
setUpGroupReset();
setUpFilterPaging();
setUpAccessConfirmation();
} );