feat: vault core/CLI/workbench, vaultd token path, filetree view-state cleanup
Land password-vault dedicated workbench and mnote-vault-core/CLI, agent token read path design, vault transport split, and retire obsolete filetree smokes. Ignore local vault reimport scripts that trip secret scanners.
This commit is contained in:
@@ -183,6 +183,49 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
runtimeState.activeTreeContextMenu = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product clipboard used by menu paste + keyboard paste.
|
||||
* Keep keyboard-module clipboard and runtimeState in lockstep so
|
||||
* 「粘贴到此处」enables after either Ctrl+C/X or menu cut/copy.
|
||||
*/
|
||||
function currentSidebarFileTreeClipboard() {
|
||||
if (runtimeState.sidebarFileTreeClipboard
|
||||
&& Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds)
|
||||
&& runtimeState.sidebarFileTreeClipboard.rowIds.length) {
|
||||
return runtimeState.sidebarFileTreeClipboard;
|
||||
}
|
||||
var kb = window.__mnoteFileTreeKeyboardRuntime;
|
||||
var fromKb = kb && typeof kb.getSidebarFileTreeClipboard === 'function'
|
||||
? kb.getSidebarFileTreeClipboard()
|
||||
: (kb && kb.sidebarFileTreeClipboard) || null;
|
||||
if (fromKb && Array.isArray(fromKb.rowIds) && fromKb.rowIds.length) {
|
||||
// Heal dual-clipboard drift (keyboard module wrote first).
|
||||
runtimeState.sidebarFileTreeClipboard = fromKb;
|
||||
return fromKb;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setSidebarFileTreeClipboard(action, rowIds) {
|
||||
var next = action && Array.isArray(rowIds) && rowIds.length
|
||||
? { action: action, rowIds: rowIds.slice() }
|
||||
: null;
|
||||
runtimeState.sidebarFileTreeClipboard = next;
|
||||
var kb = window.__mnoteFileTreeKeyboardRuntime;
|
||||
if (kb && typeof kb.setSidebarFileTreeClipboard === 'function') {
|
||||
if (next) {
|
||||
kb.setSidebarFileTreeClipboard(next.action, next.rowIds);
|
||||
} else if (typeof kb.clearSidebarFileTreeClipboard === 'function') {
|
||||
kb.clearSidebarFileTreeClipboard();
|
||||
}
|
||||
} else if (next) {
|
||||
document.documentElement.setAttribute('data-mnote-filetree-clipboard-action', next.action);
|
||||
} else {
|
||||
document.documentElement.removeAttribute('data-mnote-filetree-clipboard-action');
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function copyTreeContextValue(value, actionName) {
|
||||
var text = String(value || '');
|
||||
var done = function() {
|
||||
@@ -618,6 +661,30 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'cut' || action === 'copy') {
|
||||
var clipboardRows = selectedSidebarFileTreeRows();
|
||||
if (!clipboardRows.length && trigger && trigger.closest) {
|
||||
var single = trigger.closest('.tree-row[data-shell-mode="filetree"]');
|
||||
if (single instanceof HTMLElement) clipboardRows = [single];
|
||||
}
|
||||
var clipboardRowIds = clipboardRows.map(function(row) {
|
||||
return String(row.getAttribute('data-row-id') || '').trim();
|
||||
}).filter(Boolean);
|
||||
if (!clipboardRowIds.length) {
|
||||
recordFileTreeActionStatus('skipped', Object.assign({}, detail, { reason: 'empty-selection' }));
|
||||
return;
|
||||
}
|
||||
setSidebarFileTreeClipboard(action, clipboardRowIds);
|
||||
recordFileTreeAction(action, Object.assign({}, detail, {
|
||||
sourceRowIds: clipboardRowIds,
|
||||
clipboardAction: action,
|
||||
}));
|
||||
recordFileTreeActionStatus('applied', Object.assign({}, detail, {
|
||||
sourceRowIds: clipboardRowIds,
|
||||
clipboardAction: action,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (action === 'paste-into') {
|
||||
var pasteRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
||||
recordFileTreeAction('paste-into', detail);
|
||||
@@ -1228,7 +1295,10 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
{ action: 'new-file', icon: 'note_add', label: 'New File', when: '!workspace.readonly' },
|
||||
{ action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: currentSourceKind() !== 'local_folder', title: currentSourceKind() === 'local_folder' ? '在当前目录下创建子文件夹' : '仅 local folder 支持创建文件夹', when: '!workspace.readonly' },
|
||||
{ action: 'toggle-sidebar-folder-shortcut', icon: 'star', label: '加入/取消星标置顶', disabled: currentSourceKind() !== 'local_folder' || (detail.rowKind !== 'folder' && detail.rowKind !== 'directory'), title: currentSourceKind() === 'local_folder' ? '把当前文件夹加入或移出星标置顶' : '仅 local folder 文件夹支持星标置顶' },
|
||||
{ action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !runtimeState.sidebarFileTreeClipboard, title: runtimeState.sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空', when: '!workspace.readonly' },
|
||||
{ separator: true },
|
||||
{ action: 'cut', icon: 'content_cut', label: '剪切', shortcut: 'Ctrl+X', when: '!workspace.readonly' },
|
||||
{ action: 'copy', icon: 'content_copy', label: '复制', shortcut: 'Ctrl+C' },
|
||||
{ action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', shortcut: 'Ctrl+V', disabled: !currentSidebarFileTreeClipboard(), title: currentSidebarFileTreeClipboard() ? '粘贴到当前文件树目标' : '剪贴板为空', when: '!workspace.readonly' },
|
||||
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
|
||||
{ action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
|
||||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' },
|
||||
@@ -1929,9 +1999,16 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeChildCount');
|
||||
if (runtimeFn) return runtimeFn(documentId, fileTreeRuntimeDeps());
|
||||
if (!documentId) return 0;
|
||||
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"]');
|
||||
var row = document.querySelector(
|
||||
'#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"],'
|
||||
+ '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(documentId) + '"],'
|
||||
+ '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="' + cssEscape(documentId) + '"],'
|
||||
+ '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-node-id="' + cssEscape(documentId) + '"]'
|
||||
);
|
||||
var node = row ? row.closest('.tree-node') : null;
|
||||
var children = node ? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="document"], :scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="doc"]') : [];
|
||||
var children = node
|
||||
? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"]')
|
||||
: [];
|
||||
return children.length;
|
||||
}
|
||||
|
||||
@@ -1944,6 +2021,45 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
}, targetRow) || null;
|
||||
}
|
||||
|
||||
function fileTreeParentIdFromAttr(parentAttr) {
|
||||
var raw = String(parentAttr || '').trim();
|
||||
if (!raw) return null;
|
||||
if (raw.indexOf('doc:') === 0) return raw.slice(4);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wolai-style move target:
|
||||
* - inside → nest under target (folder/dir/doc)
|
||||
* - before/after → same parent as target, sortOrder = sibling index (+1 for after)
|
||||
*/
|
||||
function resolveFileTreeMoveTarget(targetRow, position) {
|
||||
if (!(targetRow instanceof HTMLElement)) {
|
||||
return { parentId: null, sortOrder: 0 };
|
||||
}
|
||||
var pos = position || 'inside';
|
||||
if (pos === 'inside') {
|
||||
var nestParentId = fileTreeMoveTargetParentId(targetRow);
|
||||
return {
|
||||
parentId: nestParentId,
|
||||
sortOrder: nestParentId ? fileTreeChildCount(nestParentId) : 0,
|
||||
};
|
||||
}
|
||||
var parentAttr = targetRow.getAttribute('data-parent-id') || '';
|
||||
var parentId = fileTreeParentIdFromAttr(parentAttr);
|
||||
var siblings = Array.from(
|
||||
document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')
|
||||
).filter(function(row) {
|
||||
return (row.getAttribute('data-parent-id') || '') === parentAttr;
|
||||
});
|
||||
var index = siblings.indexOf(targetRow);
|
||||
if (index < 0) index = 0;
|
||||
return {
|
||||
parentId: parentId,
|
||||
sortOrder: Math.max(0, index + (pos === 'after' ? 1 : 0)),
|
||||
};
|
||||
}
|
||||
|
||||
function fileTreeMoveSourceId(row) {
|
||||
if (!(row instanceof HTMLElement)) return '';
|
||||
return fileTreeRowDocumentId(row)
|
||||
@@ -1956,7 +2072,10 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
var rows = fileTreeRowsByRowIds(rowIds || []);
|
||||
if (rows.length === 0) return false;
|
||||
var copy = Boolean(options && options.copy);
|
||||
var targetParentId = fileTreeMoveTargetParentId(targetRow);
|
||||
var dropPosition = (options && options.dropPosition) || 'inside';
|
||||
var resolved = resolveFileTreeMoveTarget(targetRow, dropPosition);
|
||||
var targetParentId = resolved.parentId;
|
||||
var baseSortOrder = typeof resolved.sortOrder === 'number' ? resolved.sortOrder : 0;
|
||||
var workspaceId = resolveWorkspaceId(targetRow || document.body);
|
||||
var writable = await ensureFileTreeWritableTarget('move', targetRow, rowIds || [], copy);
|
||||
if (!writable) return false;
|
||||
@@ -1983,7 +2102,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
workspaceId: workspaceId,
|
||||
documentId: sourceId,
|
||||
parentId: targetParentId,
|
||||
sortOrder: targetParentId ? fileTreeChildCount(targetParentId) + i : i
|
||||
sortOrder: baseSortOrder + i
|
||||
});
|
||||
} catch (error) {
|
||||
failures.push(sourceId + ': ' + (error && error.message ? error.message : '移动失败'));
|
||||
@@ -2014,21 +2133,24 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
}
|
||||
|
||||
async function pasteSidebarFileTreeClipboard(trigger) {
|
||||
if (!runtimeState.sidebarFileTreeClipboard || !Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds) || runtimeState.sidebarFileTreeClipboard.rowIds.length === 0) return false;
|
||||
var clipboard = currentSidebarFileTreeClipboard();
|
||||
if (!clipboard) return false;
|
||||
var targetRow = trigger instanceof HTMLElement ? trigger : null;
|
||||
if (!targetRow && sidebarFileTreeSelection.focusedRowId) {
|
||||
targetRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]');
|
||||
}
|
||||
var targetDocumentId = fileTreeMoveTargetParentId(targetRow) || currentDocumentId();
|
||||
var action = runtimeState.sidebarFileTreeClipboard.action === 'cut' ? 'move' : 'copy';
|
||||
var action = clipboard.action === 'cut' ? 'move' : 'copy';
|
||||
recordFileTreeAction('paste', {
|
||||
rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '',
|
||||
documentId: targetDocumentId,
|
||||
sourceRowIds: runtimeState.sidebarFileTreeClipboard.rowIds,
|
||||
clipboardAction: runtimeState.sidebarFileTreeClipboard.action
|
||||
sourceRowIds: clipboard.rowIds,
|
||||
clipboardAction: clipboard.action
|
||||
});
|
||||
var ok = await moveSidebarFileTreeRows(runtimeState.sidebarFileTreeClipboard.rowIds, targetRow, { copy: action === 'copy' });
|
||||
if (ok && action === 'move') runtimeState.sidebarFileTreeClipboard = null;
|
||||
var ok = await moveSidebarFileTreeRows(clipboard.rowIds, targetRow, { copy: action === 'copy' });
|
||||
if (ok && action === 'move') {
|
||||
setSidebarFileTreeClipboard(null, []);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -2206,6 +2328,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
postSidebarFileTreeJson,
|
||||
fileTreeRowsByRowIds,
|
||||
fileTreeChildCount,
|
||||
resolveFileTreeMoveTarget,
|
||||
moveSidebarFileTreeRows,
|
||||
pasteSidebarFileTreeClipboard,
|
||||
deleteSelectedSidebarFileTreeRows,
|
||||
|
||||
Reference in New Issue
Block a user