Skip to content

Commit 3cc85f1

Browse files
Copilottommoor
andauthored
Fix DocumentMove dialog hiding siblings and nieces/nephews as move targets (outline#11885)
* Initial plan * fix: show siblings and descendants in DocumentMove dialog The filterSourceDocument function was incorrectly removing the document's parent node from the navigation tree, which also hid all siblings (children of the same parent) and their descendants. Instead, only the document itself and its own descendants are now excluded (to prevent circular references). The parent is kept in the tree so siblings remain visible as valid move targets. Agent-Logs-Url: https://github.com/outline/outline/sessions/12574f1c-7a7c-45a0-8444-19e24aa10782 Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
1 parent 0b213bd commit 3cc85f1

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

app/components/DocumentExplorer/DocumentMove.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useState, useMemo } from "react";
33
import { useTranslation, Trans } from "react-i18next";
44
import { toast } from "sonner";
55
import type { NavigationNode } from "@shared/types";
6+
import { descendants, flattenTree } from "@shared/utils/tree";
67
import type Document from "~/models/Document";
78
import Button from "~/components/Button";
89
import Text from "~/components/Text";
@@ -23,13 +24,23 @@ function DocumentMove({ document }: Props) {
2324
const [selectedPath, selectPath] = useState<NavigationNode | null>(null);
2425

2526
const items = useMemo(() => {
26-
// Recursively filter out the document itself and its existing parent doc, if any.
27+
// Collect the IDs of the document itself and all of its descendants so they
28+
// can be excluded from the move targets (moving to self or a descendant
29+
// would create a cycle; moving to the exact same location is a no-op).
30+
const allNodes = collectionTrees.flatMap(flattenTree);
31+
const sourceNode = allNodes.find((node) => node.id === document.id);
32+
const excludedIds = new Set<string>([document.id]);
33+
if (sourceNode) {
34+
descendants(sourceNode).forEach((n) => excludedIds.add(n.id));
35+
}
36+
37+
// Recursively filter out the document itself and its descendants.
38+
// The document's current parent is intentionally kept so that siblings
39+
// remain visible as valid move targets.
2740
const filterSourceDocument = (node: NavigationNode): NavigationNode => ({
2841
...node,
2942
children: node.children
30-
?.filter(
31-
(c) => c.id !== document.id && c.id !== document.parentDocumentId
32-
)
43+
?.filter((c) => !excludedIds.has(c.id))
3344
.map(filterSourceDocument),
3445
});
3546

@@ -43,7 +54,7 @@ function DocumentMove({ document }: Props) {
4354
);
4455

4556
return nodes;
46-
}, [policies, collectionTrees, document.id, document.parentDocumentId]);
57+
}, [policies, collectionTrees, document.id]);
4758

4859
const move = async () => {
4960
if (!selectedPath) {

0 commit comments

Comments
 (0)