diff --git a/src/actions/AddComponent.php b/src/actions/AddComponent.php index 40534c7..134df7b 100644 --- a/src/actions/AddComponent.php +++ b/src/actions/AddComponent.php @@ -16,24 +16,17 @@ public function handle( string $type ): Component { // Check if we can be added here - if ($path) { - $parentId = last(explode('/', $path)); - if (! empty($parentId)) { - $parent = Component::find()->where([ - 'id' => $parentId, - 'elementId' => $elementId, - 'fieldId' => $fieldId, - ])->one(); - if (! $parent->getType()->getSlotDefinition($slot)->allows($type)) { - throw new \RuntimeException('Not allowed here'); - } - } + $parent = (new GetParentFromPath)->handle($elementId, $fieldId, $path); + if ($parent && ! $parent->getType()->getSlotDefinition($slot)->allows($type)) { + throw new \RuntimeException('Not allowed here'); } + // Create the data $componentData = new ComponentData; $componentData->type = $type; $componentData->save(); + // Create the component $component = new Component; $component->elementId = $elementId; $component->fieldId = $fieldId; @@ -44,6 +37,7 @@ public function handle( $component->sortOrder = $sortOrder; $component->save(); + // Refresh the instance so we have the correct data reference $component->refresh(); return $component; diff --git a/src/actions/GetParentFromPath.php b/src/actions/GetParentFromPath.php new file mode 100644 index 0000000..0f87df7 --- /dev/null +++ b/src/actions/GetParentFromPath.php @@ -0,0 +1,19 @@ + $elementId, + 'fieldId' => $fieldId, + 'id' => $parentId, + ]) : null; + } +} diff --git a/src/actions/MakeModelFromArray.php b/src/actions/MakeModelFromArray.php index a92d656..4697848 100644 --- a/src/actions/MakeModelFromArray.php +++ b/src/actions/MakeModelFromArray.php @@ -6,6 +6,21 @@ use markhuot\keystone\db\ActiveRecord; use yii\base\ModelEvent; +/** + * Recursively create models from an array. + * + * ```php + * (new MakeModelFromArray)->handle(Component::class, ['id' => 123], errorOnMissing: true, createOnMissing: false) + * ``` + * + * That would search for component 123 and error out if it could not be found. + * + * ```php + * (new MakeModelFromArray)->handle(PostData::class, ['foo' => 'bar'], errorOnMissing: false, createOnMissing: true) + * ``` + * + * That would instantiate a new PostData class with the `foo` property set to `bar`. + */ class MakeModelFromArray { /** diff --git a/src/actions/MapExpandedAttributeValue.php b/src/actions/MapExpandedAttributeValue.php index b668efe..7da5558 100644 --- a/src/actions/MapExpandedAttributeValue.php +++ b/src/actions/MapExpandedAttributeValue.php @@ -2,6 +2,21 @@ namespace markhuot\keystone\actions; +/** + * Takes an array of either CSS "shorthand" or "expanded" values and returns a css-ish + * representation of them. + * + * For example, given `->handle(['useExpanded' => false, 'shorthand' => 3], 'margin')` you + * would end up with `['margin' => 3]`. + * + * If you `useExpanded` like this, `->handle(['useExpanded' => true, 'expanded' => ['top' => 3]], 'margin')` + * then you would end up with `['margin-top' => 3]`. + * + * Notice that the `$property` attribute is automatically converted to `{$property}-top` or `{$property}-left` + * for the expanded values. If you need to customize that you can pass in an `$expandedProperty` attribute + * with `&` as a placeholder for the direction. E.g., `->handle([...], 'border', 'border-&-width')` would + * return `['border-top-width' => 3]` if `'top' => 3` was passed. + */ class MapExpandedAttributeValue { public function handle(?array $value, string $property, string $expandedProperty = null) diff --git a/src/actions/MoveComponent.php b/src/actions/MoveComponent.php index 3a92b9c..bff29d3 100644 --- a/src/actions/MoveComponent.php +++ b/src/actions/MoveComponent.php @@ -2,18 +2,19 @@ namespace markhuot\keystone\actions; +use markhuot\keystone\enums\MoveComponentPosition; use markhuot\keystone\models\Component; use yii\db\Expression; class MoveComponent { - public function handle(Component $source, Component $target, string $position, string $slotName = null) + public function handle(Component $source, Component $target, MoveComponentPosition $position, string $slotName = null) { - if ($position === 'above' || $position === 'below') { + if ($position === MoveComponentPosition::BEFORE || $position === MoveComponentPosition::AFTER) { $this->handleAboveOrBelow($source, $target, $position); } - if ($position === 'beforeend') { + if ($position === MoveComponentPosition::BEFOREEND) { $this->handleBeforeEnd($source, $target, $slotName); } } @@ -39,7 +40,7 @@ public function handleAboveOrBelow(Component $source, Component $target, string $target->refresh(); // make room for the insertion - if ($position === 'above') { + if ($position === MoveComponentPosition::BEFORE) { Component::updateAll([ 'sortOrder' => new Expression('sortOrder + 1'), ], ['and', @@ -50,7 +51,7 @@ public function handleAboveOrBelow(Component $source, Component $target, string ['>=', 'sortOrder', $target->sortOrder], ]); } - if ($position === 'below') { + if ($position === MoveComponentPosition::AFTER) { Component::updateAll([ 'sortOrder' => new Expression('sortOrder + 1'), ], ['and', @@ -70,7 +71,7 @@ public function handleAboveOrBelow(Component $source, Component $target, string // move the source to the target $source->path = $target->path; $source->slot = $target->slot; - $source->sortOrder = $position == 'above' ? $target->sortOrder - 1 : $target->sortOrder + 1; + $source->sortOrder = $position == MoveComponentPosition::BEFORE ? $target->sortOrder - 1 : $target->sortOrder + 1; $source->save(); // move any children of the source diff --git a/src/actions/NormalizeFieldDataForComponent.php b/src/actions/NormalizeFieldDataForComponent.php index e7d509b..45e64d3 100644 --- a/src/actions/NormalizeFieldDataForComponent.php +++ b/src/actions/NormalizeFieldDataForComponent.php @@ -26,7 +26,7 @@ public function handle(mixed $value, string $handle) // If the field is editable, return an editable div if ($field?->getBehavior('inlineEdit')) { - if ($field->isEditableInPreview() && (Craft::$app->getRequest()->isPreview() ?? false)) { + if ($field->isEditableInLivePreview() && Craft::$app->getRequest()->getQueryParam('x-craft-live-preview') !== null) { return new InlineEditData($this->component, $field, $value); } } diff --git a/src/behaviors/InlineEditBehavior.php b/src/behaviors/InlineEditBehavior.php index 02bdccd..a8cd112 100644 --- a/src/behaviors/InlineEditBehavior.php +++ b/src/behaviors/InlineEditBehavior.php @@ -6,15 +6,15 @@ class InlineEditBehavior extends Behavior { - protected bool $editableInPreview = false; + protected bool $editableInLivePreview = false; - public function setEditableInPreview(bool $editable=true) + public function setEditableInLivePreview(bool $editable=true) { - $this->editableInPreview = $editable; + $this->editableInLivePreview = $editable; } - public function isEditableInPreview() + public function isEditableInLivePreview() { - return $this->editableInPreview; + return $this->editableInLivePreview; } } diff --git a/src/controllers/ComponentsController.php b/src/controllers/ComponentsController.php index 4958bb7..f0d4ef3 100644 --- a/src/controllers/ComponentsController.php +++ b/src/controllers/ComponentsController.php @@ -8,6 +8,7 @@ use markhuot\keystone\actions\DeleteComponent; use markhuot\keystone\actions\EditComponentData; use markhuot\keystone\actions\GetComponentType; +use markhuot\keystone\actions\GetParentFromPath; use markhuot\keystone\actions\MoveComponent; use markhuot\keystone\behaviors\BodyParamObjectBehavior; use markhuot\keystone\models\Component; @@ -29,8 +30,7 @@ public function actionAdd() $path = $this->request->getQueryParam('path'); $slot = $this->request->getQueryParam('slot'); $sortOrder = $this->request->getRequiredQueryParam('sortOrder'); - $parentId = last(explode('/', $path)); - $parent = $parentId ? Component::findOne(['elementId' => $elementId, 'fieldId' => $fieldId, 'id' => $parentId]) : null; + $parent = (new GetParentFromPath)->handle($elementId, $fieldId, $path); return $this->asCpScreen() ->title('Add component') diff --git a/src/enums/MoveComponentPosition.php b/src/enums/MoveComponentPosition.php new file mode 100644 index 0000000..c0c9c8c --- /dev/null +++ b/src/enums/MoveComponentPosition.php @@ -0,0 +1,10 @@ +{% endexport %} diff --git a/src/templates/inline-edit.twig b/src/templates/inline-edit.twig index e2bb13a..efcdf4a 100644 --- a/src/templates/inline-edit.twig +++ b/src/templates/inline-edit.twig @@ -1,20 +1,16 @@ -
- {{ value }} -
+{{ value }} -{% if craft.app.getRequest().isPreview() %} - {% js %} - document.addEventListener('input', event => { - const params = JSON.parse(event.target.dataset.id); - fetch("{{ actionUrl('keystone/components/update') }}", { - method: 'post', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'X-CSRF-Token': '{{ craft.app.request.csrfToken }}', - }, - body: JSON.stringify({...params, fields: { {{ field.handle }}: event.target.innerHTML}}), - }); +{% js %} + document.addEventListener('input', event => { + const params = JSON.parse(event.target.dataset.id); + fetch("{{ actionUrl('keystone/components/update') }}", { + method: 'post', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-Token': '{{ craft.app.request.csrfToken }}', + }, + body: JSON.stringify({...params, fields: { {{ field.handle }}: event.target.innerHTML}}), }); - {% endjs %} -{% endif %} + }); +{% endjs %} diff --git a/tests/MoveComponentsTest.php b/tests/MoveComponentsTest.php index 33c3359..a5f6021 100644 --- a/tests/MoveComponentsTest.php +++ b/tests/MoveComponentsTest.php @@ -2,6 +2,7 @@ use markhuot\keystone\actions\MakeModelFromArray; use markhuot\keystone\actions\MoveComponent; +use markhuot\keystone\enums\MoveComponentPosition; use markhuot\keystone\models\Component; use markhuot\keystone\models\http\MoveComponentRequest; @@ -17,11 +18,12 @@ it('parses post data', function () { [$source, $target] = Component::factory()->count(2)->create(); - $data = new MoveComponentRequest(); - $data->load([ + $data = (new MakeModelFromArray())->handle(MoveComponentRequest::class, [ 'source' => ['id' => $source->id, 'fieldId' => $source->fieldId, 'elementId' => $source->elementId], 'target' => ['id' => $target->id, 'fieldId' => $target->fieldId, 'elementId' => $target->elementId], - ], ''); + 'position' => 'beforeend' + ]); + \markhuot\craftpest\helpers\test\dd($data); expect($data) ->errors->toBeEmpty() @@ -39,7 +41,7 @@ expect($data->errors) ->target->not->toBeNull() ->position->not->toBeNull(); -})->only(); +}); it('moves components', function () { $components = collect([ @@ -48,7 +50,7 @@ Component::factory()->create(['sortOrder' => 2]), ]); - (new MoveComponent())->handle($components[0], $components[2], 'below'); + (new MoveComponent())->handle($components[0], $components[2], MoveComponentPosition::BEFORE); $components->each->refresh(); expect($components[0])->sortOrder->toBe(2); @@ -63,7 +65,7 @@ 'targetChild' => $targetChild, ] = $this->components; - (new MoveComponent())->handle($sourceParent, $targetChild, 'below'); + (new MoveComponent())->handle($sourceParent, $targetChild, MoveComponentPosition::AFTER); $this->components->each->refresh(); expect($sourceParent) @@ -83,7 +85,7 @@ 'targetChild' => $targetChild, ] = $this->components; - (new MoveComponent())->handle($sourceGrandParent, $targetChild, 'below'); + (new MoveComponent())->handle($sourceGrandParent, $targetChild, MoveComponentPosition::AFTER); $this->components->each->refresh(); expect($sourceGrandParent) @@ -106,7 +108,7 @@ $target = Component::factory()->create(['sortOrder' => 1]), ]); - (new MoveComponent())->handle($parent, $target, 'beforeend'); + (new MoveComponent())->handle($parent, $target, MoveComponentPosition::BEFOREEND); $components->each->refresh(); expect($parent) @@ -126,7 +128,7 @@ 'targetChild' => $targetChild, ] = $this->components; - (new MoveComponent())->handle($sourceGrandParent, $targetParent, 'beforeend'); + (new MoveComponent())->handle($sourceGrandParent, $targetParent, MoveComponentPosition::BEFOREEND); $this->components->each->refresh(); expect($targetParent)->path->toBe(null)->sortOrder->toBe(0);