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:
mirivlad 2026-06-03 04:01:55 +08:00
parent c941f05dab
commit baf57e993d
8 changed files with 53 additions and 16 deletions

View File

@ -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

View File

@ -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>

View File

@ -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; }

View File

@ -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>

View File

@ -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',

View File

@ -21,6 +21,7 @@ export default {
'nav.openFolder': 'Открыть папку',
'nav.createInside': 'Создать внутри',
'nav.createNode': 'Создать элемент',
'nav.moveToRoot': 'Переместить в корень',
'tab.overview': 'Обзор',
'tab.notes': 'Заметки',