feat: consolidate local-first mnote web runtime
This commit is contained in:
@@ -330,13 +330,18 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
}
|
||||
|
||||
function fileTreeCopyLocalAbsolutePath(detail, trigger) {
|
||||
var rootUri = typeof currentRootUri === 'function' ? currentRootUri() : '';
|
||||
var workspacePath = detail && detail.workspacePath && typeof detail.workspacePath === 'object' ? detail.workspacePath : null;
|
||||
var rootUri = String(workspacePath && workspacePath.rootUri || '').trim() || (typeof currentRootUri === 'function' ? currentRootUri() : '');
|
||||
if (!rootUri && trigger && typeof trigger.closest === 'function') {
|
||||
var row = trigger.closest('.tree-row[data-shell-mode="filetree"]');
|
||||
if (row instanceof HTMLElement) rootUri = row.getAttribute('data-root-uri') || '';
|
||||
}
|
||||
if (!rootUri && document.body) rootUri = document.body.getAttribute('data-mnote-root-uri') || '';
|
||||
var relativePath = String(detail && detail.localRelativePath || '').trim();
|
||||
var relativePath = String(
|
||||
workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath)
|
||||
|| detail && detail.localRelativePath
|
||||
|| ''
|
||||
).trim();
|
||||
if (!relativePath && detail && detail.assetId) relativePath = localFilePathFromAssetId(detail.assetId);
|
||||
if (!relativePath && detail && detail.documentId) {
|
||||
relativePath = String(detail.documentId || '')
|
||||
@@ -609,8 +614,13 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
|
||||
button.setAttribute('role', 'menuitem');
|
||||
button.setAttribute('data-action', item.action);
|
||||
button.setAttribute('data-command-id', item.commandId || item.action || '');
|
||||
if (item.when) button.setAttribute('data-command-when', item.when);
|
||||
if (item.danger || item.destructive) button.setAttribute('data-destructive', 'true');
|
||||
if (item.requiresApproval) button.setAttribute('data-requires-approval', 'true');
|
||||
button.disabled = item.disabled === true;
|
||||
if (item.title) button.title = item.title;
|
||||
if (item.disabled === true && item.title) button.setAttribute('data-disabled-reason', item.title);
|
||||
var icon = document.createElement('span');
|
||||
icon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
|
||||
icon.setAttribute('aria-hidden', 'true');
|
||||
@@ -636,10 +646,30 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
|
||||
// ── CommandContext 启用态辅助:与 Rust core-protocol CommandContext 保持同一口径 ──
|
||||
|
||||
function buildSidebarFileTreeContext(kind) {
|
||||
function currentOpenEditorsSnapshotForCommandContext() {
|
||||
try {
|
||||
if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') {
|
||||
return window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot();
|
||||
}
|
||||
} catch (_) {}
|
||||
return window.__mnoteOpenEditorsSnapshot || null;
|
||||
}
|
||||
|
||||
function commandContextTargetRow(detail) {
|
||||
if (detail && detail.targetRow instanceof HTMLElement) return detail.targetRow;
|
||||
var rowId = String(detail && detail.rowId || '').trim();
|
||||
if (rowId) {
|
||||
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]');
|
||||
if (row instanceof HTMLElement) return row;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildSidebarFileTreeContext(kind, detail) {
|
||||
var runtime = window.__mnoteFileTreeContextMenuRuntime;
|
||||
if (runtime && typeof runtime.buildSidebarFileTreeContext === 'function') {
|
||||
try {
|
||||
var targetRow = commandContextTargetRow(detail);
|
||||
return runtime.buildSidebarFileTreeContext(kind, {
|
||||
selection: sidebarFileTreeSelection,
|
||||
currentSourceKind: currentSourceKind,
|
||||
@@ -648,7 +678,10 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
},
|
||||
queryRowById: function(rowId) {
|
||||
return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]');
|
||||
}
|
||||
},
|
||||
targetRow: targetRow,
|
||||
openEditorsSnapshot: currentOpenEditorsSnapshotForCommandContext(),
|
||||
bufferState: detail && detail.bufferState,
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -800,10 +833,17 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
var isAttachment = kind === 'attachment';
|
||||
var isAsset = kind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
|
||||
var isFileTreeDownload = kind === 'filetree' && detail.downloadable;
|
||||
var ctx = buildSidebarFileTreeContext(kind);
|
||||
var ctx = buildSidebarFileTreeContext(kind, detail);
|
||||
menu.setAttribute('data-command-context-source-kind', String(ctx['workspace.sourceKind'] || ''));
|
||||
menu.setAttribute('data-command-context-readonly', String(ctx['workspace.readonly'] === true));
|
||||
menu.setAttribute('data-command-context-selection-count', String(ctx['tree.selectionCount'] || 0));
|
||||
menu.setAttribute('data-command-context-resource-kind', String(ctx['tree.selectionResourceKind'] || ''));
|
||||
menu.setAttribute('data-command-context-target-kind', String(ctx['tree.targetResourceKind'] || ''));
|
||||
menu.setAttribute('data-command-context-editor-dirty', String(ctx['editor.dirty'] === true));
|
||||
menu.setAttribute('data-command-context-ai-can-write', String(ctx['ai.canWrite'] === true));
|
||||
var items = isAttachment ? [
|
||||
{ action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly' },
|
||||
{ separator: true },
|
||||
{ action: 'copy-link', icon: 'link', label: '复制链接' },
|
||||
{ action: 'move-embed', icon: 'subdirectory_arrow_right', label: '移动/嵌入到...', shortcut: 'Alt+Shift+M/G' },
|
||||
@@ -825,9 +865,9 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' },
|
||||
{ action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' },
|
||||
{ action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) + ' 个项目' : '下载' },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly' },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly && !editor.dirty' },
|
||||
{ action: 'copy-id', icon: 'tag', label: '复制资源 ID' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' },
|
||||
{ separator: true },
|
||||
{ action: 'copy-path', icon: 'content_copy', label: 'Copy Path' },
|
||||
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
|
||||
@@ -849,9 +889,9 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
{ action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
|
||||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' },
|
||||
{ separator: true },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly' },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly && !editor.dirty' },
|
||||
{ separator: true },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }
|
||||
] : [
|
||||
{ action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' },
|
||||
{ separator: true },
|
||||
@@ -859,13 +899,18 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
{ action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' },
|
||||
{ action: 'copy-id', icon: 'tag', label: '复制页面ID' },
|
||||
{ separator: true },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', when: '!workspace.readonly' },
|
||||
{ action: 'rename', icon: 'edit', label: '重命名', when: '!workspace.readonly && !editor.dirty' },
|
||||
{ separator: true },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, destructive: true, requiresApproval: true, when: '!workspace.readonly && !editor.dirty' }
|
||||
];
|
||||
items.forEach(function(item) {
|
||||
if (item.when !== undefined && !evaluateSidebarFileTreeWhen(ctx, item.when)) {
|
||||
item = Object.assign({}, item, { disabled: true, title: item.title || '当前上下文不支持此操作' });
|
||||
var reason = ctx['workspace.readonly'] === true
|
||||
? '当前工作区只读'
|
||||
: ctx['editor.dirty'] === true
|
||||
? '当前目标有未保存修改'
|
||||
: '当前上下文不支持此操作';
|
||||
item = Object.assign({}, item, { disabled: true, title: item.title || reason });
|
||||
}
|
||||
appendTreeContextMenuButton(menu, item, detail, trigger);
|
||||
});
|
||||
@@ -895,12 +940,15 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '';
|
||||
var selectedDownloadRows = selectedSidebarFileTreeRowsForDownload(row);
|
||||
var selectedAssetRows = selectedDownloadRows.filter(isFileTreeDownloadableAssetRow);
|
||||
var workspacePath = readWorkspacePathFromRow(row);
|
||||
openTreeContextMenu('filetree', {
|
||||
documentId: documentId,
|
||||
rowId: row.getAttribute('data-row-id') || '',
|
||||
rowKind: row.getAttribute('data-row-kind') || '',
|
||||
assetId: row.getAttribute('data-asset-id') || '',
|
||||
localRelativePath: fileTreeRowLocalRelativePath(row),
|
||||
workspacePath: workspacePath,
|
||||
targetRow: row,
|
||||
title: rowTitle(row),
|
||||
workspaceId: resolveWorkspaceId(row),
|
||||
downloadable: isFileTreeDownloadableRow(row),
|
||||
@@ -1243,6 +1291,32 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
return lastSlash >= 0 ? relativePath.slice(0, lastSlash) : '';
|
||||
}
|
||||
|
||||
function readWorkspacePathFromRow(row) {
|
||||
var runtimeFn = fileTreeRuntimeFunction('readWorkspacePathFromRow');
|
||||
if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
|
||||
if (!(row instanceof HTMLElement)) return null;
|
||||
var relativePath = fileTreeRowLocalRelativePath(row);
|
||||
return {
|
||||
schema: 'mnote.workspace_path.v1',
|
||||
workspaceId: resolveWorkspaceId(row),
|
||||
sourceKind: currentSourceKind(),
|
||||
rootUri: currentRootUri(),
|
||||
relativePath: relativePath,
|
||||
localRelativePath: relativePath,
|
||||
objectIdentity: null,
|
||||
objectIdentityRaw: row.getAttribute('data-object-identity') || '',
|
||||
objectKind: row.getAttribute('data-object-kind') || '',
|
||||
resourceKind: row.getAttribute('data-object-kind') || fileTreeRowKind(row),
|
||||
rowId: row.getAttribute('data-row-id') || '',
|
||||
rowKind: fileTreeRowKind(row),
|
||||
documentId: fileTreeRowDocumentId(row),
|
||||
assetId: fileTreeRowAssetId(row),
|
||||
title: rowTitle(row),
|
||||
href: '',
|
||||
isLocalFolder: currentSourceKind() === 'local_folder'
|
||||
};
|
||||
}
|
||||
|
||||
function fileTreeRowKind(row) {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeRowKind');
|
||||
if (runtimeFn) return runtimeFn(row);
|
||||
@@ -1281,6 +1355,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
rowKind: fileTreeRowKind(row),
|
||||
assetId: assetId,
|
||||
localRelativePath: relativePath,
|
||||
workspacePath: readWorkspacePathFromRow(row),
|
||||
title: title,
|
||||
fileName: title,
|
||||
workspaceId: resolveWorkspaceId(row)
|
||||
@@ -1728,6 +1803,7 @@ export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
|
||||
decodeLocalEncodedPath,
|
||||
fileTreeRowLocalRelativePath,
|
||||
fileTreeRowLocalUploadTargetRelativePath,
|
||||
readWorkspacePathFromRow,
|
||||
fileTreeRowKind,
|
||||
isFileTreeDownloadableAssetRow,
|
||||
isFileTreeDownloadableRow,
|
||||
|
||||
Reference in New Issue
Block a user