Simplify markdown rename to inline field without breadcrumb or buttons

Replaces breadcrumb-morph rename with confirm/cancel buttons with a
simple inline field accepting full filenames. Commits on Enter, blur, or
Escape to cancel, matching tab bar and file explorer behavior. Adds
renameCancelledRef to prevent blur-commit after Escape. Removes unused
i18n strings for buttons and simplifies state by dropping extension
pinning and breadcrumb display.
This commit is contained in:
Jinjing
2026-09-19 11:31:48 -07:00
parent 6fa032ce09
commit a594cfb91e
12 changed files with 123 additions and 323 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ Orca targets macOS, Linux, and Windows. Keep all platform-dependent behavior beh
- **Keyboard shortcuts**: Never hardcode `e.metaKey`. Use a platform check (`navigator.userAgent.includes('Mac')`) to pick `metaKey` on Mac and `ctrlKey` on Linux/Windows. Electron menu accelerators should use `CmdOrCtrl`.
- **Shortcut labels in UI**: Display `⌘` / `⇧` on Mac and `Ctrl+` / `Shift+` on other platforms.
- **File paths**: Use `path.join` or Electron/Node path utilities — never assume `/` or `\`.
- **Windows terminal shells**: `--shell` picks the shell a terminal *is*; `--command` is typed into whatever shell the host spawned, so a shell choice routed through `command` silently becomes a child process. See [`docs/reference/windows-terminal-shell-selection.md`](./docs/reference/windows-terminal-shell-selection.md).
- **Windows terminal shells**: `--shell` picks the shell a terminal _is_; `--command` is typed into whatever shell the host spawned, so a shell choice routed through `command` silently becomes a child process. See [`docs/reference/windows-terminal-shell-selection.md`](./docs/reference/windows-terminal-shell-selection.md).
- **Windows setup scripts**: the setup/issue-command runner is a `.cmd` batch file unless the script starts with a `#!` line — never derive that from the user's terminal-shell preference, and never launch a `.cmd` runner with a bare `cmd.exe /c` from a Git Bash pane (MSYS rewrites the `/c`). See [`docs/reference/windows-setup-shell.md`](./docs/reference/windows-setup-shell.md).
- **Windows child processes**: start them through `runProcess`/`spawnProcess` in `src/shared/child-process/` — never `child_process` directly. It pins `windowsHide`, refuses `shell: true`, and encodes `.cmd`/`.bat` arguments so neither `CommandLineToArgvW` nor `cmd.exe` mangles them. A ratchet test fails on any new direct import. Recognised npm/pnpm `.cmd` shims are resolved to their real target so the spawn skips `cmd.exe` entirely; see [`docs/reference/windows-cmd-shim-resolution.md`](./docs/reference/windows-cmd-shim-resolution.md) before adding a shim shape or debugging one.
- **Windows process enumeration**: read the table through `src/main/windows/windows-process-table.ts`, never by forking `powershell.exe`. See [`docs/reference/windows-process-enumeration.md`](./docs/reference/windows-process-enumeration.md).
+1 -1
View File
@@ -102,7 +102,7 @@ gate, and a conflicted worktree that looks clean is granted a hosted-review crea
should not have. Withholding an affordance is a degrade; removing the evidence a gate
reads is not.
A fallback is only ever allowed to shape a *reading*. If the member is sent back to the
A fallback is only ever allowed to shape a _reading_. If the member is sent back to the
host — a token the client echoes into a later call's params — pass it through as
`z.string()` and let the send site keep it verbatim. `hostedReview`'s `provider` is the
case: the eligibility reply names it and the create call returns it, so an
@@ -33,7 +33,12 @@ const EXPECTED_EVENTS = FILE_COUNT * EVENTS_PER_FILE
const TOKENS_PER_EVENT = 200
const WORKTREES: UsageScanWorktreeRef[] = [
{ repoId: 'repo-1', worktreeId: 'wt-1', path: '/tmp/orca-usage-oracle-project', displayName: 'demo' }
{
repoId: 'repo-1',
worktreeId: 'wt-1',
path: '/tmp/orca-usage-oracle-project',
displayName: 'demo'
}
]
let corpusRoot = ''
@@ -84,7 +84,7 @@ function openRenameInput(): void {
fireEvent.click(screen.getByRole('menuitem', { name: 'Rename' }))
}
describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
describe('EditorPanelHeaderPath inline rename', () => {
beforeEach(() => {
renameFileOnDiskMock.mockReset()
Object.assign(window, { api: { ui: { writeClipboardText: vi.fn() } } })
@@ -95,40 +95,21 @@ describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
vi.stubGlobal('cancelAnimationFrame', vi.fn())
})
it('morphs into a breadcrumb strip with the basename editable', () => {
it('opens a field holding the whole name', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
expect(input.value).toBe('notes')
expect(screen.getByText('.md')).toBeDefined()
expect(screen.getByText('repo /')).toBeDefined()
expect(input.value).toBe('notes.md')
})
it('shows the full crumb chain without ellipsis cuts', () => {
renderPath(
baseFile({
id: '/repo/docs/marketing/notes.md',
filePath: '/repo/docs/marketing/notes.md',
relativePath: 'docs/marketing/notes.md'
})
)
openRenameInput()
expect(screen.getByText('repo / docs / marketing /')).toBeDefined()
})
it('lets the strip claim the full header width instead of capping at 520px', () => {
it('lets the field claim the full header width', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
const strip = input.parentElement
if (!strip) {
throw new Error('Missing rename strip')
}
expect(strip.className).toContain('w-full')
expect(strip.className).toContain('max-w-full')
expect(input.className).toContain('w-full')
expect(input.className).toContain('max-w-full')
})
it('selects the basename so typing replaces just the name', () => {
@@ -141,60 +122,42 @@ describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
expect(input.selectionEnd).toBe('notes'.length)
})
it('re-attaches the pinned extension to a bare basename', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'renamed' } })
fireEvent.keyDown(input, { key: 'Enter' })
expect(renameFileOnDiskMock).toHaveBeenCalledWith({
oldPath: '/repo/notes.md',
newName: 'renamed.md',
worktreeId: 'wt-1',
worktreePath: '/repo'
})
})
it('respects an explicitly typed extension and drops the pinned suffix', () => {
it('renames to the typed name verbatim', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'renamed.mdx' } })
expect(screen.queryByText('.md')).toBeNull()
fireEvent.keyDown(input, { key: 'Enter' })
expect(renameFileOnDiskMock).toHaveBeenCalledWith(
expect.objectContaining({ newName: 'renamed.mdx' })
)
expect(renameFileOnDiskMock).toHaveBeenCalledWith({
oldPath: '/repo/notes.md',
newName: 'renamed.mdx',
worktreeId: 'wt-1',
worktreePath: '/repo'
})
})
it('shows the same name it commits for a dotted basename', () => {
it('commits on blur like the tab bar and file explorer', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'v1.2' } })
expect(screen.queryByText('.md')).toBeNull()
fireEvent.change(input, { target: { value: 'renamed.md' } })
fireEvent.blur(input)
fireEvent.keyDown(input, { key: 'Enter' })
expect(renameFileOnDiskMock).toHaveBeenCalledWith(expect.objectContaining({ newName: 'v1.2' }))
expect(renameFileOnDiskMock).toHaveBeenCalledWith(
expect.objectContaining({ newName: 'renamed.md' })
)
})
it('keeps the pinned suffix for a leading-dot name', () => {
it('does not request a rename when the name was not edited', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: '.notes' } })
expect(screen.getByText('.md')).toBeDefined()
fireEvent.keyDown(getRenameInput('Rename file notes.md'), { key: 'Enter' })
fireEvent.keyDown(input, { key: 'Enter' })
expect(renameFileOnDiskMock).toHaveBeenCalledWith(
expect.objectContaining({ newName: '.notes.md' })
)
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
})
it('ignores an Enter that only confirms an IME candidate', () => {
@@ -202,7 +165,7 @@ describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'renamed' } })
fireEvent.change(input, { target: { value: 'renamed.md' } })
fireEvent.keyDown(input, { key: 'Enter', keyCode: 229 })
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
@@ -212,23 +175,28 @@ describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
)
})
it('does not commit when the field merely loses focus', () => {
it('cancels on Escape without a trailing blur-commit, and ignores empty renames', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'renamed' } })
fireEvent.change(input, { target: { value: 'renamed.md' } })
fireEvent.keyDown(input, { key: 'Escape' })
fireEvent.blur(input)
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
expect(screen.getByLabelText('Rename file notes.md')).toBeDefined()
expect(screen.queryByLabelText('Rename file notes.md')).toBeNull()
openRenameInput()
fireEvent.change(getRenameInput('Rename file notes.md'), { target: { value: ' ' } })
fireEvent.keyDown(getRenameInput('Rename file notes.md'), { key: 'Enter' })
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
})
it('drops rename mode when the active file changes', () => {
const rerenderPath = renderPath(baseFile())
openRenameInput()
fireEvent.change(getRenameInput('Rename file notes.md'), { target: { value: 'renamed' } })
fireEvent.change(getRenameInput('Rename file notes.md'), { target: { value: 'renamed.md' } })
rerenderPath(
baseFile({
id: '/repo/other.md',
@@ -242,52 +210,6 @@ describe('EditorPanelHeaderPath breadcrumb morph rename', () => {
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
})
it('commits via the confirm button', () => {
renderPath(baseFile())
openRenameInput()
fireEvent.change(getRenameInput('Rename file notes.md'), {
target: { value: 'renamed.md' }
})
fireEvent.mouseDown(screen.getByRole('button', { name: 'Confirm rename' }))
fireEvent.click(screen.getByRole('button', { name: 'Confirm rename' }))
expect(renameFileOnDiskMock).toHaveBeenCalledTimes(1)
expect(renameFileOnDiskMock).toHaveBeenCalledWith(
expect.objectContaining({ newName: 'renamed.md' })
)
})
it('cancels via the cancel button without renaming', () => {
renderPath(baseFile())
openRenameInput()
fireEvent.change(getRenameInput('Rename file notes.md'), {
target: { value: 'renamed.md' }
})
fireEvent.mouseDown(screen.getByRole('button', { name: 'Cancel rename' }))
fireEvent.click(screen.getByRole('button', { name: 'Cancel rename' }))
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
expect(screen.queryByLabelText('Rename file notes.md')).toBeNull()
})
it('cancels on Escape and ignores empty renames', () => {
renderPath(baseFile())
openRenameInput()
const input = getRenameInput('Rename file notes.md')
fireEvent.change(input, { target: { value: 'renamed.md' } })
fireEvent.keyDown(input, { key: 'Escape' })
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
expect(screen.queryByLabelText('Rename file notes.md')).toBeNull()
openRenameInput()
fireEvent.change(getRenameInput('Rename file notes.md'), { target: { value: ' ' } })
fireEvent.keyDown(getRenameInput('Rename file notes.md'), { key: 'Enter' })
expect(renameFileOnDiskMock).not.toHaveBeenCalled()
})
it('selects the whole name when there is no extension', () => {
const file = baseFile({
id: '/repo/Makefile',
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { Check, Copy, ExternalLink, Eye, Pencil, X } from 'lucide-react'
import { Copy, ExternalLink, Eye, Pencil } from 'lucide-react'
import {
DropdownMenu,
DropdownMenuContent,
@@ -61,13 +61,9 @@ export function EditorPanelHeaderPath({
const {
canRename,
currentFileName,
currentBaseName,
pinnedExtension,
breadcrumbSegments,
isRenaming,
renameInputRef,
openRenameInput,
setRenameDraft,
commitRename,
cancelRename
} = useEditorHeaderFileRename(activeFile)
@@ -90,101 +86,41 @@ export function EditorPanelHeaderPath({
}}
>
{isRenaming ? (
<div className="flex h-6 w-full min-w-0 max-w-full items-center gap-1 rounded-md border border-accent/40 bg-input/40 py-0.5 pl-1.5 pr-1 focus-within:border-accent focus-within:ring-1 focus-within:ring-ring">
{breadcrumbSegments.length > 0 ? (
<span className="min-w-0 shrink truncate font-mono text-xs text-muted-foreground">
{breadcrumbSegments.join(' / ')} /
</span>
) : null}
<input
ref={renameInputRef}
data-editor-header-rename-input="true"
aria-label={translate(
'auto.components.editor.EditorPanelHeader.1bb1e226ec',
'Rename file {{value0}}',
{ value0: currentFileName }
)}
defaultValue={currentBaseName}
className="h-full min-w-0 flex-1 bg-transparent font-mono text-xs font-semibold text-foreground outline-none"
spellCheck={false}
onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onClick={(event) => event.stopPropagation()}
onDoubleClick={(event) => event.stopPropagation()}
onChange={(event) => setRenameDraft(event.target.value)}
onKeyDown={(event) => {
// Why: an Enter that only confirms a CJK IME candidate must not
// commit the rename; wait for a non-composition Enter.
if (isImeCompositionKeyDown(event)) {
return
}
if (event.key === 'Enter') {
event.preventDefault()
event.stopPropagation()
commitRename()
} else if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
cancelRename()
}
}}
/>
{pinnedExtension ? (
<span className="shrink-0 font-mono text-xs text-muted-foreground">
{pinnedExtension}
</span>
) : null}
<div className="flex shrink-0 items-center">
<button
type="button"
aria-label={translate(
'auto.components.editor.EditorPanelHeader.confirmRename',
'Confirm rename'
)}
title={translate(
'auto.components.editor.EditorPanelHeader.confirmRename',
'Confirm rename'
)}
className="flex size-5 items-center justify-center rounded text-status-success hover:bg-status-success-background"
onMouseDown={(event) => {
// Why: preventDefault keeps the caret in the input so the
// field is still usable if the click misses the button.
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
commitRename()
}}
>
<Check className="size-3" strokeWidth={3} aria-hidden="true" />
</button>
<button
type="button"
aria-label={translate(
'auto.components.editor.EditorPanelHeader.cancelRename',
'Cancel rename'
)}
title={translate(
'auto.components.editor.EditorPanelHeader.cancelRename',
'Cancel rename'
)}
className="flex size-5 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
onMouseDown={(event) => {
// Why: same as confirm — the pointer leaving the field must
// not disturb the input it is about to dismiss.
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
cancelRename()
}}
>
<X className="size-3" strokeWidth={3} aria-hidden="true" />
</button>
</div>
</div>
<input
ref={renameInputRef}
data-editor-header-rename-input="true"
aria-label={translate(
'auto.components.editor.EditorPanelHeader.1bb1e226ec',
'Rename file {{value0}}',
{ value0: currentFileName }
)}
defaultValue={currentFileName}
// Why: the field spans the header rather than sizing to the name —
// a long path is exactly when the rename field needs the room.
className="h-6 w-full min-w-0 max-w-full rounded-md border border-accent/40 bg-input/40 px-1.5 font-mono text-xs text-foreground outline-none focus:border-accent focus:ring-1 focus:ring-ring"
spellCheck={false}
onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onClick={(event) => event.stopPropagation()}
onDoubleClick={(event) => event.stopPropagation()}
onKeyDown={(event) => {
// Why: an Enter that only confirms a CJK IME candidate must not
// commit the rename; wait for a non-composition Enter.
if (isImeCompositionKeyDown(event)) {
return
}
if (event.key === 'Enter') {
event.preventDefault()
event.stopPropagation()
commitRename()
} else if (event.key === 'Escape') {
event.preventDefault()
event.stopPropagation()
cancelRename()
}
}}
onBlur={commitRename}
/>
) : (
<button
type="button"
@@ -196,12 +132,17 @@ export function EditorPanelHeaderPath({
{headerCopyState.pathLabel}
</button>
)}
<span
className={`editor-header-copy-toast${copiedPathVisible ? ' is-visible' : ''}`}
aria-live="polite"
>
{headerCopyState.copyToastLabel}
</span>
{/* Why: the toast is opacity-0 rather than display-none, so leaving it
mounted reserves ~100px of the row from the rename field for a
message that cannot fire while renaming. */}
{!isRenaming && (
<span
className={`editor-header-copy-toast${copiedPathVisible ? ' is-visible' : ''}`}
aria-live="polite"
>
{headerCopyState.copyToastLabel}
</span>
)}
</div>
<DropdownMenu open={pathMenuOpen} onOpenChange={setPathMenuOpen} modal={false}>
<DropdownMenuTrigger asChild>
@@ -2,87 +2,38 @@ import { useCallback, useRef, useState } from 'react'
import type { RefCallback } from 'react'
import type { OpenFile } from '@/store/slices/editor'
import { useWorktreeById } from '@/store/selectors'
import { basename, dirname } from '@/lib/path'
import { basename } from '@/lib/path'
import { renameFileOnDisk } from '@/lib/rename-file'
import { getUntitledFileRoot } from './untitled-file-rename-path'
type EditorHeaderFileRenameState = {
canRename: boolean
currentFileName: string
currentBaseName: string
pinnedExtension: string
breadcrumbSegments: string[]
isRenaming: boolean
renameInputRef: RefCallback<HTMLInputElement>
openRenameInput: () => void
setRenameDraft: (value: string) => void
commitRename: () => void
cancelRename: () => void
}
// Breadcrumb for the morph strip: worktree name plus parent dirs, so the
// rename field keeps its location context without shifting header layout.
function getBreadcrumbSegments(relativePath: string, worktreePath: string | null): string[] {
const segments: string[] = []
if (worktreePath) {
segments.push(basename(worktreePath))
}
if (relativePath) {
for (const part of dirname(relativePath).split('/')) {
if (part && part !== '.') {
segments.push(part)
}
}
}
return segments
}
// A leading dot is part of a dotfile's name, not an extension — same split the
// hook uses on the current file name.
export function getTypedExtension(rawValue: string): string {
const dotIndex = rawValue.lastIndexOf('.')
return dotIndex > 0 ? rawValue.slice(dotIndex) : ''
}
// The morph input edits the basename while the extension stays pinned as a
// suffix, so a bare name gets the extension re-attached. A typed extension
// (same or different) is respected verbatim and the pinned suffix is hidden so
// the strip shows the name that will be committed; null means no-op.
export function resolveRenameTarget(
rawValue: string,
currentFileName: string,
currentExtension: string
): string | null {
if (!rawValue || rawValue === currentFileName) {
return null
}
if (!currentExtension || getTypedExtension(rawValue)) {
return rawValue
}
return `${rawValue}${currentExtension}`
}
export function useEditorHeaderFileRename(activeFile: OpenFile): EditorHeaderFileRenameState {
const worktree = useWorktreeById(activeFile.worktreeId)
const [isRenaming, setIsRenaming] = useState(false)
const [renameDraft, setRenameDraft] = useState('')
const [renameFilePath, setRenameFilePath] = useState(activeFile.filePath)
const renameInputElementRef = useRef<HTMLInputElement | null>(null)
const renameFocusFrameRef = useRef<number | null>(null)
// Escape fires setIsRenaming(false), which unmounts the input. The browser
// still fires focusout as the focused node is removed, so onBlur can invoke
// commitRename *after* cancel — committing the typed value against the
// user's intent. This flag suppresses the trailing blur-commit.
const renameCancelledRef = useRef(false)
// Why: the header renders one unkeyed path strip for every file, so a file
// switch mid-rename would otherwise commit the typed name against the new path.
if (renameFilePath !== activeFile.filePath) {
setRenameFilePath(activeFile.filePath)
setIsRenaming(false)
setRenameDraft('')
}
const currentFileName = basename(activeFile.filePath)
const lastDotIndex = currentFileName.lastIndexOf('.')
const hasExtension = lastDotIndex > 0
const currentBaseName = hasExtension ? currentFileName.slice(0, lastDotIndex) : currentFileName
const currentExtension = hasExtension ? currentFileName.slice(lastDotIndex) : ''
const pinnedExtension = getTypedExtension(renameDraft.trim()) ? '' : currentExtension
const breadcrumbSegments = getBreadcrumbSegments(activeFile.relativePath, worktree?.path ?? null)
// Why: read-only tabs (AI Vault View Log) are never renameable — rename would
// rewrite the agent-owned artifact's backing path.
const canRename =
@@ -96,20 +47,26 @@ export function useEditorHeaderFileRename(activeFile: OpenFile): EditorHeaderFil
if (!canRename) {
return
}
setRenameDraft(currentBaseName)
renameCancelledRef.current = false
setIsRenaming(true)
}
const commitRename = (): void => {
if (renameCancelledRef.current) {
setIsRenaming(false)
return
}
const input = renameInputElementRef.current
if (!input) {
setIsRenaming(false)
return
}
const rawVal = input.value.trim()
const newName = input.value.trim()
// onBlur follows Enter when the input unmounts; consume that trailing event
// so one user action cannot start a second rename against the old path.
renameCancelledRef.current = true
setIsRenaming(false)
const newName = resolveRenameTarget(rawVal, currentFileName, currentExtension)
if (!newName) {
if (!newName || newName === currentFileName) {
return
}
const worktreePath = getUntitledFileRoot(activeFile, worktree?.path ?? null)
@@ -122,6 +79,7 @@ export function useEditorHeaderFileRename(activeFile: OpenFile): EditorHeaderFil
}
const cancelRename = (): void => {
renameCancelledRef.current = true
setIsRenaming(false)
}
@@ -141,32 +99,33 @@ export function useEditorHeaderFileRename(activeFile: OpenFile): EditorHeaderFil
return
}
// Why: focus belongs to the rename input mount; the frame preserves the
// previous timing so header layout settles before selecting text. The
// input holds the basename with the extension pinned alongside, so
// selecting all replaces the name without touching the suffix.
// Why: focus belongs to the rename input mount; the frame lets the header
// layout settle before selecting text. Preselect the basename only, so
// typing replaces the name and leaves the extension — same as the tab bar
// and the file explorer.
renameFocusFrameRef.current = requestAnimationFrame(() => {
renameFocusFrameRef.current = null
if (renameInputElementRef.current !== el) {
return
}
el.focus()
el.select()
const dotIndex = currentFileName.lastIndexOf('.')
if (dotIndex > 0) {
el.setSelectionRange(0, dotIndex)
} else {
el.select()
}
})
},
[clearRenameFocusFrame, isRenaming]
[clearRenameFocusFrame, currentFileName, isRenaming]
)
return {
canRename,
currentFileName,
currentBaseName,
pinnedExtension,
breadcrumbSegments,
isRenaming,
renameInputRef,
openRenameInput,
setRenameDraft,
commitRename,
cancelRename
}
+1 -3
View File
@@ -14754,9 +14754,7 @@
"631dab0df3": "Next change",
"revealInFinder": "Reveal in Finder",
"openContainingFolder": "Open Containing Folder",
"revealInFileExplorer": "Reveal in File Explorer",
"confirmRename": "Confirm rename",
"cancelRename": "Cancel rename"
"revealInFileExplorer": "Reveal in File Explorer"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "Export as PDF",
+1 -6
View File
@@ -12884,12 +12884,7 @@
"f0fd4174b5": "Abre la pestaña de archivo para usar la edición enriquecida de Markdown",
"a10d9b8337": "Abrir archivo",
"2076ecfc9c": "Cambio anterior",
"631dab0df3": "Cambio siguiente",
"revealInFinder": "Mostrar en Finder",
"openContainingFolder": "Abrir carpeta contenedora",
"revealInFileExplorer": "Mostrar en el Explorador",
"confirmRename": "Confirmar cambio de nombre",
"cancelRename": "Cancelar cambio de nombre"
"631dab0df3": "Cambio siguiente"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "Exportar como PDF",
+1 -6
View File
@@ -14127,12 +14127,7 @@
"f0fd4174b5": "Ouvrez un onglet de fichier pour utiliser l'édition Markdown enrichie",
"a10d9b8337": "Ouvrir le fichier",
"2076ecfc9c": "Modification précédente",
"631dab0df3": "Modification suivante",
"revealInFinder": "Afficher dans le Finder",
"openContainingFolder": "Ouvrir le dossier contenant",
"revealInFileExplorer": "Afficher dans l'Explorateur",
"confirmRename": "Confirmer le renommage",
"cancelRename": "Annuler le renommage"
"631dab0df3": "Modification suivante"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "Exporter en PDF",
+1 -6
View File
@@ -12884,12 +12884,7 @@
"f0fd4174b5": "ファイルタブを開いてリッチ markdown 編集を使用する",
"a10d9b8337": "ファイルを開く",
"2076ecfc9c": "前の変更",
"631dab0df3": "次の変更",
"revealInFinder": "Finderで表示",
"openContainingFolder": "含まれるフォルダを開く",
"revealInFileExplorer": "エクスプローラーで表示",
"confirmRename": "名前の変更を確定する",
"cancelRename": "名前の変更をキャンセルする"
"631dab0df3": "次の変更"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "PDFとしてエクスポート",
+1 -6
View File
@@ -12950,12 +12950,7 @@
"f0fd4174b5": "풍부한 markdown 편집을 사용하려면 파일 탭을 엽니다.",
"a10d9b8337": "파일 열기",
"2076ecfc9c": "이전 변경",
"631dab0df3": "다음 변경",
"revealInFinder": "Finder에서 보기",
"openContainingFolder": "포함된 폴더 열기",
"revealInFileExplorer": "탐색기에서 보기",
"confirmRename": "이름 바꾸기 확인",
"cancelRename": "이름 바꾸기 취소"
"631dab0df3": "다음 변경"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "PDF로 내보내기",
+1 -6
View File
@@ -12964,12 +12964,7 @@
"f0fd4174b5": "打开文件选项卡以使用富文本 Markdown 编辑",
"a10d9b8337": "打开文件",
"2076ecfc9c": "上一个更改",
"631dab0df3": "下一个更改",
"revealInFinder": "在 Finder 中显示",
"openContainingFolder": "打开所在文件夹",
"revealInFileExplorer": "在资源管理器中显示",
"confirmRename": "确认重命名",
"cancelRename": "取消重命名"
"631dab0df3": "下一个更改"
},
"EditorPanelMarkdownActionsMenu": {
"3e0ce48c24": "导出为 PDF",