文件
mynode/packages/server/public/js/items/folderTree.js
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

289 行
10 KiB
JavaScript

(function() {
'use strict';
function initTree() {
const container = document.getElementById('folder-tree');
if (!container) return;
if (!window.mar10 || !window.mar10.Wunderbaum) return;
const dataEl = document.getElementById('folder-tree-data');
if (!dataEl) return;
let treeData;
try {
treeData = JSON.parse(dataEl.content.textContent || '{}');
} catch (error) {
return;
}
const collapsedNotebooksStorageKey = `folder-tree-collapsed:${window.location.pathname}`;
const loadCollapsedNotebookKeys = function() {
try {
const keys = JSON.parse(window.sessionStorage.getItem(collapsedNotebooksStorageKey) || '[]');
return new Set(Array.isArray(keys) ? keys.filter(key => typeof key === 'string') : []);
} catch (error) {
return new Set();
}
};
const collapsedNotebookKeys = loadCollapsedNotebookKeys();
const saveCollapsedNotebookKeys = function() {
try {
if (collapsedNotebookKeys.size) {
window.sessionStorage.setItem(collapsedNotebooksStorageKey, JSON.stringify([...collapsedNotebookKeys]));
} else {
window.sessionStorage.removeItem(collapsedNotebooksStorageKey);
}
} catch (error) {
// Ignore unavailable browser storage.
}
};
const restoreCollapsedNotebooks = function(nodes) {
for (const node of nodes) {
if (node.folder && collapsedNotebookKeys.has(node.key)) node.expanded = false;
if (node.children) restoreCollapsedNotebooks(node.children);
}
};
restoreCollapsedNotebooks(treeData.source || []);
const scrollStorageKey = `folder-tree-scroll:${window.location.pathname}`;
const pointerPositionStorageKey = 'folder-tree-pointer-position';
const syntheticHoverClassName = 'is-pointer-hover';
const treeRowSelector = '.wb-row[data-tree-item-type]';
const accessSessionStorage = function(callback) {
try {
return callback(window.sessionStorage);
} catch (error) {
return null;
}
};
let hoverPosition = accessSessionStorage(storage => JSON.parse(storage.getItem(pointerPositionStorageKey) || 'null'));
const applySyntheticHoverAt = function(x, y) {
if (typeof x !== 'number' || typeof y !== 'number') return;
const target = document.elementFromPoint(x, y);
const row = target ? target.closest(treeRowSelector) : null;
const previousRow = container.querySelector(`.${syntheticHoverClassName}`);
if (previousRow && previousRow !== row) previousRow.classList.remove(syntheticHoverClassName);
if (row && container.contains(row)) row.classList.add(syntheticHoverClassName);
};
const savePointerPosition = function(event, nodeId) {
if (!event || typeof event.clientX !== 'number' || typeof event.clientY !== 'number') return;
const row = nodeId || !event.target.closest ? null : event.target.closest(treeRowSelector);
const treeItem = row ? row.querySelector('[role="treeitem"]') : null;
hoverPosition = {
x: event.clientX,
y: event.clientY,
id: nodeId || (treeItem ? treeItem.id : null),
};
accessSessionStorage(storage => storage.setItem(pointerPositionStorageKey, JSON.stringify(hoverPosition)));
};
const navigate = function(node, event) {
if (!node || !node.data || !node.data.url) return;
savePointerPosition(event, rowId(node));
accessSessionStorage(storage => storage.setItem(scrollStorageKey, String(container.scrollTop)));
window.location.href = node.data.url;
};
const rowId = function(node) {
return `folder-tree-item-${node.key}`;
};
let selectedTreeItem = null;
const setSelectedTreeItem = function(treeItem) {
if (selectedTreeItem && selectedTreeItem !== treeItem) {
selectedTreeItem.setAttribute('aria-selected', 'false');
}
selectedTreeItem = treeItem;
if (selectedTreeItem) selectedTreeItem.setAttribute('aria-selected', 'true');
};
const setActiveTreeItem = function(node, moveFocus) {
if (!node) return;
const nodeElement = document.getElementById(rowId(node));
if (!nodeElement) return;
container.setAttribute('aria-activedescendant', nodeElement.id);
setSelectedTreeItem(nodeElement);
if (moveFocus) node.tree.setFocus();
};
container.setAttribute('role', 'tree');
container.setAttribute('aria-label', 'Notebook navigation');
const liveRegion = document.createElement('div');
liveRegion.className = 'sr-only';
liveRegion.setAttribute('role', 'status');
liveRegion.setAttribute('aria-atomic', 'true');
container.after(liveRegion);
const updateTreeItemLabel = function(node, treeItem) {
const siblings = node.parent ? node.parent.children || [] : [];
const position = siblings.indexOf(node) + 1;
const stateText = node.isExpandable() ? `, ${node.isExpanded() ? 'expanded' : 'collapsed'}` : '';
treeItem.setAttribute('aria-label', `${node.title}${stateText}, ${position} of ${siblings.length}`);
};
const updateExpandedState = function(node, nodeElem) {
const treeItem = nodeElem || document.getElementById(rowId(node));
if (!treeItem) return;
treeItem.removeAttribute('aria-expanded');
updateTreeItemLabel(node, treeItem);
};
const announceExpandedState = function(node) {
liveRegion.textContent = `${node.title} ${node.isExpanded() ? 'expanded' : 'collapsed'}`;
};
const renderTreeItem = function(e) {
const row = e.nodeElem ? e.nodeElem.closest('.wb-row') : null;
if (!row) return;
const siblings = e.node.parent ? e.node.parent.children || [] : [];
const position = siblings.indexOf(e.node) + 1;
row.setAttribute('role', 'presentation');
row.dataset.treeItemType = e.node.data.url ? 'note' : 'folder';
e.nodeElem.id = rowId(e.node);
const wasRowHovered = Boolean(hoverPosition && hoverPosition.id === e.nodeElem.id);
row.classList.toggle(syntheticHoverClassName, wasRowHovered);
const title = e.nodeElem.querySelector('.wb-title');
if (title && title.scrollWidth > title.clientWidth) {
title.setAttribute('title', e.node.title);
} else if (title) {
title.removeAttribute('title');
}
e.nodeElem.setAttribute('role', 'treeitem');
if (e.node.hasFocus() || e.node.isActive() || container.getAttribute('aria-activedescendant') === e.nodeElem.id) {
setSelectedTreeItem(e.nodeElem);
} else {
e.nodeElem.setAttribute('aria-selected', 'false');
}
// Added to fix screen reader on Safari
e.nodeElem.setAttribute('aria-roledescription', 'outline row');
e.nodeElem.setAttribute('aria-level', e.node.getLevel());
e.nodeElem.setAttribute('aria-posinset', position);
e.nodeElem.setAttribute('aria-setsize', siblings.length);
updateExpandedState(e.node, e.nodeElem);
if (e.node.hasFocus() || e.node.isActive()) {
setActiveTreeItem(e.node, false);
}
};
new window.mar10.Wunderbaum({
element: container,
adjustHeight: false,
header: false,
rowHeightPx: 40,
source: treeData.source || [],
navigationModeOption: 'row',
emptyChildListExpandable: true,
icon: false,
iconMap: {
expanderExpanded: 'fas fa-chevron-down',
expanderCollapsed: 'fas fa-chevron-right',
expanderLazy: 'fas fa-chevron-right',
doc: 'fas fa-sticky-note',
folder: 'fas fa-folder',
folderOpen: 'fas fa-folder-open',
folderLazy: 'fas fa-folder',
},
init: function(e) {
if (treeData.activeKey) {
const active = e.tree.findKey(treeData.activeKey);
if (active) {
e.tree._setActiveNode(active);
active.update();
setActiveTreeItem(active, false);
}
}
requestAnimationFrame(() => {
container.scrollTop = Number(accessSessionStorage(storage => storage.getItem(scrollStorageKey))) || 0;
requestAnimationFrame(() => {
if (hoverPosition) applySyntheticHoverAt(hoverPosition.x, hoverPosition.y);
container.classList.remove('is-initializing');
});
});
},
activate: function(e) {
setActiveTreeItem(e.node, true);
},
render: function(e) {
renderTreeItem(e);
},
expand: function(e) {
if (e.flag) {
collapsedNotebookKeys.delete(e.node.key);
} else {
collapsedNotebookKeys.add(e.node.key);
}
saveCollapsedNotebookKeys();
updateExpandedState(e.node);
setActiveTreeItem(e.node, container.contains(document.activeElement));
announceExpandedState(e.node);
},
click: function(e) {
if (e.node && e.node.data.folder) {
setActiveTreeItem(e.node, true);
e.node.setExpanded(!e.node.isExpanded());
return false;
} else if (e.node) {
if (e.event && e.event.preventDefault) e.event.preventDefault();
navigate(e.node, e.event);
return false;
}
},
dblclick: function() {
return false;
},
keydown: function(e) {
if (e.event && e.event.key === 'Enter' && e.node && !e.node.folder) {
e.event.preventDefault();
navigate(e.node);
return false;
}
},
});
const updateSyntheticHover = function(event) {
savePointerPosition(event);
const pointerLeftTree = event.type === 'pointerleave';
const pointerX = pointerLeftTree ? -1 : event.clientX;
const pointerY = pointerLeftTree ? -1 : event.clientY;
applySyntheticHoverAt(pointerX, pointerY);
};
container.addEventListener('pointermove', updateSyntheticHover);
container.addEventListener('pointerleave', updateSyntheticHover);
const listContainer = container.querySelector('.wb-list-container');
const nodeList = container.querySelector('.wb-node-list');
if (listContainer) listContainer.setAttribute('role', 'presentation');
if (nodeList) nodeList.setAttribute('role', 'presentation');
}
function initSidebarToggle() {
const toggle = document.querySelector('.folder-tree-toggle');
const sidebar = document.getElementById('folder-tree-sidebar');
if (!toggle || !sidebar) return;
toggle.addEventListener('click', () => {
const isOpen = sidebar.classList.toggle('is-open');
toggle.classList.toggle('is-active', isOpen);
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
const overlay = document.querySelector('.folder-tree-overlay');
if (overlay) overlay.addEventListener('click', () => toggle.click());
}
const initFolderTree = function() {
initTree();
initSidebarToggle();
};
onDocumentReady(initFolderTree);
})();