diff --git a/src/becca/entities/battribute.js b/src/becca/entities/battribute.js index ca14a90c0..49b9fa258 100644 --- a/src/becca/entities/battribute.js +++ b/src/becca/entities/battribute.js @@ -96,7 +96,7 @@ class BAttribute extends AbstractBeccaEntity { } if (this.type === 'relation' && !(this.value in this.becca.notes)) { - throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`); + throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it targets not existing note '${this.value}'.`); } } diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 07ebd612f..2e9febadb 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -97,7 +97,7 @@ class BNote extends AbstractBeccaEntity { * @private */ this.parents = []; /** @type {BNote[]} - * @private*/ + * @private */ this.children = []; /** @type {BAttribute[]} * @private */ @@ -107,11 +107,11 @@ class BNote extends AbstractBeccaEntity { * @private */ this.__attributeCache = null; /** @type {BAttribute[]|null} - * @private*/ + * @private */ this.inheritableAttributeCache = null; /** @type {BAttribute[]} - * @private*/ + * @private */ this.targetRelations = []; this.becca.addNote(this.noteId, this); @@ -532,6 +532,20 @@ class BNote extends AbstractBeccaEntity { */ hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - label name * @param {string} [value] - label value diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index 70594d647..1066a9341 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -595,6 +595,20 @@ class FNote { */ hasLabel(name) { return this.hasAttribute(LABEL, name); } + /** + * @param {string} name - label name + * @returns {boolean} true if label exists (including inherited) and does not have "false" value. + */ + isLabelTruthy(name) { + const label = this.getLabel(name); + + if (!label) { + return false; + } + + return label && label.value !== 'false'; + } + /** * @param {string} name - relation name * @returns {boolean} true if relation exists (excluding inherited) diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 3040a4ac3..17dcc99a7 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -128,6 +128,10 @@ export default class DesktopLayout { ) .child( new RibbonContainer() + // order of the widgets matter. Some of these want to "activate" themselves + // when visible, when this happens to multiple of them, the first one "wins". + // promoted attributes should always win. + .ribbon(new PromotedAttributesWidget()) .ribbon(new ScriptExecutorWidget()) .ribbon(new SearchDefinitionWidget()) .ribbon(new EditedNotesWidget()) @@ -135,7 +139,6 @@ export default class DesktopLayout { .ribbon(new NotePropertiesWidget()) .ribbon(new FilePropertiesWidget()) .ribbon(new ImagePropertiesWidget()) - .ribbon(new PromotedAttributesWidget()) .ribbon(new BasicPropertiesWidget()) .ribbon(new OwnedAttributeListWidget()) .ribbon(new InheritedAttributesWidget()) diff --git a/src/public/app/services/froca_updater.js b/src/public/app/services/froca_updater.js index 64eb653c0..070d6c2a2 100644 --- a/src/public/app/services/froca_updater.js +++ b/src/public/app/services/froca_updater.js @@ -14,7 +14,7 @@ async function processEntityChanges(entityChanges) { if (ec.entityName === 'notes') { processNoteChange(loadResults, ec); } else if (ec.entityName === 'branches') { - processBranchChange(loadResults, ec); + await processBranchChange(loadResults, ec); } else if (ec.entityName === 'attributes') { processAttributeChange(loadResults, ec); } else if (ec.entityName === 'note_reordering') { @@ -105,7 +105,7 @@ function processNoteChange(loadResults, ec) { } } -function processBranchChange(loadResults, ec) { +async function processBranchChange(loadResults, ec) { if (ec.isErased && ec.entityId in froca.branches) { utils.reloadFrontendApp(`${ec.entityName} ${ec.entityId} is erased, need to do complete reload.`); return; @@ -139,7 +139,15 @@ function processBranchChange(loadResults, ec) { loadResults.addBranch(ec.entityId, ec.componentId); const childNote = froca.notes[ec.entity.noteId]; - const parentNote = froca.notes[ec.entity.parentNoteId]; + let parentNote = froca.notes[ec.entity.parentNoteId]; + + if (childNote && !parentNote) { + // a branch cannot exist without the parent + // a note loaded into froca has to also contain all its ancestors + // this problem happened e.g. in sharing where _share was hidden and thus not loaded + // sharing meant cloning into _share, which crashed because _share was not loaded + parentNote = await froca.getNote(ec.entity.parentNoteId); + } if (branch) { branch.update(ec.entity); diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index c985d8997..98d4e8460 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -65,7 +65,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await ws.waitForMaxKnownEntityChangeId(); await appContext.tabManager.getActiveContext().setNote(notePath); - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); }; /** @@ -82,7 +82,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain await appContext.tabManager.openContextWithNote(notePath, { activate }); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; @@ -100,10 +100,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); const {ntxId} = subContexts[subContexts.length - 1]; - appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); + await appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath}); if (activate) { - appContext.triggerEvent('focusAndSelectTitle'); + await appContext.triggerEvent('focusAndSelectTitle'); } }; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 488f3709b..80efc31ba 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -285,7 +285,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { this.$title = this.$widget.find('.attr-detail-title'); this.$inputName = this.$widget.find('.attr-input-name'); - this.$inputName.on('keyup', () => this.userEditedAttribute()); + this.$inputName.on('input', ev => { + if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812 + this.userEditedAttribute(); + } + }); this.$inputName.on('change', () => this.userEditedAttribute()); this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute()); @@ -299,7 +303,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { this.$rowValue = this.$widget.find('.attr-row-value'); this.$inputValue = this.$widget.find('.attr-input-value'); - this.$inputValue.on('keyup', () => this.userEditedAttribute()); + this.$inputValue.on('input', ev => { + if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812 + this.userEditedAttribute(); + } + }); this.$inputValue.on('change', () => this.userEditedAttribute()); this.$inputValue.on('autocomplete:closed', () => this.userEditedAttribute()); this.$inputValue.on('focus', () => { @@ -328,7 +336,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { this.$rowInverseRelation = this.$widget.find('.attr-row-inverse-relation'); this.$inputInverseRelation = this.$widget.find('.attr-input-inverse-relation'); - this.$inputInverseRelation.on('keyup', () => this.userEditedAttribute()); + this.$inputInverseRelation.on('input', ev => { + if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812 + this.userEditedAttribute(); + } + }); this.$rowTargetNote = this.$widget.find('.attr-row-target-note'); this.$inputTargetNote = this.$widget.find('.attr-input-target-note'); diff --git a/src/public/app/widgets/ribbon_widgets/promoted_attributes.js b/src/public/app/widgets/ribbon_widgets/promoted_attributes.js index bbaa0d4d6..46b199509 100644 --- a/src/public/app/widgets/ribbon_widgets/promoted_attributes.js +++ b/src/public/app/widgets/ribbon_widgets/promoted_attributes.js @@ -63,7 +63,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { return { show: true, activate: true, - title: "Promoted attributes", + title: "Promoted Attributes", icon: "bx bx-table" }; } diff --git a/src/public/app/widgets/ribbon_widgets/search_definition.js b/src/public/app/widgets/ribbon_widgets/search_definition.js index 4ca8e17d9..baa6dfdd9 100644 --- a/src/public/app/widgets/ribbon_widgets/search_definition.js +++ b/src/public/app/widgets/ribbon_widgets/search_definition.js @@ -180,7 +180,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { return { show: this.isEnabled(), activate: true, - title: 'Search parameters', + title: 'Search Parameters', icon: 'bx bx-search' }; } diff --git a/src/public/app/widgets/shared_info.js b/src/public/app/widgets/shared_info.js index 8029ba24b..ebdfbb108 100644 --- a/src/public/app/widgets/shared_info.js +++ b/src/public/app/widgets/shared_info.js @@ -39,7 +39,14 @@ export default class SharedInfoWidget extends NoteContextAwareWidget { this.$sharedText.text("This note is shared publicly on"); } else { - link = `${location.protocol}//${location.host}${location.pathname}share/${shareId}`; + let host = location.host; + if (host.endsWith('/')) { + // seems like IE has trailing slash + // https://github.com/zadam/trilium/issues/3782 + host = host.substr(0, host.length - 1); + } + + link = `${location.protocol}//${host}${location.pathname}share/${shareId}`; this.$sharedText.text("This note is shared locally on"); } diff --git a/src/services/notes.js b/src/services/notes.js index 6dfa34cb0..d526372d2 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -23,7 +23,7 @@ const ValidationError = require("../errors/validation_error"); const noteTypesService = require("./note_types"); function getNewNotePosition(parentNote) { - if (parentNote.hasLabel('newNotesOnTop')) { + if (parentNote.isLabelTruthy('newNotesOnTop')) { const minNotePos = parentNote.getChildBranches() .reduce((min, note) => Math.min(min, note.notePosition), 0); @@ -841,7 +841,7 @@ function duplicateSubtree(origNoteId, newParentNoteId) { throw new Error('Duplicating root is not possible'); } - log.info(`Duplicating ${origNoteId} subtree into ${newParentNoteId}`); + log.info(`Duplicating '${origNoteId}' subtree into '${newParentNoteId}'`); const origNote = becca.notes[origNoteId]; // might be null if orig note is not in the target newParentNoteId @@ -919,7 +919,8 @@ function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapp attr.value = noteIdMapping[attr.value]; } - attr.save(); + // the relation targets may not be created yet, the mapping is pre-generated + attr.save({skipValidation: true}); } for (const childBranch of origNote.getChildBranches()) {