feat: design-system filters and global module ids in node-get/outline (S19)
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:
@@ -25,7 +25,10 @@ final class NodeSummary
|
||||
|
||||
public static function label(Block $block): string
|
||||
{
|
||||
$label = AttrMap::desktop($block->attrs(), 'module.meta.adminLabel');
|
||||
$attrs = $block->name() === 'divi/global-layout' && is_array($block->attrs()['localAttrs'] ?? null)
|
||||
? $block->attrs()['localAttrs']
|
||||
: $block->attrs();
|
||||
$label = AttrMap::desktop($attrs, 'module.meta.adminLabel');
|
||||
if (! is_string($label) || $label === '') {
|
||||
return '';
|
||||
}
|
||||
@@ -55,6 +58,29 @@ final class NodeSummary
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Library entry of a global module: a divi/global-layout instance (its root type in blockName)
|
||||
* or a block that carries globalModule itself (Divi 4 conversions). Null for every other block.
|
||||
*
|
||||
* @return array{id: string, block_name: string}|null
|
||||
*/
|
||||
public static function globalRef(Block $block): ?array
|
||||
{
|
||||
if (! $block->isGlobal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attrs = $block->attrs();
|
||||
$id = $attrs['globalModule'] ?? null;
|
||||
if (! is_string($id) && ! is_int($id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $block->name() === 'divi/global-layout' ? ($attrs['blockName'] ?? '') : $block->name();
|
||||
|
||||
return ['id' => (string) $id, 'block_name' => is_string($name) ? $name : ''];
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public static function presetIds(Block $block): array
|
||||
{
|
||||
|
||||
@@ -59,7 +59,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}>
|
||||
* 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
|
||||
{
|
||||
@@ -114,6 +115,7 @@ final class Outline
|
||||
foreach ($rows as $row) {
|
||||
$line = str_repeat(' ', (int) $row['depth']) . $row['id'] . ' ' . $row['type'];
|
||||
$line .= $row['label'] !== '' ? ' "' . $row['label'] . '"' : '';
|
||||
$line .= ($row['global_id'] ?? null) !== null ? ' [global ' . $row['global_id'] . ']' : '';
|
||||
$line .= $row['info'] !== '' ? ' [' . $row['info'] . ']' : '';
|
||||
$line .= $row['presets'] !== [] ? ' {Preset: ' . implode(', ', $row['presets']) . '}' : '';
|
||||
$line .= $row['excerpt'] !== '' ? ' – ' . $row['excerpt'] : '';
|
||||
@@ -127,15 +129,23 @@ 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}
|
||||
* presets: list<string>, children: int, editable: bool, depth: int, truncated: bool,
|
||||
* global_id: ?string}
|
||||
*/
|
||||
private function row(Block $block, string $id, int $depth): array
|
||||
{
|
||||
$editable = $block->isDivi() && ! $block->isOpaque();
|
||||
$global = NodeSummary::globalRef($block);
|
||||
$type = $block->isDivi() ? $block->shortName() : $block->name();
|
||||
if ($global !== null && $global['block_name'] !== '') {
|
||||
$type = str_starts_with($global['block_name'], 'divi/')
|
||||
? substr($global['block_name'], 5)
|
||||
: $global['block_name'];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'type' => $block->isDivi() ? $block->shortName() : $block->name(),
|
||||
'type' => $type,
|
||||
'label' => NodeSummary::label($block),
|
||||
'excerpt' => NodeSummary::excerpt($block),
|
||||
'info' => $editable ? NodeSummary::info($block) : '',
|
||||
@@ -147,6 +157,7 @@ final class Outline
|
||||
'editable' => $editable,
|
||||
'depth' => $depth,
|
||||
'truncated' => false,
|
||||
'global_id' => $global['id'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ defined('ABSPATH') || exit;
|
||||
*/
|
||||
final class DiviDesignSystemAbility implements Ability
|
||||
{
|
||||
/** Presets per answer (ruling S19); narrow with "module". */
|
||||
public const PRESET_LIMIT = 200;
|
||||
|
||||
private const PARTS = ['colors', 'variables', 'presets'];
|
||||
|
||||
public function __construct(private DesignSystemReader $reader)
|
||||
{
|
||||
}
|
||||
@@ -36,12 +41,28 @@ final class DiviDesignSystemAbility implements Ability
|
||||
{
|
||||
return 'Liest das Designsystem der Website aus Divi: globale Farben, globale Variablen, Theme-Schriften und '
|
||||
. '-Größen, Modul- und Gruppen-Presets – jeweils mit dem Platzhalter ({{color:…}}, {{var:…}}, '
|
||||
. '{{font:…}}, preset), den neue Inhalte statt fester Werte verwenden sollen.';
|
||||
. '{{font:…}}, preset), den neue Inhalte statt fester Werte verwenden sollen. '
|
||||
. 'Filter: only (colors/variables/presets) und module (Presets eines Moduls).';
|
||||
}
|
||||
|
||||
public function inputSchema(): array
|
||||
{
|
||||
return ['type' => 'object', 'properties' => [], 'additionalProperties' => false];
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'only' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'string', 'enum' => self::PARTS],
|
||||
'description' => 'Nur diese Teile liefern (Standard: alle). Schriften und Größen sind immer '
|
||||
. 'dabei.',
|
||||
],
|
||||
'module' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Nur Presets dieses Moduls, z. B. "button" oder "divi/button".',
|
||||
],
|
||||
],
|
||||
'additionalProperties' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function outputSchema(): ?array
|
||||
@@ -110,26 +131,52 @@ final class DiviDesignSystemAbility implements Ability
|
||||
public function execute(array $input): mixed
|
||||
{
|
||||
$design = $this->reader->read();
|
||||
$only = is_array($input['only'] ?? null) && $input['only'] !== []
|
||||
? array_values(array_intersect(self::PARTS, $input['only']))
|
||||
: self::PARTS;
|
||||
$notes = [
|
||||
'Schriften: {{font:heading}} bzw. {{font:body}}; Presets in der Kurzform über "preset": "<Name>".',
|
||||
'Feste Farben oder Schriften nur verwenden, wenn der Nutzer es ausdrücklich möchte.',
|
||||
];
|
||||
$skipped = array_values(array_diff(self::PARTS, $only));
|
||||
if ($skipped !== []) {
|
||||
$notes[] = 'Nicht angefordert: ' . implode(', ', $skipped) . '.';
|
||||
}
|
||||
|
||||
$presets = $design->presets();
|
||||
$module = is_string($input['module'] ?? null) ? trim($input['module']) : '';
|
||||
if ($module !== '') {
|
||||
$name = str_contains($module, '/') ? $module : 'divi/' . $module;
|
||||
$presets = array_values(array_filter(
|
||||
$presets,
|
||||
static fn (array $preset): bool => $preset['module'] === $name
|
||||
));
|
||||
}
|
||||
if (count($presets) > self::PRESET_LIMIT) {
|
||||
$notes[] = sprintf(
|
||||
'%d von %d Presets gezeigt; mit module auf ein Modul eingrenzen.',
|
||||
self::PRESET_LIMIT,
|
||||
count($presets)
|
||||
);
|
||||
$presets = array_slice($presets, 0, self::PRESET_LIMIT);
|
||||
}
|
||||
|
||||
return [
|
||||
'colors' => array_map(
|
||||
'colors' => in_array('colors', $only, true) ? array_map(
|
||||
static fn (array $color): array => $color + [
|
||||
'placeholder' => DesignSystem::colorPlaceholder($color['id'], $color['label']),
|
||||
],
|
||||
$design->colors()
|
||||
),
|
||||
'variables' => array_map(
|
||||
) : [],
|
||||
'variables' => in_array('variables', $only, true) ? array_map(
|
||||
static fn (array $variable): array => $variable
|
||||
+ ['placeholder' => '{{var:' . $variable['label'] . '}}'],
|
||||
$design->variables()
|
||||
),
|
||||
) : [],
|
||||
'fonts' => $design->fonts(),
|
||||
'sizes' => $design->sizes(),
|
||||
'presets' => $design->presets(),
|
||||
'notes' => [
|
||||
'Schriften: {{font:heading}} bzw. {{font:body}}; Presets in der Kurzform über "preset": "<Name>".',
|
||||
'Feste Farben oder Schriften nur verwenden, wenn der Nutzer es ausdrücklich möchte.',
|
||||
],
|
||||
'presets' => in_array('presets', $only, true) ? $presets : [],
|
||||
'notes' => $notes,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ final class DiviNodeGetAbility implements Ability
|
||||
],
|
||||
'builder_version' => ['type' => ['string', 'null']],
|
||||
'children' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||
'global' => ['type' => ['object', 'null']],
|
||||
],
|
||||
'required' => ['post_id', 'tree_rev', 'rev', 'id', 'type', 'editable', 'attrs', 'shortform', 'children'],
|
||||
];
|
||||
@@ -128,6 +129,16 @@ final class DiviNodeGetAbility implements Ability
|
||||
$editable = $block->isDivi() && ! $block->isOpaque();
|
||||
$version = $block->attrs()['builderVersion'] ?? null;
|
||||
|
||||
$global = NodeSummary::globalRef($block);
|
||||
if ($global !== null) {
|
||||
$global['hint'] = sprintf(
|
||||
'Globales Modul: Inhalt im Bibliothekseintrag %s bearbeiten (lit-divi-outline mit post_id %s); '
|
||||
. 'jede Änderung dort wirkt auf alle Seiten mit diesem Modul.',
|
||||
$global['id'],
|
||||
$global['id']
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'post_id' => (int) $page->post->ID,
|
||||
'tree_rev' => $page->treeRev(),
|
||||
@@ -147,6 +158,7 @@ final class DiviNodeGetAbility implements Ability
|
||||
'children' => $editable
|
||||
? array_values(array_filter(array_map([$page->index, 'idOf'], $block->children())))
|
||||
: [],
|
||||
'global' => $global,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +98,14 @@ final class DiviOutlineAbility implements Ability
|
||||
'type' => 'boolean',
|
||||
'description' => 'Weitere Kinder dieses Knotens wurden wegen "limit" abgeschnitten.',
|
||||
],
|
||||
'global_id' => [
|
||||
'type' => ['string', 'null'],
|
||||
'description' => 'Bibliotheks-ID eines globalen Moduls.',
|
||||
],
|
||||
],
|
||||
'required' => [
|
||||
'id', 'type', 'label', 'excerpt', 'info', 'presets', 'children', 'editable', 'depth',
|
||||
'truncated',
|
||||
'truncated', 'global_id',
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -187,7 +191,8 @@ final class DiviOutlineAbility implements Ability
|
||||
|
||||
if (in_array(false, array_column($rows, 'editable'), true)) {
|
||||
$hints[] = 'Knoten mit "nur lesbar" (globale Module, fremde Blöcke) lassen sich nur als Ganzes '
|
||||
. 'verschieben, duplizieren oder löschen; ihren Inhalt im Divi-Builder bearbeiten.';
|
||||
. 'verschieben, duplizieren oder löschen. Globale Module [global <ID>] im Bibliothekseintrag '
|
||||
. 'bearbeiten: lit-divi-outline mit post_id <ID> – die Änderung wirkt auf alle Seiten.';
|
||||
}
|
||||
|
||||
return $hints;
|
||||
|
||||
Reference in New Issue
Block a user