refactor: small Divi tree helpers out of DiviWriter (M3)

Block::isPlaceholder replaces the divi/placeholder literals (ledger T4);
NodeIndex::parentBlock/sectionLabel replace the two parentBlock copies and
DiviWriter::sectionLabel; DiviChange::idOf and Outline::summary replace the
duplicated id resolution and outline text; DiviSchema holds the shared
input properties. Behaviour unchanged.

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 18:24:29 +02:00
co-authored by Claude Opus 5.5
parent ecfece719d
commit d2cc82f31b
18 changed files with 188 additions and 86 deletions
@@ -121,6 +121,12 @@ final class Block extends Container
return str_starts_with($this->name, 'divi/');
}
/** The divi/placeholder wrapper the builder puts around a page's sections (transparent for IDs). */
public function isPlaceholder(): bool
{
return $this->name === 'divi/placeholder';
}
/** A global module reference or global layout (edited only in Etappe 3). */
public function isGlobal(): bool
{
+22 -2
View File
@@ -36,7 +36,7 @@ final class NodeIndex
{
$root = $document;
foreach ($document->children() as $block) {
if ($block->name() === 'divi/placeholder') {
if ($block->isPlaceholder()) {
$root = $block;
break;
}
@@ -92,6 +92,26 @@ final class NodeIndex
return $parent instanceof Block ? $this->idOf($parent) : null;
}
/** The parent node of $block; null at the top level (the root or a placeholder holds it). */
public function parentBlock(Block $block): ?Block
{
$parent = $this->parentOf($block);
return $parent instanceof Block && ! $parent->isPlaceholder() ? $parent : null;
}
/** Admin label of the section around $block (or $block itself), for automatic labels ("" when none). */
public function sectionLabel(?Block $block): string
{
for ($node = $block; $node !== null; $node = $this->parentBlock($node)) {
if ($node->name() === 'divi/section') {
return NodeSummary::label($node);
}
}
return '';
}
/** @return list<array{id: string, block: Block, depth: int}> In document order. */
public function entries(): array
{
@@ -102,7 +122,7 @@ final class NodeIndex
private function walk(Container $container, string $prefix, int $depth, array &$counters): void
{
foreach ($container->children() as $block) {
if ($prefix === '' && $block->name() === 'divi/placeholder') {
if ($prefix === '' && $block->isPlaceholder()) {
$this->walk($block, '', $depth, $counters);
continue;
}
@@ -32,6 +32,29 @@ final class Outline
return $this->truncated;
}
/**
* The outline text of a write result: the whole page ($sectionIds null) or the given
* sections one after another (none: text null), and whether any breadth cap fired (S18).
*
* @param list<string>|null $sectionIds
* @return array{text: ?string, truncated: bool}
*/
public function summary(NodeIndex $index, ?array $sectionIds = null): array
{
if ($sectionIds === []) {
return ['text' => null, 'truncated' => false];
}
$texts = [];
$truncated = false;
foreach ($sectionIds ?? [null] as $sectionId) {
$texts[] = $this->text($this->rows($index, $sectionId));
$truncated = $truncated || $this->wasTruncated();
}
return ['text' => implode("\n", $texts), 'truncated' => $truncated];
}
/**
* @return list<array{id: string, type: string, label: string, excerpt: string, info: string,
* presets: list<string>, children: int, editable: bool, depth: int, truncated: bool}>