diff --git a/.sauce/config.yml b/.sauce/config.yml index 6b465d7be3..5d836840ea 100644 --- a/.sauce/config.yml +++ b/.sauce/config.yml @@ -24,13 +24,13 @@ suites: - "Tests/IntegrationTests/Fixtures/*/*.e2e.js" platformName: "Windows 10" screenResolution: "1280x1024" - - name: "Tests in Firefox on MacOS" - # todo use chrome here and fix ci https://github.com/neos/neos-ui/issues/3591 - browserName: "firefox" - src: - - "Tests/IntegrationTests/Fixtures/*/*.e2e.js" - platformName: "macOS 13" - screenResolution: "1440x900" + # todo use chrome here and fix ci https://github.com/neos/neos-ui/issues/3591 (but even firefox fails in ci) + # - name: "Tests in Firefox on MacOS" + # browserName: "firefox" + # src: + # - "Tests/IntegrationTests/Fixtures/*/*.e2e.js" + # platformName: "macOS 13" + # screenResolution: "1440x900" npm: dependencies: - testcafe-react-selectors diff --git a/.yarn/cache/@ckeditor-ckeditor5-engine-patch-dfc8c266c9-0ca241d6d2.zip b/.yarn/cache/@ckeditor-ckeditor5-engine-patch-dfc8c266c9-0ca241d6d2.zip new file mode 100644 index 0000000000..303f1a6d5e Binary files /dev/null and b/.yarn/cache/@ckeditor-ckeditor5-engine-patch-dfc8c266c9-0ca241d6d2.zip differ diff --git a/Classes/Application/ChangeTargetWorkspace.php b/Classes/Application/ChangeTargetWorkspace.php index 18f34e5301..07fd682fe7 100644 --- a/Classes/Application/ChangeTargetWorkspace.php +++ b/Classes/Application/ChangeTargetWorkspace.php @@ -15,9 +15,9 @@ namespace Neos\Neos\Ui\Application; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; -use Neos\Neos\FrontendRouting\NodeAddress; /** * The application layer level command DTO to communicate the change of the selected target workspace for publication diff --git a/Classes/Application/ReloadNodes/NodeMap.php b/Classes/Application/ReloadNodes/NodeMap.php index 60285ffed8..38b08093ce 100644 --- a/Classes/Application/ReloadNodes/NodeMap.php +++ b/Classes/Application/ReloadNodes/NodeMap.php @@ -14,10 +14,8 @@ namespace Neos\Neos\Ui\Application\ReloadNodes; -use Neos\ContentRepository\Core\ContentRepository; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionRequest; -use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper; /** diff --git a/Classes/Application/ReloadNodes/ReloadNodesQueryHandler.php b/Classes/Application/ReloadNodes/ReloadNodesQueryHandler.php index b9d6ad520f..2b05c6903d 100644 --- a/Classes/Application/ReloadNodes/ReloadNodesQueryHandler.php +++ b/Classes/Application/ReloadNodes/ReloadNodesQueryHandler.php @@ -19,12 +19,11 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionRequest; use Neos\Neos\Domain\Service\NodeTypeNameFactory; -use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper; /** @@ -36,9 +35,6 @@ #[Flow\Scope("singleton")] final class ReloadNodesQueryHandler { - #[Flow\Inject] - protected WorkspaceProvider $workspaceProvider; - #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; @@ -152,10 +148,8 @@ public function handle(ReloadNodesQuery $query, ActionRequest $actionRequest): R - but the logic above mirrors the old behavior better. https://github.com/neos/neos-ui/issues/3517#issuecomment-2070274053 */ - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - return new ReloadNodesQueryResult( - documentId: $nodeAddressFactory->createFromNode($documentNode), + documentId: NodeAddress::fromNode($documentNode), nodes: $nodeMapBuilder->build() ); } diff --git a/Classes/Application/ReloadNodes/ReloadNodesQueryResult.php b/Classes/Application/ReloadNodes/ReloadNodesQueryResult.php index 0775bb03f9..69fa39cc3f 100644 --- a/Classes/Application/ReloadNodes/ReloadNodesQueryResult.php +++ b/Classes/Application/ReloadNodes/ReloadNodesQueryResult.php @@ -14,8 +14,8 @@ namespace Neos\Neos\Ui\Application\ReloadNodes; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\Flow\Annotations as Flow; -use Neos\Neos\FrontendRouting\NodeAddress; /** * The application layer level query result containing all nodes the UI needs @@ -38,7 +38,7 @@ public function __construct( public function jsonSerialize(): array { return [ - 'documentId' => $this->documentId->serializeForUri(), + 'documentId' => $this->documentId->toJson(), 'nodes' => $this->nodes ]; } diff --git a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php index eb6affa906..310ebdcfa6 100644 --- a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php +++ b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php @@ -18,9 +18,8 @@ use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\Domain\Workspace\WorkspaceProvider; +use Neos\Neos\Domain\Service\WorkspacePublishingService; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; -use Neos\Neos\Ui\Infrastructure\ContentRepository\ConflictsFactory; /** * The application layer level command handler to for rebasing the workspace @@ -34,7 +33,7 @@ final class SyncWorkspaceCommandHandler protected ContentRepositoryRegistry $contentRepositoryRegistry; #[Flow\Inject] - protected WorkspaceProvider $workspaceProvider; + protected WorkspacePublishingService $workspacePublishingService; #[Flow\Inject] protected NodeLabelGeneratorInterface $nodeLabelGenerator; @@ -43,13 +42,11 @@ public function handle( SyncWorkspaceCommand $command ): SyncingSucceeded|ConflictsOccurred { try { - $workspace = $this->workspaceProvider->provideForWorkspaceName( + $this->workspacePublishingService->rebaseWorkspace( $command->contentRepositoryId, - $command->workspaceName + $command->workspaceName, + $command->rebaseErrorHandlingStrategy ); - - $workspace->rebase($command->rebaseErrorHandlingStrategy); - return new SyncingSucceeded(); } catch (WorkspaceRebaseFailed $e) { $conflictsFactory = new ConflictsFactory( diff --git a/Classes/ContentRepository/Service/NeosUiNodeService.php b/Classes/ContentRepository/Service/NeosUiNodeService.php index b0185b7cb1..07d0b8ee35 100644 --- a/Classes/ContentRepository/Service/NeosUiNodeService.php +++ b/Classes/ContentRepository/Service/NeosUiNodeService.php @@ -12,14 +12,11 @@ */ -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\Neos\FrontendRouting\NodeAddress; -use Neos\Neos\Utility\NodeTypeWithFallbackProvider; /** * @internal @@ -27,26 +24,18 @@ */ class NeosUiNodeService { - use NodeTypeWithFallbackProvider; - #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; - public function findNodeBySerializedNodeAddress(string $serializedNodeAddress, ContentRepositoryId $contentRepositoryId): ?Node + public function findNodeBySerializedNodeAddress(string $serializedNodeAddress): ?Node { - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($serializedNodeAddress); + $nodeAddress = NodeAddress::fromJsonString($serializedNodeAddress); + $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - return $subgraph->findNodeById($nodeAddress->nodeAggregateId); - } - - public function deserializeNodeAddress(string $serializedNodeAddress, ContentRepositoryId $contentRepositoryId): NodeAddress - { - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - return NodeAddressFactory::create($contentRepository)->createFromUriString($serializedNodeAddress); + return $subgraph->findNodeById($nodeAddress->aggregateId); } } diff --git a/Classes/ContentRepository/Service/WorkspaceService.php b/Classes/ContentRepository/Service/WorkspaceService.php index b1d71bfb65..43cb89155f 100644 --- a/Classes/ContentRepository/Service/WorkspaceService.php +++ b/Classes/ContentRepository/Service/WorkspaceService.php @@ -11,21 +11,17 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\Neos\Domain\Service\NodeTypeNameFactory; -use Neos\Neos\FrontendRouting\NodeAddress; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\Service\UserService as DomainUserService; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; +use Neos\Neos\Domain\Service\WorkspacePublishingService; use Neos\Neos\PendingChangesProjection\Change; -use Neos\Neos\PendingChangesProjection\ChangeFinder; -use Neos\Neos\Service\UserService; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; /** @@ -44,17 +40,8 @@ class WorkspaceService #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; - /** - * @Flow\Inject - * @var UserService - */ - protected $userService; - - /** - * @Flow\Inject - * @var DomainUserService - */ - protected $domainUserService; + #[Flow\Inject] + protected WorkspacePublishingService $workspacePublishingService; /** * Get all publishable node context paths for a workspace @@ -64,36 +51,31 @@ class WorkspaceService public function getPublishableNodeInfo(WorkspaceName $workspaceName, ContentRepositoryId $contentRepositoryId): array { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); - if (is_null($workspace) || $workspace->baseWorkspaceName === null) { - return []; - } - $changeFinder = $contentRepository->projectionState(ChangeFinder::class); - $changes = $changeFinder->findByContentStreamId($workspace->currentContentStreamId); + $pendingChanges = $this->workspacePublishingService->pendingWorkspaceChanges($contentRepositoryId, $workspaceName); /** @var array{contextPath:string,documentContextPath:string,typeOfChange:int}[] $unpublishedNodes */ $unpublishedNodes = []; - foreach ($changes as $change) { + foreach ($pendingChanges as $change) { if ($change->removalAttachmentPoint) { - $nodeAddress = new NodeAddress( - $change->contentStreamId, + $nodeAddress = NodeAddress::create( + $contentRepositoryId, + $workspaceName, $change->originDimensionSpacePoint->toDimensionSpacePoint(), - $change->nodeAggregateId, - $workspaceName + $change->nodeAggregateId ); /** * See {@see Remove::apply} -> Removal Attachment Point == closest document node. */ - $documentNodeAddress = new NodeAddress( - $change->contentStreamId, + $documentNodeAddress = NodeAddress::create( + $contentRepositoryId, + $workspaceName, $change->originDimensionSpacePoint->toDimensionSpacePoint(), - $change->removalAttachmentPoint, - $workspaceName + $change->removalAttachmentPoint ); $unpublishedNodes[] = [ - 'contextPath' => $nodeAddress->serializeForUri(), - 'documentContextPath' => $documentNodeAddress->serializeForUri(), + 'contextPath' => $nodeAddress->toJson(), + 'documentContextPath' => $documentNodeAddress->toJson(), 'typeOfChange' => $this->getTypeOfChange($change) ]; } else { @@ -106,12 +88,9 @@ public function getPublishableNodeInfo(WorkspaceName $workspaceName, ContentRepo if ($node instanceof Node) { $documentNode = $subgraph->findClosestNode($node->aggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_DOCUMENT)); if ($documentNode instanceof Node) { - $contentRepository = $this->contentRepositoryRegistry->get($documentNode->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $unpublishedNodes[] = [ - 'contextPath' => $nodeAddressFactory->createFromNode($node)->serializeForUri(), - 'documentContextPath' => $nodeAddressFactory->createFromNode($documentNode) - ->serializeForUri(), + 'contextPath' => NodeAddress::fromNode($node)->toJson(), + 'documentContextPath' => NodeAddress::fromNode($documentNode)->toJson(), 'typeOfChange' => $this->getTypeOfChange($change) ]; } @@ -124,44 +103,6 @@ public function getPublishableNodeInfo(WorkspaceName $workspaceName, ContentRepo })); } - /** - * Get allowed target workspaces for current user - * - * @return array> - */ - public function getAllowedTargetWorkspaces(ContentRepository $contentRepository): array - { - $user = $this->domainUserService->getCurrentUser(); - - $workspacesArray = []; - foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { - // FIXME: This check should be implemented through a specialized Workspace Privilege or something similar - // Skip workspace not owned by current user - if ($workspace->workspaceOwner !== null && $workspace->workspaceOwner !== $user) { - continue; - } - // Skip own personal workspace - if ($workspace->workspaceName->value === $this->userService->getPersonalWorkspaceName()) { - continue; - } - - if ($workspace->isPersonalWorkspace()) { - // Skip other personal workspaces - continue; - } - - $workspaceArray = [ - 'name' => $workspace->workspaceName->jsonSerialize(), - 'title' => $workspace->workspaceTitle->jsonSerialize(), - 'description' => $workspace->workspaceDescription->jsonSerialize(), - 'readonly' => !$this->domainUserService->currentUserCanPublishToWorkspace($workspace) - ]; - $workspacesArray[$workspace->workspaceName->jsonSerialize()] = $workspaceArray; - } - - return $workspacesArray; - } - private function getTypeOfChange(Change $change): int { $result = 0; diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php index 964e2e9c47..c785ddc95b 100644 --- a/Classes/Controller/BackendController.php +++ b/Classes/Controller/BackendController.php @@ -12,6 +12,7 @@ * source code. */ +use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; @@ -19,12 +20,10 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ActionController; use Neos\Flow\Persistence\PersistenceManagerInterface; -use Neos\Flow\Security\Context; use Neos\Neos\Domain\Repository\DomainRepository; use Neos\Neos\Domain\Repository\SiteRepository; use Neos\Neos\Domain\Service\NodeTypeNameFactory; -use Neos\Neos\Domain\Service\WorkspaceNameBuilder; -use Neos\Neos\FrontendRouting\NodeAddressFactory; +use Neos\Neos\Domain\Service\WorkspaceService; use Neos\Neos\FrontendRouting\NodeUriBuilderFactory; use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult; use Neos\Neos\Service\UserService; @@ -78,12 +77,6 @@ class BackendController extends ActionController */ protected $contentRepositoryRegistry; - /** - * @Flow\Inject - * @var Context - */ - protected $securityContext; - /** * @Flow\Inject * @var ConfigurationProviderInterface @@ -126,6 +119,12 @@ class BackendController extends ActionController */ protected $nodeUriBuilderFactory; + /** + * @Flow\Inject + * @var WorkspaceService + */ + protected $workspaceService; + /** * Displays the backend interface * @@ -137,24 +136,20 @@ public function indexAction(string $node = null) $siteDetectionResult = SiteDetectionResult::fromRequest($this->request->getHttpRequest()); $contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId); - $nodeAddress = $node !== null ? NodeAddressFactory::create($contentRepository)->createFromUriString($node) : null; - unset($node); + $nodeAddress = $node !== null ? NodeAddress::fromJsonString($node) : null; $user = $this->userService->getBackendUser(); if ($user === null) { $this->redirectToUri($this->uriBuilder->uriFor('index', [], 'Login', 'Neos.Neos')); } - $currentAccount = $this->securityContext->getAccount(); - assert($currentAccount !== null); - $workspaceName = WorkspaceNameBuilder::fromAccountIdentifier($currentAccount->getAccountIdentifier()); - try { - $contentGraph = $contentRepository->getContentGraph($workspaceName); + $workspace = $this->workspaceService->getPersonalWorkspaceForUser($siteDetectionResult->contentRepositoryId, $user->getId()); } catch (WorkspaceDoesNotExist) { // todo will cause infinite loop: https://github.com/neos/neos-development-collection/issues/4401 $this->redirectToUri($this->uriBuilder->uriFor('index', [], 'Login', 'Neos.Neos')); } + $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); $backendControllerInternals = $this->contentRepositoryRegistry->buildService( $siteDetectionResult->contentRepositoryId, @@ -185,7 +180,7 @@ public function indexAction(string $node = null) if (!$nodeAddress) { $node = $siteNode; } else { - $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); + $node = $subgraph->findNodeById($nodeAddress->aggregateId); } $this->view->setOption('title', 'Neos CMS'); @@ -231,9 +226,29 @@ public function redirectToAction(string $node): void $nodeAddress = NodeAddress::fromJsonString($node); + $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); + + $nodeInstance = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( + $nodeAddress->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + )->findNodeById($nodeAddress->aggregateId); + + $workspace = $contentRepository->findWorkspaceByName($nodeAddress->workspaceName); + + // we always want to redirect to the node in the base workspace unless we are on a root workspace in which case we stay on that (currently that will not happen) + $nodeAddressInBaseWorkspace = NodeAddress::create( + $nodeAddress->contentRepositoryId, + $workspace->baseWorkspaceName ?? $nodeAddress->workspaceName, + $nodeAddress->dimensionSpacePoint, + $nodeAddress->aggregateId + ); + + $nodeUriBuilder = $this->nodeUriBuilderFactory->forActionRequest($this->request); + $this->redirectToUri( - $this->nodeUriBuilderFactory->forActionRequest($this->request) - ->uriFor($nodeAddress) + !$nodeInstance || $nodeInstance->tags->contain(SubtreeTag::disabled()) + ? $nodeUriBuilder->previewUriFor($nodeAddressInBaseWorkspace) + : $nodeUriBuilder->uriFor($nodeAddressInBaseWorkspace) ); } } diff --git a/Classes/Controller/BackendServiceController.php b/Classes/Controller/BackendServiceController.php index 6f5ca4000d..b6c0280a24 100644 --- a/Classes/Controller/BackendServiceController.php +++ b/Classes/Controller/BackendServiceController.php @@ -17,8 +17,8 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Exception\WorkspaceIsNotEmptyException; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\FlowQuery\FlowQuery; @@ -30,10 +30,8 @@ use Neos\Flow\Mvc\View\JsonView; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Security\Context; -use Neos\Neos\Domain\Service\WorkspaceNameBuilder; -use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\FrontendRouting\NodeAddress; -use Neos\Neos\FrontendRouting\NodeAddressFactory; +use Neos\Neos\Domain\Service\WorkspacePublishingService; +use Neos\Neos\Domain\Service\WorkspaceService; use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult; use Neos\Neos\Service\UserService; use Neos\Neos\Ui\Application\ChangeTargetWorkspace; @@ -46,7 +44,6 @@ use Neos\Neos\Ui\Application\PublishChangesInSite\PublishChangesInSiteCommandHandler; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQuery; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQueryHandler; -use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\SyncWorkspace\SyncWorkspaceCommand; use Neos\Neos\Ui\Application\SyncWorkspace\SyncWorkspaceCommandHandler; use Neos\Neos\Ui\ContentRepository\Service\NeosUiNodeService; @@ -136,9 +133,15 @@ class BackendServiceController extends ActionController /** * @Flow\Inject - * @var WorkspaceProvider + * @var WorkspaceService */ - protected $workspaceProvider; + protected $workspaceService; + + /** + * @Flow\Inject + * @var WorkspacePublishingService + */ + protected $workspacePublishingService; /** * @Flow\Inject @@ -182,10 +185,10 @@ public function changeAction(array $changes): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $changes = $this->changeCollectionConverter->convert($changes, $contentRepositoryId); + $changeCollection = $this->changeCollectionConverter->convert($changes, $contentRepositoryId); try { - $count = $changes->count(); - $changes->apply(); + $count = $changeCollection->count(); + $changeCollection->apply(); $success = new Info(); $success->setMessage( @@ -214,10 +217,9 @@ public function publishChangesInSiteAction(array $command): void /** @todo send from UI */ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $command['contentRepositoryId'] = $contentRepositoryId->value; - $command['siteId'] = $this->nodeService->deserializeNodeAddress( - $command['siteId'], - $contentRepositoryId - )->nodeAggregateId->value; + $command['siteId'] = NodeAddress::fromJsonString( + $command['siteId'] + )->aggregateId->value; $command = PublishChangesInSiteCommand::fromArray($command); $result = $this->publishChangesInSiteCommandHandler @@ -247,10 +249,9 @@ public function publishChangesInDocumentAction(array $command): void /** @todo send from UI */ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $command['contentRepositoryId'] = $contentRepositoryId->value; - $command['documentId'] = $this->nodeService->deserializeNodeAddress( - $command['documentId'], - $contentRepositoryId - )->nodeAggregateId->value; + $command['documentId'] = NodeAddress::fromJsonString( + $command['documentId'] + )->aggregateId->value; $command = PublishChangesInDocumentCommand::fromArray($command); $result = $this->publishChangesInDocumentCommandHandler @@ -282,11 +283,10 @@ public function discardAllChangesAction(array $command): void $command['contentRepositoryId'] = $contentRepositoryId->value; $command = DiscardAllChanges::fromArray($command); - $workspace = $this->workspaceProvider->provideForWorkspaceName( + $discardingResult = $this->workspacePublishingService->discardAllWorkspaceChanges( $command->contentRepositoryId, $command->workspaceName ); - $discardingResult = $workspace->discardAllChanges(); $this->view->assign('value', [ 'success' => [ @@ -316,17 +316,16 @@ public function discardChangesInSiteAction(array $command): void /** @todo send from UI */ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $command['contentRepositoryId'] = $contentRepositoryId->value; - $command['siteId'] = $this->nodeService->deserializeNodeAddress( - $command['siteId'], - $contentRepositoryId - )->nodeAggregateId->value; + $command['siteId'] = NodeAddress::fromJsonString( + $command['siteId'] + )->aggregateId->value; $command = DiscardChangesInSite::fromArray($command); - $workspace = $this->workspaceProvider->provideForWorkspaceName( + $discardingResult = $this->workspacePublishingService->discardChangesInSite( $command->contentRepositoryId, - $command->workspaceName + $command->workspaceName, + $command->siteId ); - $discardingResult = $workspace->discardChangesInSite($command->siteId); $this->view->assign('value', [ 'success' => [ @@ -356,17 +355,16 @@ public function discardChangesInDocumentAction(array $command): void /** @todo send from UI */ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $command['contentRepositoryId'] = $contentRepositoryId->value; - $command['documentId'] = $this->nodeService->deserializeNodeAddress( - $command['documentId'], - $contentRepositoryId - )->nodeAggregateId->value; + $command['documentId'] = NodeAddress::fromJsonString( + $command['documentId'] + )->aggregateId->value; $command = DiscardChangesInDocument::fromArray($command); - $workspace = $this->workspaceProvider->provideForWorkspaceName( + $discardingResult = $this->workspacePublishingService->discardChangesInDocument( $command->contentRepositoryId, - $command->workspaceName + $command->workspaceName, + $command->documentId ); - $discardingResult = $workspace->discardChangesInDocument($command->documentId); $this->view->assign('value', [ 'success' => [ @@ -395,31 +393,28 @@ public function discardChangesInDocumentAction(array $command): void */ public function changeBaseWorkspaceAction(string $targetWorkspaceName, string $documentNode): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); + $documentNodeAddress = NodeAddress::fromJsonString($documentNode); - $currentAccount = $this->securityContext->getAccount(); - assert($currentAccount !== null); - $userWorkspaceName = WorkspaceNameBuilder::fromAccountIdentifier( - $currentAccount->getAccountIdentifier() - ); + $user = $this->userService->getBackendUser(); + if ($user === null) { + $error = new Error(); + $error->setMessage('No authenticated account'); + $this->feedbackCollection->add($error); + $this->view->assign('value', $this->feedbackCollection); + return; + } + $userWorkspace = $this->workspaceService->getPersonalWorkspaceForUser($documentNodeAddress->contentRepositoryId, $user->getId()); /** @todo send from UI */ $command = new ChangeTargetWorkspace( - $contentRepositoryId, - $userWorkspaceName, + $documentNodeAddress->contentRepositoryId, + $userWorkspace->workspaceName, WorkspaceName::fromString($targetWorkspaceName), - $nodeAddressFactory->createFromUriString($documentNode) + $documentNodeAddress ); try { - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $command->contentRepositoryId, - $command->workspaceName - ); - $workspace->changeBaseWorkspace($command->targetWorkspaceName); + $this->workspacePublishingService->changeBaseWorkspace($documentNodeAddress->contentRepositoryId, $userWorkspace->workspaceName, WorkspaceName::fromString($targetWorkspaceName)); } catch (WorkspaceIsNotEmptyException $exception) { $error = new Error(); $error->setMessage( @@ -438,13 +433,15 @@ public function changeBaseWorkspaceAction(string $targetWorkspaceName, string $d return; } - $subgraph = $contentRepository->getContentGraph($workspace->name) + $contentRepository = $this->contentRepositoryRegistry->get($documentNodeAddress->contentRepositoryId); + $subgraph = $contentRepository->getContentGraph($userWorkspace->workspaceName) ->getSubgraph( $command->documentNode->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $documentNode = $subgraph->findNodeById($command->documentNode->nodeAggregateId); + $documentNodeInstance = $subgraph->findNodeById($command->documentNode->aggregateId); + assert($documentNodeInstance !== null); $success = new Success(); $success->setMessage( @@ -452,41 +449,36 @@ public function changeBaseWorkspaceAction(string $targetWorkspaceName, string $d ); $this->feedbackCollection->add($success); - $updateWorkspaceInfo = new UpdateWorkspaceInfo($contentRepositoryId, $userWorkspaceName); + $updateWorkspaceInfo = new UpdateWorkspaceInfo($command->contentRepositoryId, $userWorkspace->workspaceName); $this->feedbackCollection->add($updateWorkspaceInfo); // If current document node doesn't exist in the base workspace, // traverse its parents to find the one that exists // todo ensure that https://github.com/neos/neos-ui/pull/3734 doesnt need to be refixed in Neos 9.0 - $redirectNode = $documentNode; + $redirectNode = $documentNodeInstance; while (true) { - // @phpstan-ignore-next-line $redirectNodeInBaseWorkspace = $subgraph->findNodeById($redirectNode->aggregateId); if ($redirectNodeInBaseWorkspace) { break; - } else { - // @phpstan-ignore-next-line - $redirectNode = $subgraph->findParentNode($redirectNode->aggregateId); - // get parent always returns Node - if (!$redirectNode) { - throw new \Exception( - sprintf( - 'Wasn\'t able to locate any valid node in rootline of node %s in the workspace %s.', - $documentNode?->aggregateId->value, - $targetWorkspaceName - ), - 1458814469 - ); - } + } + $redirectNode = $subgraph->findParentNode($redirectNode->aggregateId); + // get parent always returns Node + if (!$redirectNode) { + throw new \Exception( + sprintf( + 'Wasn\'t able to locate any valid node in rootline of node %s in the workspace %s.', + $documentNodeInstance->aggregateId->value, + $targetWorkspaceName + ), + 1458814469 + ); } } - /** @var Node $documentNode */ - /** @var Node $redirectNode */ // If current document node exists in the base workspace, then reload, else redirect - if ($redirectNode->equals($documentNode)) { + if ($redirectNode->equals($documentNodeInstance)) { $reloadDocument = new ReloadDocument(); - $reloadDocument->setNode($documentNode); + $reloadDocument->setNode($documentNodeInstance); $this->feedbackCollection->add($reloadDocument); } else { $redirect = new Redirect(); @@ -508,13 +500,9 @@ public function changeBaseWorkspaceAction(string $targetWorkspaceName, string $d */ public function copyNodesAction(array $nodes): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - /** @var array $nodeAddresses */ $nodeAddresses = array_map( - fn (string $serializedNodeAddress) => $nodeAddressFactory->createFromUriString($serializedNodeAddress), + NodeAddress::fromJsonString(...), $nodes ); $this->clipboard->copyNodes($nodeAddresses); @@ -539,13 +527,9 @@ public function clearClipboardAction() */ public function cutNodesAction(array $nodes): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - - /** @var array $nodeAddresses */ + /** @var array $nodeAddresses */ $nodeAddresses = array_map( - fn (string $serializedNodeAddress) => $nodeAddressFactory->createFromUriString($serializedNodeAddress), + NodeAddress::fromJsonString(...), $nodes ); @@ -555,8 +539,7 @@ public function cutNodesAction(array $nodes): void public function getWorkspaceInfoAction(): void { $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $workspaceHelper = new WorkspaceHelper(); - $personalWorkspaceInfo = $workspaceHelper->getPersonalWorkspace($contentRepositoryId); + $personalWorkspaceInfo = (new WorkspaceHelper())->getPersonalWorkspace($contentRepositoryId); $this->view->assign('value', $personalWorkspaceInfo); } @@ -575,25 +558,22 @@ public function initializeGetAdditionalNodeMetadataAction(): void */ public function getAdditionalNodeMetadataAction(array $nodes): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - $result = []; foreach ($nodes as $nodeAddressString) { - $nodeAddress = $nodeAddressFactory->createFromUriString($nodeAddressString); + $nodeAddress = NodeAddress::fromJsonString($nodeAddressString); + $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); + $node = $subgraph->findNodeById($nodeAddress->aggregateId); // TODO finish implementation /*$otherNodeVariants = array_values(array_filter(array_map(function ($node) { return $this->getCurrentDimensionPresetIdentifiersForNode($node); }, $node->getOtherNodeVariants())));*/ if (!is_null($node)) { - $result[$nodeAddress->serializeForUri()] = [ + $result[$nodeAddress->toJson()] = [ // todo reimplement nodePolicyService 'policy' => [ 'disallowedNodeTypes' => [], @@ -623,18 +603,16 @@ public function initializeGetPolicyInformationAction(): void */ public function getPolicyInformationAction(array $nodes): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $result = []; foreach ($nodes as $nodeAddress) { + $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); + $node = $subgraph->findNodeById($nodeAddress->aggregateId); if (!is_null($node)) { - $result[$nodeAddress->serializeForUri()] = [ + $result[$nodeAddress->toJson()] = [ // todo reimplement nodePolicyService 'policy' => [ 'disallowedNodeTypes' => [], @@ -667,8 +645,7 @@ public function flowQueryAction(array $chain): string $flowQuery = new FlowQuery( array_map( fn ($nodeContextPath) => $this->nodeService->findNodeBySerializedNodeAddress( - $nodeContextPath, - $contentRepositoryId + $nodeContextPath ), $nodeContextPaths ) @@ -708,16 +685,13 @@ public function flowQueryAction(array $chain): string */ public function generateUriPathSegmentAction(string $contextNode, string $text): void { - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - - $contextNodeAddress = $nodeAddressFactory->createFromUriString($contextNode); + $contextNodeAddress = NodeAddress::fromJsonString($contextNode); + $contentRepository = $this->contentRepositoryRegistry->get($contextNodeAddress->contentRepositoryId); $subgraph = $contentRepository->getContentGraph($contextNodeAddress->workspaceName)->getSubgraph( $contextNodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $contextNode = $subgraph->findNodeById($contextNodeAddress->nodeAggregateId); + $contextNode = $subgraph->findNodeById($contextNodeAddress->aggregateId); $slug = $this->nodeUriPathSegmentGenerator->generateUriPathSegment($contextNode, $text); $this->view->assign('value', $slug); @@ -774,36 +748,31 @@ public function reloadNodesAction(array $query): void /** @todo send from UI */ $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; $query['contentRepositoryId'] = $contentRepositoryId->value; - $query['siteId'] = $this->nodeService->deserializeNodeAddress( - $query['siteId'], - $contentRepositoryId - )->nodeAggregateId->value; - $query['documentId'] = $this->nodeService->deserializeNodeAddress( - $query['documentId'], - $contentRepositoryId - )->nodeAggregateId->value; + $query['siteId'] = NodeAddress::fromJsonString( + $query['siteId'] + )->aggregateId->value; + $query['documentId'] = NodeAddress::fromJsonString( + $query['documentId'] + )->aggregateId->value; $query['ancestorsOfDocumentIds'] = array_map( fn (string $nodeAddress) => - $this->nodeService->deserializeNodeAddress( - $nodeAddress, - $contentRepositoryId - )->nodeAggregateId->value, + NodeAddress::fromJsonString( + $nodeAddress + )->aggregateId->value, $query['ancestorsOfDocumentIds'] ); $query['toggledNodesIds'] = array_map( fn (string $nodeAddress) => - $this->nodeService->deserializeNodeAddress( - $nodeAddress, - $contentRepositoryId - )->nodeAggregateId->value, + NodeAddress::fromJsonString( + $nodeAddress + )->aggregateId->value, $query['toggledNodesIds'] ); $query['clipboardNodesIds'] = array_map( fn (string $nodeAddress) => - $this->nodeService->deserializeNodeAddress( - $nodeAddress, - $contentRepositoryId - )->nodeAggregateId->value, + NodeAddress::fromJsonString( + $nodeAddress + )->aggregateId->value, $query['clipboardNodesIds'] ); $query = ReloadNodesQuery::fromArray($query); diff --git a/Classes/Domain/Model/Changes/AbstractStructuralChange.php b/Classes/Domain/Model/Changes/AbstractStructuralChange.php index f3dafdb559..1293f58c3e 100644 --- a/Classes/Domain/Model/Changes/AbstractStructuralChange.php +++ b/Classes/Domain/Model/Changes/AbstractStructuralChange.php @@ -16,9 +16,9 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\Flow\Annotations as Flow; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Ui\ContentRepository\Service\NeosUiNodeService; use Neos\Neos\Ui\Domain\Model\AbstractChange; use Neos\Neos\Ui\Domain\Model\Feedback\Operations\ReloadDocument; @@ -108,8 +108,7 @@ public function getSiblingNode(): ?Node if ($this->cachedSiblingNode === null) { $this->cachedSiblingNode = $this->nodeService->findNodeBySerializedNodeAddress( - $this->siblingDomAddress->getContextPath(), - $this->getSubject()->contentRepositoryId + $this->siblingDomAddress->getContextPath() ); } @@ -148,14 +147,13 @@ protected function finish(Node $node) // 1) the parent of our new (or copied or moved) node is a ContentCollection; // so we can directly update an element of this content collection - $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); if ($parentNode && $this->getNodeType($parentNode)?->isOfType('Neos.Neos:ContentCollection') && // 2) the parent DOM address (i.e. the closest RENDERED node in DOM is actually the ContentCollection; // and no other node in between $this->getParentDomAddress() && $this->getParentDomAddress()->getFusionPath() && $this->getParentDomAddress()->getContextPath() === - NodeAddressFactory::create($contentRepository)->createFromNode($parentNode)->serializeForUri() + NodeAddress::fromNode($parentNode)->toJson() ) { $renderContentOutOfBand = new RenderContentOutOfBand(); $renderContentOutOfBand->setNode($node); diff --git a/Classes/Domain/Model/Changes/CopyInto.php b/Classes/Domain/Model/Changes/CopyInto.php index 8940173219..bdd1188b7a 100644 --- a/Classes/Domain/Model/Changes/CopyInto.php +++ b/Classes/Domain/Model/Changes/CopyInto.php @@ -15,7 +15,6 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\ContentRepository\Core\SharedModel\Node\NodeName; /** * @internal These objects internally reflect possible operations made by the Neos.Ui. @@ -37,7 +36,7 @@ public function getParentNode(): ?Node { if (!isset($this->cachedParentNode)) { $this->cachedParentNode = $this->parentContextPath - ? $this->nodeService->findNodeBySerializedNodeAddress($this->parentContextPath, $this->getSubject()->contentRepositoryId) + ? $this->nodeService->findNodeBySerializedNodeAddress($this->parentContextPath) : null; } diff --git a/Classes/Domain/Model/Changes/MoveInto.php b/Classes/Domain/Model/Changes/MoveInto.php index 74dbc07956..75474d0563 100644 --- a/Classes/Domain/Model/Changes/MoveInto.php +++ b/Classes/Domain/Model/Changes/MoveInto.php @@ -13,8 +13,8 @@ */ use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Neos\Ui\Domain\Model\Feedback\Operations\UpdateNodeInfo; /** @@ -38,8 +38,7 @@ public function getParentNode(): ?Node } return $this->nodeService->findNodeBySerializedNodeAddress( - $this->parentContextPath, - $this->getSubject()->contentRepositoryId + $this->parentContextPath ); } diff --git a/Classes/Domain/Model/Feedback/Operations/NodeCreated.php b/Classes/Domain/Model/Feedback/Operations/NodeCreated.php index d5a13fe77c..f79d75b208 100644 --- a/Classes/Domain/Model/Feedback/Operations/NodeCreated.php +++ b/Classes/Domain/Model/Feedback/Operations/NodeCreated.php @@ -11,10 +11,10 @@ * source code. */ +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\Neos\FrontendRouting\NodeAddressFactory; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; @@ -91,9 +91,9 @@ public function serializePayload(ControllerContext $controllerContext) $node = $this->getNode(); $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($node->nodeTypeName); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); + return [ - 'contextPath' => $nodeAddressFactory->createFromNode($node)->serializeForUri(), + 'contextPath' => NodeAddress::fromNode($node)->toJson(), 'identifier' => $node->aggregateId->value, 'isDocument' => $nodeType?->isOfType(NodeTypeNameFactory::NAME_DOCUMENT) ]; diff --git a/Classes/Domain/Model/Feedback/Operations/Redirect.php b/Classes/Domain/Model/Feedback/Operations/Redirect.php index 43e25d3f3c..3e3b303630 100644 --- a/Classes/Domain/Model/Feedback/Operations/Redirect.php +++ b/Classes/Domain/Model/Feedback/Operations/Redirect.php @@ -7,7 +7,6 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\FrontendRouting\NodeUriBuilderFactory; use Neos\Neos\FrontendRouting\Options; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; @@ -114,11 +113,10 @@ public function serializePayload(ControllerContext $controllerContext): array ); $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); return [ 'redirectUri' => (string)$redirectUri, - 'redirectContextPath' => $nodeAddressFactory->createFromNode($node)->serializeForUri(), + 'redirectContextPath' => NodeAddress::fromNode($node)->toJson(), ]; } } diff --git a/Classes/Domain/Model/Feedback/Operations/ReloadContentOutOfBand.php b/Classes/Domain/Model/Feedback/Operations/ReloadContentOutOfBand.php index ac9ba7e918..084d5726bf 100644 --- a/Classes/Domain/Model/Feedback/Operations/ReloadContentOutOfBand.php +++ b/Classes/Domain/Model/Feedback/Operations/ReloadContentOutOfBand.php @@ -11,14 +11,14 @@ * source code. */ -use Neos\Neos\Domain\Service\RenderingModeService; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Fusion\Exception as FusionException; +use Neos\Neos\Domain\Service\RenderingModeService; use Neos\Neos\Fusion\Helper\CachingHelper; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; @@ -110,10 +110,8 @@ public function isSimilarTo(FeedbackInterface $feedback): bool public function serializePayload(ControllerContext $controllerContext): array { if (!is_null($this->nodeDomAddress)) { - $contentRepository = $this->contentRepositoryRegistry->get($this->node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); return [ - 'contextPath' => $nodeAddressFactory->createFromNode($this->node)->serializeForUri(), + 'contextPath' => NodeAddress::fromNode($this->node)->toJson(), 'nodeDomAddress' => $this->nodeDomAddress, 'renderedContent' => $this->renderContent($controllerContext) ]; diff --git a/Classes/Domain/Model/Feedback/Operations/RemoveNode.php b/Classes/Domain/Model/Feedback/Operations/RemoveNode.php index 437e349b2a..826da07d67 100644 --- a/Classes/Domain/Model/Feedback/Operations/RemoveNode.php +++ b/Classes/Domain/Model/Feedback/Operations/RemoveNode.php @@ -11,13 +11,12 @@ * source code. */ +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\FrontendRouting\NodeAddressFactory; -use Neos\Neos\FrontendRouting\NodeAddress; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Mvc\Controller\ControllerContext; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; @@ -54,11 +53,8 @@ public function __construct(Node $node, Node $parentNode) protected function initializeObject(): void { - $contentRepository = $this->contentRepositoryRegistry->get($this->node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - - $this->nodeAddress = $nodeAddressFactory->createFromNode($this->node); - $this->parentNodeAddress = $nodeAddressFactory->createFromNode($this->parentNode); + $this->nodeAddress = NodeAddress::fromNode($this->node); + $this->parentNodeAddress = NodeAddress::fromNode($this->parentNode); } public function getNode(): Node @@ -110,8 +106,8 @@ public function isSimilarTo(FeedbackInterface $feedback) public function serializePayload(ControllerContext $controllerContext) { return [ - 'contextPath' => $this->nodeAddress->serializeForUri(), - 'parentContextPath' => $this->parentNodeAddress->serializeForUri() + 'contextPath' => $this->nodeAddress->toJson(), + 'parentContextPath' => $this->parentNodeAddress->toJson() ]; } } diff --git a/Classes/Domain/Model/Feedback/Operations/RenderContentOutOfBand.php b/Classes/Domain/Model/Feedback/Operations/RenderContentOutOfBand.php index d270fb33c6..ec58a6c417 100644 --- a/Classes/Domain/Model/Feedback/Operations/RenderContentOutOfBand.php +++ b/Classes/Domain/Model/Feedback/Operations/RenderContentOutOfBand.php @@ -15,13 +15,13 @@ namespace Neos\Neos\Ui\Domain\Model\Feedback\Operations; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\Neos\Domain\Service\RenderingModeService; -use Neos\Neos\FrontendRouting\NodeAddressFactory; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Fusion\Exception as FusionException; +use Neos\Neos\Domain\Service\RenderingModeService; use Neos\Neos\Fusion\Helper\CachingHelper; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; @@ -156,10 +156,8 @@ public function isSimilarTo(FeedbackInterface $feedback): bool public function serializePayload(ControllerContext $controllerContext): array { if (!is_null($this->node)) { - $contentRepository = $this->contentRepositoryRegistry->get($this->node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); return [ - 'contextPath' => $nodeAddressFactory->createFromNode($this->node)->serializeForUri(), + 'contextPath' => NodeAddress::fromNode($this->node)->toJson(), 'parentDomAddress' => $this->getParentDomAddress(), 'siblingDomAddress' => $this->getSiblingDomAddress(), 'mode' => $this->getMode(), diff --git a/Classes/Domain/Model/Feedback/Operations/UpdateNodeInfo.php b/Classes/Domain/Model/Feedback/Operations/UpdateNodeInfo.php index 46b80d8b48..5d1dbd9ae4 100644 --- a/Classes/Domain/Model/Feedback/Operations/UpdateNodeInfo.php +++ b/Classes/Domain/Model/Feedback/Operations/UpdateNodeInfo.php @@ -13,10 +13,10 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\Flow\Mvc\ActionRequest; -use Neos\Neos\FrontendRouting\NodeAddressFactory; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; @@ -115,10 +115,9 @@ public function serializePayload(ControllerContext $controllerContext): array private function serializeNodeRecursively(Node $node, ActionRequest $actionRequest): array { $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $result = [ - $nodeAddressFactory->createFromNode($node)->serializeForUri() + NodeAddress::fromNode($node)->toJson() => $this->nodeInfoHelper->renderNodeWithPropertiesAndChildrenInformation( $node, $actionRequest diff --git a/Classes/Domain/Model/Feedback/Operations/UpdateNodePreviewUrl.php b/Classes/Domain/Model/Feedback/Operations/UpdateNodePreviewUrl.php index 4322d130b3..5e136e3ec4 100644 --- a/Classes/Domain/Model/Feedback/Operations/UpdateNodePreviewUrl.php +++ b/Classes/Domain/Model/Feedback/Operations/UpdateNodePreviewUrl.php @@ -12,11 +12,11 @@ */ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; use Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper; @@ -111,10 +111,8 @@ public function serializePayload(ControllerContext $controllerContext): array $contextPath = ''; } else { $nodeInfoHelper = new NodeInfoHelper(); - $contentRepository = $this->contentRepositoryRegistry->get($this->node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $newPreviewUrl = $nodeInfoHelper->createRedirectToNode($this->node, $controllerContext->getRequest()); - $contextPath = $nodeAddressFactory->createFromNode($this->node)->serializeForUri(); + $contextPath = NodeAddress::fromNode($this->node)->toJson(); } return [ 'newPreviewUrl' => $newPreviewUrl, diff --git a/Classes/Domain/Model/Feedback/Operations/UpdateWorkspaceInfo.php b/Classes/Domain/Model/Feedback/Operations/UpdateWorkspaceInfo.php index 53577a573b..d123ae7e15 100644 --- a/Classes/Domain/Model/Feedback/Operations/UpdateWorkspaceInfo.php +++ b/Classes/Domain/Model/Feedback/Operations/UpdateWorkspaceInfo.php @@ -11,15 +11,14 @@ * source code. */ -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Ui\ContentRepository\Service\WorkspaceService; +use Neos\Flow\Mvc\Controller\ControllerContext; +use Neos\Neos\Ui\ContentRepository\Service\WorkspaceService as UiWorkspaceService; use Neos\Neos\Ui\Domain\Model\AbstractFeedback; use Neos\Neos\Ui\Domain\Model\FeedbackInterface; -use Neos\Flow\Mvc\Controller\ControllerContext; -use Neos\Neos\Domain\Workspace\WorkspaceProvider; /** * @internal @@ -28,15 +27,15 @@ class UpdateWorkspaceInfo extends AbstractFeedback { /** * @Flow\Inject - * @var WorkspaceService + * @var ContentRepositoryRegistry */ - protected $workspaceService; + protected $contentRepositoryRegistry; /** * @Flow\Inject - * @var WorkspaceProvider + * @var UiWorkspaceService */ - protected $workspaceProvider; + protected $uiWorkspaceService; /** * UpdateWorkspaceInfo constructor. @@ -87,8 +86,8 @@ public function isSimilarTo(FeedbackInterface $feedback) if (!$feedback instanceof UpdateWorkspaceInfo) { return false; } - - return $this->getWorkspaceName()->equals($feedback->getWorkspaceName()); + $feedbackWorkspaceName = $feedback->getWorkspaceName(); + return $feedbackWorkspaceName !== null && $this->getWorkspaceName()->equals($feedbackWorkspaceName); } /** @@ -99,21 +98,18 @@ public function isSimilarTo(FeedbackInterface $feedback) */ public function serializePayload(ControllerContext $controllerContext) { - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $this->contentRepositoryId, - $this->workspaceName - ); - $totalNumberOfChanges = $workspace->countAllChanges(); - + $contentRepository = $this->contentRepositoryRegistry->get($this->contentRepositoryId); + $workspace = $contentRepository->findWorkspaceByName($this->workspaceName); + if ($workspace === null) { + return null; + } + $publishableNodes = $this->uiWorkspaceService->getPublishableNodeInfo($workspace->workspaceName, $contentRepository->id); return [ 'name' => $this->workspaceName->value, - 'totalNumberOfChanges' => $totalNumberOfChanges, - 'publishableNodes' => $this->workspaceService->getPublishableNodeInfo( - $this->workspaceName, - $this->contentRepositoryId - ), - 'baseWorkspace' => $workspace->getCurrentBaseWorkspaceName()?->value, - 'status' => $workspace->getCurrentStatus() + 'totalNumberOfChanges' => count($publishableNodes), + 'publishableNodes' => $publishableNodes, + 'baseWorkspace' => $workspace->baseWorkspaceName?->value, + 'status' => $workspace->status->value, ]; } } diff --git a/Classes/FlowQueryOperations/NeosUiDefaultNodesOperation.php b/Classes/FlowQueryOperations/NeosUiDefaultNodesOperation.php index c941c8bef9..5123e8f616 100644 --- a/Classes/FlowQueryOperations/NeosUiDefaultNodesOperation.php +++ b/Classes/FlowQueryOperations/NeosUiDefaultNodesOperation.php @@ -14,12 +14,12 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindAncestorNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\FlowQuery\FlowQuery; use Neos\Eel\FlowQuery\Operations\AbstractOperation; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Flow\Annotations as Flow; /** @@ -78,7 +78,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) list($baseNodeType, $loadingDepth, $toggledNodes, $clipboardNodesContextPaths) = $arguments; $contentRepository = $this->contentRepositoryRegistry->get($documentNode->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $baseNodeTypeConstraints = NodeTypeCriteria::fromFilterString($baseNodeType); @@ -105,15 +104,14 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) $loadingDepth, $toggledNodes, $ancestors, - $subgraph, - $nodeAddressFactory + $subgraph ) { - $baseNodeAddress = $nodeAddressFactory->createFromNode($baseNode); + $baseNodeAddress = NodeAddress::fromNode($baseNode); if ($level < $loadingDepth || // load all nodes within loadingDepth $loadingDepth === 0 || // unlimited loadingDepth // load toggled nodes - in_array($baseNodeAddress->serializeForUri(), $toggledNodes) || + in_array($baseNodeAddress->toJson(), $toggledNodes) || // load children of all parents of documentNode in_array($baseNode->aggregateId->value, array_map( fn (Node $node): string => $node->aggregateId->value, @@ -136,9 +134,9 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) } foreach ($clipboardNodesContextPaths as $clipboardNodeContextPath) { - // TODO: does not work across multiple CRs yet. - $clipboardNodeAddress = $nodeAddressFactory->createFromUriString($clipboardNodeContextPath); - $clipboardNode = $subgraph->findNodeById($clipboardNodeAddress->nodeAggregateId); + // TODO: might not work across multiple CRs yet. + $clipboardNodeAddress = NodeAddress::fromJsonString($clipboardNodeContextPath); + $clipboardNode = $subgraph->findNodeById($clipboardNodeAddress->aggregateId); if ($clipboardNode && !array_key_exists($clipboardNode->aggregateId->value, $nodes)) { $nodes[$clipboardNode->aggregateId->value] = $clipboardNode; } diff --git a/Classes/Fusion/Helper/NodeInfoHelper.php b/Classes/Fusion/Helper/NodeInfoHelper.php index 225a968604..67f2d751ef 100644 --- a/Classes/Fusion/Helper/NodeInfoHelper.php +++ b/Classes/Fusion/Helper/NodeInfoHelper.php @@ -17,7 +17,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; @@ -25,7 +24,6 @@ use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\FrontendRouting\NodeUriBuilderFactory; use Neos\Neos\Ui\Domain\Service\NodePropertyConverterService; use Neos\Neos\Ui\Domain\Service\UserLocaleService; @@ -186,13 +184,11 @@ protected function getBasicNodeInformation(Node $node): array $subgraph = $this->contentRepositoryRegistry->subgraphForNode($node); $parentNode = $subgraph->findParentNode($node->aggregateId); - $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - $nodeAddress = $nodeAddressFactory->createFromNode($node); + $nodeAddress = NodeAddress::fromNode($node); return [ - 'contextPath' => $nodeAddress->serializeForUri(), - 'nodeAddress' => $nodeAddress->serializeForUri(), + 'contextPath' => $nodeAddress->toJson(), + 'nodeAddress' => $nodeAddress->toJson(), 'name' => $node->name?->value ?? '', 'identifier' => $node->aggregateId->jsonSerialize(), 'nodeType' => $node->nodeTypeName->value, @@ -204,7 +200,7 @@ protected function getBasicNodeInformation(Node $node): array CountAncestorNodesFilter::create() ), 'children' => [], - 'parent' => $parentNode ? $nodeAddressFactory->createFromNode($parentNode)->serializeForUri() : null, + 'parent' => $parentNode ? NodeAddress::fromNode($parentNode)->toJson() : null, 'matchesCurrentDimensions' => $node->dimensionSpacePoint->equals($node->originDimensionSpacePoint), 'lastModificationDateTime' => $node->timestamps->lastModified?->format(\DateTime::ATOM), 'creationDateTime' => $node->timestamps->created->format(\DateTime::ATOM), @@ -240,9 +236,8 @@ protected function renderChildrenInformation(Node $node, string $nodeTypeFilterS $infos = []; foreach ($childNodes as $childNode) { $contentRepository = $this->contentRepositoryRegistry->get($childNode->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $infos[] = [ - 'contextPath' => $nodeAddressFactory->createFromNode($childNode)->serializeForUri(), + 'contextPath' => NodeAddress::fromNode($childNode)->toJson(), 'nodeType' => $childNode->nodeTypeName->value ]; }; @@ -336,12 +331,11 @@ public function defaultNodesForBackend( ): array { // does not support multiple CRs here yet $contentRepository = $this->contentRepositoryRegistry->get($site->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); return [ - ($nodeAddressFactory->createFromNode($site)->serializeForUri()) + (NodeAddress::fromNode($site)->toJson()) => $this->renderNodeWithPropertiesAndChildrenInformation($site, $actionRequest), - ($nodeAddressFactory->createFromNode($documentNode)->serializeForUri()) + (NodeAddress::fromNode($documentNode)->toJson()) => $this->renderNodeWithPropertiesAndChildrenInformation($documentNode, $actionRequest) ]; } @@ -356,16 +350,7 @@ public function previewUri(Node $node, ActionRequest $actionRequest): string public function createRedirectToNode(Node $node, ActionRequest $actionRequest): string { - // we always want to redirect to the node in the base workspace. - $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($node->workspaceName); - - $nodeAddress = NodeAddress::create( - $node->contentRepositoryId, - $workspace->baseWorkspaceName ?? WorkspaceName::forLive(), - $node->dimensionSpacePoint, - $node->aggregateId - ); + $nodeAddress = NodeAddress::fromNode($node); $uriBuilder = new UriBuilder(); $uriBuilder->setRequest($actionRequest); @@ -420,8 +405,7 @@ protected function buildContentChildNodeFilterString(): string public function serializedNodeAddress(Node $node): string { $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); - $nodeAddressFactory = NodeAddressFactory::create($contentRepository); - return $nodeAddressFactory->createFromNode($node)->serializeForUri(); + return NodeAddress::fromNode($node)->toJson(); } /** diff --git a/Classes/Fusion/Helper/WorkspaceHelper.php b/Classes/Fusion/Helper/WorkspaceHelper.php index c22e35837f..a3b316ff22 100644 --- a/Classes/Fusion/Helper/WorkspaceHelper.php +++ b/Classes/Fusion/Helper/WorkspaceHelper.php @@ -16,13 +16,12 @@ use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Context; -use Neos\Neos\Domain\Service\WorkspaceNameBuilder; -use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\Ui\ContentRepository\Service\WorkspaceService; +use Neos\Neos\Domain\Service\UserService; +use Neos\Neos\Domain\Service\WorkspaceService; +use Neos\Neos\Ui\ContentRepository\Service\WorkspaceService as UiWorkspaceService; /** - * @internal implementation detail of the Neos Ui to build its initialState. - * and used for the workspace-info endpoint. + * @internal implementation detail of the Neos Ui to build its initialState {@see \Neos\Neos\Ui\Infrastructure\Configuration\InitialStateProvider} */ class WorkspaceHelper implements ProtectedContextAwareInterface { @@ -34,46 +33,48 @@ class WorkspaceHelper implements ProtectedContextAwareInterface /** * @Flow\Inject - * @var WorkspaceService + * @var Context */ - protected $workspaceService; + protected $securityContext; /** * @Flow\Inject - * @var Context + * @var UiWorkspaceService */ - protected $securityContext; + protected $uiWorkspaceService; + + /** + * @Flow\Inject + * @var UserService + */ + protected $userService; /** * @Flow\Inject - * @var WorkspaceProvider + * @var WorkspaceService */ - protected $workspaceProvider; + protected $workspaceService; /** * @return array */ public function getPersonalWorkspace(ContentRepositoryId $contentRepositoryId): array { - $currentAccount = $this->securityContext->getAccount(); - assert($currentAccount !== null); - // todo use \Neos\Neos\Service\UserService::getPersonalWorkspaceName instead? - $personalWorkspaceName = WorkspaceNameBuilder::fromAccountIdentifier($currentAccount->getAccountIdentifier()); - - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $contentRepositoryId, - $personalWorkspaceName - ); - + $currentUser = $this->userService->getCurrentUser(); + if ($currentUser === null) { + return []; + } + $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $personalWorkspace = $this->workspaceService->getPersonalWorkspaceForUser($contentRepositoryId, $currentUser->getId()); + $personalWorkspacePermissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepositoryId, $personalWorkspace->workspaceName, $currentUser); + $publishableNodes = $this->uiWorkspaceService->getPublishableNodeInfo($personalWorkspace->workspaceName, $contentRepository->id); return [ - 'name' => $workspace->name, - 'totalNumberOfChanges' => $workspace->countAllChanges(), - 'publishableNodes' => $this->workspaceService->getPublishableNodeInfo($personalWorkspaceName, $contentRepositoryId), - 'baseWorkspace' => $workspace->getCurrentBaseWorkspaceName(), - // TODO: FIX readonly flag! - //'readOnly' => !$this->domainUserService->currentUserCanPublishToWorkspace($baseWorkspace) - 'readOnly' => false, - 'status' => $workspace->getCurrentStatus() + 'name' => $personalWorkspace->workspaceName->value, + 'totalNumberOfChanges' => count($publishableNodes), + 'publishableNodes' => $publishableNodes, + 'baseWorkspace' => $personalWorkspace->baseWorkspaceName?->value, + 'readOnly' => !($personalWorkspace->baseWorkspaceName !== null && $personalWorkspacePermissions->write), + 'status' => $personalWorkspace->status->value, ]; } diff --git a/Classes/Infrastructure/Configuration/ConfigurationProvider.php b/Classes/Infrastructure/Configuration/ConfigurationProvider.php index 36e4410d53..15e1752c2c 100644 --- a/Classes/Infrastructure/Configuration/ConfigurationProvider.php +++ b/Classes/Infrastructure/Configuration/ConfigurationProvider.php @@ -18,8 +18,9 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Configuration\ConfigurationManager; use Neos\Flow\Mvc\Routing\UriBuilder; +use Neos\Neos\Domain\Model\WorkspaceClassification; +use Neos\Neos\Domain\Service\WorkspaceService; use Neos\Neos\Service\UserService; -use Neos\Neos\Ui\ContentRepository\Service\WorkspaceService; use Neos\Neos\Ui\Domain\InitialData\CacheConfigurationVersionProviderInterface; use Neos\Neos\Ui\Domain\InitialData\ConfigurationProviderInterface; @@ -54,10 +55,7 @@ public function getConfiguration( ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'Neos.Neos.userInterface.navigateComponent.structureTree', ), - 'allowedTargetWorkspaces' => - $this->workspaceService->getAllowedTargetWorkspaces( - $contentRepository - ), + 'allowedTargetWorkspaces' => $this->getAllowedTargetWorkspaces($contentRepository), 'endpoints' => [ 'nodeTypeSchema' => $uriBuilder->reset() ->setCreateAbsoluteUri(true) @@ -89,4 +87,32 @@ public function getConfiguration( ] ]; } + + /** + * @return array + */ + private function getAllowedTargetWorkspaces(ContentRepository $contentRepository): array + { + $backendUser = $this->userService->getBackendUser(); + if ($backendUser === null) { + return []; + } + $result = []; + foreach ($contentRepository->findWorkspaces() as $workspace) { + $workspaceMetadata = $this->workspaceService->getWorkspaceMetadata($contentRepository->id, $workspace->workspaceName); + if (!in_array($workspaceMetadata->classification, [WorkspaceClassification::ROOT, WorkspaceClassification::SHARED], true)) { + continue; + } + $workspacePermissions = $this->workspaceService->getWorkspacePermissionsForUser($contentRepository->id, $workspace->workspaceName, $backendUser); + if ($workspacePermissions->read === false) { + continue; + } + $result[$workspace->workspaceName->value] = [ + 'name' => $workspace->workspaceName->value, + 'title' => $workspaceMetadata->title->value, + 'readonly' => !$workspacePermissions->write, + ]; + } + return $result; + } } diff --git a/Classes/Service/NodeClipboard.php b/Classes/Service/NodeClipboard.php index ad802b7be3..2f009b6795 100644 --- a/Classes/Service/NodeClipboard.php +++ b/Classes/Service/NodeClipboard.php @@ -11,7 +11,7 @@ * source code. */ -use Neos\Neos\FrontendRouting\NodeAddress; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\Flow\Annotations as Flow; /** @@ -44,7 +44,7 @@ class NodeClipboard public function copyNodes(array $nodeAddresses): void { $this->serializedNodeAddresses = array_map( - fn (NodeAddress $nodeAddress) => $nodeAddress->serializeForUri(), + fn (NodeAddress $nodeAddress) => $nodeAddress->toJson(), $nodeAddresses ); $this->mode = self::MODE_COPY; @@ -53,13 +53,13 @@ public function copyNodes(array $nodeAddresses): void /** * Save cut node to clipboard. * - * @param array $nodeAddresses + * @param array $nodeAddresses * @Flow\Session(autoStart=true) */ public function cutNodes(array $nodeAddresses): void { $this->serializedNodeAddresses = array_map( - fn (NodeAddress $nodeAddress) => $nodeAddress->serializeForUri(), + fn (NodeAddress $nodeAddress) => $nodeAddress->toJson(), $nodeAddresses ); $this->mode = self::MODE_MOVE; diff --git a/Classes/TypeConverter/ChangeCollectionConverter.php b/Classes/TypeConverter/ChangeCollectionConverter.php index a452640d63..9c438db681 100644 --- a/Classes/TypeConverter/ChangeCollectionConverter.php +++ b/Classes/TypeConverter/ChangeCollectionConverter.php @@ -12,13 +12,10 @@ */ use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\Error\Messages\Error; use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Flow\Property\PropertyMapper; -use Neos\Flow\Property\PropertyMappingConfigurationInterface; -use Neos\Flow\Property\TypeConverter\AbstractTypeConverter; use Neos\Flow\Reflection\ReflectionService; use Neos\Neos\Ui\ContentRepository\Service\NeosUiNodeService; use Neos\Neos\Ui\Domain\Model\ChangeCollection; @@ -133,7 +130,7 @@ protected function convertChangeData(array $changeData, ContentRepositoryId $con $subjectContextPath = $changeData['subject']; - $subject = $this->nodeService->findNodeBySerializedNodeAddress($subjectContextPath, $contentRepositoryId); + $subject = $this->nodeService->findNodeBySerializedNodeAddress($subjectContextPath); // we guard that `setSubject` gets a Node! if (is_null($subject)) { throw new \RuntimeException('Could not find node for subject "' . $subjectContextPath . '"', 1645657340); @@ -143,7 +140,7 @@ protected function convertChangeData(array $changeData, ContentRepositoryId $con if (isset($changeData['reference']) && method_exists($changeClassInstance, 'setReference')) { $referenceContextPath = $changeData['reference']; - $reference = $this->nodeService->findNodeBySerializedNodeAddress($referenceContextPath, $contentRepositoryId); + $reference = $this->nodeService->findNodeBySerializedNodeAddress($referenceContextPath); $changeClassInstance->setReference($reference); } diff --git a/README.md b/README.md index 2a53e02d3d..10807fef05 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ And on Packagist available via: `neos/neos-ui` ### Updating -``` +```bash composer update neos/neos-ui ``` @@ -59,7 +59,8 @@ For trying out the new UI, we recommend you to run the regularly released beta However, if you want to stay on bleeding-edge, or want to help out developing, you'll need the `9.0.x-dev` release. You can install the latest release using: -``` + +```bash composer require neos/neos-ui-compiled:9.0.x-dev neos/neos-ui:9.0.x-dev ``` @@ -67,6 +68,24 @@ composer require neos/neos-ui-compiled:9.0.x-dev neos/neos-ui:9.0.x-dev Please follow the respective guides for contributing on OSX and on Linux. +To start developing the Neos Ui you will need a running Neos instance locally. +You can use +* one of your own, local Neos 8.3 instances, +* create a new one with `composer create-project neos/neos-base-distribution neos-ui-development-instance`, +* or use the docker compose setup in this repository (see instructions below). + +### Setup Source Files and Git +To install the source files and setup git, run: + +```bash +composer require neos/neos-ui-compiled:8.4.x-dev neos/neos-ui:8.4.x-dev --prefer-source +``` + +This will sync the git repository of Neos Ui into `Packages/Application/Neos.Neos.Ui` (this might take a while). +To push your changes to GitHub you need to fork the Neos Ui and change the git remote to your fork (check with `git remove -v`). + +Run `make setup`. To check what commands are executed have a look at the `Makefile` in the root of this repository. + ### on Windows 1) Ensure you have the relevant version installed (see above). diff --git a/Tests/IntegrationTests/Fixtures/1Dimension/discarding.e2e.js b/Tests/IntegrationTests/Fixtures/1Dimension/discarding.e2e.js index db382f1d70..36a7c54bd6 100644 --- a/Tests/IntegrationTests/Fixtures/1Dimension/discarding.e2e.js +++ b/Tests/IntegrationTests/Fixtures/1Dimension/discarding.e2e.js @@ -38,7 +38,7 @@ test('Discarding: create multiple nodes nested within each other and then discar .expect(ReactSelector('Provider').getReact(({props}) => { const reduxState = props.store.getState(); return reduxState.cr.nodes.documentNode; - })).eql('user-admin__eyJsYW5ndWFnZSI6ImVuX1VTIn0=__f676459d-ca77-44bc-aeea-44114814c279', 'After discarding we are back to the main page'); + })).eql(JSON.stringify({contentRepositoryId:"onedimension",workspaceName:"admin-admington",dimensionSpacePoint:{"language":"en_US"},aggregateId:"f676459d-ca77-44bc-aeea-44114814c279"}), 'After discarding we are back to the main page'); }); test('Discarding: create a document node and then discard it', async t => { diff --git a/Tests/IntegrationTests/Fixtures/1Dimension/syncing.e2e.js b/Tests/IntegrationTests/Fixtures/1Dimension/syncing.e2e.js index 0a591b2b70..429e381490 100644 --- a/Tests/IntegrationTests/Fixtures/1Dimension/syncing.e2e.js +++ b/Tests/IntegrationTests/Fixtures/1Dimension/syncing.e2e.js @@ -11,6 +11,8 @@ import { fixture`Syncing` .afterEach(() => checkPropTypes()); +fixture.skip`TODO Tests are flaky and create catchup errors rendering following tests also kaput: https://github.com/neos/neos-ui/pull/3769#pullrequestreview-2332466270`; + const contentIframeSelector = Selector('[name="neos-content-main"]', {timeout: 2000}); test('Syncing: Create a conflict state between two editors and choose "Discard all" as a resolution strategy during rebase', async t => { @@ -306,7 +308,7 @@ async function cancelResolutionDuringStrategyChoice(t) { async function chooseDiscardAllAsResolutionStrategy(t) { await t.click(Selector('#neos-SelectResolutionStrategy-SelectBox')); - await t.click(Selector('[role="button"]').withText('Discard workspace "user-admin"')); + await t.click(Selector('[role="button"]').withText('Discard workspace "admin-admington"')); await t.click(Selector('#neos-SelectResolutionStrategy-Accept')); } diff --git a/Tests/IntegrationTests/Fixtures/1Dimension/treeMultiselect.e2e.js b/Tests/IntegrationTests/Fixtures/1Dimension/treeMultiselect.e2e.js index 885bfd3c00..a06f967e2e 100644 --- a/Tests/IntegrationTests/Fixtures/1Dimension/treeMultiselect.e2e.js +++ b/Tests/IntegrationTests/Fixtures/1Dimension/treeMultiselect.e2e.js @@ -26,7 +26,7 @@ test('Move multiple nodes via toolbar', async t => { .expect(ReactSelector('Provider').getReact(({props}) => { const reduxState = props.store.getState(); return reduxState.cr.nodes.documentNode; - })).eql('user-admin__eyJsYW5ndWFnZSI6ImVuX1VTIn0=__5b0d6ac0-40ab-47e8-b79e-39de6c0700df', 'Node B\'s node address changed'); + })).eql(JSON.stringify({contentRepositoryId:"onedimension",workspaceName:"admin-admington",dimensionSpacePoint:{"language":"en_US"},aggregateId:"5b0d6ac0-40ab-47e8-b79e-39de6c0700df"}), 'Node B\'s node address changed'); await t.click(Page.getTreeNodeButton('Home')) }); @@ -43,7 +43,7 @@ test('Move multiple nodes via DND, CMD-click', async t => { .expect(ReactSelector('Provider').getReact(({props}) => { const reduxState = props.store.getState(); return reduxState.cr.nodes.documentNode; - })).eql('user-admin__eyJsYW5ndWFnZSI6ImVuX1VTIn0=__5b0d6ac0-40ab-47e8-b79e-39de6c0700df', 'Node B\'s node address changed'); + })).eql(JSON.stringify({contentRepositoryId:"onedimension",workspaceName:"admin-admington",dimensionSpacePoint:{"language":"en_US"},aggregateId:"5b0d6ac0-40ab-47e8-b79e-39de6c0700df"}), 'Node B\'s node address changed'); await t.click(Page.getTreeNodeButton('Home')) }); @@ -60,6 +60,6 @@ test('Move multiple nodes via DND, SHIFT-click', async t => { .expect(ReactSelector('Provider').getReact(({props}) => { const reduxState = props.store.getState(); return reduxState.cr.nodes.documentNode; - })).eql('user-admin__eyJsYW5ndWFnZSI6ImVuX1VTIn0=__84eb0340-ba34-4fdb-98b1-da503f967121', 'Node C\'s node address changed'); + })).eql(JSON.stringify({contentRepositoryId:"onedimension",workspaceName:"admin-admington",dimensionSpacePoint:{"language":"en_US"},aggregateId:"84eb0340-ba34-4fdb-98b1-da503f967121"}), 'Node C\'s node address changed'); await t.click(Page.getTreeNodeButton('Home')) }); diff --git a/Tests/IntegrationTests/Fixtures/2Dimension/switchingSites.e2e.js b/Tests/IntegrationTests/Fixtures/2Dimension/switchingSites.e2e.js index f323f54eb2..2f9418b416 100644 --- a/Tests/IntegrationTests/Fixtures/2Dimension/switchingSites.e2e.js +++ b/Tests/IntegrationTests/Fixtures/2Dimension/switchingSites.e2e.js @@ -20,7 +20,7 @@ test('Switching from Neos.Test.OneDimension to Neos.Test.TwoDimensions and back' subSection('Switch to Neos.Test.TwoDimensions via main menu'); await t.click(Selector('#neos-MenuToggler')); - await t.click(Selector('[href*="twodimensions"]')); + await t.click(Selector('[href*="twodimensions"] button')); await t.expect(getUrl()).contains('twodimensions.localhost', 'Switch to Neos.Test.TwoDimensions was successful'); @@ -28,7 +28,7 @@ test('Switching from Neos.Test.OneDimension to Neos.Test.TwoDimensions and back' await waitForReact(30000); await Page.goToPage('Home'); await t.click(Selector('#neos-MenuToggler')); - await t.click(Selector('[href*="onedimension"]')); + await t.click(Selector('[href*="onedimension"] button')); await t.expect(getUrl()).contains('onedimension.localhost', 'Switch to Neos.Test.OneDimension was successful'); }); @@ -45,7 +45,7 @@ test('Switching from Neos.Test.TwoDimensions to Neos.Test.OneDimension and back' subSection('Switch to Neos.Test.OneDimension via main menu'); await t.click(Selector('#neos-MenuToggler')); - await t.click(Selector('[href*="onedimension"]')); + await t.click(Selector('[href*="onedimension"] button')); await t.expect(getUrl()).contains('onedimension.localhost', 'Switch to Neos.Test.OneDimension was successful'); @@ -53,7 +53,7 @@ test('Switching from Neos.Test.TwoDimensions to Neos.Test.OneDimension and back' await waitForReact(30000); await Page.goToPage('Home'); await t.click(Selector('#neos-MenuToggler')); - await t.click(Selector('[href*="twodimensions"]')); + await t.click(Selector('[href*="twodimensions"] button')); await t.expect(getUrl()).contains('twodimensions.localhost', 'Switch to Neos.Test.TwoDimensions was successful'); }); diff --git a/Tests/IntegrationTests/e2e-docker.sh b/Tests/IntegrationTests/e2e-docker.sh index c7cfed36a7..2dcd78bcc0 100755 --- a/Tests/IntegrationTests/e2e-docker.sh +++ b/Tests/IntegrationTests/e2e-docker.sh @@ -58,13 +58,13 @@ dc exec -T php bash <<-'BASH' ./flow cr:setup --content-repository onedimension ./flow cr:import --content-repository onedimension --path ./DistributionPackages/Neos.Test.OneDimension/Resources/Private/Content - # Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary + # TODO: Fix when part of importer: Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary ./flow site:create neos-test-onedimension Neos.Test.OneDimension Neos.TestNodeTypes:Document.HomePage ./flow domain:add neos-test-onedimension onedimension.localhost --port 8081 ./flow cr:setup --content-repository twodimensions ./flow cr:import --content-repository twodimensions --path ./DistributionPackages/Neos.Test.TwoDimensions/Resources/Private/Content - # Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary + # TODO: Fix when part of importer: Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary ./flow site:create neos-test-twodimensions Neos.Test.TwoDimensions Neos.TestNodeTypes:Document.HomePage ./flow domain:add neos-test-twodimensions twodimensions.localhost --port 8081 diff --git a/Tests/IntegrationTests/e2e.sh b/Tests/IntegrationTests/e2e.sh index 491915f5f2..15bba7c8bd 100755 --- a/Tests/IntegrationTests/e2e.sh +++ b/Tests/IntegrationTests/e2e.sh @@ -80,13 +80,13 @@ function run_tests() { ./flow cr:setup --content-repository onedimension ./flow cr:import --content-repository onedimension --path ./DistributionPackages/Neos.Test.OneDimension/Resources/Private/Content - # Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary + # TODO: Fix when part of importer: Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary ./flow site:create neos-test-onedimension Neos.Test.OneDimension Neos.TestNodeTypes:Document.HomePage ./flow domain:add neos-test-onedimension onedimension.localhost --port 8081 ./flow cr:setup --content-repository twodimensions ./flow cr:import --content-repository twodimensions --path ./DistributionPackages/Neos.Test.TwoDimensions/Resources/Private/Content - # Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary + # TODO: Fix when part of importer: Connect to a Neos site, todo the nodeTypeName parameter is obsolete but necessary ./flow site:create neos-test-twodimensions Neos.Test.TwoDimensions Neos.TestNodeTypes:Document.HomePage ./flow domain:add neos-test-twodimensions twodimensions.localhost --port 8081 diff --git a/package.json b/package.json index 162b50448e..9e88f177c6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "moment": "^2.20.1", "vfile-message": "^2.0.2", "isemail@3.2.0": "patch:isemail@npm:3.2.0#./patches/isemail-npm-3.2.0-browserified.patch", - "react-codemirror2@7.2.1": "patch:react-codemirror2@npm:7.2.1#./patches/react-codemirror2-npm-7.2.1-browserified.patch" + "react-codemirror2@7.2.1": "patch:react-codemirror2@npm:7.2.1#./patches/react-codemirror2-npm-7.2.1-browserified.patch", + "@ckeditor/ckeditor5-engine@^16.0.0": "patch:@ckeditor/ckeditor5-engine@npm:16.0.0#./patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch" }, "scripts": { "lint": "tsc --noemit && stylelint 'packages/*/src/**/*.css' && yarn eslint 'packages/*/src/**/*.{js,jsx,ts,tsx}'", diff --git a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js index 7579373ac1..9f8a78afe6 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js +++ b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js @@ -2,6 +2,7 @@ import debounce from 'lodash.debounce'; import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor'; import {actions} from '@neos-project/neos-ui-redux-store'; import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit' +// FIXME import from @ckeditor/ckeditor5-engine/theme/placeholder.css instead! (Needs build setup configuration) import './placeholder.vanilla-css'; let currentEditor = null; diff --git a/packages/neos-ui-ckeditor5-bindings/src/placeholder.vanilla-css b/packages/neos-ui-ckeditor5-bindings/src/placeholder.vanilla-css index 7249952164..ba96571daf 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/placeholder.vanilla-css +++ b/packages/neos-ui-ckeditor5-bindings/src/placeholder.vanilla-css @@ -1,8 +1,17 @@ -.ck.ck-placeholder:before, .ck .ck-placeholder:before { - content: attr(data-placeholder); +.ck.ck-placeholder:after, .ck .ck-placeholder { + display: inline-flex; +} - /* See ckeditor/ckeditor5#469. */ - pointer-events: none; +.ck .ck-placeholder br[data-cke-filler="true"] { + display: none; +} +.ck.ck-placeholder:after, .ck .ck-placeholder:after { + content: attr(data-placeholder); + pointer-events: none; color: #999; } + +.ck.ck-read-only .ck-placeholder:after { + display: none; +} diff --git a/packages/neos-ui-ckeditor5-bindings/src/plugins/neosPlaceholder.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/neosPlaceholder.js deleted file mode 100644 index 721fe8858f..0000000000 --- a/packages/neos-ui-ckeditor5-bindings/src/plugins/neosPlaceholder.js +++ /dev/null @@ -1,75 +0,0 @@ -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import {stripTags} from '@neos-project/utils-helpers'; -import './neosPlaceholder.vanilla-css'; - -// If the data is "empty" (BR, P) or the placeholder then return an empty string. -// Otherwise return the original data -const htmlIsEmptyish = data => { - if (!data) { - return true; - } - - if (data.length > 20) { - return false; - } - const value = data.replace(/[\n|\t|\u200b]*/g, '').toLowerCase().trim(); - const a = ( - !value || - value === '
' || - value === ' ' || - value === '

 

' || - value === '

 

' || - value === '


' || - value === ' ' || - value === ' 
' || - value === '
' || - value.match(/^<([a-z0-9]+)>
<\/\1>$/) - ); - return a; -}; - -export default class NeosPlaceholder extends Plugin { - static get pluginName() { - return 'NeosPlaceholder'; - } - - getPlaceholder() { - return stripTags(this.editor.config.get('neosPlaceholder')); - } - - addPlaceholder() { - this.editor.editing.view.change(writer => { - writer.setAttribute('data-neos-placeholder', this.getPlaceholder(), this.editor.editing.view.document.getRoot()); - }); - } - - removePlaceholder() { - this.editor.editing.view.change(writer => { - writer.removeAttribute('data-neos-placeholder', this.editor.editing.view.document.getRoot()); - }); - } - - updatePlaceholder() { - if (htmlIsEmptyish(this.editor.getData({trim: 'none'})) && !this.editor.ui.focusTracker.isFocused) { - this.addPlaceholder(); - } else { - this.removePlaceholder(); - } - } - - init() { - if (this.editor.config.get('neosPlaceholder')) { - this.updatePlaceholder(); - - const {model} = this.editor.data; - - model.on('applyOperation', () => { - this.updatePlaceholder(); - return true; - }); - this.editor.ui.focusTracker.on('change:isFocused', () => { - this.updatePlaceholder(); - }); - } - } -} diff --git a/packages/neos-ui-editors/src/Library/LinkInput.js b/packages/neos-ui-editors/src/Library/LinkInput.js index 1260b8486f..00c47b5367 100644 --- a/packages/neos-ui-editors/src/Library/LinkInput.js +++ b/packages/neos-ui-editors/src/Library/LinkInput.js @@ -148,6 +148,12 @@ export default class LinkInput extends PureComponent { } handleSearchTermChange = searchTerm => { + // trim leading whitespace as it can cause issues + // with the further processing + if (typeof searchTerm === 'string') { + searchTerm = searchTerm.trimStart(); + } + this.setState({searchTerm}); if (isUriOrInternalLink(searchTerm)) { @@ -268,9 +274,16 @@ export default class LinkInput extends PureComponent { } handleManualSetLink = () => { - this.props.onLinkChange(this.state.searchTerm); + let {searchTerm} = this.state; + // trim tailing whitespace as it can cause issues + if (typeof searchTerm === 'string') { + searchTerm = searchTerm.trim(); + this.setState({searchTerm}); + } + + this.props.onLinkChange(searchTerm); this.setState({ - isEditMode: !this.state.searchTerm + isEditMode: !searchTerm }); } diff --git a/packages/neos-ui-guest-frame/src/dom.js b/packages/neos-ui-guest-frame/src/dom.js index 381eb0f0bc..33eb768352 100644 --- a/packages/neos-ui-guest-frame/src/dom.js +++ b/packages/neos-ui-guest-frame/src/dom.js @@ -54,14 +54,14 @@ export const findAllPropertiesInGuestFrame = () => // // Find all DOM nodes that represent a particular node property in the guest frame // -export const findAllOccurrencesOfNodePropertyInGuestFrame = (contextPath, propertyName) => findAllInGuestFrame(`[data-__neos-editable-node-contextpath="${contextPath}"][data-__neos-property="${propertyName}"]`); +export const findAllOccurrencesOfNodePropertyInGuestFrame = (contextPath, propertyName) => findAllInGuestFrame(`[data-__neos-editable-node-contextpath="${CSS.escape(contextPath)}"][data-__neos-property="${CSS.escape(propertyName)}"]`); // // Find all DOM nodes that represent CR node properties in the guest frame // export const findRelativePropertiesInGuestFrame = contentDomNode => [].slice.call(contentDomNode.querySelectorAll( - `[data-__neos-property][data-__neos-editable-node-contextpath="${contentDomNode.getAttribute('data-__neos-node-contextpath')}"]` + `[data-__neos-property][data-__neos-editable-node-contextpath="${CSS.escape(contentDomNode.getAttribute('data-__neos-node-contextpath'))}"]` )).concat(...( contentDomNode.hasAttribute('data-__neos-property') ? [contentDomNode] : [] @@ -71,9 +71,9 @@ export const findRelativePropertiesInGuestFrame = contentDomNode => // Find a specific DOM node that represents a CR node in the guest frame // export const findNodeInGuestFrame = (contextPath, fusionPath) => fusionPath ? findInGuestFrame( - `[data-__neos-node-contextpath="${contextPath}"][data-__neos-fusion-path="${fusionPath}"]` + `[data-__neos-node-contextpath="${CSS.escape(contextPath)}"][data-__neos-fusion-path="${CSS.escape(fusionPath)}"]` ) : findInGuestFrame( - `[data-__neos-node-contextpath="${contextPath}"]` + `[data-__neos-node-contextpath="${CSS.escape(contextPath)}"]` ); // @@ -81,9 +81,9 @@ export const findNodeInGuestFrame = (contextPath, fusionPath) => fusionPath ? fi // fusion path in the guest frame // export const findAllOccurrencesOfNodeInGuestFrame = (contextPath, fusionPath) => fusionPath ? findAllInGuestFrame( - `[data-__neos-node-contextpath="${contextPath}"][data-__neos-fusion-path="${fusionPath}"]` + `[data-__neos-node-contextpath="${CSS.escape(contextPath)}"][data-__neos-fusion-path="${CSS.escape(fusionPath)}"]` ) : findAllInGuestFrame( - `[data-__neos-node-contextpath="${contextPath}"]` + `[data-__neos-node-contextpath="${CSS.escape(contextPath)}"]` ); // diff --git a/packages/neos-ui/src/Containers/ErrorBoundary/style.module.css b/packages/neos-ui/src/Containers/ErrorBoundary/style.module.css index b33b789663..b11e8853a9 100644 --- a/packages/neos-ui/src/Containers/ErrorBoundary/style.module.css +++ b/packages/neos-ui/src/Containers/ErrorBoundary/style.module.css @@ -29,3 +29,7 @@ flex-wrap: wrap; gap: var(--spacing-Half) var(--spacing-Full); } + +.logo > svg { + height: 40px; +} diff --git a/packages/neos-ui/src/System/terminateDueToFatalInitializationError.js b/packages/neos-ui/src/System/terminateDueToFatalInitializationError.js index 992c642a01..a10a4caa63 100644 --- a/packages/neos-ui/src/System/terminateDueToFatalInitializationError.js +++ b/packages/neos-ui/src/System/terminateDueToFatalInitializationError.js @@ -1,5 +1,4 @@ -import {Logo} from '@neos-project/react-ui-components'; - +import logoSvg from '@neos-project/react-ui-components/src/Logo/resource/logo.svg'; import styles from '../Containers/ErrorBoundary/style.module.css'; export function terminateDueToFatalInitializationError(reason) { @@ -11,8 +10,7 @@ export function terminateDueToFatalInitializationError(reason) { document.body.innerHTML = `
- - Neos + ${logoSvg}

Sorry, but the Neos UI could not be initialized.

diff --git a/packages/neos-ui/src/manifest.js b/packages/neos-ui/src/manifest.js index 22cb23bb6e..749a16240e 100644 --- a/packages/neos-ui/src/manifest.js +++ b/packages/neos-ui/src/manifest.js @@ -422,7 +422,7 @@ manifest('main', {}, (globalRegistry, {routes}) => { const tempNodeInGuest = getGuestFrameDocument().createElement(wrapTagName); tempNodeInGuest.innerHTML = renderedContent; const contentElement = tempNodeInGuest - .querySelector(`[data-__neos-node-contextpath="${contextPath}"]`); + .querySelector(`[data-__neos-node-contextpath="${CSS.escape(contextPath)}"]`); if (!contentElement) { console.error(`!!! Content Element (rendered out-of-band) with context path "${contextPath}" not found in returned HTML from server (which you see below) - Reloading the full page!`); @@ -527,7 +527,7 @@ manifest('main', {}, (globalRegistry, {routes}) => { const tempNodeInGuest = getGuestFrameDocument().createElement('div'); tempNodeInGuest.innerHTML = renderedContent; const contentElement = tempNodeInGuest - .querySelector(`[data-__neos-node-contextpath="${contextPath}"]`); + .querySelector(`[data-__neos-node-contextpath="${CSS.escape(contextPath)}"]`); if (!contentElement) { console.error(`!!! Content Element (reloaded out-of-band) with context path "${contextPath}" not found in returned HTML from server (which you see below) - Reloading the full page!`); diff --git a/patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch b/patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch new file mode 100644 index 0000000000..ccf8c1751b --- /dev/null +++ b/patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch @@ -0,0 +1,22 @@ +diff --git a/src/view/placeholder.js b/src/view/placeholder.js +index ec83640d4f7d12d8278c921a3c641917543ca859..09e96933b392f38d079ac3126ead49690e408228 100755 +--- a/src/view/placeholder.js ++++ b/src/view/placeholder.js +@@ -155,16 +155,7 @@ export function needsPlaceholder( element ) { + const isEmptyish = !Array.from( element.getChildren() ) + .some( element => !element.is( 'uiElement' ) ); + +- // If the element is empty and the document is blurred. +- if ( !doc.isFocused && isEmptyish ) { +- return true; +- } +- +- const viewSelection = doc.selection; +- const selectionAnchor = viewSelection.anchor; +- +- // If document is focused and the element is empty but the selection is not anchored inside it. +- if ( isEmptyish && selectionAnchor && selectionAnchor.parent !== element ) { ++ if ( isEmptyish ) { + return true; + } + diff --git a/yarn.lock b/yarn.lock index 2716cb2476..38867f1d82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2037,7 +2037,7 @@ __metadata: languageName: node linkType: hard -"@ckeditor/ckeditor5-engine@npm:^16.0.0": +"@ckeditor/ckeditor5-engine@npm:16.0.0": version: 16.0.0 resolution: "@ckeditor/ckeditor5-engine@npm:16.0.0" dependencies: @@ -2047,6 +2047,16 @@ __metadata: languageName: node linkType: hard +"@ckeditor/ckeditor5-engine@patch:@ckeditor/ckeditor5-engine@npm:16.0.0#./patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch::locator=root-workspace-0b6124%40workspace%3A.": + version: 16.0.0 + resolution: "@ckeditor/ckeditor5-engine@patch:@ckeditor/ckeditor5-engine@npm%3A16.0.0#./patches/@ckeditor-ckeditor5-engine-npm-16.0.0-placeholder.patch::version=16.0.0&hash=582e01&locator=root-workspace-0b6124%40workspace%3A." + dependencies: + "@ckeditor/ckeditor5-utils": ^16.0.0 + lodash-es: ^4.17.10 + checksum: 0ca241d6d28da9d7104c943d9ff2bf88591eb2e2726189029c3c43f8d59f726a10decc2aeb21db95a7e0242a4fc37fc480270869ca5a2f7d5db381107a385e38 + languageName: node + linkType: hard + "@ckeditor/ckeditor5-enter@npm:^16.0.0": version: 16.0.0 resolution: "@ckeditor/ckeditor5-enter@npm:16.0.0"