收口工作台资源 tab 与本地文件树 P1
This commit is contained in:
@@ -1832,6 +1832,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return String(item && (item.iconHint || item.rowKind || 'file') || 'file').trim() || 'file';
|
||||
}
|
||||
|
||||
function fileCapabilitiesAttr(item) {
|
||||
try {
|
||||
return JSON.stringify(Array.isArray(item && item.capabilities) ? item.capabilities : []);
|
||||
} catch (_error) {
|
||||
return '[]';
|
||||
}
|
||||
}
|
||||
|
||||
function isFileTreeProjectionPageRow(rowKind, assetId) {
|
||||
if (assetId) return false;
|
||||
return rowKind === 'document' || rowKind === 'doc' || rowKind === 'markdown';
|
||||
@@ -1873,7 +1881,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var childHtml = expandable
|
||||
? '<ul class="tree-children' + (expanded ? '' : ' tree-children--collapsed') + '">' + renderFileRows(nodeId, grouped, activeId, activeRowId) + '</ul>'
|
||||
: '';
|
||||
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
|
||||
return '<li class="tree-node" data-node-id="' + escapeHtml(nodeId) + '"><div class="tree-row" role="treeitem" aria-level="' + (depth + 1) + '" aria-expanded="' + String(expandable && expanded) + '" data-rust-rendered-row="filetree" data-testid="' + testId + '" data-row-id="' + escapeHtml(rowId) + '" data-row-kind="' + escapeHtml(rowKind) + '" data-node-id="' + escapeHtml(nodeId) + '"' + (parent ? ' data-parent-id="' + escapeHtml(parent) + '"' : '') + ' data-document-id="' + escapeHtml(documentId) + '" data-doc-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '" data-object-identity="' + escapeHtml(objectIdentityAttr(objectIdentity)) + '" data-object-kind="' + escapeHtml(String(objectIdentity.objectKind || '')) + '" data-capabilities="' + escapeHtml(fileCapabilitiesAttr(item)) + '" data-shell-mode="filetree" data-selected="' + String(selected) + '" data-active="false" draggable="true">' + toggle + '<span class="tree-kind-badge" data-kind="' + escapeHtml(iconKind) + '" aria-hidden="true"></span><button type="button" class="tree-link" data-rust-action="open" data-row-id="' + escapeHtml(rowId) + '" data-document-id="' + escapeHtml(documentId) + '" data-asset-id="' + escapeHtml(assetId) + '"><span class="tree-link-title">' + escapeHtml(title) + '</span></button><div class="tree-actions">' + createAction + '<button type="button" class="tree-action" data-testid="filetree-action-menu" data-rust-action="menu" data-row-id="' + escapeHtml(rowId) + '" aria-label="更多操作">…</button></div></div>' + childHtml + '</li>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@@ -2214,11 +2222,40 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
async function openConvexAssetFromFileTree(detail) {
|
||||
var assetId = String(detail && detail.assetId || '').trim();
|
||||
if (!assetId) return;
|
||||
var forceNewWindow = String(detail && detail.openTarget || '').trim() === 'new-window';
|
||||
var openTarget = String(detail && detail.openTarget || '').trim().toLowerCase();
|
||||
var forceNewWindow = openTarget === 'new-window';
|
||||
if (openTarget === 'side') {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceAsSideTarget === 'function') {
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceAsSideTarget({
|
||||
objectIdentity: String(detail.objectIdentity || detail.assetId || ''),
|
||||
assetId: assetId,
|
||||
title: String(detail.title || detail.fileName || assetId || ''),
|
||||
kind: String(detail.iconKind || detail.assetType || 'file'),
|
||||
documentId: String(detail.documentId || currentDocumentId() || ''),
|
||||
workspaceId: String(detail.workspaceId || resolveWorkspaceId(document.body) || '')
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
var localFilePath = localFilePathFromAssetId(assetId);
|
||||
if (localFilePath) {
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
if (isMindmapAssetDetail(detail) || String(detail && detail.assetType || '').trim() === 'mindmap') {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:mindmap:' + String(detail && detail.documentId || currentDocumentId() || '').trim() + ':' + assetId,
|
||||
assetId: assetId,
|
||||
mindmapId: assetId,
|
||||
title: String(detail && detail.title || localFileName || '思维导图').trim(),
|
||||
fileName: localFileName,
|
||||
kind: 'mindmap',
|
||||
documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
});
|
||||
return;
|
||||
}
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'local-mindmap-object-shell');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
navigateToMindmapObject(String(detail && detail.documentId || currentDocumentId() || '').trim(), assetId, String(detail.workspaceId || '').trim());
|
||||
@@ -2255,6 +2292,21 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
var documentId = String(detail && detail.documentId || '').trim();
|
||||
if (isMindmapAssetDetail(detail) && documentId) {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:mindmap:' + documentId + ':' + assetId,
|
||||
assetId: assetId,
|
||||
mindmapId: assetId,
|
||||
title: String(detail.title || '思维导图').trim(),
|
||||
fileName: String(detail.title || '思维导图').trim(),
|
||||
kind: 'mindmap',
|
||||
documentId: documentId,
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
});
|
||||
return;
|
||||
}
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-object-shell');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
navigateToMindmapObject(documentId, assetId, String(detail.workspaceId || '').trim());
|
||||
@@ -2324,6 +2376,130 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
});
|
||||
}
|
||||
|
||||
function fileTreeRowCapabilities(row) {
|
||||
if (!(row instanceof HTMLElement)) return [];
|
||||
try {
|
||||
var parsed = JSON.parse(row.getAttribute('data-capabilities') || '[]');
|
||||
return Array.isArray(parsed) ? parsed.map(function(item) { return String(item || '').trim(); }).filter(Boolean) : [];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function fileTreeRowIsReadonly(row) {
|
||||
return fileTreeRowCapabilities(row).some(function(capability) {
|
||||
return ['readonly', 'readOnly', 'permissionDenied'].indexOf(capability) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
function blockReadonlyFileTreeAction(action, detail, message) {
|
||||
var normalizedAction = String(action || 'drop').trim() || 'drop';
|
||||
var text = String(message || '目标位置是只读,不能拖放到这里').trim();
|
||||
var targetRowId = String(detail && (detail.targetRowId || detail.rowId) || '').trim();
|
||||
var documentId = String(detail && detail.documentId || '').trim();
|
||||
var assetId = String(detail && detail.assetId || '').trim();
|
||||
recordFileTreeAction(normalizedAction, {
|
||||
rowId: targetRowId,
|
||||
documentId: documentId,
|
||||
assetId: assetId,
|
||||
readonly: true
|
||||
});
|
||||
recordFileTreeActionStatus('blocked', {
|
||||
rowId: targetRowId,
|
||||
documentId: documentId,
|
||||
assetId: assetId,
|
||||
readonly: true,
|
||||
fallback: 'alert'
|
||||
});
|
||||
document.documentElement.setAttribute('data-mnote-filetree-readonly-blocked', normalizedAction);
|
||||
document.documentElement.setAttribute('data-mnote-filetree-readonly-message', text);
|
||||
if (targetRowId) {
|
||||
document.documentElement.setAttribute('data-mnote-filetree-readonly-target-row-id', targetRowId);
|
||||
var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(targetRowId) + '"]');
|
||||
if (row instanceof HTMLElement) {
|
||||
row.setAttribute('data-readonly-blocked', normalizedAction);
|
||||
row.setAttribute('data-readonly-message', text);
|
||||
}
|
||||
}
|
||||
window.alert(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
function fileTreeDocumentParentsForPreflight() {
|
||||
return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')).map(function(row) {
|
||||
var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '';
|
||||
if (!documentId) return null;
|
||||
var parentRowId = row.getAttribute('data-parent-id') || '';
|
||||
var parentDocumentId = parentRowId.indexOf('doc:') === 0 ? parentRowId.slice(4) : parentRowId || null;
|
||||
return { documentId: documentId, parentId: parentDocumentId };
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function fileTreeTargetChildrenForPreflight(targetRow) {
|
||||
var node = targetRow instanceof HTMLElement ? targetRow.closest('.tree-node') : null;
|
||||
if (!node) return [];
|
||||
return Array.from(node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"]')).map(function(row) {
|
||||
return {
|
||||
rowKind: row.getAttribute('data-row-kind') || '',
|
||||
documentId: row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || null,
|
||||
assetId: row.getAttribute('data-asset-id') || null,
|
||||
title: rowTitle(row)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function fileTreeDropPreflightRows() {
|
||||
return fileTreeRowsForUploadPreflight().map(function(row) {
|
||||
var domRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(row.rowId) + '"]');
|
||||
row.title = rowTitle(domRow);
|
||||
return row;
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureFileTreeWritableTarget(action, targetRow, rowIds, copy) {
|
||||
var normalizedAction = String(action || 'drop').trim() || 'drop';
|
||||
if (!(targetRow instanceof HTMLElement)) return true;
|
||||
var detail = {
|
||||
targetRowId: targetRow.getAttribute('data-row-id') || '',
|
||||
rowId: targetRow.getAttribute('data-row-id') || '',
|
||||
documentId: targetRow.getAttribute('data-document-id') || targetRow.getAttribute('data-doc-id') || '',
|
||||
assetId: targetRow.getAttribute('data-asset-id') || ''
|
||||
};
|
||||
if (fileTreeRowIsReadonly(targetRow)) {
|
||||
return blockReadonlyFileTreeAction(normalizedAction, detail, '目标位置是只读,不能拖放到这里');
|
||||
}
|
||||
try {
|
||||
var response = await fetch('/api/tree/filetree/drop-preflight', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(targetRow),
|
||||
copy: Boolean(copy),
|
||||
sourceCapabilities: ['read', 'write', 'move'],
|
||||
targetCapabilities: fileTreeRowCapabilities(targetRow),
|
||||
targetDocumentId: detail.documentId || null,
|
||||
targetRowId: detail.targetRowId || null,
|
||||
focusedRowId: sidebarFileTreeSelection.focusedRowId || null,
|
||||
activeDocumentId: currentDocumentId() || null,
|
||||
rowIds: Array.isArray(rowIds) ? rowIds : [],
|
||||
rows: fileTreeDropPreflightRows(),
|
||||
targetChildren: fileTreeTargetChildrenForPreflight(targetRow),
|
||||
documentParents: fileTreeDocumentParentsForPreflight()
|
||||
})
|
||||
});
|
||||
if (response.ok) return true;
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
var message = payload && (payload.error || payload.message) ? String(payload.error || payload.message) : '目标位置是只读,不能拖放到这里';
|
||||
if (message.indexOf('只读') >= 0 || message.toLowerCase().indexOf('readonly') >= 0) {
|
||||
return blockReadonlyFileTreeAction(normalizedAction, detail, message);
|
||||
}
|
||||
return true;
|
||||
} catch (_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function fileTreeDocumentWorkspacesForUploadPreflight(workspaceId) {
|
||||
var seen = new Set();
|
||||
return fileTreeRowsForUploadPreflight().filter(function(row) {
|
||||
@@ -3327,6 +3503,28 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function recordFileTreeAction(action, detail) {
|
||||
var normalized = String(action || '').trim() || 'unknown';
|
||||
var rowId = String(detail && detail.rowId || '').trim();
|
||||
var documentId = String(detail && detail.documentId || '').trim();
|
||||
var assetId = String(detail && detail.assetId || '').trim();
|
||||
document.documentElement.setAttribute('data-mnote-filetree-last-action', normalized);
|
||||
if (rowId) document.documentElement.setAttribute('data-mnote-filetree-last-action-row-id', rowId);
|
||||
if (documentId) document.documentElement.setAttribute('data-mnote-filetree-last-action-document-id', documentId);
|
||||
if (assetId) document.documentElement.setAttribute('data-mnote-filetree-last-action-asset-id', assetId);
|
||||
window.dispatchEvent(new CustomEvent('tree.filetree.action', {
|
||||
detail: Object.assign({}, detail || {}, { action: normalized })
|
||||
}));
|
||||
}
|
||||
|
||||
function recordFileTreeActionStatus(status, detail) {
|
||||
var normalized = String(status || '').trim() || 'unknown';
|
||||
document.documentElement.setAttribute('data-mnote-filetree-last-action-status', normalized);
|
||||
window.dispatchEvent(new CustomEvent('tree.filetree.action.status', {
|
||||
detail: Object.assign({}, detail || {}, { status: normalized })
|
||||
}));
|
||||
}
|
||||
|
||||
function documentHref(documentId, workspaceId) {
|
||||
var url = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin);
|
||||
if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
|
||||
@@ -3395,10 +3593,17 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var title = detail.title || '无标题';
|
||||
var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
|
||||
if (isAsset && action === 'new-window') {
|
||||
recordFileTreeAction('new-window', detail);
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
|
||||
return;
|
||||
}
|
||||
if (isAsset && action === 'open-right') {
|
||||
recordFileTreeAction('open-right', detail);
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'side' });
|
||||
return;
|
||||
}
|
||||
if (action === 'open-right') {
|
||||
recordFileTreeAction('open-right', detail);
|
||||
dispatchSidebarEvent('tree.page.open-right', detail);
|
||||
return;
|
||||
}
|
||||
@@ -3428,10 +3633,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
if (action === 'delete-trash' && isAsset) {
|
||||
if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗?')) return;
|
||||
void deleteSingleFileTreeAsset(detail, trigger).catch(function(error) {
|
||||
window.alert(error && error.message ? error.message : '资源删除失败');
|
||||
}).then(function() {
|
||||
recordFileTreeAction('delete-trash', detail);
|
||||
recordFileTreeActionStatus('pending', detail);
|
||||
void deleteSingleFileTreeAsset(detail, trigger).then(function() {
|
||||
recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
|
||||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||||
}).catch(function(error) {
|
||||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||||
window.alert(error && error.message ? error.message : '资源删除失败');
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -3439,6 +3648,17 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
void createPage(trigger || document.body, documentId || null);
|
||||
return;
|
||||
}
|
||||
if (action === 'paste-into') {
|
||||
var pasteRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
||||
recordFileTreeAction('paste-into', detail);
|
||||
void pasteSidebarFileTreeClipboard(pasteRow).then(function(ok) {
|
||||
recordFileTreeActionStatus(ok ? 'applied' : 'skipped', detail);
|
||||
}).catch(function(error) {
|
||||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||||
window.alert(error && error.message ? error.message : '粘贴失败');
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action === 'copy-path') {
|
||||
void copyTreeContextValue(title, 'copy-path');
|
||||
return;
|
||||
@@ -3496,13 +3716,17 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var deleteTargetId = documentId || String(detail.rowId || '').trim();
|
||||
if (action === 'delete-trash' && deleteTargetId) {
|
||||
if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗?')) return;
|
||||
recordFileTreeAction('delete-trash', detail);
|
||||
recordFileTreeActionStatus('pending', detail);
|
||||
void dispatchTreeCommand(trigger || document.body, {
|
||||
action: 'archive',
|
||||
workspaceId: workspaceId,
|
||||
documentId: deleteTargetId
|
||||
}).then(function() {
|
||||
recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
|
||||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||||
}).catch(function(error) {
|
||||
recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
|
||||
window.alert(error && error.message ? error.message : '删除失败');
|
||||
});
|
||||
}
|
||||
@@ -3594,7 +3818,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ separator: true },
|
||||
{ action: 'new-file', icon: 'note_add', label: 'New File' },
|
||||
{ action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: true, title: 'Convex 文件夹对象尚未进入正式 tree command' },
|
||||
{ action: 'paste-into', icon: 'content_paste', label: 'Paste Into', disabled: true, title: '请使用 Ctrl/Cmd+V 粘贴;右键 Paste Into 待接 selection target' },
|
||||
{ action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !sidebarFileTreeClipboard, title: sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空' },
|
||||
{ action: 'refresh', icon: 'refresh', label: 'Refresh' },
|
||||
{ action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
|
||||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' },
|
||||
@@ -3886,11 +4110,20 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var action = sidebarFileTreeClipboard.action === 'cut' ? 'move' : 'copy';
|
||||
var rows = fileTreeRowsByRowIds(sidebarFileTreeClipboard.rowIds);
|
||||
if (rows.length === 0) return false;
|
||||
var writable = await ensureFileTreeWritableTarget('paste', targetRow, sidebarFileTreeClipboard.rowIds, action === 'copy');
|
||||
if (!writable) return false;
|
||||
recordFileTreeAction('paste', {
|
||||
rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '',
|
||||
documentId: targetDocumentId,
|
||||
sourceRowIds: sidebarFileTreeClipboard.rowIds,
|
||||
clipboardAction: sidebarFileTreeClipboard.action
|
||||
});
|
||||
var plan = buildSidebarFileTreeDeletePlan(rows);
|
||||
var workspaceId = resolveWorkspaceId(targetRow || document.body);
|
||||
var failures = [];
|
||||
if (action === 'copy') {
|
||||
dispatchSidebarEvent('tree.filetree.copy-requested', { rowIds: sidebarFileTreeClipboard.rowIds, targetDocumentId: targetDocumentId });
|
||||
recordFileTreeActionStatus('copy-requested', { documentId: targetDocumentId });
|
||||
return true;
|
||||
}
|
||||
for (var i = 0; i < plan.docRows.length; i += 1) {
|
||||
@@ -3922,10 +4155,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
recordFileTreeActionStatus('failed', { documentId: targetDocumentId, fallback: 'alert' });
|
||||
window.alert('部分对象移动失败:' + failures.join(';'));
|
||||
return false;
|
||||
}
|
||||
sidebarFileTreeClipboard = null;
|
||||
recordFileTreeActionStatus('applied', { documentId: targetDocumentId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3935,6 +4170,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var total = plan.docRows.length + plan.folderRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length;
|
||||
if (total === 0) return false;
|
||||
if (!window.confirm(sidebarFileTreeDeleteConfirmText(plan))) return false;
|
||||
recordFileTreeAction('bulk-delete', { rowId: trigger instanceof HTMLElement ? trigger.getAttribute('data-row-id') || '' : '', count: total });
|
||||
recordFileTreeActionStatus('pending', { count: total });
|
||||
var failures = [];
|
||||
for (var i = 0; i < plan.docRows.length; i += 1) {
|
||||
var docRow = plan.docRows[i];
|
||||
@@ -4026,10 +4263,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
sidebarFileTreeSelection.focusedRowId = null;
|
||||
syncSidebarFileTreeSelection();
|
||||
if (failures.length > 0) {
|
||||
recordFileTreeActionStatus('failed', { count: total, failures: failures.slice(0, 20), fallback: 'alert' });
|
||||
window.alert('部分对象删除失败:' + failures.slice(0, 5).join(', ') + (failures.length > 5 ? '…' : ''));
|
||||
return false;
|
||||
}
|
||||
document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true');
|
||||
recordFileTreeActionStatus('archived', { count: total, undo: 'trash-modal' });
|
||||
if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
|
||||
return true;
|
||||
}
|
||||
@@ -8337,7 +8576,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (files.length) {
|
||||
dispatchSidebarEvent('tree.filetree.external-drop', Object.assign({}, detail, { files: files }));
|
||||
} else {
|
||||
dispatchSidebarEvent('tree.filetree.internal-drop', Object.assign({}, detail, { rowIds: rowIds, copy: event.altKey === true || event.ctrlKey === true || event.metaKey === true }));
|
||||
void Promise.resolve(ensureFileTreeWritableTarget('drop', targetRow, rowIds, event.altKey === true || event.ctrlKey === true || event.metaKey === true)).then(function(writable) {
|
||||
if (!writable) return;
|
||||
dispatchSidebarEvent('tree.filetree.internal-drop', Object.assign({}, detail, { rowIds: rowIds, copy: event.altKey === true || event.ctrlKey === true || event.metaKey === true }));
|
||||
});
|
||||
}
|
||||
draggingFileTreeRowIds = [];
|
||||
}
|
||||
@@ -9195,6 +9437,17 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("确认删除选中的 "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_filetree_blocks_readonly_paste_and_drop_with_action_status() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function blockReadonlyFileTreeAction"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-filetree-readonly-blocked"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-filetree-readonly-message"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("recordFileTreeActionStatus('blocked'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/tree/filetree/drop-preflight"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("ensureFileTreeWritableTarget('paste'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("ensureFileTreeWritableTarget('drop'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_filetree_local_table_bulk_delete_uses_tree_command() {
|
||||
let table_loop_start = SIDEBAR_TREE_JS
|
||||
|
||||
Reference in New Issue
Block a user