Files
WP-LIT-MCP/assets/js/admin.js
T
Jan-Niclas LoosenandClaude Sonnet 5.5 96fa58188b 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>
2026-11-05 16:36:16 +01:00

376 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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';
// "URL kopieren" – like WordPress' "Copy URL to clipboard" in the media library.
function setUpCopyButton() {
var hideTimeout;
if ( ! window.ClipboardJS || ! document.querySelector( '.lit-mcp-copy' ) ) {
return;
}
new window.ClipboardJS( '.lit-mcp-copy' ).on( 'success', function ( event ) {
var success = event.trigger.parentNode.querySelector( '.success' );
event.clearSelection();
event.trigger.focus();
if ( success ) {
clearTimeout( hideTimeout );
success.classList.remove( 'hidden' );
hideTimeout = setTimeout( function () {
success.classList.add( 'hidden' );
}, 3000 );
}
if ( window.wp && window.wp.a11y && window.litMcpAdmin ) {
window.wp.a11y.speak( window.litMcpAdmin.copied );
}
} );
}
// Tool groups: WordPress' own postbox toggles, closed state saved per user.
function setUpGroupToggles() {
if ( window.postboxes && window.pagenow && document.querySelector( '.lit-mcp-groups' ) ) {
window.postboxes.add_postbox_toggles( window.pagenow );
}
}
/*
* Tools: a group's mode and its "nur lesen" switch show on the group's rows at once,
* the same way the page renders them after saving (the server stays the authority).
*/
function setUpLiveGroupFeedback() {
var groups = document.querySelector( '.lit-mcp-groups' );
if ( ! groups ) {
return;
}
groups.querySelectorAll( '.lit-mcp-group' ).forEach( function ( group ) {
var mode = group.querySelector( '.lit-mcp-group-mode' );
var readonly = group.querySelector( '.lit-mcp-group-readonly' );
var tools = group.querySelectorAll( '.lit-mcp-tool-mode' );
if ( mode ) {
mode.addEventListener( 'change', function () {
tools.forEach( function ( select ) {
labelInheritedOption( select, mode, groups.dataset.likeGroup );
} );
} );
}
if ( readonly ) {
readonly.addEventListener( 'change', function () {
tools.forEach( function ( select ) {
if ( select.dataset.writes ) {
lockTool( select, readonly.checked );
}
} );
} );
}
} );
}
// First option: "Standard (…)" without a group mode, "wie Gruppe (…)" with one.
function labelInheritedOption( select, mode, likeGroup ) {
var label;
if ( select.dataset.required ) {
return;
}
if ( ! mode.value ) {
select.options[ 0 ].text = select.dataset.defaultLabel;
return;
}
label = mode.options[ mode.selectedIndex ].text;
if ( select.dataset.directOnly && mode.value === 'indirect' ) {
label = optionText( select, 'direct' ) || label;
}
select.options[ 0 ].text = likeGroup.replace( '%s', label );
}
function optionText( select, value ) {
var option = select.querySelector( 'option[value="' + value + '"]' );
return option ? option.text : '';
}
/*
* "Nur lesen": the select of a writing tool is disabled and its note shown. A disabled
* select is not submitted, so its own setting is kept in a hidden field meanwhile
* (the server does the same when it renders a locked row).
*/
function lockTool( select, locked ) {
var cell = select.parentNode;
var note = cell.querySelector( '.lit-mcp-readonly-note' );
var keep = cell.querySelector( 'input[type="hidden"][name="' + select.name + '"]' );
select.disabled = locked;
if ( locked && select.value && ! keep ) {
keep = document.createElement( 'input' );
keep.type = 'hidden';
keep.name = select.name;
keep.value = select.value;
cell.appendChild( keep );
}
if ( ! locked && keep ) {
keep.remove();
}
if ( note ) {
note.hidden = ! locked;
}
}
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(
function ( control ) {
control.addEventListener( 'change', function () {
var paged = control.form && control.form.querySelector( 'input[name="paged"]' );
if ( paged ) {
paged.value = '1';
}
} );
}
);
}
// Zugriff: ask before saving a role set that leaves nobody, or the admin themselves, with access.
function setUpAccessConfirmation() {
var form = document.querySelector( '.lit-mcp-access-form' );
if ( ! form ) {
return;
}
form.addEventListener( 'submit', function ( event ) {
var checked = Array.prototype.map.call(
form.querySelectorAll( 'input[type="checkbox"]:checked' ),
function ( box ) {
return box.value;
}
);
var own = form.dataset.ownRoles ? form.dataset.ownRoles.split( ' ' ) : null;
var question = '';
if ( checked.length === 0 ) {
question = form.dataset.confirmNone;
} else if ( own && ! own.some( function ( role ) { return checked.indexOf( role ) !== -1; } ) ) {
question = form.dataset.confirmOwn;
}
if ( question && ! window.confirm( question ) ) {
event.preventDefault();
}
} );
}
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();
} );
}() );