refactor: split sidebar filetree upload runtime
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
export const createSidebarFileTreeUploadRuntime = (dependencies = {}) => {
|
||||
const {
|
||||
cssEscape,
|
||||
currentDocumentId,
|
||||
fileTreeRuntimeDeps,
|
||||
fileTreeRuntimeFunction,
|
||||
recordFileTreeAction,
|
||||
recordFileTreeActionStatus,
|
||||
resolveWorkspaceId,
|
||||
rowTitle,
|
||||
selectedSidebarFileTreeSelection,
|
||||
} = dependencies;
|
||||
const sidebarFileTreeSelection = selectedSidebarFileTreeSelection;
|
||||
|
||||
function fileTreeRowsForUploadPreflight() {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeRowsForUploadPreflight');
|
||||
if (runtimeFn) return runtimeFn(fileTreeRuntimeDeps());
|
||||
return Array.from(document.querySelectorAll('.tree-row[data-shell-mode="filetree"]')).map(function(row) {
|
||||
return {
|
||||
rowId: row.getAttribute('data-row-id') || '',
|
||||
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,
|
||||
assetDocumentId: row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || null,
|
||||
assetType: row.querySelector('.tree-kind-badge') ? row.querySelector('.tree-kind-badge').getAttribute('data-kind') : null,
|
||||
storagePath: null
|
||||
};
|
||||
}).filter(function(row) {
|
||||
return row.rowId || row.documentId || row.assetId;
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeDocumentParentsForPreflight');
|
||||
if (runtimeFn) return runtimeFn(fileTreeRuntimeDeps());
|
||||
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 runtimeFn = fileTreeRuntimeFunction('fileTreeTargetChildrenForPreflight');
|
||||
if (runtimeFn) return runtimeFn(targetRow, fileTreeRuntimeDeps());
|
||||
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() {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeDropPreflightRows');
|
||||
if (runtimeFn) return runtimeFn(Object.assign({}, fileTreeRuntimeDeps(), {
|
||||
fileTreeRowsForUploadPreflight: fileTreeRowsForUploadPreflight
|
||||
}));
|
||||
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 runtimeFn = fileTreeRuntimeFunction('fileTreeDocumentWorkspacesForUploadPreflight');
|
||||
if (runtimeFn) {
|
||||
return runtimeFn(workspaceId, Object.assign({}, fileTreeRuntimeDeps(), {
|
||||
fileTreeRowsForUploadPreflight: fileTreeRowsForUploadPreflight
|
||||
}));
|
||||
}
|
||||
var seen = new Set();
|
||||
return fileTreeRowsForUploadPreflight().filter(function(row) {
|
||||
if (!row.documentId || seen.has(row.documentId)) return false;
|
||||
seen.add(row.documentId);
|
||||
return true;
|
||||
}).map(function(row) {
|
||||
return { documentId: row.documentId, workspaceId: workspaceId || null };
|
||||
});
|
||||
}
|
||||
|
||||
async function preflightFileTreeUploadTarget(detail) {
|
||||
var bodyRuntime = fileTreeRuntimeFunction('fileTreeUploadTargetPreflightBody');
|
||||
var body = bodyRuntime ? bodyRuntime(detail || {}, Object.assign({}, fileTreeRuntimeDeps(), {
|
||||
focusedRowId: sidebarFileTreeSelection.focusedRowId || null,
|
||||
fileTreeRowsForUploadPreflight: fileTreeRowsForUploadPreflight,
|
||||
currentDocumentId: currentDocumentId,
|
||||
resolveWorkspaceId: resolveWorkspaceId
|
||||
})) : (function() {
|
||||
var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim();
|
||||
return {
|
||||
workspaceId: workspaceId || null,
|
||||
targetDocumentId: detail && detail.documentId ? String(detail.documentId) : null,
|
||||
targetRowId: detail && detail.targetRowId ? String(detail.targetRowId) : null,
|
||||
focusedRowId: sidebarFileTreeSelection.focusedRowId || null,
|
||||
activeDocumentId: currentDocumentId() || null,
|
||||
rows: fileTreeRowsForUploadPreflight(),
|
||||
documentWorkspaces: fileTreeDocumentWorkspacesForUploadPreflight(workspaceId)
|
||||
};
|
||||
})();
|
||||
var response = await fetch('/api/tree/filetree/upload-target-preflight', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
if (!response.ok || !payload || !payload.plan) {
|
||||
throw new Error(payload && payload.error ? payload.error : '文件树上传目标预检失败');
|
||||
}
|
||||
return payload.plan;
|
||||
}
|
||||
|
||||
function fallbackFileTreeUploadTarget(detail) {
|
||||
var runtimeFn = fileTreeRuntimeFunction('fileTreeUploadTargetFallback');
|
||||
if (runtimeFn) {
|
||||
return runtimeFn(detail || {}, {
|
||||
resolveWorkspaceId: resolveWorkspaceId,
|
||||
currentDocumentId: currentDocumentId
|
||||
});
|
||||
}
|
||||
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 || ''),
|
||||
uploadIntent: String(detail.uploadIntent || 'filetree.folder.drop')
|
||||
};
|
||||
}
|
||||
if (!workspaceId || !documentId) {
|
||||
throw new Error('请选择一个目标页面后再拖入文件');
|
||||
}
|
||||
return {
|
||||
workspaceId: workspaceId,
|
||||
targetDocumentId: documentId,
|
||||
targetMindmapId: null,
|
||||
targetSubPath: null,
|
||||
uploadIntent: String(detail && detail.uploadIntent || 'editor.markdown.attach')
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveFileTreeUploadTarget(detail) {
|
||||
if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
return fallbackFileTreeUploadTarget(detail || {});
|
||||
}
|
||||
try {
|
||||
var plan = await preflightFileTreeUploadTarget(detail || {});
|
||||
var normalizePlanRuntime = fileTreeRuntimeFunction('normalizeFileTreeUploadTargetPlan');
|
||||
if (normalizePlanRuntime) {
|
||||
return normalizePlanRuntime(detail || {}, plan);
|
||||
}
|
||||
if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
plan.targetRelativePath = String(detail.targetRelativePath || '');
|
||||
}
|
||||
if (detail && detail.uploadIntent) {
|
||||
plan.uploadIntent = String(detail.uploadIntent);
|
||||
} else if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) {
|
||||
plan.uploadIntent = 'filetree.folder.drop';
|
||||
} else if (!plan.uploadIntent) {
|
||||
plan.uploadIntent = 'editor.markdown.attach';
|
||||
}
|
||||
return plan;
|
||||
} catch (error) {
|
||||
console.warn('[mnote upload] upload target preflight fallback', error);
|
||||
return fallbackFileTreeUploadTarget(detail || {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
fileTreeRowsForUploadPreflight,
|
||||
fileTreeRowCapabilities,
|
||||
fileTreeRowIsReadonly,
|
||||
blockReadonlyFileTreeAction,
|
||||
fileTreeDocumentParentsForPreflight,
|
||||
fileTreeTargetChildrenForPreflight,
|
||||
fileTreeDropPreflightRows,
|
||||
ensureFileTreeWritableTarget,
|
||||
fileTreeDocumentWorkspacesForUploadPreflight,
|
||||
preflightFileTreeUploadTarget,
|
||||
fallbackFileTreeUploadTarget,
|
||||
resolveFileTreeUploadTarget,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user