feat: Divi path IDs, attribute paths and outline

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 11:35:03 +02:00
co-authored by Claude Opus 5.5
parent 8222240ed6
commit 08d70abfc7
6 changed files with 654 additions and 0 deletions
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\BlockTree;
/**
* The page tree as rows and as compact text (lit-divi-outline, spec §5.3/§5.7; also the
* "outline" of write results). Preset IDs are shown with their names when known.
*/
final class Outline
{
/** @param array<string, string> $presetNames Preset ID => name. */
public function __construct(private array $presetNames = [])
{
}
/**
* @return list<array{id: string, type: string, label: string, excerpt: string, info: string,
* presets: list<string>, children: int, editable: bool, depth: int}>
*/
public function rows(NodeIndex $index, ?string $rootId = null, ?int $maxDepth = null): array
{
$rows = [];
$baseDepth = null;
foreach ($index->entries() as $entry) {
$id = $entry['id'];
if ($rootId !== null && $id !== $rootId && ! str_starts_with($id, $rootId . '.')) {
continue;
}
$baseDepth ??= $entry['depth'];
$depth = $entry['depth'] - $baseDepth;
if ($maxDepth !== null && $depth > $maxDepth) {
continue;
}
$rows[] = $this->row($entry['block'], $id, $depth);
}
return $rows;
}
/** @param list<array<string, mixed>> $rows Output of rows(). */
public function text(array $rows): string
{
$lines = [];
foreach ($rows as $row) {
$line = str_repeat(' ', (int) $row['depth']) . $row['id'] . ' ' . $row['type'];
$line .= $row['label'] !== '' ? ' "' . $row['label'] . '"' : '';
$line .= $row['info'] !== '' ? ' [' . $row['info'] . ']' : '';
$line .= $row['presets'] !== [] ? ' {Preset: ' . implode(', ', $row['presets']) . '}' : '';
$line .= $row['excerpt'] !== '' ? ' – ' . $row['excerpt'] : '';
$line .= $row['editable'] ? '' : ' (nur lesbar)';
$lines[] = $line;
}
return implode("\n", $lines);
}
/**
* @return array{id: string, type: string, label: string, excerpt: string, info: string,
* presets: list<string>, children: int, editable: bool, depth: int}
*/
private function row(Block $block, string $id, int $depth): array
{
$editable = $block->isDivi() && ! $block->isOpaque();
return [
'id' => $id,
'type' => $block->isDivi() ? $block->shortName() : $block->name(),
'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)
),
'children' => $editable ? count($block->children()) : 0,
'editable' => $editable,
'depth' => $depth,
];
}
}