mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
Handle double-click submission in DocumentExplorer actions (#12417)
* Initial plan * Support double-click submit in document explorer * Remove test * Fix double-click submit in document explorer Single click now sets the selection instead of toggling it, so the two clicks preceding a dblclick no longer flicker the selection on/off. Submit handlers accept the node directly to avoid the stale-state race across the click sequence, and button onClick handlers are wrapped so the synthetic MouseEvent isn't passed in as the path argument. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * PR feedback --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Tom Moor <tom@getoutline.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -40,8 +40,8 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
return nodes;
|
||||
}, [policies, collectionTrees]);
|
||||
|
||||
const copy = async () => {
|
||||
if (!selectedPath) {
|
||||
const copy = async (path = selectedPath) => {
|
||||
if (!path) {
|
||||
toast.message(t("Select a location to copy"));
|
||||
return;
|
||||
}
|
||||
@@ -52,10 +52,8 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
publish,
|
||||
recursive,
|
||||
title: document.title,
|
||||
collectionId: selectedPath.collectionId,
|
||||
...(selectedPath.type === "document"
|
||||
? { parentDocumentId: selectedPath.id }
|
||||
: {}),
|
||||
collectionId: path.collectionId,
|
||||
...(path.type === "document" ? { parentDocumentId: path.id } : {}),
|
||||
});
|
||||
|
||||
toast.success(t("Document copied"));
|
||||
@@ -111,7 +109,7 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
t("Select a location to copy")
|
||||
)}
|
||||
</Text>
|
||||
<Button disabled={!selectedPath || copying} onClick={copy}>
|
||||
<Button disabled={!selectedPath || copying} onClick={() => copy()}>
|
||||
{copying ? `${t("Copying")}…` : t("Copy")}
|
||||
</Button>
|
||||
</Footer>
|
||||
|
||||
@@ -31,7 +31,7 @@ import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
/** Action taken upon submission of selected item, could be publish, move etc. */
|
||||
onSubmit: () => void;
|
||||
onSubmit: (item: NavigationNode | null) => void;
|
||||
/** A side-effect of item selection */
|
||||
onSelect: (item: NavigationNode | null) => void;
|
||||
/** Items to be shown in explorer */
|
||||
@@ -255,6 +255,13 @@ function DocumentExplorer({
|
||||
}
|
||||
};
|
||||
|
||||
const submitNode = (node: number) => {
|
||||
const selectedNode = nodes[node];
|
||||
|
||||
selectNode(selectedNode);
|
||||
onSubmit(selectedNode);
|
||||
};
|
||||
|
||||
const ListItem = observer(
|
||||
({
|
||||
index,
|
||||
@@ -311,7 +318,8 @@ function DocumentExplorer({
|
||||
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
|
||||
}}
|
||||
onPointerMove={() => setActiveNode(index)}
|
||||
onClick={() => toggleSelect(index)}
|
||||
onClick={() => selectNode(nodes[index])}
|
||||
onDoubleClick={() => submitNode(index)}
|
||||
icon={renderedIcon}
|
||||
title={title}
|
||||
path={path}
|
||||
@@ -325,7 +333,8 @@ function DocumentExplorer({
|
||||
width: `calc(${style.width} - ${HORIZONTAL_PADDING * 2}px)`,
|
||||
}}
|
||||
onPointerMove={() => setActiveNode(index)}
|
||||
onClick={() => toggleSelect(index)}
|
||||
onClick={() => selectNode(nodes[index])}
|
||||
onDoubleClick={() => submitNode(index)}
|
||||
onDisclosureClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
toggleCollapse(index);
|
||||
@@ -387,7 +396,7 @@ function DocumentExplorer({
|
||||
}
|
||||
case "Enter": {
|
||||
if (isModKey(ev)) {
|
||||
onSubmit();
|
||||
onSubmit(selectedNode);
|
||||
} else {
|
||||
toggleSelect(activeNode);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ type Props = {
|
||||
onDisclosureClick: (ev: React.MouseEvent) => void;
|
||||
/** Fired on pointer movement over the node; used to update the active highlight. */
|
||||
onPointerMove: (ev: React.MouseEvent) => void;
|
||||
/** Fired when the node is clicked to toggle its selection. */
|
||||
/** Fired when the node is clicked to select it. */
|
||||
onClick: (ev: React.MouseEvent) => void;
|
||||
/** Fired when the node is double-clicked to submit the current selection. */
|
||||
onDoubleClick: (ev: React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
function DocumentExplorerNode(
|
||||
@@ -46,6 +48,7 @@ function DocumentExplorerNode(
|
||||
onDisclosureClick,
|
||||
onPointerMove,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
}: Props,
|
||||
ref: React.RefObject<HTMLSpanElement>
|
||||
) {
|
||||
@@ -59,6 +62,7 @@ function DocumentExplorerNode(
|
||||
selected={selected}
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
style={style}
|
||||
onPointerMove={onPointerMove}
|
||||
role="option"
|
||||
|
||||
@@ -17,6 +17,7 @@ type Props = {
|
||||
|
||||
onPointerMove: (ev: React.MouseEvent) => void;
|
||||
onClick: (ev: React.MouseEvent) => void;
|
||||
onDoubleClick: (ev: React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
function DocumentExplorerSearchResult({
|
||||
@@ -28,6 +29,7 @@ function DocumentExplorerSearchResult({
|
||||
path,
|
||||
onPointerMove,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -36,6 +38,7 @@ function DocumentExplorerSearchResult({
|
||||
selected={selected}
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
style={style}
|
||||
onPointerMove={onPointerMove}
|
||||
role="option"
|
||||
|
||||
@@ -56,17 +56,17 @@ function DocumentMove({ document }: Props) {
|
||||
return nodes;
|
||||
}, [policies, collectionTrees, document.id]);
|
||||
|
||||
const move = async () => {
|
||||
if (!selectedPath) {
|
||||
const move = async (path = selectedPath) => {
|
||||
if (!path) {
|
||||
toast.message(t("Select a location to move"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setMoving(true);
|
||||
const { type, id: parentDocumentId } = selectedPath;
|
||||
const { type, id: parentDocumentId } = path;
|
||||
|
||||
const collectionId = selectedPath.collectionId as string;
|
||||
const collectionId = path.collectionId as string;
|
||||
|
||||
if (type === "document") {
|
||||
await document.move({ collectionId, parentDocumentId });
|
||||
@@ -103,7 +103,7 @@ function DocumentMove({ document }: Props) {
|
||||
t("Select a location to move")
|
||||
)}
|
||||
</Text>
|
||||
<Button disabled={!selectedPath || moving} onClick={move}>
|
||||
<Button disabled={!selectedPath || moving} onClick={() => move()}>
|
||||
{moving ? `${t("Moving")}…` : t("Move")}
|
||||
</Button>
|
||||
</Footer>
|
||||
|
||||
@@ -33,15 +33,14 @@ function TemplateMove({ template }: Props) {
|
||||
[policies, collectionTrees]
|
||||
);
|
||||
|
||||
const move = async () => {
|
||||
if (!selectedPath) {
|
||||
const move = async (path = selectedPath) => {
|
||||
if (!path) {
|
||||
toast.message(t("Select a location to move"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const collectionId = (selectedPath.collectionId ??
|
||||
selectedPath.id) as string;
|
||||
const collectionId = (path.collectionId ?? path.id) as string;
|
||||
await template.save({ collectionId });
|
||||
|
||||
toast.success(t("Template moved"));
|
||||
@@ -76,7 +75,7 @@ function TemplateMove({ template }: Props) {
|
||||
t("Select a location to move")
|
||||
)}
|
||||
</Text>
|
||||
<Button disabled={!selectedPath} onClick={move}>
|
||||
<Button disabled={!selectedPath} onClick={() => move()}>
|
||||
{t("Move")}
|
||||
</Button>
|
||||
</Footer>
|
||||
|
||||
@@ -33,16 +33,16 @@ function DocumentPublish({ document }: Props) {
|
||||
[policies, collectionTrees]
|
||||
);
|
||||
|
||||
const publish = async () => {
|
||||
if (!selectedPath) {
|
||||
const publish = async (path = selectedPath) => {
|
||||
if (!path) {
|
||||
toast.message(t("Select a location to publish"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { type, id: parentDocumentId } = selectedPath;
|
||||
const { type, id: parentDocumentId } = path;
|
||||
|
||||
const collectionId = selectedPath.collectionId as string;
|
||||
const collectionId = path.collectionId as string;
|
||||
|
||||
// Also move it under if selected path corresponds to another doc
|
||||
if (type === "document") {
|
||||
@@ -83,7 +83,7 @@ function DocumentPublish({ document }: Props) {
|
||||
t("Select a location to publish")
|
||||
)}
|
||||
</StyledText>
|
||||
<Button disabled={!selectedPath} onClick={publish}>
|
||||
<Button disabled={!selectedPath} onClick={() => publish()}>
|
||||
{t("Publish")}
|
||||
</Button>
|
||||
</Footer>
|
||||
|
||||
Reference in New Issue
Block a user