diff --git a/Classes/Domain/Model/Changes/CopyAfter.php b/Classes/Domain/Model/Changes/CopyAfter.php index 30d3dfc638..9030f845e7 100644 --- a/Classes/Domain/Model/Changes/CopyAfter.php +++ b/Classes/Domain/Model/Changes/CopyAfter.php @@ -70,7 +70,9 @@ public function apply(): void ), $subject->workspaceName, $subject, - OriginDimensionSpacePoint::fromDimensionSpacePoint($subject->dimensionSpacePoint), + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($previousSibling->dimensionSpacePoint), $parentNodeOfPreviousSibling->aggregateId, $succeedingSibling?->aggregateId ); diff --git a/Classes/Domain/Model/Changes/CopyBefore.php b/Classes/Domain/Model/Changes/CopyBefore.php index 87e0881b6d..b42fd16df8 100644 --- a/Classes/Domain/Model/Changes/CopyBefore.php +++ b/Classes/Domain/Model/Changes/CopyBefore.php @@ -65,7 +65,9 @@ public function apply(): void ), $subject->workspaceName, $subject, - OriginDimensionSpacePoint::fromDimensionSpacePoint($subject->dimensionSpacePoint), + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($succeedingSibling->dimensionSpacePoint), $parentNodeOfSucceedingSibling->aggregateId, $succeedingSibling->aggregateId ); diff --git a/Classes/Domain/Model/Changes/CopyInto.php b/Classes/Domain/Model/Changes/CopyInto.php index bdd1188b7a..e8cd6f1a68 100644 --- a/Classes/Domain/Model/Changes/CopyInto.php +++ b/Classes/Domain/Model/Changes/CopyInto.php @@ -74,7 +74,9 @@ public function apply(): void ), $subject->workspaceName, $subject, - OriginDimensionSpacePoint::fromDimensionSpacePoint($subject->dimensionSpacePoint), + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($parentNode->dimensionSpacePoint), $parentNode->aggregateId, null ); diff --git a/Classes/Domain/Model/Changes/MoveAfter.php b/Classes/Domain/Model/Changes/MoveAfter.php index 8f80c28472..846d7aeca8 100644 --- a/Classes/Domain/Model/Changes/MoveAfter.php +++ b/Classes/Domain/Model/Changes/MoveAfter.php @@ -13,9 +13,13 @@ */ use InvalidArgumentException; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; -use Neos\Neos\Ui\Domain\Model\Feedback\Operations\RemoveNode; +use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; use Neos\Neos\Ui\Domain\Model\Feedback\Operations\UpdateNodeInfo; /** @@ -71,19 +75,49 @@ public function apply(): void $hasEqualParentNode = $parentNode->aggregateId ->equals($parentNodeOfPreviousSibling->aggregateId); - $contentRepository = $this->contentRepositoryRegistry->get($subject->contentRepositoryId); - $command = MoveNodeAggregate::create( - $subject->workspaceName, - $subject->dimensionSpacePoint, - $subject->aggregateId, - RelationDistributionStrategy::STRATEGY_GATHER_ALL, - $hasEqualParentNode ? null : $parentNodeOfPreviousSibling->aggregateId, - $precedingSibling->aggregateId, - $succeedingSibling?->aggregateId, - ); - $contentRepository->handle($command); + if (!$precedingSibling->dimensionSpacePoint->equals($subject->dimensionSpacePoint)) { + // WORKAROUND for MOVE ACROSS DIMENSIONS: + // - we want it to work like a copy/paste, followed by an original delete. + // - This is to ensure the user can use it as expected from text editors, where context + // is not preserved during cut/paste. + // - LATERON, we need to expose CreateNodeVariant (which creates connected variants) in the UI as well. + $command = CopyNodesRecursively::createFromSubgraphAndStartNode( + $contentRepository->getContentGraph($subject->workspaceName)->getSubgraph( + $subject->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ), + $subject->workspaceName, + $subject, + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($precedingSibling->dimensionSpacePoint), + $parentNodeOfPreviousSibling->aggregateId, + $succeedingSibling?->aggregateId + ); + + $contentRepository->handle($command); + + $command = RemoveNodeAggregate::create( + $subject->workspaceName, + $subject->aggregateId, + $subject->dimensionSpacePoint, + NodeVariantSelectionStrategy::STRATEGY_ALL_SPECIALIZATIONS, + ); + $contentRepository->handle($command); + } else { + $command = MoveNodeAggregate::create( + $subject->workspaceName, + $subject->dimensionSpacePoint, + $subject->aggregateId, + RelationDistributionStrategy::STRATEGY_GATHER_ALL, + $hasEqualParentNode ? null : $parentNodeOfPreviousSibling->aggregateId, + $precedingSibling->aggregateId, + $succeedingSibling?->aggregateId, + ); + $contentRepository->handle($command); + } $updateParentNodeInfo = new UpdateNodeInfo(); $updateParentNodeInfo->setNode($parentNodeOfPreviousSibling); diff --git a/Classes/Domain/Model/Changes/MoveBefore.php b/Classes/Domain/Model/Changes/MoveBefore.php index 879c52fc35..e0e69add59 100644 --- a/Classes/Domain/Model/Changes/MoveBefore.php +++ b/Classes/Domain/Model/Changes/MoveBefore.php @@ -12,9 +12,13 @@ * source code. */ +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; -use Neos\Neos\Ui\Domain\Model\Feedback\Operations\RemoveNode; +use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; use Neos\Neos\Ui\Domain\Model\Feedback\Operations\UpdateNodeInfo; /** @@ -69,19 +73,49 @@ public function apply(): void $contentRepository = $this->contentRepositoryRegistry->get($subject->contentRepositoryId); - $contentRepository->handle( - MoveNodeAggregate::create( + if (!$succeedingSibling->dimensionSpacePoint->equals($subject->dimensionSpacePoint)) { + // WORKAROUND for MOVE ACROSS DIMENSIONS: + // - we want it to work like a copy/paste, followed by an original delete. + // - This is to ensure the user can use it as expected from text editors, where context + // is not preserved during cut/paste. + // - LATERON, we need to expose CreateNodeVariant (which creates connected variants) in the UI as well. + $command = CopyNodesRecursively::createFromSubgraphAndStartNode( + $contentRepository->getContentGraph($subject->workspaceName)->getSubgraph( + $subject->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ), + $subject->workspaceName, + $subject, + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($succeedingSibling->dimensionSpacePoint), + $succeedingSiblingParent->aggregateId, + $succeedingSibling->aggregateId + ); + $contentRepository->handle($command); + + $command = RemoveNodeAggregate::create( $subject->workspaceName, - $subject->dimensionSpacePoint, $subject->aggregateId, - RelationDistributionStrategy::STRATEGY_GATHER_ALL, - $hasEqualParentNode - ? null - : $succeedingSiblingParent->aggregateId, - $precedingSibling?->aggregateId, - $succeedingSibling->aggregateId, - ) - ); + $subject->dimensionSpacePoint, + NodeVariantSelectionStrategy::STRATEGY_ALL_SPECIALIZATIONS, + ); + $contentRepository->handle($command); + } else { + $contentRepository->handle( + MoveNodeAggregate::create( + $subject->workspaceName, + $subject->dimensionSpacePoint, + $subject->aggregateId, + RelationDistributionStrategy::STRATEGY_GATHER_ALL, + $hasEqualParentNode + ? null + : $succeedingSiblingParent->aggregateId, + $precedingSibling?->aggregateId, + $succeedingSibling->aggregateId, + ) + ); + } $updateParentNodeInfo = new UpdateNodeInfo(); $updateParentNodeInfo->setNode($succeedingSiblingParent); diff --git a/Classes/Domain/Model/Changes/MoveInto.php b/Classes/Domain/Model/Changes/MoveInto.php index 75474d0563..2f37946271 100644 --- a/Classes/Domain/Model/Changes/MoveInto.php +++ b/Classes/Domain/Model/Changes/MoveInto.php @@ -12,9 +12,14 @@ * source code. */ +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; +use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; use Neos\Neos\Ui\Domain\Model\Feedback\Operations\UpdateNodeInfo; /** @@ -78,15 +83,45 @@ public function apply(): void ->equals($parentNode->aggregateId); $contentRepository = $this->contentRepositoryRegistry->get($subject->contentRepositoryId); - $contentRepository->handle( - MoveNodeAggregate::create( + if (!$parentNode->dimensionSpacePoint->equals($subject->dimensionSpacePoint)) { + // WORKAROUND for MOVE ACROSS DIMENSIONS: + // - we want it to work like a copy/paste, followed by an original delete. + // - This is to ensure the user can use it as expected from text editors, where context + // is not preserved during cut/paste. + // - LATERON, we need to expose CreateNodeVariant (which creates connected variants) in the UI as well. + $command = CopyNodesRecursively::createFromSubgraphAndStartNode( + $contentRepository->getContentGraph($subject->workspaceName)->getSubgraph( + $subject->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ), + $subject->workspaceName, + $subject, + // NOTE: in order to be able to copy/paste across dimensions, we need to use + // the TARGET NODE's DimensionSpacePoint to create the node in the target dimension. + OriginDimensionSpacePoint::fromDimensionSpacePoint($parentNode->dimensionSpacePoint), + $parentNode->aggregateId, + null + ); + $contentRepository->handle($command); + + $command = RemoveNodeAggregate::create( $subject->workspaceName, - $subject->dimensionSpacePoint, $subject->aggregateId, - RelationDistributionStrategy::STRATEGY_GATHER_ALL, - $hasEqualParentNode ? null : $parentNode->aggregateId, - ) - ); + $subject->dimensionSpacePoint, + NodeVariantSelectionStrategy::STRATEGY_ALL_SPECIALIZATIONS, + ); + $contentRepository->handle($command); + } else { + $contentRepository->handle( + MoveNodeAggregate::create( + $subject->workspaceName, + $subject->dimensionSpacePoint, + $subject->aggregateId, + RelationDistributionStrategy::STRATEGY_GATHER_ALL, + $hasEqualParentNode ? null : $parentNode->aggregateId, + ) + ); + } $updateParentNodeInfo = new UpdateNodeInfo(); $updateParentNodeInfo->setNode($parentNode); diff --git a/Tests/IntegrationTests/docker-compose.neos-dev-instance.yaml b/Tests/IntegrationTests/docker-compose.neos-dev-instance.yaml index b0dd656f7f..fa822f2292 100644 --- a/Tests/IntegrationTests/docker-compose.neos-dev-instance.yaml +++ b/Tests/IntegrationTests/docker-compose.neos-dev-instance.yaml @@ -14,12 +14,15 @@ services: # Enable GD PHP_EXTENSION_GD: 1 COMPOSER_CACHE_DIR: /home/circleci/.composer/cache + DB_HOST: db db: image: mariadb:10.11 environment: MYSQL_DATABASE: neos MYSQL_ROOT_PASSWORD: not_a_real_password + ports: + - 13309:3306 command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] volumes: composer_cache: diff --git a/Tests/IntegrationTests/pageModel.js b/Tests/IntegrationTests/pageModel.js index 1a3c31e6a5..c018ad505f 100644 --- a/Tests/IntegrationTests/pageModel.js +++ b/Tests/IntegrationTests/pageModel.js @@ -59,7 +59,7 @@ export class PublishDropDown { static publishDropdownDiscardAll = ReactSelector('PublishDropDown ContextDropDownContents').find('button').withText('Discard all'); - static publishDropdownPublishAll = ReactSelector('PublishDropDown ShallowDropDownContents').find('button').withText('Publish all'); + static publishDropdownPublishAll = ReactSelector('PublishDropDown ContextDropDownContents').find('button').withText('Publish all'); static async discardAll() { const $discardAllBtn = Selector(this.publishDropdownDiscardAll); diff --git a/Tests/IntegrationTests/start-neos-dev-instance.sh b/Tests/IntegrationTests/start-neos-dev-instance.sh index c018e135a8..0733f3f266 100644 --- a/Tests/IntegrationTests/start-neos-dev-instance.sh +++ b/Tests/IntegrationTests/start-neos-dev-instance.sh @@ -41,6 +41,7 @@ dc exec -T php bash <<-'BASH' ./flow flow:cache:warmup ./flow doctrine:migrate ./flow user:create --username=admin --password=admin --first-name=John --last-name=Doe --roles=Administrator || true + ./flow user:create --username=editor --password=editor --first-name=Some --last-name=FooBarEditor --roles=Editor || true BASH echo "" @@ -68,7 +69,11 @@ dc exec -T php bash <<-BASH if ./flow site:list | grep -q 'Node name'; then ./flow site:prune '*' fi - ./flow site:import --package-key=Neos.TestSite + ./flow cr:setup + ./flow cr:setup --content-repository onedimension + ./flow cr:setup --content-repository twodimensions + ./flow cr:import --content-repository onedimension Packages/Sites/Neos.Test.OneDimension/Resources/Private/Content + ./flow site:create neos-test-onedimension Neos.Test.OneDimension Neos.TestNodeTypes:Document.HomePage ./flow resource:publish BASH @@ -85,5 +90,5 @@ dc exec -T php bash <<-'BASH' # enable changes of the Neos.TestNodeTypes outside of the container to appear in the container via sym link to mounted volume rm -rf /usr/src/app/TestDistribution/Packages/Application/Neos.TestNodeTypes - ln -s /usr/src/neos-ui/Tests/IntegrationTests/SharedNodeTypesPackage/ /usr/src/app/TestDistribution/Packages/Application/Neos.TestNodeTypes + ln -s /usr/src/neos-ui/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes /usr/src/app/TestDistribution/Packages/Application/Neos.TestNodeTypes BASH diff --git a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js index f2be3beb4a..6a2360ca40 100644 --- a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js +++ b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js @@ -16,7 +16,7 @@ const searchOptions = (searchTerm, processedSelectBoxOptions) => })) export default class DimensionSelector extends PureComponent { static propTypes = { - icon: PropTypes.string.isRequired, + icon: PropTypes.string, dimensionLabel: PropTypes.string.isRequired, presets: PropTypes.object.isRequired, activePreset: PropTypes.string.isRequired,