fix: prevent duplicate toolbar buttons when rich editor plugin enables already-present buttons (#19666)

* feat: implement applyEnableToolbarButtonsModification for dynamic button enabling

* fix: remove commented-out code for enabling toolbar buttons

* test: add tests for toolbar button duplication in RichEditor plugin

* fix: improve toolbar button detection logic in RichEditor plugin
This commit is contained in:
RenHai Wen
2026-04-11 12:58:25 +01:00
committed by GitHub
parent 4bc3e570ee
commit 57e37e2051
3 changed files with 133 additions and 1 deletions
@@ -87,7 +87,7 @@ trait InteractsWithToolbarButtons
$buttons = match ($modification['type']) {
'disableAll' => [],
'disable' => $this->applyDisableToolbarButtonsModification($buttons, $modification['buttons']),
'enable' => [...$buttons, ...$modification['buttons']],
'enable' => $this->applyEnableToolbarButtonsModification($buttons, $modification['buttons']),
default => throw new Exception('Unknown toolbar buttons modification type: [' . $modification['type'] . '].'),
};
}
@@ -170,6 +170,87 @@ trait InteractsWithToolbarButtons
return $modified;
}
/**
* @param array<int, string | object | array<int, string | object>> $buttons
* @param array<int, string | object | array<int, string | object>> $buttonsToEnable
* @return array<int, string | object | array<int, string | object>>
*/
protected function applyEnableToolbarButtonsModification(array $buttons, array $buttonsToEnable): array
{
$modified = $buttons;
foreach ($buttonsToEnable as $button) {
if (is_object($button)) {
$modified[] = $button;
continue;
}
if (is_array($button)) {
$filteredGroup = [];
foreach ($button as $item) {
if (is_object($item)) {
$filteredGroup[] = $item;
continue;
}
if ($this->hasToolbarButtonInArray($modified, $item) || in_array($item, $filteredGroup)) {
continue;
}
$filteredGroup[] = $item;
}
if (filled($filteredGroup)) {
$modified[] = $filteredGroup;
}
continue;
}
if ($this->hasToolbarButtonInArray($modified, $button)) {
continue;
}
$modified[] = $button;
}
return $modified;
}
/**
* @param array<int, string | object | array<int, string | object>> $buttons
*/
protected function hasToolbarButtonInArray(array $buttons, string $button): bool
{
foreach ($buttons as $item) {
if (is_array($item)) {
if ($this->hasToolbarButtonInArray($item, $button)) {
return true;
}
continue;
}
if (is_string($item) && ($item === $button)) {
return true;
}
if (is_object($item) && $this->isToolbarButtonInObject($item, $button)) {
return true;
}
}
return false;
}
protected function isToolbarButtonInObject(object $item, string $button): bool
{
return false;
}
/**
* @return array<array{type: string, buttons?: array<string | array<string | array<string>>>}>
*/
@@ -828,6 +828,15 @@ class RichEditor extends Field implements Contracts\CanBeLengthConstrained
return $modifications;
}
protected function isToolbarButtonInObject(object $item, string $button): bool
{
if ($item instanceof ToolbarButtonGroup) {
return in_array($button, $item->getButtons());
}
return false;
}
/**
* @return array<string | array<string>>
*/
@@ -432,6 +432,48 @@ describe('plugins', function (): void {
->toContain('bold');
});
test('plugin implementing `HasToolbarButtons` does not duplicate toolbar buttons already returned by `toolbarButtons()`', function (): void {
$buttons = Schema::make(Livewire::make())
->statePath('data')
->components([
RichEditor::make('content')
->toolbarButtons([
['bold', 'highlight'],
['undo', 'redo'],
])
->plugins([new TestRichContentPlugin(enabledButtons: ['highlight'])]),
])
->getComponents()[0]
->getToolbarButtons();
$flatButtons = array_merge(...$buttons);
$highlightButtons = array_values(array_filter($flatButtons, fn (mixed $button): bool => $button === 'highlight'));
expect($buttons)
->toHaveCount(2)
->and($buttons[0])->toEqual(['bold', 'highlight'])
->and($buttons[1])->toEqual(['undo', 'redo'])
->and($highlightButtons)->toHaveCount(1);
});
test('plugin implementing `HasToolbarButtons` does not duplicate toolbar buttons already enabled by user `enableToolbarButtons()`', function (): void {
$schema = Schema::make(Livewire::make())
->statePath('data')
->components([
RichEditor::make('content')
->plugins([new TestRichContentPlugin(enabledButtons: ['highlight'])]),
]);
$richEditor = $schema->getComponents()[0];
$richEditor->enableToolbarButtons(['highlight']);
$flatButtons = array_merge(...$richEditor->getToolbarButtons());
$highlightButtons = array_values(array_filter($flatButtons, fn (mixed $button): bool => $button === 'highlight'));
expect($highlightButtons)
->toHaveCount(1);
});
test('plugin implementing `HasToolbarButtons` can disable toolbar buttons', function (): void {
$richEditor = Schema::make(Livewire::make())
->statePath('data')