chore: align mvp design governance
- 统一 local-first MVP 后阶段架构口径,补充 process 执行总序和 Reasonix 协作记录 - 归档已完成的 design checklist,标注参考型 process,更新 AGENTS/REASONIX/架构文档 - 补充文件树/主编辑器下载与上下文菜单相关实现、bug 记录和 smoke 脚本 验证:git diff --check;codegraph sync .;cargo test -p mnote-web;node --check scripts/task476-filetree-editor-context-menu-download-smoke.js
This commit is contained in:
@@ -1764,6 +1764,18 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return String(item && item.title || '无标题').trim() || '无标题';
|
||||
}
|
||||
|
||||
function fileWorkspaceRelativePath(item) {
|
||||
var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {};
|
||||
var workspacePath = meta.workspacePath && typeof meta.workspacePath === 'object' ? meta.workspacePath : {};
|
||||
var fromWorkspacePath = String(workspacePath.relativePath || '').trim();
|
||||
if (fromWorkspacePath) return fromWorkspacePath;
|
||||
var extra = meta.extra && typeof meta.extra === 'object' ? meta.extra : {};
|
||||
var source = extra.source && typeof extra.source === 'object' ? extra.source : {};
|
||||
var fromSource = String(source.relativePath || '').trim();
|
||||
if (fromSource) return fromSource;
|
||||
return String(item && (item.relativePath || item.rootRelativePath) || '').trim();
|
||||
}
|
||||
|
||||
function groupRowsByParent(rows) {
|
||||
var ids = new Set(rows.map(nodeIdOf).filter(Boolean));
|
||||
var grouped = new Map();
|
||||
@@ -1890,6 +1902,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var parent = parentIdOf(item);
|
||||
var documentId = fileDocumentId(item);
|
||||
var assetId = fileAssetId(item);
|
||||
var relativePath = fileWorkspaceRelativePath(item);
|
||||
var objectIdentity = fileObjectIdentity(item);
|
||||
var iconKind = iconKindOf(item);
|
||||
var title = isFileTreeProjectionPageRow(rowKind, assetId)
|
||||
@@ -1909,7 +1922,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-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>';
|
||||
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-local-relative-path="' + escapeHtml(relativePath) + '" 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) + '" data-local-relative-path="' + escapeHtml(relativePath) + '"><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('');
|
||||
}
|
||||
|
||||
@@ -2581,6 +2594,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
function fallbackFileTreeUploadTarget(detail) {
|
||||
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
|
||||
var documentId = String(detail && detail.documentId || currentDocumentId() || '').trim();
|
||||
if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
return {
|
||||
workspaceId: workspaceId,
|
||||
targetDocumentId: documentId,
|
||||
targetMindmapId: null,
|
||||
targetSubPath: null,
|
||||
targetRelativePath: String(detail.targetRelativePath || '')
|
||||
};
|
||||
}
|
||||
if (!workspaceId || !documentId) {
|
||||
throw new Error('请选择一个目标页面后再拖入文件');
|
||||
}
|
||||
@@ -2593,8 +2615,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
async function resolveFileTreeUploadTarget(detail) {
|
||||
if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
return fallbackFileTreeUploadTarget(detail || {});
|
||||
}
|
||||
try {
|
||||
return await preflightFileTreeUploadTarget(detail || {});
|
||||
var plan = await preflightFileTreeUploadTarget(detail || {});
|
||||
if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
plan.targetRelativePath = String(detail.targetRelativePath || '');
|
||||
}
|
||||
return plan;
|
||||
} catch (error) {
|
||||
console.warn('[mnote upload] upload target preflight fallback', error);
|
||||
return fallbackFileTreeUploadTarget(detail || {});
|
||||
@@ -2869,6 +2898,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var detail = detailFromEditorAttachmentLink(link);
|
||||
var assetId = String(detail && detail.assetId || '').trim();
|
||||
if (!assetId) return;
|
||||
var localFilePath = localFilePathFromAssetId(assetId);
|
||||
if (localFilePath) {
|
||||
var localMeta = {
|
||||
assetId: assetId,
|
||||
fileSize: String(detail && detail.fileSize || '').trim()
|
||||
};
|
||||
attachmentMetaCache[assetId] = localMeta;
|
||||
applyEditorAttachmentMeta(link, localMeta);
|
||||
return;
|
||||
}
|
||||
if (attachmentMetaCache[assetId]) {
|
||||
applyEditorAttachmentMeta(link, attachmentMetaCache[assetId]);
|
||||
return;
|
||||
@@ -3258,13 +3297,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (currentSourceKind() === 'local_folder') {
|
||||
var rootUri = (new URLSearchParams(window.location.search).get('rootUri') || '').trim();
|
||||
var documentId = String(plan && plan.targetDocumentId || currentDocumentId() || '').trim();
|
||||
if (!rootUri || !documentId) {
|
||||
var hasFolderTarget = plan && Object.prototype.hasOwnProperty.call(plan, 'targetRelativePath');
|
||||
if (!rootUri || (!documentId && !hasFolderTarget)) {
|
||||
throw new Error('本地 Markdown 上传缺少 rootUri 或 documentId');
|
||||
}
|
||||
var localForm = new FormData();
|
||||
localForm.append('file', file);
|
||||
localForm.append('rootUri', rootUri);
|
||||
localForm.append('documentId', documentId);
|
||||
if (documentId) localForm.append('documentId', documentId);
|
||||
if (hasFolderTarget) localForm.append('targetRelativePath', String(plan.targetRelativePath || ''));
|
||||
localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment');
|
||||
var localResponse = await fetch('/api/local-folder/assets/upload', {
|
||||
method: 'POST',
|
||||
@@ -3546,6 +3587,28 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function triggerBrowserDownload(url) {
|
||||
if (!url) return false;
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
link.download = '';
|
||||
link.style.position = 'fixed';
|
||||
link.style.left = '-9999px';
|
||||
link.style.top = '0';
|
||||
document.body.appendChild(link);
|
||||
try {
|
||||
link.click();
|
||||
} catch (_) {
|
||||
if (typeof window.open === 'function') window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
window.setTimeout(function() {
|
||||
if (link.parentElement) link.parentElement.removeChild(link);
|
||||
}, 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
function recordFileTreeAction(action, detail) {
|
||||
var normalized = String(action || '').trim() || 'unknown';
|
||||
var rowId = String(detail && detail.rowId || '').trim();
|
||||
@@ -3698,6 +3761,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var workspaceId = detail.workspaceId || resolveWorkspaceId(trigger || document.body);
|
||||
var title = detail.title || '无标题';
|
||||
var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
|
||||
if (detail.contextKind === 'filetree' && action === 'download') {
|
||||
downloadSelectedFileTreeAssetRows(detail, trigger);
|
||||
return;
|
||||
}
|
||||
if (isAsset && action === 'new-window') {
|
||||
recordFileTreeAction('new-window', detail);
|
||||
void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
|
||||
@@ -3903,6 +3970,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
menu.setAttribute('data-kind', kind);
|
||||
var isAttachment = kind === 'attachment';
|
||||
var isAsset = kind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
|
||||
var isFileTreeDownload = kind === 'filetree' && detail.downloadable;
|
||||
var items = isAttachment ? [
|
||||
{ action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true },
|
||||
@@ -3926,6 +3994,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
] : isAsset ? [
|
||||
{ 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' },
|
||||
{ action: 'copy-id', icon: 'tag', label: '复制资源 ID' },
|
||||
{ action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true },
|
||||
@@ -3935,6 +4004,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
{ action: 'reveal', icon: 'my_location', label: 'Reveal' }
|
||||
] : kind === 'filetree' ? [
|
||||
{ action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' },
|
||||
{ action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || 0) + ' 个项目' : '下载', disabled: !isFileTreeDownload, title: isFileTreeDownload ? '下载当前本地文件或文件夹' : '当前项目没有可下载的本地路径' },
|
||||
{ separator: true },
|
||||
{ action: 'copy-link', icon: 'link', label: '复制访问链接' },
|
||||
{ action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' },
|
||||
@@ -3987,19 +4057,31 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
function openFileTreeContextMenu(row, x, y, trigger) {
|
||||
if (!(row instanceof HTMLElement)) return;
|
||||
var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '';
|
||||
var selectedDownloadRows = selectedSidebarFileTreeRowsForDownload(row);
|
||||
var selectedAssetRows = selectedDownloadRows.filter(isFileTreeDownloadableAssetRow);
|
||||
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),
|
||||
title: rowTitle(row),
|
||||
workspaceId: resolveWorkspaceId(row)
|
||||
workspaceId: resolveWorkspaceId(row),
|
||||
downloadable: isFileTreeDownloadableRow(row),
|
||||
selectedDownloadCount: selectedDownloadRows.length,
|
||||
selectedDownloadRowIds: selectedDownloadRows.map(function(downloadRow) { return downloadRow.getAttribute('data-row-id') || ''; }).filter(Boolean),
|
||||
selectedAssetCount: selectedAssetRows.length,
|
||||
selectedAssetRowIds: selectedAssetRows.map(function(assetRow) { return assetRow.getAttribute('data-row-id') || ''; }).filter(Boolean)
|
||||
}, x, y, trigger || row);
|
||||
}
|
||||
|
||||
function visibleFileTreeRows() {
|
||||
return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]'))
|
||||
.filter(function(row) { return row instanceof HTMLElement && row.offsetParent !== null; });
|
||||
.filter(function(row) {
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
if (row.closest('.tree-children--collapsed')) return false;
|
||||
return row.offsetParent !== null || row.getClientRects().length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
function syncSidebarFileTreeSelection() {
|
||||
@@ -4116,11 +4198,130 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return '';
|
||||
}
|
||||
|
||||
function decodeLocalEncodedPath(value) {
|
||||
var path = String(value || '').trim().replace(/~2F/g, '/');
|
||||
if (!path) return '';
|
||||
try {
|
||||
return decodeURIComponent(path);
|
||||
} catch (_) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
function fileTreeRowLocalRelativePath(row) {
|
||||
if (!(row instanceof HTMLElement)) return '';
|
||||
var direct = String(row.getAttribute('data-local-relative-path') || '').trim();
|
||||
if (direct) return direct;
|
||||
var assetPath = localFilePathFromAssetId(fileTreeRowAssetId(row));
|
||||
if (assetPath) return assetPath;
|
||||
var rowId = String(row.getAttribute('data-row-id') || '').trim();
|
||||
var nodeId = String(row.getAttribute('data-node-id') || '').trim();
|
||||
var documentId = fileTreeRowDocumentId(row);
|
||||
var kind = fileTreeRowKind(row);
|
||||
if (rowId.indexOf('local:asset:') === 0) return rowId.slice('local:asset:'.length);
|
||||
if (rowId.indexOf('local:markdown:') === 0) return rowId.slice('local:markdown:'.length);
|
||||
if (rowId.indexOf('local:folder:') === 0) return rowId.slice('local:folder:'.length);
|
||||
if (rowId.indexOf('local:node:') === 0) return rowId.slice('local:node:'.length);
|
||||
if (nodeId.indexOf('local:node:') === 0) return nodeId.slice('local:node:'.length);
|
||||
if ((kind === 'document' || kind === 'doc' || kind === 'markdown') && documentId.indexOf('local-md:') === 0) {
|
||||
return decodeLocalEncodedPath(documentId.slice('local-md:'.length));
|
||||
}
|
||||
if ((kind === 'folder' || kind === 'directory' || kind === 'index') && documentId.indexOf('local-dir:') === 0) {
|
||||
return decodeLocalEncodedPath(documentId.slice('local-dir:'.length));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function fileTreeRowKind(row) {
|
||||
if (!(row instanceof HTMLElement)) return '';
|
||||
return String(row.getAttribute('data-row-kind') || '').trim();
|
||||
}
|
||||
|
||||
function isFileTreeDownloadableAssetRow(row) {
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
var kind = fileTreeRowKind(row);
|
||||
if (kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || kind === 'folder' || kind === 'directory') return false;
|
||||
return Boolean(fileTreeRowAssetId(row));
|
||||
}
|
||||
|
||||
function isFileTreeDownloadableRow(row) {
|
||||
if (!(row instanceof HTMLElement)) return false;
|
||||
if (currentSourceKind() !== 'local_folder') return isFileTreeDownloadableAssetRow(row);
|
||||
return Boolean(fileTreeRowLocalRelativePath(row) || isFileTreeDownloadableAssetRow(row));
|
||||
}
|
||||
|
||||
function fileTreeAssetDownloadDetail(row) {
|
||||
if (!isFileTreeDownloadableRow(row)) return null;
|
||||
var title = rowTitle(row);
|
||||
var relativePath = fileTreeRowLocalRelativePath(row);
|
||||
var assetId = fileTreeRowAssetId(row) || (relativePath ? 'local-file:' + relativePath : '');
|
||||
return {
|
||||
contextKind: 'filetree',
|
||||
documentId: fileTreeRowDocumentId(row),
|
||||
rowId: row.getAttribute('data-row-id') || '',
|
||||
rowKind: fileTreeRowKind(row),
|
||||
assetId: assetId,
|
||||
localRelativePath: relativePath,
|
||||
title: title,
|
||||
fileName: title,
|
||||
workspaceId: resolveWorkspaceId(row)
|
||||
};
|
||||
}
|
||||
|
||||
function selectedSidebarFileTreeRowsForDownload(contextRow) {
|
||||
var contextRowId = contextRow instanceof HTMLElement ? contextRow.getAttribute('data-row-id') || '' : '';
|
||||
var selectedRows = selectedSidebarFileTreeRows().filter(isFileTreeDownloadableRow);
|
||||
if (contextRowId && sidebarFileTreeSelection.selectedRowIds.has(contextRowId) && selectedRows.length > 0) return selectedRows;
|
||||
return isFileTreeDownloadableRow(contextRow) ? [contextRow] : selectedRows;
|
||||
}
|
||||
|
||||
function selectedSidebarFileTreeAssetRowsForDownload(contextRow) {
|
||||
return selectedSidebarFileTreeRowsForDownload(contextRow).filter(isFileTreeDownloadableAssetRow);
|
||||
}
|
||||
|
||||
function downloadSelectedFileTreeAssetRows(detail, trigger) {
|
||||
var contextRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
|
||||
if (!(contextRow instanceof HTMLElement) && detail && detail.rowId) {
|
||||
contextRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.rowId) + '"]');
|
||||
}
|
||||
var rows = selectedSidebarFileTreeRowsForDownload(contextRow);
|
||||
var seen = new Set();
|
||||
var downloads = [];
|
||||
rows.forEach(function(row) {
|
||||
var item = fileTreeAssetDownloadDetail(row);
|
||||
var key = item && (item.localRelativePath || item.assetId || item.rowId);
|
||||
if (!item || !key || seen.has(key)) return;
|
||||
seen.add(key);
|
||||
downloads.push(item);
|
||||
});
|
||||
if (downloads.length === 0 && detail && detail.assetId) {
|
||||
downloads.push(Object.assign({}, detail, { fileName: detail.fileName || detail.title || '附件' }));
|
||||
} else if (downloads.length === 0 && detail && detail.localRelativePath) {
|
||||
downloads.push(Object.assign({}, detail, {
|
||||
assetId: detail.assetId || 'local-file:' + detail.localRelativePath,
|
||||
fileName: detail.fileName || detail.title || '附件'
|
||||
}));
|
||||
}
|
||||
if (downloads.length === 0) {
|
||||
recordFileTreeActionStatus('blocked', Object.assign({}, detail || {}, { reason: 'no-downloadable-assets' }));
|
||||
return false;
|
||||
}
|
||||
var primary = Object.assign({}, downloads[0], {
|
||||
count: downloads.length,
|
||||
assetIds: downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean),
|
||||
localRelativePaths: downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean)
|
||||
});
|
||||
recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary);
|
||||
recordFileTreeActionStatus('requested', primary);
|
||||
document.documentElement.setAttribute('data-mnote-filetree-download-count', String(downloads.length));
|
||||
document.documentElement.setAttribute('data-mnote-filetree-download-asset-ids', downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean).join(','));
|
||||
document.documentElement.setAttribute('data-mnote-filetree-download-paths', downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean).join(','));
|
||||
downloads.forEach(function(item) {
|
||||
void openEditorAttachmentDownload(item);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasSelectedDocumentAncestor(row, selectedDocRowIds) {
|
||||
var node = row instanceof HTMLElement ? row.closest('.tree-node') : null;
|
||||
while (node && node.parentElement) {
|
||||
@@ -8010,7 +8211,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (localFilePath) {
|
||||
var localDownloadUrl = buildLocalFileOpenUrl(localFilePath, true);
|
||||
if (localDownloadUrl) {
|
||||
window.open(localDownloadUrl, '_blank', 'noopener,noreferrer');
|
||||
triggerBrowserDownload(localDownloadUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -8028,15 +8229,25 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
var localRelativePath = String(detail.localRelativePath || '').trim();
|
||||
if (localRelativePath) {
|
||||
var localPathDownloadUrl = buildLocalFileOpenUrl(localRelativePath, true);
|
||||
if (localPathDownloadUrl) {
|
||||
triggerBrowserDownload(localPathDownloadUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var target = detail.fileUrl || detail.href;
|
||||
if (!target) return;
|
||||
window.open(target, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
function openEditorAttachmentMenu(link, trigger) {
|
||||
function openEditorAttachmentMenu(link, trigger, point) {
|
||||
var detail = detailFromEditorAttachmentLink(link);
|
||||
var rect = trigger && trigger.getBoundingClientRect ? trigger.getBoundingClientRect() : link.getBoundingClientRect();
|
||||
openTreeContextMenu('attachment', detail, rect.right, rect.bottom + 4, trigger || link);
|
||||
var x = point && typeof point.x === 'number' ? point.x : rect.right;
|
||||
var y = point && typeof point.y === 'number' ? point.y : rect.bottom + 4;
|
||||
openTreeContextMenu('attachment', detail, x, y, trigger || link);
|
||||
}
|
||||
|
||||
function openEditorAttachmentLink(link) {
|
||||
@@ -8485,7 +8696,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
if (fileAction === 'menu') {
|
||||
e.preventDefault();
|
||||
selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false });
|
||||
if (rowId && !sidebarFileTreeSelection.selectedRowIds.has(rowId)) {
|
||||
selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false });
|
||||
}
|
||||
var point = rowCenter(fileBtn || fileRow);
|
||||
dispatchSidebarEvent('tree.filetree.context-menu', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null });
|
||||
openFileTreeContextMenu(fileRow, point.x, point.y, fileBtn || fileRow);
|
||||
@@ -8559,6 +8772,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
});
|
||||
|
||||
document.addEventListener('contextmenu', function(event) {
|
||||
var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row');
|
||||
if (editorAttachmentLink instanceof HTMLAnchorElement) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
enhanceEditorAttachmentLink(editorAttachmentLink);
|
||||
openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });
|
||||
return;
|
||||
}
|
||||
var fileRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]');
|
||||
if (fileRow) {
|
||||
event.preventDefault();
|
||||
@@ -8904,7 +9125,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
targetRowId: targetRow ? targetRow.getAttribute('data-row-id') : null,
|
||||
targetRowKind: targetRow ? targetRow.getAttribute('data-row-kind') : 'root',
|
||||
documentId: targetRow ? targetRow.getAttribute('data-document-id') || targetRow.getAttribute('data-doc-id') : null,
|
||||
assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null
|
||||
assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null,
|
||||
targetRelativePath: targetRow && (targetRow.getAttribute('data-row-kind') === 'folder' || targetRow.getAttribute('data-row-kind') === 'directory')
|
||||
? fileTreeRowLocalRelativePath(targetRow)
|
||||
: null
|
||||
};
|
||||
if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false');
|
||||
activeFileTreeDropRow = null;
|
||||
@@ -9718,6 +9942,21 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function fileTreeMenuTargetParentId"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("if (action === 'new-folder')"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("fileTreeCopyPath(detail, trigger)"));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("if (detail.contextKind === 'filetree' && action === 'download')"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("downloadSelectedFileTreeAssetRows(detail, trigger)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function selectedSidebarFileTreeRowsForDownload"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function selectedSidebarFileTreeAssetRowsForDownload"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-local-relative-path"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("targetRelativePath"));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
"recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary)"
|
||||
));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-filetree-download-count"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function triggerBrowserDownload"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("var localFilePath = localFilePathFromAssetId(assetId);"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("attachmentMetaCache[assetId] = localMeta;"));
|
||||
assert!(SIDEBAR_TREE_JS.contains(
|
||||
"var pageRow = closestAction(e.target, '.tree-row[data-shell-mode=\"page\"]');"
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user