mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
refactoring of the complex note tree entitiesReloadedEvent
This commit is contained in:
parent
f37f47ce5b
commit
76f874ef6d
@ -228,7 +228,7 @@ class FNote {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return aNoteId < bNoteId ? -1 : 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user