From ac7d5f2e81d0d5405757d0dcccd575d04b0cdad1 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 25 Jan 2020 14:37:12 +0100 Subject: [PATCH] fixes, separation of notefull from noteshort --- src/public/javascripts/dialogs/attributes.js | 2 -- src/public/javascripts/dialogs/note_info.js | 12 +++---- src/public/javascripts/entities/note_full.js | 12 ++----- .../javascripts/services/note_detail.js | 8 ++--- .../javascripts/services/note_tooltip.js | 17 ++++----- .../javascripts/services/tab_context.js | 35 +++---------------- src/public/javascripts/widgets/attributes.js | 6 ++-- src/public/javascripts/widgets/note_detail.js | 17 +++++---- src/public/javascripts/widgets/note_info.js | 10 +++--- .../widgets/promoted_attributes.js | 4 +-- .../javascripts/widgets/type_widgets/code.js | 2 +- .../javascripts/widgets/type_widgets/file.js | 6 ++-- .../javascripts/widgets/type_widgets/image.js | 4 +-- .../widgets/type_widgets/relation_map.js | 4 +-- .../widgets/type_widgets/search.js | 2 +- .../javascripts/widgets/type_widgets/text.js | 10 ++---- .../widgets/type_widgets/type_widget.js | 2 +- src/routes/api/tree.js | 14 ++++---- 18 files changed, 64 insertions(+), 103 deletions(-) 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
for a case when note's content is pure text (e.g. "[protected]") which // then fails the jquery non-empty text test - content += '
' + note.content + '
'; + content += '
' + noteFull.content + '
'; } else if (note.type === 'code') { content += $("
")
-            .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,