Add a right-click context menu to the editor header file path button with Copy Path, Copy Relative Path, and platform-aware Reveal in Finder/File Explorer. Also add the same reveal action to the editor tab context menu, and reorder tab close actions so Close All appears above Close Tabs To The Right.
This commit is contained in:
parent
90a994b951
commit
117d100019
|
|
@ -5,11 +5,19 @@ across multiple components. Autosave now lives in a smaller headless controller
|
|||
so hidden editor UI no longer participates in shutdown. */
|
||||
import React, { useCallback, useEffect, useRef, useState, Suspense } from 'react'
|
||||
import * as monaco from 'monaco-editor'
|
||||
import { Columns2, FileText, Rows2 } from 'lucide-react'
|
||||
import { Columns2, Copy, ExternalLink, FileText, Rows2 } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { detectLanguage } from '@/lib/language-detect'
|
||||
import { getEditorHeaderCopyState, getEditorHeaderOpenFileState } from './editor-header'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '../tab-bar/SortableTab'
|
||||
import type { MarkdownViewMode, OpenFile } from '@/store/slices/editor'
|
||||
import MarkdownViewToggle from './MarkdownViewToggle'
|
||||
import { EditorContent } from './EditorContent'
|
||||
|
|
@ -24,6 +32,16 @@ import {
|
|||
type EditorPathMutationTarget
|
||||
} from './editor-autosave'
|
||||
|
||||
const isMac = navigator.userAgent.includes('Mac')
|
||||
const isLinux = navigator.userAgent.includes('Linux')
|
||||
|
||||
/** Platform-appropriate label: macOS → Finder, Windows → File Explorer, Linux → Files */
|
||||
const revealLabel = isMac
|
||||
? 'Reveal in Finder'
|
||||
: isLinux
|
||||
? 'Open Containing Folder'
|
||||
: 'Reveal in File Explorer'
|
||||
|
||||
type FileContent = {
|
||||
content: string
|
||||
isBinary: boolean
|
||||
|
|
@ -61,6 +79,8 @@ export default function EditorPanel({
|
|||
)
|
||||
const [sideBySide, setSideBySide] = useState(settings?.diffDefaultView === 'side-by-side')
|
||||
const [prevDiffView, setPrevDiffView] = useState(settings?.diffDefaultView)
|
||||
const [pathMenuOpen, setPathMenuOpen] = useState(false)
|
||||
const [pathMenuPoint, setPathMenuPoint] = useState({ x: 0, y: 0 })
|
||||
|
||||
// Why: When the user changes their global diff-view preference in Settings,
|
||||
// sync the local toggle to match during render (avoids flash of stale diff mode).
|
||||
|
|
@ -74,6 +94,12 @@ export default function EditorPanel({
|
|||
const openFilesRef = useRef(openFiles)
|
||||
openFilesRef.current = openFiles
|
||||
|
||||
useEffect(() => {
|
||||
const closeMenu = (): void => setPathMenuOpen(false)
|
||||
window.addEventListener(CLOSE_ALL_CONTEXT_MENUS_EVENT, closeMenu)
|
||||
return () => window.removeEventListener(CLOSE_ALL_CONTEXT_MENUS_EVENT, closeMenu)
|
||||
}, [])
|
||||
|
||||
// Why: keepCurrentModel / keepCurrent*Model retain Monaco models after unmount
|
||||
// so undo history survives tab switches. When a tab is *closed*, the user has
|
||||
// signalled they're done with the file — dispose the models to reclaim memory
|
||||
|
|
@ -454,7 +480,15 @@ export default function EditorPanel({
|
|||
{!isCombinedDiff && (
|
||||
<div className="editor-header">
|
||||
<div className="editor-header-text">
|
||||
<div className="editor-header-path-row">
|
||||
<div
|
||||
className="editor-header-path-row"
|
||||
onContextMenuCapture={(event) => {
|
||||
event.preventDefault()
|
||||
window.dispatchEvent(new Event(CLOSE_ALL_CONTEXT_MENUS_EVENT))
|
||||
setPathMenuPoint({ x: event.clientX, y: event.clientY })
|
||||
setPathMenuOpen(true)
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="editor-header-path"
|
||||
|
|
@ -470,6 +504,43 @@ export default function EditorPanel({
|
|||
{headerCopyState.copyToastLabel}
|
||||
</span>
|
||||
</div>
|
||||
<DropdownMenu open={pathMenuOpen} onOpenChange={setPathMenuOpen} modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
aria-hidden
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none fixed size-px opacity-0"
|
||||
style={{ left: pathMenuPoint.x, top: pathMenuPoint.y }}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" sideOffset={0} align="start">
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void window.api.ui.writeClipboardText(activeFile.filePath)
|
||||
}}
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
Copy Path
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
void window.api.ui.writeClipboardText(activeFile.relativePath)
|
||||
}}
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
Copy Relative Path
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
window.api.shell.openPath(activeFile.filePath)
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1.5" />
|
||||
{revealLabel}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{isSingleDiff && (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { X, FileCode, GitCompareArrows, Copy, ShieldAlert } from 'lucide-react'
|
||||
import { X, FileCode, GitCompareArrows, Copy, ShieldAlert, ExternalLink } from 'lucide-react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -16,6 +16,16 @@ import type { GitFileStatus } from '../../../../shared/types'
|
|||
import type { OpenFile } from '../../store/slices/editor'
|
||||
import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from './SortableTab'
|
||||
|
||||
const isMac = navigator.userAgent.includes('Mac')
|
||||
const isLinux = navigator.userAgent.includes('Linux')
|
||||
|
||||
/** Platform-appropriate label: macOS → Finder, Windows → File Explorer, Linux → Files */
|
||||
const revealLabel = isMac
|
||||
? 'Reveal in Finder'
|
||||
: isLinux
|
||||
? 'Open Containing Folder'
|
||||
: 'Reveal in File Explorer'
|
||||
|
||||
export default function EditorFileTab({
|
||||
file,
|
||||
isActive,
|
||||
|
|
@ -177,10 +187,10 @@ export default function EditorFileTab({
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-48" sideOffset={0} align="start">
|
||||
<DropdownMenuItem onSelect={onClose}>Close</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={onCloseAll}>Close All Editor Tabs</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={onCloseToRight} disabled={!hasTabsToRight}>
|
||||
Close Tabs To The Right
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={onCloseAll}>Close All Editor Tabs</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
|
|
@ -198,6 +208,15 @@ export default function EditorFileTab({
|
|||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
Copy Relative Path
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
window.api.shell.openPath(file.filePath)
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1.5" />
|
||||
{revealLabel}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
|
|
|
|||
Loading…
Reference in New Issue