Files
mnote/rust/crates/mnote-web/browser/filetree-dnd-runtime.js
T

175 lines
7.1 KiB
JavaScript

// MNote filetree DND 运行时外置模块。
// 承接 filetree dragstart/dragover/drop/dragend 的业务逻辑,
// 不处理 page tree DND(留在 layout.rs)。
// inline script 保留完整 fallback。
// ─── 常量 ───────────────────────────────────────────────
var FILETREE_DRAG_MIME = 'application/x-mnote-file-tree';
// ─── 状态 ───────────────────────────────────────────────
var draggingFileTreeRowIds = [];
var activeFileTreeDropRow = null;
// ─── 纯 helper ─────────────────────────────────────────
function filetreeDragPayload(rowIds) {
return JSON.stringify({
type: 'mnote-file-tree-dnd',
version: 1,
rowIds: rowIds || draggingFileTreeRowIds,
});
}
function filetreeDropDetail(targetRow, fileTree, deps) {
deps = deps || {};
var resolveWorkspaceId = deps.resolveWorkspaceId || function() { return ''; };
var fileTreeRowLocalUploadTargetRelativePath = deps.fileTreeRowLocalUploadTargetRelativePath || function() { return ''; };
return {
workspaceId: resolveWorkspaceId(targetRow || fileTree),
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,
targetRelativePath: targetRow ? fileTreeRowLocalUploadTargetRelativePath(targetRow) : '',
uploadIntent: 'filetree.folder.drop',
};
}
function filetreeHasFiles(dataTransfer) {
return dataTransfer && Array.prototype.indexOf.call(dataTransfer.types || [], 'Files') >= 0;
}
function filetreeHasInternalDrag(dataTransfer) {
return dataTransfer && Array.prototype.indexOf.call(dataTransfer.types || [], FILETREE_DRAG_MIME) >= 0;
}
function parseFileTreeDragPayload(raw) {
if (!raw) return [];
try {
var parsed = JSON.parse(raw);
if (Array.isArray(parsed.rowIds)) return parsed.rowIds;
} catch (_) {}
return [];
}
function clearFileTreeDropFeedback() {
if (activeFileTreeDropRow instanceof HTMLElement) {
activeFileTreeDropRow.setAttribute('data-drop-target', 'false');
}
activeFileTreeDropRow = null;
}
function resetFileTreeDragState() {
draggingFileTreeRowIds = [];
clearFileTreeDropFeedback();
}
// ─── 文件树 DND 开始 ───────────────────────────────────
function startFileTreeDrag(fileRow, deps) {
deps = deps || {};
var selectedSidebarFileTreeRowIdsForDrag = deps.selectedSidebarFileTreeRowIdsForDrag || function(r) {
return [r.getAttribute('data-row-id') || ''].filter(Boolean);
};
draggingFileTreeRowIds = selectedSidebarFileTreeRowIdsForDrag(fileRow);
return {
payload: filetreeDragPayload(draggingFileTreeRowIds),
effectAllowed: 'copyMove',
};
}
// ─── 文件树 DND 悬浮 ───────────────────────────────────
function handleFileTreeDragOver(event, deps) {
deps = deps || {};
var closestAction = deps.closestAction || function() { return null; };
var fileTree = document.getElementById('sidebar-file-tree-root');
if (!fileTree || !fileTree.contains(event.target)) return false;
var hasFiles = filetreeHasFiles(event.dataTransfer);
var hasInternal = filetreeHasInternalDrag(event.dataTransfer);
if (!hasFiles && !hasInternal && draggingFileTreeRowIds.length === 0) return false;
event.preventDefault();
clearFileTreeDropFeedback();
activeFileTreeDropRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]') || fileTree;
if (activeFileTreeDropRow instanceof HTMLElement) {
activeFileTreeDropRow.setAttribute('data-drop-target', 'true');
}
if (event.dataTransfer) {
var copyModifier = event.altKey || event.ctrlKey || event.metaKey;
event.dataTransfer.dropEffect = hasFiles || copyModifier ? 'copy' : 'move';
}
return true;
}
// ─── 文件树 DND 释放 ───────────────────────────────────
function handleFileTreeDrop(event, deps) {
deps = deps || {};
var closestAction = deps.closestAction || function() { return null; };
var resolveWorkspaceId = deps.resolveWorkspaceId || function() { return ''; };
var fileTreeRowLocalUploadTargetRelativePath = deps.fileTreeRowLocalUploadTargetRelativePath
|| function() { return ''; };
var ensureFileTreeWritableTarget = deps.ensureFileTreeWritableTarget || function() { return Promise.resolve(true); };
var dispatchSidebarEvent = deps.dispatchSidebarEvent || function() {};
var fileTree = document.getElementById('sidebar-file-tree-root');
if (!fileTree || !fileTree.contains(event.target)) return;
var targetRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]');
var files = event.dataTransfer ? Array.from(event.dataTransfer.files || []) : [];
var raw = event.dataTransfer
? event.dataTransfer.getData(FILETREE_DRAG_MIME)
|| event.dataTransfer.getData('application/x-mnote-file-tree')
|| ''
: '';
var rowIds = draggingFileTreeRowIds.slice();
if (raw) {
rowIds = parseFileTreeDragPayload(raw);
}
if (!files.length && !rowIds.length) return;
event.preventDefault();
var detail = filetreeDropDetail(targetRow, fileTree, {
resolveWorkspaceId: resolveWorkspaceId,
fileTreeRowLocalUploadTargetRelativePath: fileTreeRowLocalUploadTargetRelativePath,
});
clearFileTreeDropFeedback();
if (files.length) {
dispatchSidebarEvent('tree.filetree.external-drop', Object.assign({}, detail, { files: files }));
} else {
var copyModifier = event.altKey === true || event.ctrlKey === true || event.metaKey === true;
void Promise.resolve(ensureFileTreeWritableTarget('drop', targetRow, rowIds, copyModifier))
.then(function(writable) {
if (!writable) return;
dispatchSidebarEvent('tree.filetree.internal-drop', Object.assign({}, detail, {
rowIds: rowIds,
copy: copyModifier,
}));
});
}
draggingFileTreeRowIds = [];
}
// ─── 导出 ───────────────────────────────────────────────
window.__mnoteFileTreeDndRuntime = {
FILETREE_DRAG_MIME: FILETREE_DRAG_MIME,
draggingFileTreeRowIds: draggingFileTreeRowIds,
activeFileTreeDropRow: activeFileTreeDropRow,
filetreeDragPayload: filetreeDragPayload,
filetreeDropDetail: filetreeDropDetail,
filetreeHasFiles: filetreeHasFiles,
filetreeHasInternalDrag: filetreeHasInternalDrag,
parseFileTreeDragPayload: parseFileTreeDragPayload,
clearFileTreeDropFeedback: clearFileTreeDropFeedback,
resetFileTreeDragState: resetFileTreeDragState,
startFileTreeDrag: startFileTreeDrag,
handleFileTreeDragOver: handleFileTreeDragOver,
handleFileTreeDrop: handleFileTreeDrop,
};