- scope local-folder PageTree revision and document sidebar rendering to fileTreeScope - preserve projected table/image attrs for local Markdown aggregate fallback - avoid FileTree restore forced layouts on cold design open - add API ChatOnly provider runtime and local OCR task handling regressions
112 lines
4.5 KiB
JavaScript
112 lines
4.5 KiB
JavaScript
// 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
|
|
};
|