[next]next版,随手记,创建副本、导出、文档信息功能转移到左侧右键菜单

This commit is contained in:
RememBerBer
2026-08-27 10:42:03 +08:00
parent 998cde47d9
commit b5c2206c88
3 changed files with 54 additions and 23 deletions
+27 -20
View File
@@ -3,8 +3,6 @@ import {
Check,
ChevronDown,
Columns2,
CopyPlus,
Download,
Eye,
FilePenLine,
FilePlus2,
@@ -13,7 +11,6 @@ import {
FoldVertical,
GitBranch,
ImagePlus,
Info,
List,
ListOrdered,
PanelLeftClose,
@@ -551,21 +548,23 @@ export function QuickNoteTool() {
await Promise.all([loadTree(), reloadCurrentNoteFromDisk(), refreshGitChangeCount()])
}
async function selectNode(node: QuickNoteNode): Promise<void> {
async function selectNode(node: QuickNoteNode): Promise<QuickNoteFile | null> {
if (node.kind === 'directory') {
if (dirty) await saveCurrent(false)
update({ selectedPath: node.relativePath, selectedKind: 'directory', note: null, content: '', metadataDirty: false })
return
return null
}
if (node.relativePath === state.note?.relativePath) return
if (dirty && !await saveCurrent(false)) return
if (node.relativePath === state.note?.relativePath) return state.note
if (dirty && !await saveCurrent(false)) return null
update({ busy: true })
try {
const note = await window.mootool.readQuickNote(node.relativePath)
update({ selectedPath: note.relativePath, selectedKind: 'file', note, content: note.content, busy: false, metadataDirty: false })
return note
} catch (error) {
update({ busy: false })
toast.error(errorMessage(error))
return null
}
}
@@ -594,6 +593,7 @@ export function QuickNoteTool() {
async function openTreeAction(node: QuickNoteNode, mode: 'rename' | 'move' | 'delete'): Promise<void> {
if (node.kind === 'directory') {
if (dirty && !await saveCurrent(false)) return
update({
selectedPath: node.relativePath,
selectedKind: 'directory',
@@ -684,13 +684,15 @@ export function QuickNoteTool() {
}
}
async function duplicateNote(): Promise<void> {
if (!state.note) return
if (dirty && !await saveCurrent(false)) return
async function duplicateNote(node: QuickNoteNode): Promise<void> {
if (node.kind !== 'file') return
const note = await selectNode(node)
if (!note) return
if (node.relativePath === state.note?.relativePath && dirty && !await saveCurrent(false)) return
try {
const note = await window.mootool.duplicateQuickNote(state.note.relativePath)
const duplicate = await window.mootool.duplicateQuickNote(node.relativePath)
await loadTree()
update({ selectedPath: note.relativePath, selectedKind: 'file', note, content: note.content, metadataDirty: false })
update({ selectedPath: duplicate.relativePath, selectedKind: 'file', note: duplicate, content: duplicate.content, metadataDirty: false })
} catch (error) {
toast.error(errorMessage(error))
}
@@ -772,16 +774,24 @@ export function QuickNoteTool() {
attachmentInsertionQueueRef.current = operation
}
async function exportNote(): Promise<void> {
if (!state.note) return
async function exportNote(node: QuickNoteNode): Promise<void> {
if (node.kind !== 'file') return
const note = await selectNode(node)
if (!note) return
const content = node.relativePath === state.note?.relativePath ? state.content : note.content
const path = await window.mootool.saveTextFile({
kind: 'text',
defaultName: `${state.note.metadata.title}.txt`,
content: state.content
defaultName: `${note.metadata.title}.txt`,
content
})
if (path) toast.success(t('quickNote.export'))
}
async function showDocumentInfo(node: QuickNoteNode): Promise<void> {
if (node.kind !== 'file') return
if (await selectNode(node)) update({ infoOpen: true })
}
function insertAttachment(
attachment: QuickNoteAttachment,
requestedSelection?: TextSelection
@@ -967,7 +977,7 @@ export function QuickNoteTool() {
</div>
<div ref={treeScrollRef} className="quick-note-tree-scroll" onScroll={(event) => { quickNoteTreeScrollTop = event.currentTarget.scrollTop }}>
{state.nodes.length
? <QuickNoteTree nodes={state.nodes} selectedPath={state.selectedPath} expanded={state.expanded} onSelect={(node) => { void selectNode(node) }} onToggle={toggleDirectory} onMove={(node, targetDirectory) => { void moveTreeEntry(node, targetDirectory) }} onRenameRequest={(node) => { void openTreeAction(node, 'rename') }} onMoveRequest={(node) => { void openTreeAction(node, 'move') }} onDeleteRequest={(node) => { void openTreeAction(node, 'delete') }} onRevealRequest={(node) => { void window.mootool.revealQuickNoteEntry(node.relativePath).catch((error) => toast.error(errorMessage(error))) }} renameLabel={t('quickNote.rename')} moveLabel={t('quickNote.move')} deleteLabel={t('quickNote.delete')} revealLabel={t('quickNote.revealInFinder')} />
? <QuickNoteTree nodes={state.nodes} selectedPath={state.selectedPath} expanded={state.expanded} onSelect={(node) => { void selectNode(node) }} onToggle={toggleDirectory} onMove={(node, targetDirectory) => { void moveTreeEntry(node, targetDirectory) }} onRenameRequest={(node) => { void openTreeAction(node, 'rename') }} onMoveRequest={(node) => { void openTreeAction(node, 'move') }} onDuplicateRequest={(node) => { void duplicateNote(node) }} onExportRequest={(node) => { void exportNote(node) }} onInfoRequest={(node) => { void showDocumentInfo(node) }} onDeleteRequest={(node) => { void openTreeAction(node, 'delete') }} onRevealRequest={(node) => { void window.mootool.revealQuickNoteEntry(node.relativePath).catch((error) => toast.error(errorMessage(error))) }} renameLabel={t('quickNote.rename')} moveLabel={t('quickNote.move')} duplicateLabel={t('quickNote.duplicate')} exportLabel={t('quickNote.export')} infoLabel={t('quickNote.info')} deleteLabel={t('quickNote.delete')} revealLabel={t('quickNote.revealInFinder')} />
: <div className="quick-note-empty">{t('quickNote.empty')}</div>}
</div>
</aside>
@@ -1021,11 +1031,8 @@ export function QuickNoteTool() {
<WorkspaceDragZone className="quick-note-toolbar__drag-zone" />
<IconButton label={t('quickNote.find')} icon={Search} disabled={!state.note} active={state.findOpen} onClick={() => { if (state.findOpen) update({ findOpen: false }); else openFindReplace() }} />
<IconButton label={t('quickNote.save')} icon={Save} disabled={!state.note || state.busy} active={dirty} onClick={() => { void saveCurrent() }} />
<IconButton label={t('quickNote.duplicate')} icon={CopyPlus} disabled={!state.note} onClick={() => { void duplicateNote() }} />
<IconButton label={t('quickNote.attachment')} icon={ImagePlus} disabled={!state.note} onClick={() => { void importAttachment() }} />
<IconButton label={t('quickNote.export')} icon={Download} disabled={!state.note} onClick={() => { void exportNote() }} />
<IconButton label={t('quickNote.quickReplace')} icon={Replace} active={state.quickReplaceOpen} onClick={() => update({ quickReplaceOpen: !state.quickReplaceOpen })} />
<IconButton label={t('quickNote.info')} icon={Info} disabled={!state.note} onClick={() => update({ infoOpen: true })} />
<IconButton label={t('quickNote.git')} icon={GitBranch} badge={state.gitChangeCount} onClick={() => update({ gitOpen: true })} />
<IconButton label={t('quickNote.openVault')} icon={FolderOpen} onClick={() => { void window.mootool.openQuickNoteVault() }} />
<IconButton label={t('quickNote.delete')} icon={Trash2} disabled={!state.selectedPath} onClick={() => openAction('delete')} />
+21 -3
View File
@@ -13,10 +13,16 @@ type QuickNoteTreeProps = {
onMove: (node: Pick<QuickNoteNode, 'relativePath' | 'kind'>, targetDirectory: string) => void
onRenameRequest: (node: QuickNoteNode) => void
onMoveRequest: (node: QuickNoteNode) => void
onDuplicateRequest: (node: QuickNoteNode) => void
onExportRequest: (node: QuickNoteNode) => void
onInfoRequest: (node: QuickNoteNode) => void
onDeleteRequest: (node: QuickNoteNode) => void
onRevealRequest: (node: QuickNoteNode) => void
renameLabel: string
moveLabel: string
duplicateLabel: string
exportLabel: string
infoLabel: string
deleteLabel: string
revealLabel: string
}
@@ -24,7 +30,7 @@ type QuickNoteTreeProps = {
const quickNotePathType = 'application/x-mootool-quick-note-path'
const quickNoteKindType = 'application/x-mootool-quick-note-kind'
export function QuickNoteTree({ nodes, selectedPath, expanded, onSelect, onToggle, onMove, onRenameRequest, onMoveRequest, onDeleteRequest, onRevealRequest, renameLabel, moveLabel, deleteLabel, revealLabel }: QuickNoteTreeProps) {
export function QuickNoteTree({ nodes, selectedPath, expanded, onSelect, onToggle, onMove, onRenameRequest, onMoveRequest, onDuplicateRequest, onExportRequest, onInfoRequest, onDeleteRequest, onRevealRequest, renameLabel, moveLabel, duplicateLabel, exportLabel, infoLabel, deleteLabel, revealLabel }: QuickNoteTreeProps) {
const toolActive = useToolActivity()
const menuRef = useRef<HTMLDivElement>(null)
const [contextMenu, setContextMenu] = useState<{ node: QuickNoteNode; left: number; top: number } | null>(null)
@@ -69,7 +75,6 @@ export function QuickNoteTree({ nodes, selectedPath, expanded, onSelect, onToggl
onToggle={onToggle}
onMove={onMove}
onOpenContextMenu={(menuNode, left, top) => {
onSelect(menuNode)
setContextMenu({ node: menuNode, left, top })
}}
/>
@@ -85,6 +90,14 @@ export function QuickNoteTree({ nodes, selectedPath, expanded, onSelect, onToggl
>
<button type="button" role="menuitem" onClick={() => { onRenameRequest(contextMenu.node); setContextMenu(null) }}>{renameLabel}</button>
<button type="button" role="menuitem" onClick={() => { onMoveRequest(contextMenu.node); setContextMenu(null) }}>{moveLabel}</button>
{contextMenu.node.kind === 'file' && (
<>
<button type="button" role="menuitem" onClick={() => { onDuplicateRequest(contextMenu.node); setContextMenu(null) }}>{duplicateLabel}</button>
<button type="button" role="menuitem" onClick={() => { onExportRequest(contextMenu.node); setContextMenu(null) }}>{exportLabel}</button>
<button type="button" role="menuitem" onClick={() => { onInfoRequest(contextMenu.node); setContextMenu(null) }}>{infoLabel}</button>
<div className="quick-note-tree-menu__separator" role="separator" />
</>
)}
<button type="button" role="menuitem" onClick={() => { onDeleteRequest(contextMenu.node); setContextMenu(null) }}>{deleteLabel}</button>
<button type="button" role="menuitem" onClick={() => { onRevealRequest(contextMenu.node); setContextMenu(null) }}>{revealLabel}</button>
</div>,
@@ -120,7 +133,12 @@ function QuickNoteTreeNode({ node, depth, selectedPath, expanded, onSelect, onTo
}}
onContextMenu={(event) => {
event.preventDefault()
onOpenContextMenu(node, Math.min(event.clientX, window.innerWidth - 164), Math.min(event.clientY, window.innerHeight - 138))
const menuHeight = node.kind === 'file' ? 238 : 140
onOpenContextMenu(
node,
Math.max(8, Math.min(event.clientX, window.innerWidth - 176)),
Math.max(8, Math.min(event.clientY, window.innerHeight - menuHeight))
)
}}
onDragStart={(event) => {
event.dataTransfer.effectAllowed = 'move'
+6
View File
@@ -901,6 +901,12 @@
text-align: left;
}
.quick-note-tree-menu__separator {
height: 1px;
margin: 4px 7px;
background: var(--border-control);
}
.quick-note-tree-menu button:hover,
.quick-note-tree-menu button:focus-visible,
.vault-tree-menu button:hover,