diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index add45be9d..2677d84a5 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -172,8 +172,6 @@ function AttributesModel() { toastService.showMessage("Attributes have been saved."); - appContext.getActiveTabContext().attributes.refreshAttributes(); - // FIXME detail should be also reloaded appContext.trigger('reloadTree'); }; diff --git a/src/public/javascripts/dialogs/note_info.js b/src/public/javascripts/dialogs/note_info.js index 10e56c874..886415440 100644 --- a/src/public/javascripts/dialogs/note_info.js +++ b/src/public/javascripts/dialogs/note_info.js @@ -16,13 +16,13 @@ export function showDialog() { $dialog.modal(); - const activeNote = appContext.getActiveTabNote(); + const {note, noteFull} = appContext.getActiveTabContext(); - $noteId.text(activeNote.noteId); - $dateCreated.text(activeNote.dateCreated); - $dateModified.text(activeNote.dateModified); - $type.text(activeNote.type); - $mime.text(activeNote.mime); + $noteId.text(note.noteId); + $dateCreated.text(noteFull.dateCreated); + $dateModified.text(noteFull.dateModified); + $type.text(note.type); + $mime.text(note.mime); } $okButton.on('click', () => $dialog.modal('hide')); diff --git a/src/public/javascripts/entities/note_full.js b/src/public/javascripts/entities/note_full.js index 5ee5723b0..d1b667f51 100644 --- a/src/public/javascripts/entities/note_full.js +++ b/src/public/javascripts/entities/note_full.js @@ -3,10 +3,8 @@ import NoteShort from './note_short.js'; /** * Represents full note, specifically including note's content. */ -class NoteFull extends NoteShort { - constructor(treeCache, row, noteShort) { - super(treeCache, row, []); - +class NoteFull { + constructor(row) { /** @param {string} */ this.content = row.content; @@ -21,12 +19,6 @@ class NoteFull extends NoteShort { /** @param {string} */ this.utcDateModified = row.utcDateModified; - - /* ugly */ - this.parents = noteShort.parents; - this.parentToBranch = noteShort.parentToBranch; - this.children = noteShort.children; - this.childToBranch = noteShort.childToBranch; } } diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 2d4a2bf3b..7f8d70d38 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -15,12 +15,10 @@ function getActiveEditor() { } } -async function loadNote(noteId) { +async function loadNoteFull(noteId) { const row = await server.get('notes/' + noteId); - const noteShort = await treeCache.getNote(noteId); - - return new NoteFull(treeCache, row, noteShort); + return new NoteFull(row); } function focusOnTitle() { @@ -65,7 +63,7 @@ $(window).on('beforeunload', () => { }); export default { - loadNote, + loadNoteFull, focusOnTitle, focusAndSelectTitle, getActiveEditor, diff --git a/src/public/javascripts/services/note_tooltip.js b/src/public/javascripts/services/note_tooltip.js index 25bec7e88..37b6e34e8 100644 --- a/src/public/javascripts/services/note_tooltip.js +++ b/src/public/javascripts/services/note_tooltip.js @@ -2,6 +2,7 @@ import noteDetailService from "./note_detail.js"; import treeService from "./tree.js"; import linkService from "./link.js"; import server from "./server.js"; +import treeCache from "./tree_cache.js"; function setupGlobalTooltip() { $(document).on("mouseenter", "a", mouseEnterHandler); @@ -42,12 +43,10 @@ async function mouseEnterHandler() { const noteId = treeService.getNoteIdFromNotePath(notePath); - const notePromise = noteDetailService.loadNote(noteId); - const attributePromise = server.get(`notes/${noteId}/attributes`); + const note = await treeCache.getNote(noteId); + const noteFull = await noteDetailService.loadNoteFull(noteId); - const [note, attributes] = await Promise.all([notePromise, attributePromise]); - - const html = await renderTooltip(note, attributes); + const html = await renderTooltip(note, noteFull); // we need to check if we're still hovering over the element // since the operation to get tooltip content was async, it is possible that @@ -72,7 +71,9 @@ function mouseLeaveHandler() { $(this).tooltip('dispose'); } -async function renderTooltip(note, attributes) { +async function renderTooltip(note, noteFull) { + const attributes = await note.getAttributes(); + let content = ''; const promoted = attributes.filter(attr => (attr.type === 'label-definition' || attr.type === 'relation-definition') @@ -116,11 +117,11 @@ async function renderTooltip(note, attributes) { if (note.type === 'text') { // surround with
") - .text(note.content) + .text(noteFull.content) .prop('outerHTML'); } else if (note.type === 'image') { diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index bc1a7b67c..97ad7d720 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -1,13 +1,13 @@ import protectedSessionHolder from "./protected_session_holder.js"; import server from "./server.js"; import bundleService from "./bundle.js"; -import Attributes from "./attributes.js"; import utils from "./utils.js"; import optionsService from "./options.js"; import appContext from "./app_context.js"; import treeService from "./tree.js"; import noteDetailService from "./note_detail.js"; import Component from "../widgets/component.js"; +import treeCache from "./tree_cache.js"; let showSidebarInNewTab = true; @@ -28,10 +28,6 @@ class TabContext extends Component { this.tabId = state.tabId || utils.randomString(4); this.state = state; - this.attributes = new Attributes(this.appContext, this); - - this.children.push(this.attributes); - this.trigger('tabOpened', {tabId: this.tabId}); } @@ -52,8 +48,11 @@ class TabContext extends Component { this.notePath = notePath; const noteId = treeService.getNoteIdFromNotePath(notePath); + /** @property {NoteShort} */ + this.note = await treeCache.getNote(noteId); + /** @property {NoteFull} */ - this.note = await noteDetailService.loadNote(noteId); + this.noteFull = await noteDetailService.loadNoteFull(noteId); //this.cleanup(); // esp. on windows autocomplete is not getting closed automatically @@ -85,30 +84,6 @@ class TabContext extends Component { this.trigger('tabRemoved', {tabId: this.tabId}); } - async saveNote() { - return; // FIXME - - if (this.note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) { - return; - } - - this.note.title = this.$noteTitle.val(); - this.note.content = this.getComponent().getContent(); - - // it's important to set the flag back to false immediatelly after retrieving title and content - // otherwise we might overwrite another change (especially async code) - this.isNoteChanged = false; - - const resp = await server.put('notes/' + this.note.noteId, this.note.dto); - - this.note.dateModified = resp.dateModified; - this.note.utcDateModified = resp.utcDateModified; - - if (this.note.isProtected) { - protectedSessionHolder.touchProtectedSession(); - } - } - isActive() { return this.appContext.activeTabId === this.tabId; } diff --git a/src/public/javascripts/widgets/attributes.js b/src/public/javascripts/widgets/attributes.js index 4a700b23b..cab4d6ad0 100644 --- a/src/public/javascripts/widgets/attributes.js +++ b/src/public/javascripts/widgets/attributes.js @@ -23,9 +23,9 @@ class AttributesWidget extends StandardWidget { return [$showFullButton]; } - async refreshWithNote() { - const attributes = await this.tabContext.attributes.getAttributes(); - const ownedAttributes = attributes.filter(attr => attr.noteId === this.tabContext.note.noteId); + async refreshWithNote(note) { + const attributes = await note.getAttributes(); + const ownedAttributes = note.getOwnedAttributes(); if (attributes.length === 0) { this.$body.text("No attributes yet..."); diff --git a/src/public/javascripts/widgets/note_detail.js b/src/public/javascripts/widgets/note_detail.js index 7d5a196e7..256bb486e 100644 --- a/src/public/javascripts/widgets/note_detail.js +++ b/src/public/javascripts/widgets/note_detail.js @@ -36,16 +36,19 @@ export default class NoteDetailWidget extends TabAwareWidget { this.typeWidgetPromises = {}; this.spacedUpdate = new SpacedUpdate(async () => { - const note = this.tabContext.note; - note.content = this.getTypeWidget().getContent(); + const {noteFull} = this.tabContext; + const {noteId} = this.tabContext.note; - const resp = await server.put('notes/' + note.noteId, note.dto); + const dto = note.dto; + dto.content = noteFull.content = this.getTypeWidget().getContent(); + + const resp = await server.put('notes/' + noteId, dto); // FIXME: minor - does not propagate to other tab contexts with this note though - note.dateModified = resp.dateModified; - note.utcDateModified = resp.utcDateModified; + noteFull.dateModified = resp.dateModified; + noteFull.utcDateModified = resp.utcDateModified; - this.trigger('noteChangesSaved', {noteId: note.noteId}) + this.trigger('noteChangesSaved', {noteId}) }); } @@ -156,7 +159,7 @@ export default class NoteDetailWidget extends TabAwareWidget { let type = note.type; if (type === 'text' && !disableAutoBook - && utils.isHtmlEmpty(note.content) + && utils.isHtmlEmpty(this.tabContext.noteFull.content) && note.hasChildren()) { type = 'book'; diff --git a/src/public/javascripts/widgets/note_info.js b/src/public/javascripts/widgets/note_info.js index d898065f3..3d5c3dd8e 100644 --- a/src/public/javascripts/widgets/note_info.js +++ b/src/public/javascripts/widgets/note_info.js @@ -49,14 +49,16 @@ class NoteInfoWidget extends StandardWidget { const $type = this.$body.find(".note-info-type"); const $mime = this.$body.find(".note-info-mime"); + const {noteFull} = this.tabContext; + $noteId.text(note.noteId); $dateCreated - .text(note.dateCreated) - .attr("title", note.dateCreated); + .text(noteFull.dateCreated) + .attr("title", noteFull.dateCreated); $dateModified - .text(note.dateModified) - .attr("title", note.dateCreated); + .text(noteFull.dateModified) + .attr("title", noteFull.dateCreated); $type.text(note.type); diff --git a/src/public/javascripts/widgets/promoted_attributes.js b/src/public/javascripts/widgets/promoted_attributes.js index afc057c0a..6ef8f900e 100644 --- a/src/public/javascripts/widgets/promoted_attributes.js +++ b/src/public/javascripts/widgets/promoted_attributes.js @@ -35,10 +35,10 @@ export default class PromotedAttributesWidget extends TabAwareWidget { return this.$widget; } - async refreshWithNote() { + async refreshWithNote(note) { this.$container.empty(); - const attributes = await this.tabContext.attributes.getAttributes(); + const attributes = await note.getAttributes(); const promoted = attributes.filter(attr => (attr.type === 'label-definition' || attr.type === 'relation-definition') diff --git a/src/public/javascripts/widgets/type_widgets/code.js b/src/public/javascripts/widgets/type_widgets/code.js index fced17492..ce791579d 100644 --- a/src/public/javascripts/widgets/type_widgets/code.js +++ b/src/public/javascripts/widgets/type_widgets/code.js @@ -75,7 +75,7 @@ export default class CodeTypeWidget extends TypeWidget { this.spacedUpdate.allowUpdateWithoutChange(() => { // CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check) // we provide fallback - this.codeEditor.setValue(note.content || ""); + this.codeEditor.setValue(this.tabContext.noteFull.content || ""); const info = CodeMirror.findModeByMIME(note.mime); diff --git a/src/public/javascripts/widgets/type_widgets/file.js b/src/public/javascripts/widgets/type_widgets/file.js index d41cf3118..c539e5032 100644 --- a/src/public/javascripts/widgets/type_widgets/file.js +++ b/src/public/javascripts/widgets/type_widgets/file.js @@ -118,7 +118,7 @@ export default class FileTypeWidget extends TypeWidget { } async doRefresh(note) { - const attributes = await server.get('notes/' + note.noteId + '/attributes'); + const attributes = await note.getAttributes(); const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); this.$widget.show(); @@ -128,9 +128,9 @@ export default class FileTypeWidget extends TypeWidget { this.$fileSize.text(note.contentLength + " bytes"); this.$fileType.text(note.mime); - if (note.content) { + if (this.tabContext.noteFull.content) { this.$previewContent.show(); - this.$previewContent.text(note.content); + this.$previewContent.text(this.tabContext.noteFull.content); } else { this.$previewContent.empty().hide(); diff --git a/src/public/javascripts/widgets/type_widgets/image.js b/src/public/javascripts/widgets/type_widgets/image.js index 865eda329..bfe59aa53 100644 --- a/src/public/javascripts/widgets/type_widgets/image.js +++ b/src/public/javascripts/widgets/type_widgets/image.js @@ -123,7 +123,7 @@ class NoteDetailImage extends TypeWidget { } async doRefresh(note) { - const attributes = await server.get('notes/' + note.noteId + '/attributes'); + const attributes = await note.getAttributes(); const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); this.$widget.show(); @@ -132,7 +132,7 @@ class NoteDetailImage extends TypeWidget { this.$fileSize.text(note.contentLength + " bytes"); this.$fileType.text(note.mime); - const imageHash = note.utcDateModified.replace(" ", "_"); + const imageHash = this.tabContext.noteFull.utcDateModified.replace(" ", "_"); this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}?${imageHash}`); } diff --git a/src/public/javascripts/widgets/type_widgets/relation_map.js b/src/public/javascripts/widgets/type_widgets/relation_map.js index a517c4d97..f876b1947 100644 --- a/src/public/javascripts/widgets/type_widgets/relation_map.js +++ b/src/public/javascripts/widgets/type_widgets/relation_map.js @@ -254,9 +254,9 @@ export default class RelationMapTypeWidget extends TypeWidget { } }; - if (this.tabContext.note.content) { + if (this.tabContext.noteFull.content) { try { - this.mapData = JSON.parse(this.tabContext.note.content); + this.mapData = JSON.parse(this.tabContext.noteFull.content); } catch (e) { console.log("Could not parse content: ", e); } diff --git a/src/public/javascripts/widgets/type_widgets/search.js b/src/public/javascripts/widgets/type_widgets/search.js index fdc5cb100..1abb1eaa7 100644 --- a/src/public/javascripts/widgets/type_widgets/search.js +++ b/src/public/javascripts/widgets/type_widgets/search.js @@ -45,7 +45,7 @@ export default class SearchTypeWidget extends TypeWidget { this.$component.show(); try { - const json = JSON.parse(note.content); + const json = JSON.parse(this.tabContext.noteFull.content); this.$searchString.val(json.searchString); } diff --git a/src/public/javascripts/widgets/type_widgets/text.js b/src/public/javascripts/widgets/type_widgets/text.js index 8763cd110..71198656a 100644 --- a/src/public/javascripts/widgets/type_widgets/text.js +++ b/src/public/javascripts/widgets/type_widgets/text.js @@ -137,10 +137,10 @@ export default class TextTypeWidget extends TypeWidget { } async doRefresh(note) { - this.textEditor.isReadOnly = await this.isReadOnly(); + this.textEditor.isReadOnly = await note.hasLabel('readOnly'); this.spacedUpdate.allowUpdateWithoutChange(() => { - this.textEditor.setData(note.content); + this.textEditor.setData(this.tabContext.noteFull.content); }); } @@ -160,12 +160,6 @@ export default class TextTypeWidget extends TypeWidget { && !content.includes("attr.type === 'label' && attr.name === 'readOnly'); - } - focus() { this.$editor.trigger('focus'); } diff --git a/src/public/javascripts/widgets/type_widgets/type_widget.js b/src/public/javascripts/widgets/type_widgets/type_widget.js index 0529c499b..428395f91 100644 --- a/src/public/javascripts/widgets/type_widgets/type_widget.js +++ b/src/public/javascripts/widgets/type_widgets/type_widget.js @@ -5,7 +5,7 @@ export default class TypeWidget extends TabAwareWidget { static getType() {} /** - * @param {NoteFull} note + * @param {NoteShort} note */ doRefresh(note) {} diff --git a/src/routes/api/tree.js b/src/routes/api/tree.js index 28b8f210f..d5c04c9b2 100644 --- a/src/routes/api/tree.js +++ b/src/routes/api/tree.js @@ -8,14 +8,7 @@ async function getNotesAndBranchesAndAttributes(noteIds) { noteIds = Array.from(new Set(noteIds)); const notes = await treeService.getNotes(noteIds); - const noteMap = {}; - noteIds = []; - - for (const note of notes) { - note.attributes = []; - noteMap[note.noteId] = note; - noteIds.push(note.noteId); - } + noteIds = notes.map(note => note.noteId); // joining child note to filter out not completely synchronised notes which would then cause errors later // cannot do that with parent because of root note's 'none' parent @@ -37,14 +30,19 @@ async function getNotesAndBranchesAndAttributes(noteIds) { const attributes = await sql.getManyRows(` SELECT + attributeId, noteId, type, name, value, + position, isInheritable FROM attributes WHERE isDeleted = 0 AND noteId IN (???)`, noteIds); + // sorting in memory is faster + attributes.sort((a, b) => a.position - b.position < 0 ? -1 : 1); + return { branches, notes,