diff --git a/src/public/app/services/app_context.js b/src/public/app/services/app_context.js index 238db11d3..862861f6a 100644 --- a/src/public/app/services/app_context.js +++ b/src/public/app/services/app_context.js @@ -11,6 +11,7 @@ import Component from "../widgets/component.js"; import keyboardActionsService from "./keyboard_actions.js"; import MobileScreenSwitcherExecutor from "../widgets/mobile_widgets/mobile_screen_switcher.js"; import MainTreeExecutors from "./main_tree_executors.js"; +import protectedSessionHolder from "./protected_session_holder.js"; class AppContext extends Component { constructor(isMainWindow) { @@ -110,6 +111,8 @@ const appContext = new AppContext(window.glob.isMainWindow); // we should save all outstanding changes before the page/app is closed $(window).on('beforeunload', () => { + protectedSessionHolder.resetSessionCookie(); + appContext.triggerEvent('beforeUnload'); }); diff --git a/src/public/app/services/protected_session_holder.js b/src/public/app/services/protected_session_holder.js index 7f0dc2a7d..fc041b51e 100644 --- a/src/public/app/services/protected_session_holder.js +++ b/src/public/app/services/protected_session_holder.js @@ -12,15 +12,19 @@ setInterval(() => { resetProtectedSession(); } -}, 5000); +}, 10000); function setProtectedSessionId(id) { // using session cookie so that it disappears after browser/tab is closed utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, id); } -function resetProtectedSession() { +function resetSessionCookie() { utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); +} + +function resetProtectedSession() { + resetSessionCookie(); // most secure solution - guarantees nothing remained in memory // since this expires because user doesn't use the app, it shouldn't be disruptive @@ -47,8 +51,9 @@ function touchProtectedSessionIfNecessary(note) { export default { setProtectedSessionId, + resetSessionCookie, resetProtectedSession, isProtectedSessionAvailable, touchProtectedSession, touchProtectedSessionIfNecessary -}; \ No newline at end of file +}; diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 6ad2efea4..38473fe06 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -109,11 +109,17 @@ async function addImagesToNote(images, note, content) { const {note: imageNote, url} = await imageService.saveImage(note.noteId, buffer, filename, true); + await new Attribute({ + noteId: imageNote.noteId, + type: 'label', + name: 'hideInAutocomplete' + }).save(); + await new Attribute({ noteId: note.noteId, type: 'relation', - value: imageNote.noteId, - name: 'imageLink' + name: 'imageLink', + value: imageNote.noteId }).save(); console.log(`Replacing ${imageId} with ${url}`); @@ -155,4 +161,4 @@ module.exports = { addClipping, openNote, handshake -}; \ No newline at end of file +}; diff --git a/src/services/note_cache.js b/src/services/note_cache.js index e1b4efee2..d1f6134af 100644 --- a/src/services/note_cache.js +++ b/src/services/note_cache.js @@ -1,7 +1,6 @@ const sql = require('./sql'); const sqlInit = require('./sql_init'); const eventService = require('./events'); -const repository = require('./repository'); const protectedSessionService = require('./protected_session'); const utils = require('./utils'); const hoistedNoteService = require('./hoisted_note'); @@ -14,9 +13,6 @@ let branches /** @type {Object.} */ let attributes; -/** @type {Object.} */ -let noteAttributeCache = {}; - let childParentToBranch = {}; class Note { @@ -28,7 +24,7 @@ class Note { /** @param {boolean} */ this.isProtected = !!row.isProtected; /** @param {boolean} */ - this.isDecrypted = false; + this.isDecrypted = !row.isProtected || !!row.isContentAvailable; /** @param {Note[]} */ this.parents = []; /** @param {Note[]} */ @@ -45,6 +41,10 @@ class Note { /** @param {string|null} */ this.fulltextCache = null; + + if (protectedSessionService.isProtectedSessionAvailable()) { + decryptProtectedNote(this); + } } /** @return {Attribute[]} */ @@ -114,6 +114,12 @@ class Note { return this.hasAttribute('label', 'archived'); } + get isHideInAutocompleteOrArchived() { + return this.attributes.find(attr => + attr.type === 'label' + && ["archived", "hideInAutocomplete"].includes(attr.name)); + } + get hasInheritableOwnedArchivedLabel() { return !!this.ownedAttributes.find(attr => attr.type === 'label' && attr.name === 'archived' && attr.isInheritable); } @@ -126,6 +132,11 @@ class Note { get fulltext() { if (!this.fulltextCache) { + if (this.isHideInAutocompleteOrArchived) { + this.fulltextCache = " "; // can't be empty + return this.fulltextCache; + } + this.fulltextCache = this.title.toLowerCase(); for (const attr of this.attributes) { @@ -193,6 +204,21 @@ class Branch { /** @param {string} */ this.prefix = row.prefix; + if (this.branchId === 'root') { + return; + } + + const childNote = notes[this.noteId]; + const parentNote = this.parentNote; + + if (!childNote) { + console.log(`Cannot find child note ${this.noteId} of a branch ${this.branchId}`); + return; + } + + childNote.parents.push(parentNote); + parentNote.children.push(childNote); + childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; } @@ -222,6 +248,8 @@ class Attribute { this.value = row.value; /** @param {boolean} */ this.isInheritable = !!row.isInheritable; + + notes[this.noteId].ownedAttributes.push(this); } get isAffectingSubtree() { @@ -264,42 +292,21 @@ async function load() { attributes = await getMappedRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, row => new Attribute(row)); - for (const attr of Object.values(attributes)) { - notes[attr.noteId].ownedAttributes.push(attr); - } - - for (const branch of Object.values(branches)) { - if (branch.branchId === 'root') { - continue; - } - - const childNote = notes[branch.noteId]; - const parentNote = branch.parentNote; - - if (!childNote) { - console.log(`Cannot find child note ${branch.noteId} of a branch ${branch.branchId}`); - continue; - } - - childNote.parents.push(parentNote); - parentNote.children.push(childNote); - } - - if (protectedSessionService.isProtectedSessionAvailable()) { - await decryptProtectedNotes(); - } - loaded = true; loadedPromiseResolve(); } -async function decryptProtectedNotes() { - for (const note of notes) { - if (note.isProtected && !note.isDecrypted) { - note.title = protectedSessionService.decryptString(note.title); +function decryptProtectedNote(note) { + if (note.isProtected && !note.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { + note.title = protectedSessionService.decryptString(note.title); - note.isDecrypted = true; - } + note.isDecrypted = true; + } +} + +async function decryptProtectedNotes() { + for (const note of Object.values(notes)) { + decryptProtectedNote(note); } } @@ -770,11 +777,17 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED // we can assume we have protected session since we managed to update note.title = entity.title; - note.isDecrypted = true; + note.isProtected = entity.isProtected; + note.isDecrypted = !entity.isProtected || !!entity.isContentAvailable; note.fulltextCache = null; + + decryptProtectedNote(note); } else { - notes[noteId] = new Note(entity); + const note = new Note(entity); + notes[noteId] = note; + + decryptProtectedNote(note); } } else if (entityName === 'branches') { @@ -848,8 +861,6 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED attributes[attributeId] = attr; if (note) { - note.ownedAttributes.push(attr); - if (attr.isAffectingSubtree) { note.invalidateSubtreeCaches(); }