diff --git a/db/demo.zip b/db/demo.zip index f9efd3e44..5344f44c2 100644 Binary files a/db/demo.zip and b/db/demo.zip differ diff --git a/db/migrations/0204__migrate_bookmarks_to_clones.js b/db/migrations/0204__migrate_bookmarks_to_clones.js index 82cc11ebe..6b180a446 100644 --- a/db/migrations/0204__migrate_bookmarks_to_clones.js +++ b/db/migrations/0204__migrate_bookmarks_to_clones.js @@ -12,5 +12,10 @@ module.exports = () => { attr.markAsDeleted("0204__migrate_bookmarks_to_clones"); } + + // bookmarkFolder used to work in 0.57 without the bookmarked label + for (const attr of becca.findAttributes('label','bookmarkFolder')) { + cloningService.toggleNoteInParent(true, attr.noteId, '_lbBookmarks'); + } }); }; diff --git a/src/becca/becca_service.js b/src/becca/becca_service.js index d22c1054e..9d19edcd7 100644 --- a/src/becca/becca_service.js +++ b/src/becca/becca_service.js @@ -82,10 +82,8 @@ function getNoteTitleArrayForPath(notePathArray) { throw new Error(`${notePathArray} is not an array.`); } - const hoistedNoteId = cls.getHoistedNoteId(); - - if (notePathArray.length === 1 && notePathArray[0] === hoistedNoteId) { - return [getNoteTitle(hoistedNoteId)]; + if (notePathArray.length === 1) { + return [getNoteTitle(notePathArray[0])]; } const titles = []; @@ -94,6 +92,7 @@ function getNoteTitleArrayForPath(notePathArray) { let hoistedNotePassed = false; // this is a notePath from outside of hoisted subtree so full title path needs to be returned + const hoistedNoteId = cls.getHoistedNoteId(); const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId); for (const noteId of notePathArray) { diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 71e351f38..c41c202b3 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -11,6 +11,7 @@ const BNoteRevision = require("./bnote_revision"); const TaskContext = require("../../services/task_context"); const dayjs = require("dayjs"); const utc = require('dayjs/plugin/utc'); +const eventService = require("../../services/events"); dayjs.extend(utc) const LABEL = 'label'; @@ -314,6 +315,11 @@ class BNote extends AbstractBeccaEntity { utcDateChanged: pojo.utcDateModified, isSynced: true }); + + eventService.emit(eventService.ENTITY_CHANGED, { + entityName: 'note_contents', + entity: this + }); } setJsonContent(content) { @@ -1124,6 +1130,13 @@ class BNote extends AbstractBeccaEntity { return notePaths; } + /** + * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree + */ + isHiddenCompletely() { + return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')); + } + /** * @param ancestorNoteId * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths @@ -1368,7 +1381,7 @@ class BNote extends AbstractBeccaEntity { } isOptions() { - return this.noteId.startsWith("options"); + return this.noteId.startsWith("_options"); } get isDeleted() { diff --git a/src/public/app/doc_notes/launchbar_history_navigation.html b/src/public/app/doc_notes/launchbar_history_navigation.html new file mode 100644 index 000000000..bf4411572 --- /dev/null +++ b/src/public/app/doc_notes/launchbar_history_navigation.html @@ -0,0 +1,3 @@ +
Back and Forward buttons allow you to move in the navigation history.
+ +These launchers are active only in the desktop build and will be ignored in the server edition where you can use the native browser navigation buttons instead.
diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index fa08995f2..42e8eadbe 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -360,6 +360,13 @@ class FNote { return notePaths; } + /** + * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree + */ + isHiddenCompletely() { + return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')); + } + __filterAttrs(attributes, type, name) { this.__validateTypeName(type, name); @@ -852,7 +859,7 @@ class FNote { } isOptions() { - return this.noteId.startsWith("options"); + return this.noteId.startsWith("_options"); } } diff --git a/src/public/app/services/note_autocomplete.js b/src/public/app/services/note_autocomplete.js index 157c24261..07c75d447 100644 --- a/src/public/app/services/note_autocomplete.js +++ b/src/public/app/services/note_autocomplete.js @@ -230,6 +230,10 @@ function init() { $.fn.getSelectedNoteId = function () { const notePath = $(this).getSelectedNotePath(); + if (!notePath) { + return null; + } + const chunks = notePath.split('/'); return chunks.length >= 1 ? chunks[chunks.length - 1] : null; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 92d7d6b9b..7ceb9bbcb 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -247,7 +247,7 @@ const ATTR_HELP = { "runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.", "runOnChildNoteCreation": "executes when new note is created under the note where this relation is defined", "runOnNoteTitleChange": "executes when note title is changed (includes note creation as well)", - "runOnNoteContentChange": "executes when note content is changed (includes note creation as well).", + "runOnNoteContentChange": "executes when note content is changed (includes note creation as well).", "runOnNoteChange": "executes when note is changed (includes note creation as well). Does not include content changes", "runOnNoteDeletion": "executes when note is being deleted", "runOnBranchCreation": "executes when a branch is created. Branch is a link between parent note and child note and is created e.g. when cloning or moving note.", diff --git a/src/public/app/widgets/buttons/command_button.js b/src/public/app/widgets/buttons/command_button.js index 376c5a822..12e0dd821 100644 --- a/src/public/app/widgets/buttons/command_button.js +++ b/src/public/app/widgets/buttons/command_button.js @@ -39,7 +39,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget { /** * @param {function|string} command - * @returns {CommandButtonWidget} + * @returns {this} */ command(command) { this.settings.command = command; diff --git a/src/public/app/widgets/buttons/global_menu.js b/src/public/app/widgets/buttons/global_menu.js index ff881aa20..63045532a 100644 --- a/src/public/app/widgets/buttons/global_menu.js +++ b/src/public/app/widgets/buttons/global_menu.js @@ -303,7 +303,7 @@ export default class GlobalMenuWidget extends BasicWidget { const resp = await fetch(RELEASES_API_URL); const data = await resp.json(); - return data.tag_name.substring(1); + return data?.tag_name?.substring(1); } downloadLatestVersionCommand() { diff --git a/src/public/app/widgets/buttons/history_navigation.js b/src/public/app/widgets/buttons/history_navigation.js index 430aeae21..08a86810f 100644 --- a/src/public/app/widgets/buttons/history_navigation.js +++ b/src/public/app/widgets/buttons/history_navigation.js @@ -23,6 +23,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget { doRender() { super.doRender(); + if (!utils.isElectron()) { + return; + } + this.webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents(); // without this the history is preserved across frontend reloads diff --git a/src/public/app/widgets/dialogs/note_revisions.js b/src/public/app/widgets/dialogs/note_revisions.js index 17d578c2d..f8c6584ab 100644 --- a/src/public/app/widgets/dialogs/note_revisions.js +++ b/src/public/app/widgets/dialogs/note_revisions.js @@ -234,14 +234,14 @@ export default class NoteRevisionsDialog extends BasicWidget { renderMathInElement($content[0], {trust: true}); } } - else if (revisionItem.type === 'code') { + else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { this.$content.html($("").text(fullNoteRevision.content));
}
else if (revisionItem.type === 'image') {
this.$content.html($("
")
// reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note
- .attr("src", `data:${note.mime};base64,${fullNoteRevision.content}`)
+ .attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
}
diff --git a/src/public/app/widgets/floating_buttons/code_buttons.js b/src/public/app/widgets/floating_buttons/code_buttons.js
index 9363856a5..f1763870b 100644
--- a/src/public/app/widgets/floating_buttons/code_buttons.js
+++ b/src/public/app/widgets/floating_buttons/code_buttons.js
@@ -76,7 +76,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
this.$saveToNoteButton.toggle(
note.mime === 'text/x-sqlite;schema=trilium'
- && !note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'))
+ && note.isHiddenCompletely()
);
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js
index 8e9fcdf04..204bb77db 100644
--- a/src/public/app/widgets/note_tree.js
+++ b/src/public/app/widgets/note_tree.js
@@ -1309,6 +1309,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
await this.tree.reload([rootNode]);
});
+ await this.filterHoistedBranch();
+
if (activeNotePath) {
const node = await this.getNodeFromPath(activeNotePath, true);
diff --git a/src/public/app/widgets/ribbon_widgets/search_definition.js b/src/public/app/widgets/ribbon_widgets/search_definition.js
index 0dc4d4841..4ca8e17d9 100644
--- a/src/public/app/widgets/ribbon_widgets/search_definition.js
+++ b/src/public/app/widgets/ribbon_widgets/search_definition.js
@@ -270,7 +270,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
async refreshWithNote(note) {
this.$component.show();
- this.$saveToNoteButton.toggle(!note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')));
+ this.$saveToNoteButton.toggle(note.isHiddenCompletely());
this.$searchOptions.empty();
diff --git a/src/routes/api/options.js b/src/routes/api/options.js
index a176bb45f..ddb4321ec 100644
--- a/src/routes/api/options.js
+++ b/src/routes/api/options.js
@@ -112,7 +112,7 @@ function update(name, value) {
}
function getUserThemes() {
- const notes = searchService.searchNotes("#appTheme");
+ const notes = searchService.searchNotes("#appTheme", {ignoreHoistedNote: true});
const ret = [];
for (const note of notes) {
diff --git a/src/services/build.js b/src/services/build.js
index f3031a049..dc5dc5172 100644
--- a/src/services/build.js
+++ b/src/services/build.js
@@ -1 +1 @@
-module.exports = { buildDate:"2023-01-11T23:44:33+01:00", buildRevision: "bdfdc0402ddb23e9af002580f368bc52e4268b3a" };
+module.exports = { buildDate:"2023-01-16T22:39:28+01:00", buildRevision: "9fd0b85ff2be264be35ec2052c956b654f0dac9e" };
diff --git a/src/services/builtin_attributes.js b/src/services/builtin_attributes.js
index d3558b884..1597c6599 100644
--- a/src/services/builtin_attributes.js
+++ b/src/services/builtin_attributes.js
@@ -71,6 +71,7 @@ module.exports = [
{ type: 'relation', name: 'runOnNoteCreation', isDangerous: true },
{ type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true },
{ type: 'relation', name: 'runOnNoteChange', isDangerous: true },
+ { type: 'relation', name: 'runOnNoteContentChange', isDangerous: true },
{ type: 'relation', name: 'runOnNoteDeletion', isDangerous: true },
{ type: 'relation', name: 'runOnBranchCreation', isDangerous: true },
{ type: 'relation', name: 'runOnBranchDeletion', isDangerous: true },
diff --git a/src/services/handlers.js b/src/services/handlers.js
index 588b80555..4bb21f910 100644
--- a/src/services/handlers.js
+++ b/src/services/handlers.js
@@ -4,6 +4,8 @@ const treeService = require('./tree');
const noteService = require('./notes');
const becca = require('../becca/becca');
const BAttribute = require('../becca/entities/battribute');
+const hiddenSubtreeService = require("./hidden_subtree");
+const oneTimeTimer = require("./one_time_timer");
function runAttachedRelations(note, relationName, originEntity) {
if (!note) {
@@ -206,6 +208,16 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) =>
if (entityName === 'branches') {
runAttachedRelations(entity.getNote(), 'runOnBranchDeletion', entity);
}
+
+ if (entityName === 'notes' && entity.noteId.startsWith("_")) {
+ // "named" note has been deleted, we will probably need to rebuild the hidden subtree
+ // scheduling so that bulk deletes won't trigger so many checks
+ oneTimeTimer.scheduleExecution('hidden-subtree-check', 1000, () => {
+ console.log("Checking hidden subtree");
+
+ hiddenSubtreeService.checkHiddenSubtree();
+ });
+ }
});
module.exports = {
diff --git a/src/services/hidden_subtree.js b/src/services/hidden_subtree.js
index ff8036c1c..ba62ab288 100644
--- a/src/services/hidden_subtree.js
+++ b/src/services/hidden_subtree.js
@@ -1,6 +1,8 @@
const becca = require("../becca/becca");
const noteService = require("./notes");
const BAttribute = require("../becca/entities/battribute");
+const log = require("./log");
+const migrationService = require("./migration");
const LBTPL_ROOT = "_lbTplRoot";
const LBTPL_BASE = "_lbTplBase";
@@ -179,8 +181,10 @@ const HIDDEN_SUBTREE_DEFINITION = {
isExpanded: true,
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
children: [
- { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square' },
- { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square' },
+ { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square',
+ attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
+ { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square',
+ attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
{ id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' },
]
},
@@ -237,6 +241,12 @@ const HIDDEN_SUBTREE_DEFINITION = {
};
function checkHiddenSubtree() {
+ if (!migrationService.isDbUpToDate()) {
+ // on-delete hook might get triggered during some future migration and cause havoc
+ log.info("Will not check hidden subtree until migration is finished.");
+ return;
+ }
+
checkHiddenSubtreeRecursively('root', HIDDEN_SUBTREE_DEFINITION);
}
diff --git a/src/services/import/enex.js b/src/services/import/enex.js
index b982d642a..cb1c3dcaa 100644
--- a/src/services/import/enex.js
+++ b/src/services/import/enex.js
@@ -76,8 +76,8 @@ function importEnex(taskContext, file, parentNote) {
content = content.replace(/<\/ol>\s*${result.content}
+ ${escapeHtml(result.content)}