feat: Outline und Knoten zeigen Gruppen-Presets mit Namen

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 16:41:42 +02:00
co-authored by Claude Haiku 5.5
parent ca3bbf5ad1
commit c3f08e2d33
7 changed files with 308 additions and 21 deletions
@@ -84,14 +84,37 @@ final class NodeSummary
/** @return list<string> */
public static function presetIds(Block $block): array
{
$presets = $block->attrs()['modulePreset'] ?? [];
if (is_string($presets)) {
$presets = [$presets];
return self::idList($block->attrs()['modulePreset'] ?? []);
}
/**
* Option group presets of the block (formats §3 "Verweise in Blöcken"): groupPreset.<groupId> holds
* {presetId: [id, …], groupName: "divi/<group>"}. Empty IDs are dropped; an entry without a valid
* groupName or without any ID is left out.
*
* @return array<string, array{groupName: string, presetIds: list<string>}> groupId => entry
*/
public static function groupPresets(Block $block): array
{
$entries = $block->attrs()['groupPreset'] ?? [];
if (! is_array($entries)) {
return [];
}
return is_array($presets)
? array_values(array_filter($presets, static fn (mixed $id): bool => is_string($id) && $id !== ''))
: [];
$groups = [];
foreach ($entries as $groupId => $entry) {
if (! is_array($entry)) {
continue;
}
$name = $entry['groupName'] ?? null;
$ids = self::idList($entry['presetId'] ?? []);
if (is_string($name) && $name !== '' && $ids !== []) {
$groups[(string) $groupId] = ['groupName' => $name, 'presetIds' => $ids];
}
}
return $groups;
}
/** Layout facts: section type, column widths of a row (e.g. "8_24+16_24"). */
@@ -162,6 +185,22 @@ final class NodeSummary
return trim((string) preg_replace('/\s+/u', ' ', $text));
}
/**
* Preset IDs as Divi stores them: a list, or a single string (legacy).
*
* @return list<string>
*/
private static function idList(mixed $ids): array
{
if (is_string($ids)) {
$ids = [$ids];
}
return is_array($ids)
? array_values(array_filter($ids, static fn (mixed $id): bool => is_string($id) && $id !== ''))
: [];
}
private static function rank(string $path): int
{
$index = array_search(explode('.', $path)[0], self::PRIORITY, true);
+35 -8
View File
@@ -62,8 +62,8 @@ final class Outline
/**
* @return list<array{id: string, type: string, label: string, excerpt: string, info: string,
* presets: list<string>, children: int, editable: bool, depth: int, truncated: bool,
* global_id: ?string}>
* presets: list<string>, group_presets: list<string>, children: int, editable: bool, depth: int,
* truncated: bool, global_id: ?string}>
*/
public function rows(NodeIndex $index, ?string $rootId = null, ?int $maxDepth = null, ?int $limit = null): array
{
@@ -121,6 +121,8 @@ final class Outline
$line .= ($row['global_id'] ?? null) !== null ? ' [global ' . $row['global_id'] . ']' : '';
$line .= $row['info'] !== '' ? ' [' . $row['info'] . ']' : '';
$line .= $row['presets'] !== [] ? ' {Preset: ' . implode(', ', $row['presets']) . '}' : '';
$groupPresets = $row['group_presets'] ?? [];
$line .= $groupPresets !== [] ? ' {Gruppen-Presets: ' . implode('; ', $groupPresets) . '}' : '';
$line .= $row['excerpt'] !== '' ? ' – ' . $row['excerpt'] : '';
$line .= $row['editable'] ? '' : ' (nur lesbar)';
$line .= ($row['truncated'] ?? false) ? ' (weitere Kinder abgeschnitten)' : '';
@@ -132,8 +134,8 @@ final class Outline
/**
* @return array{id: string, type: string, label: string, excerpt: string, info: string,
* presets: list<string>, children: int, editable: bool, depth: int, truncated: bool,
* global_id: ?string}
* presets: list<string>, group_presets: list<string>, children: int, editable: bool, depth: int,
* truncated: bool, global_id: ?string}
*/
private function row(Block $block, string $id, int $depth): array
{
@@ -152,10 +154,8 @@ final class Outline
'label' => NodeSummary::label($block),
'excerpt' => NodeSummary::excerpt($block),
'info' => $editable ? NodeSummary::info($block) : '',
'presets' => array_map(
fn (string $presetId): string => $this->presetNames[$presetId] ?? $presetId,
NodeSummary::presetIds($block)
),
'presets' => $this->namesOf(NodeSummary::presetIds($block)),
'group_presets' => $this->groupPresetLines($block),
'children' => $editable ? count($block->children()) : 0,
'editable' => $editable,
'depth' => $depth,
@@ -163,4 +163,31 @@ final class Outline
'global_id' => $this->globalIds ? ($global['id'] ?? null) : null,
];
}
/**
* One entry per option group: "<short group name>: <preset name>[, <preset name>]", e.g.
* "font-body: Fließtext" (the short name is the key "group_presets" of the write tools takes).
*
* @return list<string>
*/
private function groupPresetLines(Block $block): array
{
$lines = [];
foreach (NodeSummary::groupPresets($block) as $group) {
$name = $group['groupName'];
$short = str_starts_with($name, 'divi/') ? substr($name, 5) : $name;
$lines[] = $short . ': ' . implode(', ', $this->namesOf($group['presetIds']));
}
return $lines;
}
/**
* @param list<string> $presetIds
* @return list<string> The preset names, the ID where the name is unknown.
*/
private function namesOf(array $presetIds): array
{
return array_map(fn (string $presetId): string => $this->presetNames[$presetId] ?? $presetId, $presetIds);
}
}
@@ -10,6 +10,7 @@ use LIT\MCP\Abilities\Errors;
use LIT\MCP\Abilities\JsonMap;
use LIT\MCP\Abilities\Rev;
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\NodeSummary;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
@@ -42,9 +43,9 @@ final class DiviNodeGetAbility implements Ability
{
return 'Liest einen Knoten der Divi-Seite vollständig: attrs als Attributpfad → Werte je Breakpoint '
. '(Vollform, z. B. "title.decoration.font.font": {"desktop": {"value": {…}}}), die Kurzform-Werte, '
. 'Presets und die IDs der Kinder. Pfad-IDs aus lit-divi-outline. Werte wie $variable(…)$ (globale '
. 'Farben, Variablen, dynamische Inhalte) werden unverändert angezeigt, beim Schreiben aber erneut '
. 'geprüft, nie ungeprüft übernommen.';
. 'Presets, Gruppen-Presets und die IDs der Kinder. Pfad-IDs aus lit-divi-outline. Werte wie '
. '$variable(…)$ (globale Farben, Variablen, dynamische Inhalte) werden unverändert angezeigt, beim '
. 'Schreiben aber erneut geprüft, nie ungeprüft übernommen.';
}
public function inputSchema(): array
@@ -83,6 +84,27 @@ final class DiviNodeGetAbility implements Ability
'required' => ['id', 'name'],
],
],
'group_presets' => [
'type' => 'array',
'description' => 'Gruppen-Presets des Moduls: group_id = Attributpfad der Gruppe, group = '
. 'Kurzname (Schlüssel für group_presets).',
'items' => [
'type' => 'object',
'properties' => [
'group_id' => ['type' => 'string'],
'group' => ['type' => 'string'],
'presets' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => ['id' => ['type' => 'string'], 'name' => ['type' => 'string']],
'required' => ['id', 'name'],
],
],
],
'required' => ['group_id', 'group', 'presets'],
],
],
'builder_version' => ['type' => ['string', 'null']],
'children' => ['type' => 'array', 'items' => ['type' => 'string']],
'global' => ['type' => ['object', 'null']],
@@ -163,6 +185,7 @@ final class DiviNodeGetAbility implements Ability
static fn (string $presetId): array => ['id' => $presetId, 'name' => $names[$presetId] ?? ''],
NodeSummary::presetIds($block)
),
'group_presets' => $this->groupPresets($block, $names),
'builder_version' => is_string($version) ? $version : null,
'children' => $editable
? array_values(array_filter(array_map([$page->index, 'idOf'], $block->children())))
@@ -170,4 +193,26 @@ final class DiviNodeGetAbility implements Ability
'global' => $global,
];
}
/**
* @param array<string, string> $names Preset ID => name.
* @return list<array{group_id: string, group: string, presets: list<array{id: string, name: string}>}>
*/
private function groupPresets(Block $block, array $names): array
{
$groups = [];
foreach (NodeSummary::groupPresets($block) as $groupId => $group) {
$name = $group['groupName'];
$groups[] = [
'group_id' => $groupId,
'group' => str_starts_with($name, 'divi/') ? substr($name, 5) : $name,
'presets' => array_map(
static fn (string $presetId): array => ['id' => $presetId, 'name' => $names[$presetId] ?? ''],
$group['presetIds']
),
];
}
return $groups;
}
}
@@ -38,7 +38,7 @@ final class DiviOutlineAbility implements Ability
public function description(): string
{
return 'Liest den Aufbau einer Divi-Seite: Knoten mit Pfad-ID (s0, s0.r1, s0.r1.c0.m2), Modultyp, '
. 'Admin-Label, Textauszug und Preset. Liefert tree_rev – Pflicht für jede Divi-Änderung. '
. 'Admin-Label, Textauszug, Preset und Gruppen-Presets. Liefert tree_rev – Pflicht für jede Divi-Änderung. '
. 'Optional nur ein Teilbaum (id), begrenzte Tiefe (depth) oder begrenzte Breite je Ebene (limit); '
. 'abgeschnittene Kinder zeigt "truncated".';
}
@@ -91,6 +91,12 @@ final class DiviOutlineAbility implements Ability
'excerpt' => ['type' => 'string'],
'info' => ['type' => 'string'],
'presets' => ['type' => 'array', 'items' => ['type' => 'string']],
'group_presets' => [
'type' => 'array',
'items' => ['type' => 'string'],
'description' => 'Gruppen-Presets des Moduls als "<Gruppe>: <Name>[, <Name>]", '
. 'z. B. "font-body: Fließtext". Die Gruppe ist der Schlüssel für group_presets.',
],
'children' => ['type' => 'integer'],
'editable' => ['type' => 'boolean'],
'depth' => ['type' => 'integer'],
@@ -104,8 +110,8 @@ final class DiviOutlineAbility implements Ability
],
],
'required' => [
'id', 'type', 'label', 'excerpt', 'info', 'presets', 'children', 'editable', 'depth',
'truncated', 'global_id',
'id', 'type', 'label', 'excerpt', 'info', 'presets', 'group_presets', 'children',
'editable', 'depth', 'truncated', 'global_id',
],
],
],