feat: move-to-root, active tab highlight, show-in-explorer for all file items
- Add 'Move to root' context menu item for non-root sidebar tree nodes - Improve active tab visual contrast (background + font-weight) - FileTreeRow: show-in-explorer inline button & context menu for all items - OpenFolder now resolves file-type nodes to parent directory - handleShowInFolder uses item.nodeId || item.id for FileDTO compatibility - Add i18n key nav.moveToRoot (ru/en)
This commit is contained in:
parent
c941f05dab
commit
baf57e993d
|
|
@ -5,6 +5,7 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"verstak/internal/core/nodes"
|
||||
"verstak/internal/core/plugins"
|
||||
"verstak/internal/i18n"
|
||||
|
||||
|
|
@ -117,11 +118,14 @@ func (a *App) OpenFolder(nodeID string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Join(a.vault, n.FsPath)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
dir = a.vault
|
||||
target := filepath.Join(a.vault, n.FsPath)
|
||||
if n.Type == nodes.TypeFile {
|
||||
target = filepath.Dir(target)
|
||||
}
|
||||
cmd := exec.Command("xdg-open", dir)
|
||||
if _, err := os.Stat(target); os.IsNotExist(err) {
|
||||
target = a.vault
|
||||
}
|
||||
cmd := exec.Command("xdg-open", target)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,8 +16,8 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-y7k2Kn7r.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CJu7OoK7.css">
|
||||
<script type="module" crossorigin src="/assets/main-flblZtvd.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-DsSP02cl.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -752,7 +752,20 @@
|
|||
async function openNodeFolder(node) {
|
||||
closeContextMenu()
|
||||
try {
|
||||
await wailsCall('OpenNodeFolder', node.id)
|
||||
await wailsCall('OpenFolder', node.id)
|
||||
} catch (e) { error = String(e) }
|
||||
}
|
||||
|
||||
async function moveNodeToRoot(node) {
|
||||
closeContextMenu()
|
||||
try {
|
||||
await wailsCall('MoveNode', node.id, '')
|
||||
await reloadTreePreservingExpanded()
|
||||
const refreshed = await wailsCall('GetNodeDetail', node.id)
|
||||
if (refreshed) {
|
||||
selectedSection = ''
|
||||
selectNode(refreshed)
|
||||
}
|
||||
} catch (e) { error = String(e) }
|
||||
}
|
||||
|
||||
|
|
@ -1623,6 +1636,12 @@
|
|||
{/each}
|
||||
<div class="context-menu-divider"></div>
|
||||
{/if}
|
||||
{#if contextMenu.node && contextMenu.node.parent_id}
|
||||
<button class="context-menu-item" on:click={() => moveNodeToRoot(contextMenu.node)}>
|
||||
{t('nav.moveToRoot')}
|
||||
</button>
|
||||
<div class="context-menu-divider"></div>
|
||||
{/if}
|
||||
<button class="context-menu-item" on:click={() => openRenameForNode(contextMenu.node)}>
|
||||
{t('common.rename')}
|
||||
</button>
|
||||
|
|
@ -1868,8 +1887,8 @@
|
|||
/* Tabs */
|
||||
.tabs { display: flex; border-bottom: 1px solid #2a2a3c; flex-shrink: 0; padding: 0 24px; }
|
||||
.tab { padding: 10px 16px; border: none; background: none; color: #888; font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent; font-family: inherit; }
|
||||
.tab:hover { color: #ccc; }
|
||||
.tab.active { color: #e4e4ef; border-bottom-color: #6366f1; }
|
||||
.tab:hover { color: #a5b4fc; }
|
||||
.tab.active { color: #e4e4ef; border-bottom-color: #6366f1; background: rgba(99,102,241,0.06); font-weight: 500; }
|
||||
|
||||
/* Tab content */
|
||||
.tab-content { flex: 1; overflow-y: auto; }
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
|
||||
function handleShowInFolder() {
|
||||
dispatch('showInFolder', item.id)
|
||||
dispatch('showInFolder', item.nodeId || item.id)
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
|
|
@ -152,6 +152,11 @@
|
|||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn" on:click|stopPropagation={handleShowInFolder} title={t('file.showInExplorer')} aria-label={t('file.showInExplorer')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
{:else}
|
||||
<button class="action-btn" on:click|stopPropagation={() => dispatch('navigate', item.id)} title={t('file.openFolder')} aria-label={t('file.openFolder')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
|
@ -159,6 +164,11 @@
|
|||
<line x1="9" y1="14" x2="15" y2="14"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-btn" on:click|stopPropagation={handleShowInFolder} title={t('file.showInExplorer')} aria-label={t('file.showInExplorer')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
<button class="action-btn" on:click|stopPropagation={toggleMenu} title={t('file.more')} aria-label={t('file.more')} aria-expanded={menuOpen}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
|
@ -188,12 +198,10 @@
|
|||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
{t('file.openExternal')}
|
||||
</button>
|
||||
{#if isFolder}
|
||||
<button class="menu-item" on:click={handleShowInFolder} role="menuitem">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
{t('file.showInExplorer')}
|
||||
</button>
|
||||
{/if}
|
||||
<button class="menu-item" on:click={handleShowInFolder} role="menuitem">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
{t('file.showInExplorer')}
|
||||
</button>
|
||||
<div class="menu-sep"></div>
|
||||
<button class="menu-item" on:click={handleRename} role="menuitem">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export default {
|
|||
'nav.openFolder': 'Open folder',
|
||||
'nav.createInside': 'Create inside',
|
||||
'nav.createNode': 'Create element',
|
||||
'nav.moveToRoot': 'Move to root',
|
||||
'nav.selectPrompt': 'Select a section or case',
|
||||
'nav.brand': 'Verstak',
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default {
|
|||
'nav.openFolder': 'Открыть папку',
|
||||
'nav.createInside': 'Создать внутри',
|
||||
'nav.createNode': 'Создать элемент',
|
||||
'nav.moveToRoot': 'Переместить в корень',
|
||||
|
||||
'tab.overview': 'Обзор',
|
||||
'tab.notes': 'Заметки',
|
||||
|
|
|
|||
Loading…
Reference in New Issue