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:
co-authored by
Claude Sonnet 5.5
parent
165e61d7fe
commit
96fa58188b
+195
-11
@@ -9,6 +9,11 @@
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.lit-mcp-logo-link {
|
||||
display: inline-block;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.lit-mcp-logo {
|
||||
display: block;
|
||||
height: 30px;
|
||||
@@ -25,6 +30,33 @@
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Phones: the tabs form an even grid instead of ragged wrapped lines, each at least 44 px high
|
||||
* (core's tab is about 34 px). The active tab keeps core's look, marked by its colour and underline.
|
||||
*/
|
||||
@media screen and (max-width: 782px) {
|
||||
.lit-mcp .nav-tab-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
|
||||
gap: 4px;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.lit-mcp .nav-tab-wrapper .nav-tab {
|
||||
float: none;
|
||||
margin: 0;
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
border: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.lit-mcp .nav-tab-wrapper .nav-tab-active {
|
||||
border-color: #2271b1;
|
||||
box-shadow: inset 0 -2px 0 #2271b1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Muted secondary text (tool names, client URLs). */
|
||||
.lit-mcp-muted,
|
||||
.lit-mcp-tool-name {
|
||||
@@ -46,6 +78,19 @@
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
/* Label and field share a line; the field takes the rest and wraps below on narrow screens. */
|
||||
.lit-mcp-url-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 8px;
|
||||
}
|
||||
|
||||
.lit-mcp-url-line .lit-mcp-copy-row {
|
||||
flex: 1 1 22rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.lit-mcp-copy-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -83,21 +128,59 @@
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
.lit-mcp-status-yes-alt .dashicons,
|
||||
.lit-mcp-result-yes-alt .dashicons {
|
||||
color: #00a32a;
|
||||
.lit-mcp-state .dashicons {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.lit-mcp-status-warning .dashicons,
|
||||
.lit-mcp-result-warning .dashicons {
|
||||
color: #dba617;
|
||||
/* Divi tab: the report is a plain list, the icon replaces the bullet. */
|
||||
.lit-mcp-divi-report {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.lit-mcp-status-dismiss .dashicons,
|
||||
.lit-mcp-result-dismiss .dashicons {
|
||||
.lit-mcp-divi-report ul {
|
||||
list-style: disc;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.lit-mcp-status-yes-alt .dashicons {
|
||||
color: #008a20;
|
||||
}
|
||||
|
||||
.lit-mcp-status-warning .dashicons {
|
||||
color: #996800;
|
||||
}
|
||||
|
||||
.lit-mcp-status-dismiss .dashicons {
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
/* Folded help ("Wo finde ich das?") and the Divi detail table. */
|
||||
.lit-mcp-hint,
|
||||
.lit-mcp-divi-details {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.lit-mcp-hint summary,
|
||||
.lit-mcp-divi-details summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lit-mcp-hint p {
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.lit-mcp-divi-details summary {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 1em 0 0.5em;
|
||||
}
|
||||
|
||||
/* Only on phones: text that replaces a hidden column. */
|
||||
.lit-mcp-phone-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab "Zugriff". */
|
||||
.lit-mcp-connections + .description {
|
||||
margin-top: 8px;
|
||||
@@ -113,6 +196,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lit-mcp-phone-only {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.lit-mcp-connections .column-actions {
|
||||
width: auto;
|
||||
}
|
||||
@@ -122,7 +209,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab "Tools": legend, collapsible groups (WordPress postboxes) with aligned columns. */
|
||||
/* Tab "Tools": search and open/close above the groups, legend, collapsible groups (WordPress postboxes). */
|
||||
.lit-mcp-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 12px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.lit-mcp-tool-search {
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.lit-mcp-filter-status {
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
.lit-mcp-groups [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lit-mcp-group-reset {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Phones: touch targets of at least 24 px (WCAG 2.5.8) for the reset link and the folded help. */
|
||||
@media screen and (max-width: 782px) {
|
||||
.lit-mcp-group-reset {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.lit-mcp-hint summary,
|
||||
.lit-mcp-divi-details summary {
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Explains the icons that replace the "Art" words on phones. */
|
||||
.lit-mcp-kind-legend {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lit-mcp-legend {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
@@ -144,7 +272,13 @@
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
/*
|
||||
* The title keeps room of its own (12rem): when title, arrow and controls do not fit one line, the controls
|
||||
* drop to the next line instead of squeezing the title into a narrow column or sending the arrow alone below it.
|
||||
*/
|
||||
.lit-mcp-groups .postbox .hndle {
|
||||
flex: 1 1 12rem;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
@@ -254,12 +388,16 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 782px) {
|
||||
.lit-mcp-group-controls {
|
||||
order: 3;
|
||||
/* No "order": title, toggle, controls read the same for the eye and for Tab. */
|
||||
.lit-mcp-groups .lit-mcp-group-controls {
|
||||
flex-basis: 100%;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.lit-mcp-kind-legend {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Room for "Standard (indirekt)" in the select on phones. */
|
||||
.lit-mcp-tools-table .column-tool {
|
||||
width: 40%;
|
||||
@@ -297,6 +435,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Divi tab: on phones each row of the feature table becomes a card named by data-label. */
|
||||
@media screen and (max-width: 782px) {
|
||||
.lit-mcp-divi-features thead {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
}
|
||||
|
||||
.lit-mcp-divi-features tr,
|
||||
.lit-mcp-divi-features td {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* One card per row: a rule and room between them, the feature name first and bold. */
|
||||
.lit-mcp-divi-features tr {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.lit-mcp-divi-features td:first-child {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lit-mcp-divi-features code {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.lit-mcp-divi-features td::before {
|
||||
content: attr(data-label) ": ";
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lit-mcp-divi-features ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab "Protokoll": filters in their own block above the table (core hides ".tablenav .actions"
|
||||
on phones); a core-style list table whose change is the wide primary column. */
|
||||
.lit-mcp-log-filters {
|
||||
@@ -311,6 +488,13 @@
|
||||
max-width: 14rem;
|
||||
}
|
||||
|
||||
.lit-mcp-dates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 8px;
|
||||
}
|
||||
|
||||
.lit-mcp-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+206
-2
@@ -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();
|
||||
} );
|
||||
|
||||
Reference in New Issue
Block a user