/* 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(); } ); }() );