diff --git a/apps/desktop/src/components/dnd/dnd-provider.tsx b/apps/desktop/src/components/dnd/dnd-provider.tsx index 86d02c1e..9b4ec9c7 100644 --- a/apps/desktop/src/components/dnd/dnd-provider.tsx +++ b/apps/desktop/src/components/dnd/dnd-provider.tsx @@ -13,7 +13,10 @@ import { handleEditorDrop } from "./editor-drop-handler" import { EditorDropLine } from "./editor-drop-indicator" import { isPoint } from "./editor-drop-indicator.helpers" import { EditorDropOwnershipProvider } from "./editor-drop-ownership" -import { ExplorerDragOverlay } from "./explorer-drag-overlay" +import { + ExplorerDragOverlay, + getExplorerDragOverlayName, +} from "./explorer-drag-overlay" import { EMPTY_DRAGGED_EXPLORER_PATHS, ExplorerDragPathsProvider, @@ -61,7 +64,7 @@ function renderDragOverlay(source: DragOverlaySource) { if (isFileEntryDragData(source.data) && source.data.name) { return ( ) diff --git a/apps/desktop/src/components/dnd/dnd-types.ts b/apps/desktop/src/components/dnd/dnd-types.ts index 7b756cc9..58093a63 100644 --- a/apps/desktop/src/components/dnd/dnd-types.ts +++ b/apps/desktop/src/components/dnd/dnd-types.ts @@ -12,6 +12,7 @@ export type FileEntryDragData = { path?: string isDirectory?: boolean name?: string + displayName?: string } export type EditorDragData = { @@ -80,15 +81,15 @@ export function isFileEntryDragData(data: unknown): data is FileEntryDragData { } const hasKnownKey = "path" in data || "isDirectory" in data || "name" in data - if (!hasKnownKey) { + if (!hasKnownKey && !("displayName" in data)) { return false } - - const { path, isDirectory, name } = data + const { path, isDirectory, name, displayName } = data return ( (path === undefined || typeof path === "string") && (isDirectory === undefined || typeof isDirectory === "boolean") && - (name === undefined || typeof name === "string") + (name === undefined || typeof name === "string") && + (displayName === undefined || typeof displayName === "string") ) } diff --git a/apps/desktop/src/components/dnd/explorer-drag-overlay.test.tsx b/apps/desktop/src/components/dnd/explorer-drag-overlay.test.tsx new file mode 100644 index 00000000..6cdf6700 --- /dev/null +++ b/apps/desktop/src/components/dnd/explorer-drag-overlay.test.tsx @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest" +import { getExplorerDragOverlayName } from "./explorer-drag-overlay" + +describe("getExplorerDragOverlayName", () => { + it("prefers the display name when present", () => { + expect( + getExplorerDragOverlayName({ + name: "note.md", + displayName: "note", + isDirectory: false, + }), + ).toBe("note") + }) + + it("falls back to the source name when no display name is provided", () => { + expect( + getExplorerDragOverlayName({ + name: "folder", + isDirectory: true, + }), + ).toBe("folder") + }) +}) diff --git a/apps/desktop/src/components/dnd/explorer-drag-overlay.tsx b/apps/desktop/src/components/dnd/explorer-drag-overlay.tsx index d4f5665d..04b52ec9 100644 --- a/apps/desktop/src/components/dnd/explorer-drag-overlay.tsx +++ b/apps/desktop/src/components/dnd/explorer-drag-overlay.tsx @@ -1,11 +1,16 @@ import { cn } from "@mdit/ui/lib/utils" import { ChevronRight } from "lucide-react" +import type { FileEntryDragData } from "./dnd-types" type ExplorerDragOverlayProps = { name: string isDirectory: boolean } +export function getExplorerDragOverlayName(data: FileEntryDragData): string { + return data.displayName ?? data.name ?? "" +} + function getExplorerDragOverlayClassName(isDirectory: boolean) { return cn( "pointer-events-none flex min-w-0 max-w-80 items-center gap-1 rounded-sm bg-transparent px-0 py-0 text-sm text-accent-foreground/95 shadow-none ring-0 outline-none border-0", diff --git a/apps/desktop/src/components/file-explorer/tree/file-tree-node.tsx b/apps/desktop/src/components/file-explorer/tree/file-tree-node.tsx index 83f70a49..a32969ee 100644 --- a/apps/desktop/src/components/file-explorer/tree/file-tree-node.tsx +++ b/apps/desktop/src/components/file-explorer/tree/file-tree-node.tsx @@ -45,12 +45,12 @@ export function FileTreeNode({ }, [entry.isDirectory, entry.name]) const baseName = useMemo(() => { - if (entry.isDirectory || !extension) { + if (!extension) { return entry.name } - return entry.name.slice(0, entry.name.length - extension.length) - }, [entry.isDirectory, entry.name, extension]) + return entry.name.slice(0, -extension.length) + }, [entry.name, extension]) const isMarkdown = useMemo( () => !entry.isDirectory && extension.toLowerCase() === ".md", diff --git a/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.test.ts b/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.test.ts new file mode 100644 index 00000000..ab4e64a8 --- /dev/null +++ b/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest" +import { getExplorerDragData } from "./use-tree-node-interactions" + +describe("getExplorerDragData", () => { + it("includes an overlay display name without the file extension", () => { + expect( + getExplorerDragData({ + path: "/notes/archive.tar.gz", + name: "archive.tar.gz", + isDirectory: false, + }), + ).toEqual({ + path: "/notes/archive.tar.gz", + name: "archive.tar.gz", + isDirectory: false, + displayName: "archive.tar", + }) + }) +}) diff --git a/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.ts b/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.ts index 61183455..c339fa58 100644 --- a/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.ts +++ b/apps/desktop/src/components/file-explorer/tree/use-tree-node-interactions.ts @@ -1,8 +1,10 @@ import { useDraggable } from "@dnd-kit/react" import type { FileTreeRenderNode } from "@mdit/file-tree" import { useCallback } from "react" +import type { FileEntryDragData } from "@/components/dnd/dnd-types" import { useDraggedExplorerPaths } from "@/components/dnd/explorer-drag-state" import type { WorkspaceEntry } from "@/store" +import { getExplorerEntryDisplayName } from "../utils/display-name" type UseTreeNodeInteractionsParams = { node: FileTreeRenderNode @@ -13,6 +15,15 @@ type UseTreeNodeInteractionsParams = { onEntryContextMenu: (entry: WorkspaceEntry) => void | Promise } +export function getExplorerDragData(entry: WorkspaceEntry): FileEntryDragData { + return { + path: entry.path, + isDirectory: entry.isDirectory, + name: entry.name, + displayName: getExplorerEntryDisplayName(entry.name, entry.isDirectory), + } +} + export function useTreeNodeInteractions({ node, onEntryPrimaryAction, @@ -27,11 +38,7 @@ export function useTreeNodeInteractions({ const { ref: draggableRef, isDragging: isSourceDragging } = useDraggable({ id: entry.path, - data: { - path: entry.path, - isDirectory: entry.isDirectory, - name: entry.name, - }, + data: getExplorerDragData(entry), disabled: isBusy, }) const isDragging = isSourceDragging || draggedExplorerPaths.has(entry.path) diff --git a/apps/desktop/src/components/file-explorer/utils/display-name.test.ts b/apps/desktop/src/components/file-explorer/utils/display-name.test.ts new file mode 100644 index 00000000..1981c104 --- /dev/null +++ b/apps/desktop/src/components/file-explorer/utils/display-name.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest" +import { getExplorerEntryDisplayName } from "./display-name" + +describe("getExplorerEntryDisplayName", () => { + it("strips the last file extension for files", () => { + expect(getExplorerEntryDisplayName("note.md", false)).toBe("note") + expect(getExplorerEntryDisplayName("archive.tar.gz", false)).toBe( + "archive.tar", + ) + }) + + it("preserves dotfiles", () => { + expect(getExplorerEntryDisplayName(".env", false)).toBe(".env") + }) + + it("preserves directory names", () => { + expect(getExplorerEntryDisplayName("folder.with.dot", true)).toBe( + "folder.with.dot", + ) + }) +}) diff --git a/apps/desktop/src/components/file-explorer/utils/display-name.ts b/apps/desktop/src/components/file-explorer/utils/display-name.ts new file mode 100644 index 00000000..0c0fd1af --- /dev/null +++ b/apps/desktop/src/components/file-explorer/utils/display-name.ts @@ -0,0 +1,12 @@ +import { getFileNameWithoutExtension } from "@mdit/utils/path-utils" + +export function getExplorerEntryDisplayName( + name: string, + isDirectory: boolean, +): string { + if (isDirectory) { + return name + } + + return getFileNameWithoutExtension(name) +}