diff --git a/resources/css/elements/dropdowns.css b/resources/css/elements/dropdowns.css
index f7d51f7aef..5d9d4c8d5a 100644
--- a/resources/css/elements/dropdowns.css
+++ b/resources/css/elements/dropdowns.css
@@ -40,6 +40,12 @@
.divider {
@apply h-px bg-gray-400 overflow-hidden;
margin: 6px -8px;
+ /* Hide dividers that come first, last or immediately after another (due to v-if) */
+ & + &,
+ &:first-child,
+ &:last-child {
+ display: none;
+ }
}
.align-left & {
diff --git a/resources/js/components/assets/Editor/Editor.vue b/resources/js/components/assets/Editor/Editor.vue
index bdc5bdc38b..1c5251bb17 100644
--- a/resources/js/components/assets/Editor/Editor.vue
+++ b/resources/js/components/assets/Editor/Editor.vue
@@ -64,6 +64,16 @@
php artisan config:cache
command to regenerate the cache.',
'licensing_error_invalid_domain' => 'Invalid domain',
'licensing_error_invalid_edition' => 'License is for :edition edition',
diff --git a/resources/views/entries/edit.blade.php b/resources/views/entries/edit.blade.php
index 96adf175cc..ee8cae9e06 100644
--- a/resources/views/entries/edit.blade.php
+++ b/resources/views/entries/edit.blade.php
@@ -36,6 +36,8 @@
initial-listing-url="{{ cp_route('collections.show', $collection) }}"
:preview-targets="{{ json_encode($previewTargets) }}"
:autosave-interval="{{ json_encode($autosaveInterval) }}"
+ :initial-item-actions="{{ json_encode($itemActions) }}"
+ item-action-url="{{ cp_route('collections.entries.actions.run', $collection) }}"
>
@endsection
diff --git a/resources/views/terms/edit.blade.php b/resources/views/terms/edit.blade.php
index bcf3e9e6a6..6be8d5041d 100644
--- a/resources/views/terms/edit.blade.php
+++ b/resources/views/terms/edit.blade.php
@@ -36,6 +36,8 @@
create-another-url="{{ cp_route('taxonomies.terms.create', [$taxonomy, $locale]) }}"
listing-url="{{ cp_route('taxonomies.show', $taxonomy) }}"
:preview-targets="{{ json_encode($previewTargets) }}"
+ :initial-item-actions="{{ json_encode($itemActions) }}"
+ item-action-url="{{ cp_route('taxonomies.terms.actions.run', $taxonomy) }}"
>
@endsection
diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php
index 6138df9584..64f4943299 100644
--- a/resources/views/users/edit.blade.php
+++ b/resources/views/users/edit.blade.php
@@ -17,6 +17,8 @@
:can-edit-password="{{ Statamic\Support\Str::bool($canEditPassword) }}"
:can-edit-blueprint="{{ Statamic\Support\Str::bool($user->can('configure fields')) }}"
:requires-current-password="{{ Statamic\Support\Str::bool($requiresCurrentPassword) }}"
+ :initial-item-actions="{{ json_encode($itemActions) }}"
+ item-action-url="{{ cp_route('users.actions.run') }}"
>
@endsection
diff --git a/src/Actions/Action.php b/src/Actions/Action.php
index 226d11ef06..7a91d390c9 100644
--- a/src/Actions/Action.php
+++ b/src/Actions/Action.php
@@ -62,6 +62,10 @@ public function authorizeBulk($user, $items)
public function context($context)
{
+ if (! isset($context['view'])) {
+ $context['view'] = 'list';
+ }
+
$this->context = $context;
return $this;
@@ -104,6 +108,17 @@ public function warningText()
return null;
}
+ public function dirtyWarningText()
+ {
+ /** @translation */
+ return "Any unsaved changes will not be reflected in this action's behavior.";
+ }
+
+ public function bypassesDirtyWarning(): bool
+ {
+ return false;
+ }
+
public function toArray()
{
return [
@@ -113,6 +128,8 @@ public function toArray()
'buttonText' => $this->buttonText(),
'confirmationText' => $this->confirmationText(),
'warningText' => $this->warningText(),
+ 'dirtyWarningText' => $this->dirtyWarningText(),
+ 'bypassesDirtyWarning' => $this->bypassesDirtyWarning(),
'dangerous' => $this->dangerous,
'fields' => $this->fields()->toPublishArray(),
'values' => $this->fields()->preProcess()->values(),
diff --git a/src/Actions/AssignGroups.php b/src/Actions/AssignGroups.php
index db4fee7dd9..3cfb9116b3 100644
--- a/src/Actions/AssignGroups.php
+++ b/src/Actions/AssignGroups.php
@@ -14,7 +14,7 @@ public static function title()
public function visibleTo($item)
{
- return $item instanceof UserContract && UserGroup::all()->isNotEmpty();
+ return $this->context['view'] === 'list' && $item instanceof UserContract && UserGroup::all()->isNotEmpty();
}
public function authorize($authed, $user)
diff --git a/src/Actions/AssignRoles.php b/src/Actions/AssignRoles.php
index 1c97ccb5ab..b4a4fdc7fc 100644
--- a/src/Actions/AssignRoles.php
+++ b/src/Actions/AssignRoles.php
@@ -14,7 +14,7 @@ public static function title()
public function visibleTo($item)
{
- return $item instanceof UserContract && Role::all()->isNotEmpty();
+ return $this->context['view'] === 'list' && $item instanceof UserContract && Role::all()->isNotEmpty();
}
public function authorize($authed, $user)
diff --git a/src/Actions/Delete.php b/src/Actions/Delete.php
index 6270c3f504..30dbd99877 100644
--- a/src/Actions/Delete.php
+++ b/src/Actions/Delete.php
@@ -52,8 +52,31 @@ public function confirmationText()
return 'Are you sure you want to delete this?|Are you sure you want to delete these :count items?';
}
+ public function bypassesDirtyWarning(): bool
+ {
+ return true;
+ }
+
public function run($items, $values)
{
$items->each->delete();
}
+
+ public function redirect($items, $values)
+ {
+ if ($this->context['view'] !== 'form') {
+ return;
+ }
+
+ $item = $items->first();
+
+ switch (true) {
+ case $item instanceof Contracts\Entries\Entry:
+ return cp_route('collections.show', $item->collection()->handle());
+ case $item instanceof Contracts\Taxonomies\Term:
+ return cp_route('taxonomies.show', $item->taxonomy()->handle());
+ case $item instanceof Contracts\Auth\User:
+ return cp_route('users.index');
+ }
+ }
}
diff --git a/src/Actions/DuplicateEntry.php b/src/Actions/DuplicateEntry.php
index 9f0bfe808b..96e90e2426 100644
--- a/src/Actions/DuplicateEntry.php
+++ b/src/Actions/DuplicateEntry.php
@@ -10,6 +10,8 @@
class DuplicateEntry extends Action
{
+ private $newItems;
+
public static function title()
{
return __('Duplicate');
@@ -48,12 +50,18 @@ public function warningText()
}
}
+ public function dirtyWarningText()
+ {
+ /** @translation */
+ return 'Any unsaved changes will not be duplicated into the new entry.';
+ }
+
public function run($items, $values)
{
- $items
+ $this->newItems = $items
->map(fn ($entry) => $entry->hasOrigin() ? $entry->root() : $entry)
->unique()
- ->each(fn ($original) => $this->duplicateEntry($original));
+ ->map(fn ($original) => $this->duplicateEntry($original));
}
private function duplicateEntry(Entry $original, ?string $origin = null)
@@ -99,6 +107,8 @@ private function duplicateEntry(Entry $original, ?string $origin = null)
->appendTo($originalParent->id(), $entry)
->save();
}
+
+ return $entry;
}
protected function getEntryParentFromStructure(Entry $entry)
@@ -156,4 +166,13 @@ public function authorize($user, $item)
{
return $user->can('create', [Entry::class, $item->collection(), $item->site()]);
}
+
+ public function redirect($items, $values)
+ {
+ if ($this->context['view'] !== 'form') {
+ return;
+ }
+
+ return $this->newItems->first()->editUrl();
+ }
}
diff --git a/src/Actions/DuplicateTerm.php b/src/Actions/DuplicateTerm.php
index 899f683cb6..ef02aa5ebe 100644
--- a/src/Actions/DuplicateTerm.php
+++ b/src/Actions/DuplicateTerm.php
@@ -8,6 +8,8 @@
class DuplicateTerm extends Action
{
+ private $newItems;
+
public static function title()
{
return __('Duplicate');
@@ -20,7 +22,7 @@ public function visibleTo($item)
public function run($items, $values)
{
- $items->each(function (Term $original) {
+ $this->newItems = $items->map(function (Term $original) {
[$title, $slug] = $this->generateTitleAndSlug($original);
$data = $original->data()
@@ -37,6 +39,8 @@ public function run($items, $values)
->data($data);
$term->save();
+
+ return $term;
});
}
@@ -72,4 +76,13 @@ public function authorize($user, $item)
{
return $user->can('create', [Term::class, $item->taxonomy()]);
}
+
+ public function redirect($items, $values)
+ {
+ if ($this->context['view'] !== 'form') {
+ return;
+ }
+
+ return $this->newItems->first()->editUrl();
+ }
}
diff --git a/src/Actions/Impersonate.php b/src/Actions/Impersonate.php
index ce3db84560..6136f1e695 100644
--- a/src/Actions/Impersonate.php
+++ b/src/Actions/Impersonate.php
@@ -10,8 +10,6 @@
class Impersonate extends Action
{
- protected $confirm = false;
-
public static function title()
{
return __('Start Impersonating');
@@ -67,4 +65,21 @@ public function redirect($users, $values)
return $users->first()->can('access cp') ? cp_route('index') : '/';
}
+
+ public function confirmationText()
+ {
+ /** @translation */
+ return 'statamic::messages.impersonate_action_confirmation';
+ }
+
+ public function buttonText()
+ {
+ /** @translation */
+ return 'Confirm';
+ }
+
+ public function bypassesDirtyWarning(): bool
+ {
+ return true;
+ }
}
diff --git a/src/Actions/Publish.php b/src/Actions/Publish.php
index b0bb9c0709..d573174cbc 100644
--- a/src/Actions/Publish.php
+++ b/src/Actions/Publish.php
@@ -14,7 +14,7 @@ public static function title()
public function visibleTo($item)
{
- return $item instanceof Entry && ! $item->published();
+ return $this->context['view'] === 'list' && $item instanceof Entry && ! $item->published();
}
public function visibleToBulk($items)
diff --git a/src/Actions/SendPasswordReset.php b/src/Actions/SendPasswordReset.php
index 8a5a1c3700..22e8e6a516 100644
--- a/src/Actions/SendPasswordReset.php
+++ b/src/Actions/SendPasswordReset.php
@@ -27,6 +27,11 @@ public function confirmationText()
return 'Send password reset email to this user?|Send password reset email to these :count users?';
}
+ public function dirtyWarningText()
+ {
+ return null;
+ }
+
public function buttonText()
{
/** @translation */
diff --git a/src/Actions/Unpublish.php b/src/Actions/Unpublish.php
index f2d6ab367d..2d27e0f8dc 100644
--- a/src/Actions/Unpublish.php
+++ b/src/Actions/Unpublish.php
@@ -9,7 +9,7 @@ class Unpublish extends Action
{
public function visibleTo($item)
{
- return $item instanceof Entry && $item->published();
+ return $this->context['view'] === 'list' && $item instanceof Entry && $item->published();
}
public function visibleToBulk($items)
diff --git a/src/Http/Controllers/CP/ActionController.php b/src/Http/Controllers/CP/ActionController.php
index 9247cb17d1..59c4388e9b 100644
--- a/src/Http/Controllers/CP/ActionController.php
+++ b/src/Http/Controllers/CP/ActionController.php
@@ -5,6 +5,7 @@
use Illuminate\Http\Request;
use Statamic\Facades\Action;
use Statamic\Facades\User;
+use Statamic\Support\Arr;
use Symfony\Component\HttpFoundation\Response;
abstract class ActionController extends CpController
@@ -38,16 +39,25 @@ public function run(Request $request)
$response = $action->run($items, $values);
if ($redirect = $action->redirect($items, $values)) {
- return ['redirect' => $redirect];
+ return [
+ 'redirect' => $redirect,
+ 'bypassesDirtyWarning' => $action->bypassesDirtyWarning(),
+ ];
} elseif ($download = $action->download($items, $values)) {
return $download instanceof Response ? $download : response()->download($download);
}
if (is_string($response)) {
- return ['message' => $response];
+ $response = ['message' => $response];
}
- return $response ?: [];
+ $response = $response ?: [];
+
+ if (Arr::get($context, 'view') === 'form') {
+ $response['data'] = $this->getItemData($items->first(), $context);
+ }
+
+ return $response;
}
public function bulkActions(Request $request)
@@ -65,4 +75,6 @@ public function bulkActions(Request $request)
}
abstract protected function getSelectedItems($items, $context);
+
+ abstract protected function getItemData($item, $context): array;
}
diff --git a/src/Http/Controllers/CP/Collections/EntriesController.php b/src/Http/Controllers/CP/Collections/EntriesController.php
index 9fb5891bd4..b0d41f87c9 100644
--- a/src/Http/Controllers/CP/Collections/EntriesController.php
+++ b/src/Http/Controllers/CP/Collections/EntriesController.php
@@ -7,6 +7,7 @@
use Statamic\Contracts\Entries\Entry as EntryContract;
use Statamic\CP\Breadcrumbs;
use Statamic\Exceptions\BlueprintNotFoundException;
+use Statamic\Facades\Action;
use Statamic\Facades\Asset;
use Statamic\Facades\Entry;
use Statamic\Facades\Site;
@@ -156,6 +157,7 @@ public function edit(Request $request, $collection, $entry)
'canManagePublishState' => User::current()->can('publish', $entry),
'previewTargets' => $collection->previewTargets()->all(),
'autosaveInterval' => $collection->autosaveInterval(),
+ 'itemActions' => Action::for($entry, ['collection' => $collection->handle(), 'view' => 'form']),
];
if ($request->wantsJson()) {
diff --git a/src/Http/Controllers/CP/Collections/EntryActionController.php b/src/Http/Controllers/CP/Collections/EntryActionController.php
index 888687ee6e..f6a7f909f8 100644
--- a/src/Http/Controllers/CP/Collections/EntryActionController.php
+++ b/src/Http/Controllers/CP/Collections/EntryActionController.php
@@ -2,15 +2,33 @@
namespace Statamic\Http\Controllers\CP\Collections;
+use Statamic\Facades\Action;
use Statamic\Facades\Entry;
use Statamic\Http\Controllers\CP\ActionController;
+use Statamic\Http\Resources\CP\Entries\Entry as EntryResource;
class EntryActionController extends ActionController
{
+ use ExtractsFromEntryFields;
+
protected function getSelectedItems($items, $context)
{
return $items->map(function ($item) {
return Entry::find($item);
});
}
+
+ protected function getItemData($entry, $context): array
+ {
+ $entry = $entry->fresh();
+
+ $blueprint = $entry->blueprint();
+
+ [$values] = $this->extractFromFields($entry, $blueprint);
+
+ return array_merge((new EntryResource($entry))->resolve()['data'], [
+ 'values' => $values,
+ 'itemActions' => Action::for($entry, $context),
+ ]);
+ }
}
diff --git a/src/Http/Controllers/CP/Taxonomies/ExtractsFromTermFields.php b/src/Http/Controllers/CP/Taxonomies/ExtractsFromTermFields.php
new file mode 100644
index 0000000000..71c84d63e9
--- /dev/null
+++ b/src/Http/Controllers/CP/Taxonomies/ExtractsFromTermFields.php
@@ -0,0 +1,27 @@
+values() would have given us.
+ $values = $term->inDefaultLocale()->data()->merge(
+ $term->data()
+ );
+
+ $fields = $blueprint
+ ->fields()
+ ->addValues($values->all())
+ ->preProcess();
+
+ $values = $fields->values()->merge([
+ 'title' => $term->value('title'),
+ 'slug' => $term->slug(),
+ ]);
+
+ return [$values->all(), $fields->meta()];
+ }
+}
diff --git a/src/Http/Controllers/CP/Taxonomies/TermActionController.php b/src/Http/Controllers/CP/Taxonomies/TermActionController.php
index 91d2304ebd..3b04b2eb80 100644
--- a/src/Http/Controllers/CP/Taxonomies/TermActionController.php
+++ b/src/Http/Controllers/CP/Taxonomies/TermActionController.php
@@ -2,15 +2,33 @@
namespace Statamic\Http\Controllers\CP\Taxonomies;
+use Statamic\Facades\Action;
use Statamic\Facades\Term;
use Statamic\Http\Controllers\CP\ActionController;
+use Statamic\Http\Resources\CP\Taxonomies\Term as TermResource;
class TermActionController extends ActionController
{
+ use ExtractsFromTermFields;
+
protected function getSelectedItems($items, $context)
{
return $items->map(function ($item) {
return Term::find($item);
})->filter();
}
+
+ protected function getItemData($term, $context): array
+ {
+ $term = $term->fresh();
+
+ $blueprint = $term->blueprint();
+
+ [$values] = $this->extractFromFields($term, $blueprint);
+
+ return array_merge((new TermResource($term))->resolve()['data'], [
+ 'values' => $values,
+ 'itemActions' => Action::for($term, $context),
+ ]);
+ }
}
diff --git a/src/Http/Controllers/CP/Taxonomies/TermsController.php b/src/Http/Controllers/CP/Taxonomies/TermsController.php
index f1a8516db2..77446b793d 100644
--- a/src/Http/Controllers/CP/Taxonomies/TermsController.php
+++ b/src/Http/Controllers/CP/Taxonomies/TermsController.php
@@ -5,6 +5,7 @@
use Illuminate\Http\Request;
use Statamic\Contracts\Taxonomies\Term as TermContract;
use Statamic\CP\Breadcrumbs;
+use Statamic\Facades\Action;
use Statamic\Facades\Asset;
use Statamic\Facades\Site;
use Statamic\Facades\Term;
@@ -19,7 +20,8 @@
class TermsController extends CpController
{
- use QueriesFilters;
+ use ExtractsFromTermFields,
+ QueriesFilters;
public function index(FilteredRequest $request, $taxonomy)
{
@@ -141,6 +143,7 @@ public function edit(Request $request, $taxonomy, $term)
'revisionsEnabled' => $term->revisionsEnabled(),
'breadcrumbs' => $this->breadcrumbs($taxonomy),
'previewTargets' => $taxonomy->previewTargets()->all(),
+ 'itemActions' => Action::for($term, ['taxonomy' => $taxonomy->handle(), 'view' => 'form']),
];
if ($request->wantsJson()) {
@@ -204,8 +207,15 @@ public function update(Request $request, $taxonomy, $term, $site)
$saved = $term->updateLastModified(User::current())->save();
}
+ [$values] = $this->extractFromFields($term, $term->blueprint());
+
return (new TermResource($term))
- ->additional(['saved' => $saved]);
+ ->additional([
+ 'saved' => $saved,
+ 'data' => [
+ 'values' => $values,
+ ],
+ ]);
}
public function create(Request $request, $taxonomy, $site)
@@ -313,27 +323,6 @@ public function store(Request $request, $taxonomy, $site)
->additional(['saved' => $saved]);
}
- protected function extractFromFields($term, $blueprint)
- {
- // The values should only be data merged with the origin data.
- // We don't want injected taxonomy values, which $term->values() would have given us.
- $values = $term->inDefaultLocale()->data()->merge(
- $term->data()
- );
-
- $fields = $blueprint
- ->fields()
- ->addValues($values->all())
- ->preProcess();
-
- $values = $fields->values()->merge([
- 'title' => $term->value('title'),
- 'slug' => $term->slug(),
- ]);
-
- return [$values->all(), $fields->meta()];
- }
-
protected function extractAssetsFromValues($values)
{
return collect($values)
diff --git a/src/Http/Controllers/CP/Users/ExtractsFromUserFields.php b/src/Http/Controllers/CP/Users/ExtractsFromUserFields.php
new file mode 100644
index 0000000000..31ff82e054
--- /dev/null
+++ b/src/Http/Controllers/CP/Users/ExtractsFromUserFields.php
@@ -0,0 +1,22 @@
+data()
+ ->merge($user->computedData())
+ ->merge(['email' => $user->email()]);
+
+ $fields = $blueprint
+ ->removeField('password')
+ ->removeField('password_confirmation')
+ ->fields()
+ ->addValues($values->all())
+ ->preProcess();
+
+ return [$fields->values()->all(), $fields->meta()->all()];
+ }
+}
diff --git a/src/Http/Controllers/CP/Users/UserActionController.php b/src/Http/Controllers/CP/Users/UserActionController.php
index ca2b83c499..4e715d0e08 100644
--- a/src/Http/Controllers/CP/Users/UserActionController.php
+++ b/src/Http/Controllers/CP/Users/UserActionController.php
@@ -2,15 +2,31 @@
namespace Statamic\Http\Controllers\CP\Users;
+use Statamic\Facades\Action;
use Statamic\Facades\User;
use Statamic\Http\Controllers\CP\ActionController;
class UserActionController extends ActionController
{
+ use ExtractsFromUserFields;
+
protected function getSelectedItems($items, $context)
{
return $items->map(function ($item) {
return User::find($item);
});
}
+
+ protected function getItemData($user, $context): array
+ {
+ $blueprint = $user->blueprint();
+
+ [$values] = $this->extractFromFields($user, $blueprint);
+
+ return [
+ 'title' => $user->title(),
+ 'values' => array_merge($values, ['id' => $user->id()]),
+ 'itemActions' => Action::for($user, $context),
+ ];
+ }
}
diff --git a/src/Http/Controllers/CP/Users/UsersController.php b/src/Http/Controllers/CP/Users/UsersController.php
index 792fd05f68..61e2424417 100644
--- a/src/Http/Controllers/CP/Users/UsersController.php
+++ b/src/Http/Controllers/CP/Users/UsersController.php
@@ -6,6 +6,7 @@
use Statamic\Auth\Passwords\PasswordReset;
use Statamic\Contracts\Auth\User as UserContract;
use Statamic\Exceptions\NotFoundHttpException;
+use Statamic\Facades\Action;
use Statamic\Facades\CP\Toast;
use Statamic\Facades\Scope;
use Statamic\Facades\Search;
@@ -22,7 +23,8 @@
class UsersController extends CpController
{
- use QueriesFilters;
+ use ExtractsFromUserFields,
+ QueriesFilters;
/**
* @var UserContract
@@ -235,21 +237,12 @@ public function edit(Request $request, $user)
$blueprint->ensureField('super', ['type' => 'toggle', 'display' => __('permissions.super')]);
}
- $values = $user->data()
- ->merge($user->computedData())
- ->merge(['email' => $user->email()]);
-
- $fields = $blueprint
- ->removeField('password')
- ->removeField('password_confirmation')
- ->fields()
- ->addValues($values->all())
- ->preProcess();
+ [$values, $meta] = $this->extractFromFields($user, $blueprint);
$viewData = [
'title' => $user->email(),
- 'values' => $fields->values()->all(),
- 'meta' => $fields->meta(),
+ 'values' => array_merge($values, ['id' => $user->id()]),
+ 'meta' => $meta,
'blueprint' => $user->blueprint()->toPublishArray(),
'reference' => $user->reference(),
'actions' => [
@@ -259,6 +252,7 @@ public function edit(Request $request, $user)
],
'canEditPassword' => User::fromUser($request->user())->can('editPassword', $user),
'requiresCurrentPassword' => $request->user()->id === $user->id(),
+ 'itemActions' => Action::for($user, ['view' => 'form']),
];
if ($request->wantsJson()) {
@@ -274,7 +268,7 @@ public function update(Request $request, $user)
$this->authorize('edit', $user);
- $fields = $user->blueprint()->fields()->except(['password'])->addValues($request->all());
+ $fields = $user->blueprint()->fields()->except(['password'])->addValues($request->except('id'));
$fields
->validator()
@@ -304,9 +298,14 @@ public function update(Request $request, $user)
$save = $user->save();
+ [$values] = $this->extractFromFields($user, $user->blueprint());
+
return [
'title' => $user->title(),
'saved' => is_bool($save) ? $save : true,
+ 'data' => [
+ 'values' => $values,
+ ],
];
}
}
diff --git a/src/Http/Resources/CP/Assets/Asset.php b/src/Http/Resources/CP/Assets/Asset.php
index 49c56cf8fa..4e0ff2c437 100644
--- a/src/Http/Resources/CP/Assets/Asset.php
+++ b/src/Http/Resources/CP/Assets/Asset.php
@@ -53,6 +53,7 @@ public function toArray($request)
'actions' => Action::for($this->resource, [
'container' => $this->container()->handle(),
'folder' => $this->folder(),
+ 'view' => 'form',
]),
'blueprint' => $this->blueprint()->toPublishArray(),