Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3efe99c
TASK: Introduce initial test for get-references-summary
mhsdesign Oct 21, 2025
30b91f5
WIP: Initial copy of `GetReferencesSummary`
mhsdesign Oct 21, 2025
72e9b3c
WIP: 'GetReferencesSummary' Test adjusted, API created to fetch refer…
sajuschi Oct 21, 2025
7414778
WIP: Add policy
mhsdesign Oct 21, 2025
e4dfa96
WIP: Add policy lol 2
mhsdesign Oct 21, 2025
4b2c17d
WIP: add behat test scenario for multiple references, fetch reference…
so-grimm Oct 21, 2025
3e191fc
WIP: 'GetReferencesSummary' Test adjusted, properties changed for 'Ge…
sajuschi Oct 22, 2025
3edc464
WIP: Display references from the new API EP
Oct 22, 2025
5707ffd
WIP: More style adjustments for the Reference Item
Oct 22, 2025
d8d015c
WIP: Add hover actions
Oct 22, 2025
3a71f60
WIP: Add neos-ui-reference-editor-custom-node-tree
Oct 22, 2025
f2cbe61
WIP: implement endpoint for GetChildrenForTreeNode and added tests
so-grimm Oct 22, 2025
1b97113
WIP: implement endpoint for GetTree and added tests for getTree inclu…
so-grimm Oct 22, 2025
5ee3af7
WIP: Use props.identifier for request
Oct 22, 2025
62e2cf0
WIP: Inline `ReferencesList`
mhsdesign Oct 22, 2025
0c9799c
WIP: Introduce global reference editor state handling for opening rel…
mhsdesign Oct 22, 2025
44a9862
WIP: Fix empty referenceIds
Oct 22, 2025
2577330
WIP: Class Reference created to wrap single reference in GetReference…
sajuschi Oct 22, 2025
cc28de9
TASK: Allow to que additional changes (future reference and subtree c…
mhsdesign Oct 23, 2025
dc4955c
WIP: Show reference property editors
mhsdesign Oct 23, 2025
b47eec8
WIP: Que changes from the reference editor
mhsdesign Oct 24, 2025
da25ed2
WIP: Wip handle inspector discard event to flush references editor ->…
mhsdesign Oct 24, 2025
f68c4e9
WIP: Wip add new dedicated Reference Change type
mhsdesign Oct 24, 2025
b137bd5
WIP: secondary editor
laurahaenel Oct 24, 2025
3b3f01d
TASK: show selected nodes in secondary editor
laurahaenel Oct 24, 2025
85a2dfc
WIP: reference property editing
mhsdesign Oct 24, 2025
99e68f7
WIP: structure secondary editor
laurahaenel Oct 24, 2025
1c521dc
WIP: fix install
laurahaenel Oct 24, 2025
1d3635d
WIP: deduplicate qued reference changes (only one is to be committed)
mhsdesign Oct 24, 2025
c0b53a3
WIP: subscribe to selected nodes & select node action
laurahaenel Oct 24, 2025
5c74944
TASK: mock api
laurahaenel Oct 24, 2025
bdf0fe4
WIP: persist reference properties
mhsdesign Oct 24, 2025
28462f0
WIP: add initial editor test
mhsdesign Oct 24, 2025
40977df
WIP: add prevent closing when reference property editing is dirty
mhsdesign Oct 24, 2025
bb9a513
WIP: fix laura
mhsdesign Oct 24, 2025
e5bbc76
WIP: fix apply disabled state and fix dialog zindex
mhsdesign Oct 24, 2025
2bc22e2
WIP: add initial references count from server
mhsdesign Oct 24, 2025
eba7de5
WIP: bugfix editor never turns transient values to initial State whic…
mhsdesign Oct 24, 2025
b45fa09
WIP: loading state on ref values
laurahaenel Oct 24, 2025
10d92c1
WIP: fix state handling on transient and initial values for pending p…
laurahaenel Oct 24, 2025
57a5395
WIP: add test for discard and apply pending changes
laurahaenel Oct 24, 2025
71d53ed
WIP: copy GetNodeSummary Query from link editor for reference editor
mhsdesign Oct 25, 2025
8d1d76e
WIP: use dedicated icon for reference property editing
mhsdesign Oct 25, 2025
0811e5c
WIP: try out simple `IconCard` as presentation (todo move to shared p…
mhsdesign Oct 25, 2025
98ffdc8
TASK: Update references editor to also use `Flow\Route` and `Abstract…
mhsdesign Dec 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ trim_trailing_whitespace = true
indent_style = space
indent_size = 4

[*.{yml,yaml,json,xlf}]
[*.{yml,yaml,feature,json,xlf}]
indent_size = 2

[*.md]
Expand Down
139 changes: 139 additions & 0 deletions Classes/Domain/Model/Changes/Reference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Neos\Neos\Ui\Domain\Model\Changes;

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite;
use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences;
use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferencesForName;
use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferencesToWrite;
use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferenceToWrite;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName;
use Neos\Neos\Ui\Domain\Model\AbstractChange;
use Neos\Neos\Ui\Domain\Model\Feedback\Operations\ReloadContentOutOfBand;
use Neos\Neos\Ui\Domain\Model\Feedback\Operations\UpdateNodeInfo;
use Neos\Neos\Ui\Domain\Model\RenderedNodeDomAddress;

/**
* Changes a reference on a node
* @internal These objects internally reflect possible operations made by the Neos.Ui.
* They are sorely an implementation detail. You should not use them!
* Please look into the php command API of the Neos CR instead.
*/
class Reference extends AbstractChange
{
public function __construct(

Check failure on line 35 in Classes/Domain/Model/Changes/Reference.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.2, 9.1)

Method Neos\Neos\Ui\Domain\Model\Changes\Reference::__construct() has parameter $serializedReferences with no value type specified in iterable type array.

Check failure on line 35 in Classes/Domain/Model/Changes/Reference.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.3, 9.1)

Method Neos\Neos\Ui\Domain\Model\Changes\Reference::__construct() has parameter $serializedReferences with no value type specified in iterable type array.
// private Node $subject, lol todo
private string $referenceName,
private ?RenderedNodeDomAddress $nodeDomAddress,
private array $serializedReferences
) {
}

public function canApply(): bool
{
$nodeType = $this->getNodeType($this->subject);
if (!$nodeType) {
return false;
}
return $nodeType->hasReference($this->referenceName);
}

public function apply(): void
{
if ($this->canApply() === false) {
return;
}

$this->handleNodeReferenceChange();
$this->createFeedback();
}

private function createFeedback(): void
{
$subject = $this->subject;

// We have to refetch the Node after modifications because its a read-only model
// These 'Change' classes have been designed with mutable Neos < 9 Nodes and thus this might seem hacky
// When fully redesigning the Neos Ui php integration this will fixed
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($subject);
$originalNodeAggregateId = $subject->aggregateId;
$node = $subgraph->findNodeById($originalNodeAggregateId);
if (is_null($node)) {
throw new \InvalidArgumentException(
'Cannot apply reference on missing node ' . $originalNodeAggregateId->value,
1645560836
);
}

$this->updateWorkspaceInfo();
$parentNode = $subgraph->findParentNode($node->aggregateId);

// This might be needed to update node label and other things that we can calculate only on the server
$updateNodeInfo = new UpdateNodeInfo();
$updateNodeInfo->setNode($node);
$this->feedbackCollection->add($updateNodeInfo);

$reloadIfChangedConfigurationPathForReference = sprintf('references.%s.ui.reloadIfChanged', $this->referenceName);
if (
$this->getNodeType($node)?->getConfiguration($reloadIfChangedConfigurationPathForReference)
) {
if (!$this->nodeDomAddress) {
$this->reloadDocument($node);
} elseif ($this->nodeDomAddress->getFusionPath()
&& $parentNode
&& $this->getNodeType($parentNode)?->isOfType('Neos.Neos:ContentCollection')) {
$reloadContentOutOfBand = new ReloadContentOutOfBand();
$reloadContentOutOfBand->setNode($node);
$reloadContentOutOfBand->setNodeDomAddress($this->nodeDomAddress);
$this->feedbackCollection->add($reloadContentOutOfBand);
} else {
$this->reloadDocument($node);
}
}

$reloadPageIfChangedConfigurationPathForReference = sprintf('references.%s.ui.reloadPageIfChanged', $this->referenceName);
if (
$this->getNodeType($node)?->getConfiguration($reloadPageIfChangedConfigurationPathForReference)
) {
$this->reloadDocument($node);
}
}

private function handleNodeReferenceChange(): void
{
$contentRepository = $this->contentRepositoryRegistry->get($this->subject->contentRepositoryId);

$nodeReferencesToWrite = [];
foreach ($this->serializedReferences as $serializedReference) {
$nodeReferencesToWrite[] = NodeReferenceToWrite::fromTargetAndProperties(
target: NodeAggregateId::fromString($serializedReference['targetNodeId']),
properties: PropertyValuesToWrite::fromArray($serializedReference['properties'] ?? [])
);
}

$contentRepository->handle(
SetNodeReferences::create(
$this->subject->workspaceName,
$this->subject->aggregateId,
$this->subject->originDimensionSpacePoint,
NodeReferencesToWrite::create(
NodeReferencesForName::fromReferences(
ReferenceName::fromString($this->referenceName),
$nodeReferencesToWrite
)
)
)
);
}
}
8 changes: 8 additions & 0 deletions Classes/Domain/Model/RenderedNodeDomAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
*/
protected $fusionPath;

public static function fromArray(array $array): self

Check failure on line 34 in Classes/Domain/Model/RenderedNodeDomAddress.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.2, 9.1)

Method Neos\Neos\Ui\Domain\Model\RenderedNodeDomAddress::fromArray() has parameter $array with no value type specified in iterable type array.

Check failure on line 34 in Classes/Domain/Model/RenderedNodeDomAddress.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.3, 9.1)

Method Neos\Neos\Ui\Domain\Model\RenderedNodeDomAddress::fromArray() has parameter $array with no value type specified in iterable type array.
{
$me = new self();
$me->contextPath = $array['contextPath'];
$me->fusionPath = $array['fusionPath'];
return $me;
}

/**
* Set the context path
*
Expand Down
12 changes: 8 additions & 4 deletions Classes/Domain/Service/NodePropertyConverterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag;
use Neos\ContentRepository\Core\NodeType\NodeType;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountReferencesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindReferencesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
Expand Down Expand Up @@ -100,17 +101,18 @@
}

/**
* todo remove all
* @return list<string>|string|null
*/
private function getReference(Node $node, string $referenceName): array|string|null
private function getReferencesCount(Node $node, string $referenceName): int

Check failure on line 107 in Classes/Domain/Service/NodePropertyConverterService.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.2, 9.1)

PHPDoc tag @return with type array<int, string>|string|null is incompatible with native type int.

Check failure on line 107 in Classes/Domain/Service/NodePropertyConverterService.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.3, 9.1)

PHPDoc tag @return with type array<int, string>|string|null is incompatible with native type int.
{
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($node);
$references = $subgraph->findReferences(
return $subgraph->countReferences(
$node->aggregateId,
FindReferencesFilter::create(referenceName: $referenceName)
CountReferencesFilter::create(referenceName: $referenceName)
);

$referenceIdentifiers = [];

Check failure on line 115 in Classes/Domain/Service/NodePropertyConverterService.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.2, 9.1)

Unreachable statement - code above always terminates.

Check failure on line 115 in Classes/Domain/Service/NodePropertyConverterService.php

View workflow job for this annotation

GitHub Actions / Lint and Test (8.3, 9.1)

Unreachable statement - code above always terminates.
foreach ($references as $reference) {
$referenceIdentifiers[] = $reference->node->aggregateId->value;
}
Expand Down Expand Up @@ -182,7 +184,9 @@
$properties[$propertyName] = $this->getProperty($node, $propertyName);
}
foreach ($this->getNodeType($node)->getReferences() as $referenceName => $_) {
$properties[$referenceName] = $this->getReference($node, $referenceName);
$properties[$referenceName] = [
'referencesCount' => $this->getReferencesCount($node, $referenceName)
];
}
return $properties;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\Neos\Ui\ReferencesEditor\Application\GetChildrenForTreeNode\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Neos\Neos\Ui\Infrastructure\MVC\AbstractQueryController;
use Neos\Neos\Ui\Infrastructure\MVC\QueryResponseHelper;
use Neos\Neos\Ui\ReferencesEditor\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQuery;
use Neos\Neos\Ui\ReferencesEditor\Application\GetChildrenForTreeNode\GetChildrenForTreeNodeQueryHandler;
use Neos\Neos\Ui\LinkEditor\Application\Shared\NodeWasNotFound;
use Psr\Http\Message\ResponseInterface;

#[Flow\Scope("singleton")]
final class GetChildrenForTreeNodeController extends AbstractQueryController
{
#[Flow\Inject]
protected GetChildrenForTreeNodeQueryHandler $queryHandler;

#[Flow\Route('neos/references-editor/get-children-for-tree-node')]
public function processQueryAction(): ResponseInterface
{
$arguments = $this->request->getArguments();
if (!isset($arguments['contentRepositoryId'])) {
/** @todo send from UI */
$siteDetectionResult = SiteDetectionResult::fromRequest($this->request->getHttpRequest());
$arguments['contentRepositoryId'] = $siteDetectionResult->contentRepositoryId->value;
}

try {
$query = GetChildrenForTreeNodeQuery::fromArray($arguments);
$queryResult = $this->queryHandler->handle($query);

return QueryResponseHelper::createSuccess($queryResult);
} catch (NodeWasNotFound $e) {
return QueryResponseHelper::createServerSideErrorForBadRequest($e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\Neos\Ui\ReferencesEditor\Application\GetChildrenForTreeNode;

use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\NodeType\NodeTypeNames;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class GetChildrenForTreeNodeQuery
{
public function __construct(
public readonly ContentRepositoryId $contentRepositoryId,
public readonly WorkspaceName $workspaceName,
public readonly DimensionSpacePoint $dimensionSpacePoint,
public readonly NodeAggregateId $treeNodeId,
public readonly string $nodeTypeFilter,
public readonly NodeTypeNames $allowedNodeTypes,
) {
}

/**
* @param array<string,mixed> $array
*/
public static function fromArray(array $array): self
{
isset($array['contentRepositoryId'])
or throw new \InvalidArgumentException('Content Repository Id must be set');
is_string($array['contentRepositoryId'])
or throw new \InvalidArgumentException('Content Repository Id must be a string');

isset($array['workspaceName'])
or throw new \InvalidArgumentException('Workspace name must be set');
is_string($array['workspaceName'])
or throw new \InvalidArgumentException('Workspace name must be a string');

isset($array['treeNodeId'])
or throw new \InvalidArgumentException('Tree node id must be set');
is_string($array['treeNodeId'])
or throw new \InvalidArgumentException('Tree node id must be a string');

!isset($array['nodeTypeFilter']) or is_string($array['nodeTypeFilter'])
or throw new \InvalidArgumentException('Node type filter must be a string');

!isset($array['allowedNodeTypes']) or is_array($array['allowedNodeTypes'])
or throw new \InvalidArgumentException('allowed node types must be an array');

return new self(
contentRepositoryId: ContentRepositoryId::fromString($array['contentRepositoryId']),
workspaceName: WorkspaceName::fromString($array['workspaceName']),
dimensionSpacePoint: DimensionSpacePoint::fromLegacyDimensionArray($array['dimensionValues'] ?? []),
treeNodeId: NodeAggregateId::fromString($array['treeNodeId']),
nodeTypeFilter: $array['nodeTypeFilter'] ?? '',
allowedNodeTypes: NodeTypeNames::fromStringArray($array['allowedNodeTypes'] ?? []),
);
}
}
Loading
Loading