// MNote 主壳文件树选择运行时外置模块。 // 当前只承接 selection 状态同步和选择算法;事件监听、DND 执行和命令分发仍留在主壳。 function cssEscapeValue(value) { if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(value); return String(value).replace(/["\\]/g, '\\$&'); } function visibleFileTreeRows(deps) { if (deps && typeof deps.visibleFileTreeRows === 'function') { return deps.visibleFileTreeRows(); } return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')) .filter(function(row) { if (!(row instanceof HTMLElement)) return false; // 文件树展开恢复会在短时间内多次同步选择状态。这里不能读取 // offsetParent/getClientRects,否则会和 DOM 写入交错触发强制布局。 return !row.closest('.tree-children--collapsed'); }); } function selectedRowIdSet(selection) { var selected = selection && selection.selectedRowIds; if (selected instanceof Set) return new Set(selected); if (Array.isArray(selected)) return new Set(selected); return new Set(); } function syncSidebarFileTreeSelection(selection, deps) { var state = selection || {}; var selected = selectedRowIdSet(state); var focusedRowId = String(state.focusedRowId || ''); visibleFileTreeRows(deps).forEach(function(row) { var rowId = row.getAttribute('data-row-id') || ''; row.setAttribute('data-selected', String(Boolean(rowId && selected.has(rowId)))); row.setAttribute('data-focused', String(rowId === focusedRowId)); }); var detail = { selectedRowIds: Array.from(selected), anchorRowId: state.anchorRowId || null, focusedRowId: state.focusedRowId || null }; window.dispatchEvent(new CustomEvent('tree.filetree.selection.changed', { detail: detail })); return detail; } function selectSidebarFileTreeRow(row, selection, modifiers, deps) { if (!(row instanceof HTMLElement)) return []; var rowId = row.getAttribute('data-row-id') || ''; if (!rowId) return []; var state = selection || {}; var rows = visibleFileTreeRows(deps); var visibleRowIds = rows.map(function(item) { return item.getAttribute('data-row-id') || ''; }).filter(Boolean); var selected = selectedRowIdSet(state); var shiftKey = Boolean(modifiers && modifiers.shiftKey); var ctrlKey = Boolean(modifiers && (modifiers.ctrlKey || modifiers.metaKey)); if (shiftKey && state.anchorRowId) { var anchorIndex = visibleRowIds.indexOf(state.anchorRowId); var targetIndex = visibleRowIds.indexOf(rowId); if (anchorIndex >= 0 && targetIndex >= 0) { selected = new Set(visibleRowIds.slice(Math.min(anchorIndex, targetIndex), Math.max(anchorIndex, targetIndex) + 1)); } else { selected = new Set([rowId]); state.anchorRowId = rowId; } } else if (ctrlKey) { if (selected.has(rowId) && selected.size > 1) selected.delete(rowId); else selected.add(rowId); state.anchorRowId = rowId; } else { selected = new Set([rowId]); state.anchorRowId = rowId; } state.selectedRowIds = selected; state.focusedRowId = rowId; syncSidebarFileTreeSelection(state, deps); return Array.from(selected); } function selectedSidebarFileTreeRowIdsForDrag(row, selection) { var rowId = row instanceof HTMLElement ? row.getAttribute('data-row-id') || '' : ''; var selected = selectedRowIdSet(selection || {}); if (rowId && selected.has(rowId)) return Array.from(selected); return rowId ? [rowId] : []; } function selectedSidebarFileTreeRows(selection, deps) { var state = selection || {}; var selectedIds = selectedRowIdSet(state); var rows = visibleFileTreeRows(deps).filter(function(row) { var rowId = row.getAttribute('data-row-id') || ''; return rowId && selectedIds.has(rowId); }); if (rows.length > 0) return rows; if (state.focusedRowId) { var escape = deps && typeof deps.cssEscape === 'function' ? deps.cssEscape : cssEscapeValue; var focused = document.querySelector( '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + escape(state.focusedRowId) + '"]' ); if (focused instanceof HTMLElement) return [focused]; } return []; } window.__mnoteFileTreeSelectionRuntime = { visibleFileTreeRows: visibleFileTreeRows, syncSidebarFileTreeSelection: syncSidebarFileTreeSelection, selectSidebarFileTreeRow: selectSidebarFileTreeRow, selectedSidebarFileTreeRowIdsForDrag: selectedSidebarFileTreeRowIdsForDrag, selectedSidebarFileTreeRows: selectedSidebarFileTreeRows };