From 211ff90ee8ea185311770da6be5dc34802fb602e Mon Sep 17 00:00:00 2001 From: zadam Date: Tue, 23 Nov 2021 23:09:29 +0100 Subject: [PATCH] add "top" label to keep notes on top, allow sorting by label, #2343 --- .../attribute_widgets/attribute_detail.js | 3 ++ src/services/attributes.js | 2 + src/services/handlers.js | 30 ++++++++----- src/services/tree.js | 43 ++++++++++++++++--- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 35be1e15b..388c1f8ce 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -189,6 +189,7 @@ const ATTR_HELP = { "runAtHour": "On which hour should this run. Should be used together with #run=hourly. Can be defined multiple times for more runs during the day.", "disableInclusion": "scripts with this label won't be included into parent script execution.", "sorted": "keeps child notes sorted by title alphabetically", + "top": "keep given note on top in its parent (applies only on sorted parents)", "hidePromotedAttributes": "Hide promoted attributes on this note", "readOnly": "editor is in read only mode. Works only for text and code notes.", "autoReadOnlyDisabled": "text/code notes can be set automatically into read mode when they are too large. You can disable this behavior on per-note basis by adding this label to the note", @@ -208,6 +209,8 @@ const ATTR_HELP = { "inbox": "default inbox location for new notes", "hoistedInbox": "default inbox location for new notes when hoisted to some ancestor of this note", "sqlConsoleHome": "default location of SQL console notes", + "bookmarked": "note with this label will appear in bookmarks", + "bookmarkFolder": "note with this label will appear in bookmarks as folder (allowing access to its children)" }, "relation": { "runOnNoteCreation": "executes when note is created on backend", diff --git a/src/services/attributes.js b/src/services/attributes.js index d2c8ac215..2a2d5978d 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -49,6 +49,8 @@ const BUILTIN_ATTRIBUTES = [ { type: 'label', name: 'mapRootNoteId' }, { type: 'label', name: 'bookmarked' }, { type: 'label', name: 'bookmarkFolder' }, + { type: 'label', name: 'sorted' }, + { type: 'label', name: 'top' }, // relation names { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, diff --git a/src/services/handlers.js b/src/services/handlers.js index 57b2d4d90..8a8a0e072 100644 --- a/src/services/handlers.js +++ b/src/services/handlers.js @@ -39,6 +39,10 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => { eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], ({ entityName, entity }) => { if (entityName === 'attributes') { runAttachedRelations(entity.getNote(), 'runOnAttributeChange', entity); + + if (entity.type === 'label' && entity.name === 'sorted') { + handleSortedAttribute(entity); + } } else if (entityName === 'notes') { runAttachedRelations(entity, 'runOnNoteChange', entity); @@ -83,17 +87,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => } } else if (entity.type === 'label' && entity.name === 'sorted') { - treeService.sortNotesIfNeeded(entity.noteId); - - if (entity.isInheritable) { - const note = becca.notes[entity.noteId]; - - if (note) { - for (const noteId of note.getSubtreeNoteIds()) { - treeService.sortNotesIfNeeded(noteId); - } - } - } + handleSortedAttribute(entity); } } else if (entityName === 'notes') { @@ -122,6 +116,20 @@ function processInverseRelations(entityName, entity, handler) { } } +function handleSortedAttribute(entity) { + treeService.sortNotesIfNeeded(entity.noteId); + + if (entity.isInheritable) { + const note = becca.notes[entity.noteId]; + + if (note) { + for (const noteId of note.getSubtreeNoteIds()) { + treeService.sortNotesIfNeeded(noteId); + } + } + } +} + eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => { processInverseRelations(entityName, entity, (definition, note, targetNote) => { // we need to make sure that also target's inverse attribute exists and if not, then create it diff --git a/src/services/tree.js b/src/services/tree.js index a5cbb5430..41442df85 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -108,9 +108,9 @@ function loadSubtreeNoteIds(parentNoteId, subtreeNoteIds) { } } -function sortNotes(parentNoteId, sortBy = 'title', reverse = false, foldersFirst = false) { - if (!sortBy) { - sortBy = 'title'; +function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, foldersFirst = false) { + if (!customSortBy) { + customSortBy = 'title'; } sql.transactional(() => { @@ -129,10 +129,41 @@ function sortNotes(parentNoteId, sortBy = 'title', reverse = false, foldersFirst } } - let aEl = normalize(a[sortBy]); - let bEl = normalize(b[sortBy]); + function fetchValue(note, key) { + const rawValue = ['title', 'dateCreated', 'dateModified'].includes(key) + ? note[key] + : note.getLabelValue(key); - return aEl < bEl ? -1 : 1; + return normalize(rawValue); + } + + function compare(a, b) { + return b === null || b === undefined || a < b ? -1 : 1; + } + + const topAEl = fetchValue(a, 'top'); + const topBEl = fetchValue(b, 'top'); + + console.log(a.title, topAEl); + console.log(b.title, topBEl); + console.log("comp", compare(topAEl, topBEl) && !reverse); + + if (topAEl !== topBEl) { + // since "top" should not be reversible, we'll reverse it once more to nullify this effect + return compare(topAEl, topBEl) * (reverse ? -1 : 1); + } + + const customAEl = fetchValue(a, customSortBy); + const customBEl = fetchValue(b, customSortBy); + + if (customAEl !== customBEl) { + return compare(customAEl, customBEl); + } + + const titleAEl = fetchValue(a, 'title'); + const titleBEl = fetchValue(b, 'title'); + + return compare(titleAEl, titleBEl); }); if (reverse) {