refactoring of the complex note tree entitiesReloadedEvent

This commit is contained in:
zadam 2023-11-03 08:58:57 +01:00
parent f37f47ce5b
commit 76f874ef6d
3 changed files with 122 additions and 97 deletions

View File

@ -228,7 +228,7 @@ class FNote {
return 1; return 1;
} }
return 0; return aNoteId < bNoteId ? -1 : 1;
}); });
} }

View File

@ -132,29 +132,29 @@ function getParentProtectedStatus(node) {
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
} }
function getNoteIdFromUrl(url) { function getNoteIdFromUrl(urlOrNotePath) {
if (!url) { if (!urlOrNotePath) {
return null; return null;
} }
const [notePath] = url.split("?"); const [notePath] = urlOrNotePath.split("?");
const segments = notePath.split("/"); const segments = notePath.split("/");
return segments[segments.length - 1]; return segments[segments.length - 1];
} }
async function getBranchIdFromUrl(url) { async function getBranchIdFromUrl(urlOrNotePath) {
const {noteId, parentNoteId} = getNoteIdAndParentIdFromUrl(url); const {noteId, parentNoteId} = getNoteIdAndParentIdFromUrl(urlOrNotePath);
return await froca.getBranchId(parentNoteId, noteId); return await froca.getBranchId(parentNoteId, noteId);
} }
function getNoteIdAndParentIdFromUrl(url) { function getNoteIdAndParentIdFromUrl(urlOrNotePath) {
if (!url) { if (!urlOrNotePath) {
return {}; return {};
} }
const [notePath] = url.split("?"); const [notePath] = urlOrNotePath.split("?");
if (notePath === 'root') { if (notePath === 'root') {
return { return {

View File

@ -1093,70 +1093,81 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return; return;
} }
const activeNode = this.getActiveNode(); const nodeCtx = this.#getActiveNodeCtx();
const activeNodeFocused = activeNode && activeNode.hasFocus();
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
let activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; const refreshCtx = {
let activeNoteId = activeNode ? activeNode.data.noteId : null; noteIdsToUpdate: new Set(),
noteIdsToReload: new Set()
};
const noteIdsToUpdate = new Set(); this.#processAttributeRows(loadResults.getAttributeRows(), refreshCtx);
const noteIdsToReload = new Set();
for (const ecAttr of loadResults.getAttributeRows()) { const { movedActiveNode, parentsOfAddedNodes } = await this.#processBranchRows(loadResults.getBranchRows(), refreshCtx);
for (const noteId of loadResults.getNoteIds()) {
refreshCtx.noteIdsToUpdate.add(noteId);
}
await this.#executeTreeUpdates(refreshCtx, loadResults);
await this.#setActiveNode(nodeCtx, movedActiveNode, parentsOfAddedNodes);
if (refreshCtx.noteIdsToReload.size > 0 || refreshCtx.noteIdsToUpdate.size > 0) {
// workaround for https://github.com/mar10/fancytree/issues/1054
this.filterHoistedBranch();
}
}
#processAttributeRows(attributeRows, refreshCtx) {
for (const attrRow of attributeRows) {
const dirtyingLabels = ['iconClass', 'cssClass', 'workspace', 'workspaceIconClass', 'color']; const dirtyingLabels = ['iconClass', 'cssClass', 'workspace', 'workspaceIconClass', 'color'];
if (ecAttr.type === 'label' && dirtyingLabels.includes(ecAttr.name)) { if (attrRow.type === 'label' && dirtyingLabels.includes(attrRow.name)) {
if (ecAttr.isInheritable) { if (attrRow.isInheritable) {
noteIdsToReload.add(ecAttr.noteId); refreshCtx.noteIdsToReload.add(attrRow.noteId);
} else {
refreshCtx.noteIdsToUpdate.add(attrRow.noteId);
} }
else { } else if (attrRow.type === 'label' && attrRow.name === 'archived') {
noteIdsToUpdate.add(ecAttr.noteId); const note = froca.getNoteFromCache(attrRow.noteId);
}
}
else if (ecAttr.type === 'label' && ecAttr.name === 'archived') {
const note = froca.getNoteFromCache(ecAttr.noteId);
if (note) { if (note) {
// change of archived status can mean the note should not be displayed in the tree at all // change of archived status can mean the note should not be displayed in the tree at all
// depending on the value of this.hideArchivedNotes // depending on the value of this.hideArchivedNotes
for (const parentNote of note.getParentNotes()) { for (const parentNote of note.getParentNotes()) {
noteIdsToReload.add(parentNote.noteId); refreshCtx.noteIdsToReload.add(parentNote.noteId);
} }
} }
} } else if (attrRow.type === 'relation' && (attrRow.name === 'template' || attrRow.name === 'inherit')) {
else if (ecAttr.type === 'relation' && (ecAttr.name === 'template' || ecAttr.name === 'inherit')) {
// missing handling of things inherited from template // missing handling of things inherited from template
noteIdsToReload.add(ecAttr.noteId); refreshCtx.noteIdsToReload.add(attrRow.noteId);
} } else if (attrRow.type === 'relation' && attrRow.name === 'imageLink') {
else if (ecAttr.type === 'relation' && ecAttr.name === 'imageLink') { const note = froca.getNoteFromCache(attrRow.noteId);
const note = froca.getNoteFromCache(ecAttr.noteId);
if (note && note.getChildNoteIds().includes(ecAttr.value)) { if (note && note.getChildNoteIds().includes(attrRow.value)) {
// there's a new /deleted imageLink between note and its image child - which can show/hide // there's a new /deleted imageLink between note and its image child - which can show/hide
// the image (if there is an imageLink relation between parent and child, // the image (if there is an imageLink relation between parent and child,
// then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree) // then it is assumed to be "contained" in the note and thus does not have to be displayed in the tree)
noteIdsToReload.add(ecAttr.noteId); refreshCtx.noteIdsToReload.add(attrRow.noteId);
} }
} }
} }
}
async #processBranchRows(branchRows, refreshCtx) {
const allBranchesDeleted = branchRows.every(branchRow => !!branchRow.isDeleted);
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded. // activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
let movedActiveNode = null; let movedActiveNode = null;
let parentsOfAddedNodes = []; let parentsOfAddedNodes = [];
const allBranchRows = loadResults.getBranchRows(); for (const branchRow of branchRows) {
const allBranchesDeleted = allBranchRows.every(branchRow => !!branchRow.isDeleted);
for (const branchRow of allBranchRows) {
if (branchRow.parentNoteId === '_share') { if (branchRow.parentNoteId === '_share') {
// all shared notes have a sign in the tree, even the descendants of shared notes // all shared notes have a sign in the tree, even the descendants of shared notes
noteIdsToReload.add(branchRow.noteId); refreshCtx.noteIdsToReload.add(branchRow.noteId);
} } else {
else {
// adding noteId itself to update all potential clones // adding noteId itself to update all potential clones
noteIdsToUpdate.add(branchRow.noteId); refreshCtx.noteIdsToUpdate.add(branchRow.noteId);
} }
if (branchRow.isDeleted) { if (branchRow.isDeleted) {
@ -1179,7 +1190,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
node.remove(); node.remove();
} }
noteIdsToUpdate.add(branchRow.parentNoteId); refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
} }
} else { } else {
for (const parentNode of this.getNodesByNoteId(branchRow.parentNoteId)) { for (const parentNode of this.getNodesByNoteId(branchRow.parentNoteId)) {
@ -1195,7 +1206,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (foundNode) { if (foundNode) {
// the branch already exists in the tree // the branch already exists in the tree
if (branchRow.isExpanded !== foundNode.isExpanded()) { if (branchRow.isExpanded !== foundNode.isExpanded()) {
noteIdsToReload.add(frocaBranch.noteId); refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
} }
} else { } else {
// make sure it's loaded // make sure it's loaded
@ -1203,28 +1214,31 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
parentNode.addChildren([this.prepareNode(frocaBranch, true)]); parentNode.addChildren([this.prepareNode(frocaBranch, true)]);
if (frocaBranch.isExpanded && note.hasChildren()) { if (frocaBranch.isExpanded && note.hasChildren()) {
noteIdsToReload.add(frocaBranch.noteId); refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
} }
this.sortChildren(parentNode); this.sortChildren(parentNode);
// this might be a first child which would force an icon change // this might be a first child which would force an icon change
noteIdsToUpdate.add(branchRow.parentNoteId); refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
} }
} }
} }
} }
for (const noteId of loadResults.getNoteIds()) { return {
noteIdsToUpdate.add(noteId); movedActiveNode,
} parentsOfAddedNodes
};
}
async #executeTreeUpdates(refreshCtx, loadResults) {
await this.batchUpdate(async () => { await this.batchUpdate(async () => {
for (const noteId of noteIdsToReload) { for (const noteId of refreshCtx.noteIdsToReload) {
for (const node of this.getNodesByNoteId(noteId)) { for (const node of this.getNodesByNoteId(noteId)) {
await node.load(true); await node.load(true);
noteIdsToUpdate.add(noteId); refreshCtx.noteIdsToUpdate.add(noteId);
} }
} }
@ -1238,68 +1252,79 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}); });
// for some reason, node update cannot be in the batchUpdate() block (node is not re-rendered) // for some reason, node update cannot be in the batchUpdate() block (node is not re-rendered)
for (const noteId of noteIdsToUpdate) { for (const noteId of refreshCtx.noteIdsToUpdate) {
for (const node of this.getNodesByNoteId(noteId)) { for (const node of this.getNodesByNoteId(noteId)) {
await this.updateNode(node); await this.updateNode(node);
} }
} }
}
#getActiveNodeCtx() {
const nodeCtx = {
activeNotePath: null,
activeNodeFocused: null,
nextNotePath: null
};
const activeNode = this.getActiveNode();
nodeCtx.activeNodeFocused = activeNode?.hasFocus();
nodeCtx.activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
nodeCtx.nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
return nodeCtx;
}
async #setActiveNode(nodeCtx, movedActiveNode, parentsOfAddedNodes) {
if (movedActiveNode) { if (movedActiveNode) {
for (const parentNode of parentsOfAddedNodes) { for (const parentNode of parentsOfAddedNodes) {
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId); const foundNode = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId);
if (found) { if (foundNode) {
activeNotePath = treeService.getNotePath(found); nodeCtx.activeNotePath = treeService.getNotePath(foundNode);
activeNoteId = found.data.noteId; break;
break
} }
} }
} }
if (activeNotePath) { if (!nodeCtx.activeNotePath) {
let node = await this.expandToNote(activeNotePath, false); return;
}
if (node && node.data.noteId !== activeNoteId) { let node = await this.expandToNote(nodeCtx.activeNotePath, false);
// if the active note has been moved elsewhere then it won't be found by the path, if (node && node.data.noteId !== treeService.getNoteIdFromUrl(nodeCtx.activeNotePath)) {
// so we switch to the alternative of trying to find it by noteId // if the active note has been moved elsewhere then it won't be found by the path,
const notesById = this.getNodesByNoteId(activeNoteId); // so we switch to the alternative of trying to find it by noteId
const notesById = this.getNodesByNoteId(treeService.getNoteIdFromUrl(nodeCtx.activeNotePath));
// if there are multiple clones, then we'd rather not activate anyone // if there are multiple clones, then we'd rather not activate anyone
node = notesById.length === 1 ? notesById[0] : null; node = notesById.length === 1 ? notesById[0] : null;
}
if (node) {
if (nodeCtx.activeNodeFocused) {
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
this.tree.$container.focus();
} }
await node.setActive(true, {noEvents: true, noFocus: !nodeCtx.activeNodeFocused});
} else {
// this is used when the original note has been deleted, and we want to move the focus to the note above/below
node = await this.expandToNote(nodeCtx.nextNotePath, false);
if (node) { if (node) {
if (activeNodeFocused) { // FIXME: this is conceptually wrong
// needed by Firefox: https://github.com/zadam/trilium/issues/1865 // here note tree is responsible for updating global state of the application
this.tree.$container.focus(); // this should be done by NoteContext / TabManager and note tree should only listen to
} // changes in active note and just set the "active" state
// We don't await since that can bring up infinite cycles when e.g. custom widget does some backend requests which wait for max sync ID processed
appContext.tabManager.getActiveContext().setNote(nodeCtx.nextNotePath).then(() => {
const newActiveNode = this.getActiveNode();
await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); // return focus if the previously active node was also focused
if (newActiveNode && nodeCtx.activeNodeFocused) {
newActiveNode.setFocus(true);
}
});
} }
else {
// this is used when the original note has been deleted, and we want to move the focus to the note above/below
node = await this.expandToNote(nextNotePath, false);
if (node) {
// FIXME: this is conceptually wrong
// here note tree is responsible for updating global state of the application
// this should be done by NoteContext / TabManager and note tree should only listen to
// changes in active note and just set the "active" state
// We don't await since that can bring up infinite cycles when e.g. custom widget does some backend requests which wait for max sync ID processed
appContext.tabManager.getActiveContext().setNote(nextNotePath).then(() => {
const newActiveNode = this.getActiveNode();
// return focus if the previously active node was also focused
if (newActiveNode && activeNodeFocused) {
newActiveNode.setFocus(true);
}
});
}
}
}
if (noteIdsToReload.size > 0 || noteIdsToUpdate.size > 0) {
// workaround for https://github.com/mar10/fancytree/issues/1054
this.filterHoistedBranch();
} }
} }