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
+72 -12
View File
@@ -87,6 +87,7 @@ final class ToolsTab implements AdminTab
echo '<form method="post" action="options.php">';
settings_fields(self::GROUP);
submit_button(null, 'primary', 'submit-top');
$this->renderToolbar();
// The admin script mirrors a group's mode onto its rows before saving; it needs the label pattern.
printf(
'<div class="lit-mcp-groups" data-like-group="%s">',
@@ -105,10 +106,11 @@ final class ToolsTab implements AdminTab
private function renderIntro(): void
{
echo '<p class="description">'
. esc_html__('Lege fest, welche Tools der KI-Assistent nutzen kann.', 'wp-lit-mcp') . ' '
. esc_html__('Änderungen gelten ab der nächsten Verbindung.', 'wp-lit-mcp') . ' '
. esc_html__('Im KI-Assistenten die Verbindung dafür gegebenenfalls neu herstellen.', 'wp-lit-mcp')
. '</p>';
. esc_html__('Lege fest, welche Tools der KI-Assistent nutzen kann.', 'wp-lit-mcp') . '</p>';
echo '<div class="notice notice-info inline"><p><strong>'
. esc_html__('Änderungen gelten ab der nächsten Verbindung.', 'wp-lit-mcp') . '</strong> '
. esc_html__('Stelle die Verbindung im KI-Assistenten danach neu her.', 'wp-lit-mcp')
. '</p></div>';
echo '<dl class="lit-mcp-legend">';
$legend = [
@@ -123,6 +125,26 @@ final class ToolsTab implements AdminTab
echo '</dl>';
}
/** Search, "only changed" and open/close for the many tools; needs the admin script, so it hides without JS. */
private function renderToolbar(): void
{
printf(
'<div class="lit-mcp-toolbar hide-if-no-js">'
. '<label class="screen-reader-text" for="lit-mcp-tool-search">%1$s</label>'
. '<input type="search" id="lit-mcp-tool-search" class="lit-mcp-tool-search" placeholder="%1$s">'
. '<label><input type="checkbox" class="lit-mcp-only-changed"> %2$s</label>'
. '<button type="button" class="button lit-mcp-collapse-all">%3$s</button>'
. '<button type="button" class="button lit-mcp-expand-all">%4$s</button>'
. '<span class="lit-mcp-filter-status" role="status"></span></div>',
esc_attr__('Tool suchen', 'wp-lit-mcp'),
esc_html__('Nur Tools mit eigener Einstellung', 'wp-lit-mcp'),
esc_html__('Alle einklappen', 'wp-lit-mcp'),
esc_html__('Alle aufklappen', 'wp-lit-mcp')
);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in kindLegend().
echo '<p class="description lit-mcp-kind-legend">' . $this->kindLegend() . '</p>';
}
/** Only when the direct tools get many: then the count is worth a warning, otherwise it is noise. */
private function renderCounter(int $direct): void
{
@@ -196,7 +218,7 @@ final class ToolsTab implements AdminTab
)),
esc_html($this->summary($modes, $policy->isReadonly($group)))
);
$this->renderGroupControls($policy, $group, $mixed);
// Title, toggle, controls: the same order for the eye and for Tab, so the stylesheet needs no "order".
printf(
'<div class="handle-actions hide-if-no-js"><button type="button" class="handlediv" aria-expanded="%s">'
. '<span class="screen-reader-text">%s</span>'
@@ -208,6 +230,7 @@ final class ToolsTab implements AdminTab
$label
))
);
$this->renderGroupControls($policy, $group, $label, $mixed);
echo '</div><div class="inside">';
printf(
@@ -239,7 +262,7 @@ final class ToolsTab implements AdminTab
return $readonly ? __('nur lesen', 'wp-lit-mcp') . ' · ' . $summary : $summary;
}
private function renderGroupControls(ExposurePolicy $policy, string $group, bool $mixed): void
private function renderGroupControls(ExposurePolicy $policy, string $group, string $label, bool $mixed): void
{
$selectId = 'lit-mcp-group-mode-' . self::idPart($group);
$groupMode = $policy->groupMode($group);
@@ -275,6 +298,15 @@ final class ToolsTab implements AdminTab
checked($policy->isReadonly($group), true, false),
esc_html__('Nur lesen', 'wp-lit-mcp')
);
printf(
'<button type="button" class="button-link lit-mcp-group-reset hide-if-no-js" aria-label="%s">%s</button>',
esc_attr(sprintf(
/* translators: %s: group label */
__('Auf Standard zurücksetzen: %s', 'wp-lit-mcp'),
$label
)),
esc_html__('Auf Standard zurücksetzen', 'wp-lit-mcp')
);
if ($mixed) {
echo '<span class="description">'
. esc_html__('gemischt – einzelne Tools haben eigene Einstellungen', 'wp-lit-mcp')
@@ -389,13 +421,12 @@ final class ToolsTab implements AdminTab
private function kind(Ability $ability): string
{
$annotations = $ability->annotations();
$styles = self::kindStyles();
[$icon, $text] = match (true) {
isset(self::DELETING[$ability->name()]) => ['trash', self::DELETING[$ability->name()] === 'trash'
? __('Papierkorb', 'wp-lit-mcp')
: __('löscht', 'wp-lit-mcp')],
($annotations['destructive'] ?? false) === true => ['update', __('überschreibt', 'wp-lit-mcp')],
($annotations['readonly'] ?? false) === true => ['visibility', __('liest', 'wp-lit-mcp')],
default => ['edit', __('schreibt', 'wp-lit-mcp')],
isset(self::DELETING[$ability->name()]) => $styles[self::DELETING[$ability->name()]],
($annotations['destructive'] ?? false) === true => $styles['overwrite'],
($annotations['readonly'] ?? false) === true => $styles['read'],
default => $styles['write'],
};
return sprintf(
@@ -406,6 +437,35 @@ final class ToolsTab implements AdminTab
);
}
/** @return array<string, array{string, string}> Kind → [dashicon, word]. */
private static function kindStyles(): array
{
return [
'read' => ['visibility', __('liest', 'wp-lit-mcp')],
'write' => ['edit', __('schreibt', 'wp-lit-mcp')],
'overwrite' => ['update', __('überschreibt', 'wp-lit-mcp')],
'delete' => ['trash', __('löscht', 'wp-lit-mcp')],
'trash' => ['trash', __('Papierkorb', 'wp-lit-mcp')],
];
}
/** On phones the "Art" column shows icons only; this line says what they mean (touch has no tooltips). */
private function kindLegend(): string
{
$styles = self::kindStyles();
$parts = [];
foreach (['read', 'write', 'overwrite', 'delete'] as $key) {
[$icon, $text] = $styles[$key];
$parts[] = sprintf(
'<span class="dashicons dashicons-%s" aria-hidden="true"></span> %s',
esc_attr($icon),
esc_html($key === 'delete' ? $text . ' / ' . $styles['trash'][1] : $text)
);
}
return implode(' · ', $parts);
}
/** Safe fragment for an HTML id from a group id or tool name ("lit/guide" → "lit-guide"). */
private static function idPart(string $value): string
{