| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | const sql = require('./sql'); | 
					
						
							|  |  |  | const sqlInit = require('./sql_init'); | 
					
						
							| 
									
										
										
										
											2018-04-20 00:12:01 -04:00
										 |  |  | const eventService = require('./events'); | 
					
						
							| 
									
										
										
										
											2018-04-18 23:11:30 -04:00
										 |  |  | const repository = require('./repository'); | 
					
						
							| 
									
										
										
										
											2018-04-20 00:12:01 -04:00
										 |  |  | const protectedSessionService = require('./protected_session'); | 
					
						
							| 
									
										
										
										
											2018-05-26 10:24:33 -04:00
										 |  |  | const utils = require('./utils'); | 
					
						
							| 
									
										
										
										
											2018-12-13 21:18:35 +01:00
										 |  |  | const hoistedNoteService = require('./hoisted_note'); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | const stringSimilarity = require('string-similarity'); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-23 21:15:32 +02:00
										 |  |  | let loaded = false; | 
					
						
							| 
									
										
										
										
											2019-10-25 21:57:08 +02:00
										 |  |  | let loadedPromiseResolve; | 
					
						
							|  |  |  | /** Is resolved after the initial load */ | 
					
						
							|  |  |  | let loadedPromise = new Promise(res => { | 
					
						
							|  |  |  |     loadedPromiseResolve = res; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-28 19:22:46 +02:00
										 |  |  | let noteTitles = {}; | 
					
						
							|  |  |  | let protectedNoteTitles = {}; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | let noteIds; | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  | let childParentToBranchId = {}; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | const childToParent = {}; | 
					
						
							| 
									
										
										
										
											2018-08-08 16:14:35 +02:00
										 |  |  | let archived = {}; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  | // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
 | 
					
						
							|  |  |  | let prefixes = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | async function load() { | 
					
						
							| 
									
										
										
										
											2018-05-26 10:24:33 -04:00
										 |  |  |     noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |     noteIds = Object.keys(noteTitles); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-26 10:24:33 -04:00
										 |  |  |     prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`); | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  |     const relations = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (const rel of relations) { | 
					
						
							|  |  |  |         childToParent[rel.noteId] = childToParent[rel.noteId] || []; | 
					
						
							|  |  |  |         childToParent[rel.noteId].push(rel.parentNoteId); | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  |         childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-08 16:14:35 +02:00
										 |  |  |     archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`); | 
					
						
							| 
									
										
										
										
											2018-07-23 21:15:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-08 20:19:41 +01:00
										 |  |  |     if (protectedSessionService.isProtectedSessionAvailable()) { | 
					
						
							|  |  |  |         await loadProtectedNotes(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     for (const noteId in childToParent) { | 
					
						
							|  |  |  |         resortChildToParent(noteId); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-23 21:15:32 +02:00
										 |  |  |     loaded = true; | 
					
						
							| 
									
										
										
										
											2019-10-25 21:57:08 +02:00
										 |  |  |     loadedPromiseResolve(); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-08 20:19:41 +01:00
										 |  |  | async function loadProtectedNotes() { | 
					
						
							|  |  |  |     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const noteId in protectedNoteTitles) { | 
					
						
							|  |  |  |         protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  | function highlightResults(results, allTokens) { | 
					
						
							|  |  |  |     // we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
 | 
					
						
							|  |  |  |     // which would make the resulting HTML string invalid.
 | 
					
						
							| 
									
										
										
										
											2018-11-26 22:35:19 +01:00
										 |  |  |     // { and } are used for marking <b> and </b> tag (to avoid matches on single 'b' character)
 | 
					
						
							|  |  |  |     allTokens = allTokens.map(token => token.replace('/[<\{\}]/g', '')); | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // sort by the longest so we first highlight longest matches
 | 
					
						
							|  |  |  |     allTokens.sort((a, b) => a.length > b.length ? -1 : 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:16:33 +01:00
										 |  |  |     for (const result of results) { | 
					
						
							|  |  |  |         result.highlighted = result.title; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  |     for (const token of allTokens) { | 
					
						
							|  |  |  |         const tokenRegex = new RegExp("(" + utils.escapeRegExp(token) + ")", "gi"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const result of results) { | 
					
						
							| 
									
										
										
										
											2018-11-26 22:35:19 +01:00
										 |  |  |             result.highlighted = result.highlighted.replace(tokenRegex, "{$1}"); | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-26 22:35:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (const result of results) { | 
					
						
							|  |  |  |         result.highlighted = result.highlighted | 
					
						
							|  |  |  |             .replace(/{/g, "<b>") | 
					
						
							|  |  |  |             .replace(/}/g, "</b>"); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-12 21:28:38 +01:00
										 |  |  | async function findNotes(query) { | 
					
						
							| 
									
										
										
										
											2018-08-03 13:06:56 +02:00
										 |  |  |     if (!noteTitles || !query.length) { | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |         return []; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-06 18:28:53 +02:00
										 |  |  |     const allTokens = query | 
					
						
							|  |  |  |         .trim() // necessary because even with .split() trailing spaces are tokens which causes havoc
 | 
					
						
							|  |  |  |         .toLowerCase() | 
					
						
							|  |  |  |         .split(/[ -]/) | 
					
						
							|  |  |  |         .filter(token => token !== '/'); // '/' is used as separator
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  |     const tokens = allTokens.slice(); | 
					
						
							| 
									
										
										
										
											2018-12-12 21:28:38 +01:00
										 |  |  |     let results = []; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-20 00:12:01 -04:00
										 |  |  |     let noteIds = Object.keys(noteTitles); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (protectedSessionService.isProtectedSessionAvailable()) { | 
					
						
							|  |  |  |         noteIds = noteIds.concat(Object.keys(protectedNoteTitles)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const noteId of noteIds) { | 
					
						
							| 
									
										
										
										
											2018-08-17 10:06:52 +02:00
										 |  |  |         // autocomplete should be able to find notes by their noteIds as well (only leafs)
 | 
					
						
							|  |  |  |         if (noteId === query) { | 
					
						
							|  |  |  |             search(noteId, [], [], results); | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |         // for leaf note it doesn't matter if "archived" label is inheritable or not
 | 
					
						
							| 
									
										
										
										
											2018-08-08 16:14:35 +02:00
										 |  |  |         if (noteId in archived) { | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         const parents = childToParent[noteId]; | 
					
						
							|  |  |  |         if (!parents) { | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         for (const parentNoteId of parents) { | 
					
						
							| 
									
										
										
										
											2018-08-08 16:14:35 +02:00
										 |  |  |             // for parent note archived needs to be inheritable
 | 
					
						
							|  |  |  |             if (archived[parentNoteId] === 1) { | 
					
						
							| 
									
										
										
										
											2018-05-26 10:50:13 -04:00
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 20:22:41 -04:00
										 |  |  |             const title = getNoteTitle(noteId, parentNoteId).toLowerCase(); | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |             const foundTokens = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (const token of tokens) { | 
					
						
							|  |  |  |                 if (title.includes(token)) { | 
					
						
							|  |  |  |                     foundTokens.push(token); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |             if (foundTokens.length > 0) { | 
					
						
							|  |  |  |                 const remainingTokens = tokens.filter(token => !foundTokens.includes(token)); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |                 search(parentNoteId, remainingTokens, [noteId], results); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 21:18:35 +01:00
										 |  |  |     if (hoistedNoteService.getHoistedNoteId() !== 'root') { | 
					
						
							|  |  |  |         results = results.filter(res => res.pathArray.includes(hoistedNoteService.getHoistedNoteId())); | 
					
						
							| 
									
										
										
										
											2018-12-12 21:28:38 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 22:22:26 +01:00
										 |  |  |     // sort results by depth of the note. This is based on the assumption that more important results
 | 
					
						
							|  |  |  |     // are closer to the note root.
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |     results.sort((a, b) => { | 
					
						
							| 
									
										
										
										
											2018-11-20 22:22:26 +01:00
										 |  |  |         if (a.pathArray.length === b.pathArray.length) { | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |             return a.title < b.title ? -1 : 1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 22:22:26 +01:00
										 |  |  |         return a.pathArray.length < b.pathArray.length ? -1 : 1; | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-04-18 20:56:23 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |     const apiResults = results.slice(0, 200).map(res => { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             noteId: res.noteId, | 
					
						
							|  |  |  |             branchId: res.branchId, | 
					
						
							|  |  |  |             path: res.pathArray.join('/'), | 
					
						
							|  |  |  |             title: res.titleArray.join(' / ') | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     highlightResults(apiResults, allTokens); | 
					
						
							| 
									
										
										
										
											2018-11-07 09:35:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |     return apiResults; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  | function isNotePathArchived(notePath) { | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     // if the note is archived directly
 | 
					
						
							|  |  |  |     if (archived[notePath[notePath.length - 1]] !== undefined) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let i = 0; i < notePath.length - 1; i++) { | 
					
						
							|  |  |  |         // this is going through parents so archived must be inheritable
 | 
					
						
							|  |  |  |         if (archived[notePath[i]] === 1) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * This assumes that note is available. "archived" note means that there isn't a single non-archived note-path | 
					
						
							|  |  |  |  * leading to this note. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param noteId | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function isArchived(noteId) { | 
					
						
							|  |  |  |     const notePath = getSomePath(noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return isNotePathArchived(notePath); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  | function search(noteId, tokens, path, results) { | 
					
						
							| 
									
										
										
										
											2018-04-18 20:56:23 -04:00
										 |  |  |     if (tokens.length === 0) { | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         const retPath = getSomePath(noteId, path); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  |         if (retPath && !isNotePathArchived(retPath)) { | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  |             const thisNoteId = retPath[retPath.length - 1]; | 
					
						
							|  |  |  |             const thisParentNoteId = retPath[retPath.length - 2]; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  |             results.push({ | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  |                 noteId: thisNoteId, | 
					
						
							|  |  |  |                 branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`], | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |                 pathArray: retPath, | 
					
						
							|  |  |  |                 titleArray: getNoteTitleArrayForPath(retPath) | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  |             }); | 
					
						
							| 
									
										
										
										
											2018-04-18 20:56:23 -04:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     const parents = childToParent[noteId]; | 
					
						
							| 
									
										
										
										
											2018-08-13 21:01:14 +02:00
										 |  |  |     if (!parents || noteId === 'root') { | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const parentNoteId of parents) { | 
					
						
							| 
									
										
										
										
											2018-08-08 16:14:35 +02:00
										 |  |  |         // archived must be inheritable
 | 
					
						
							| 
									
										
										
										
											2018-08-13 21:01:14 +02:00
										 |  |  |         if (archived[parentNoteId] === 1) { | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-05-26 10:50:13 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-13 21:01:14 +02:00
										 |  |  |         const title = getNoteTitle(noteId, parentNoteId).toLowerCase(); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |         const foundTokens = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const token of tokens) { | 
					
						
							|  |  |  |             if (title.includes(token)) { | 
					
						
							|  |  |  |                 foundTokens.push(token); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (foundTokens.length > 0) { | 
					
						
							|  |  |  |             const remainingTokens = tokens.filter(token => !foundTokens.includes(token)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |             search(parentNoteId, remainingTokens, path.concat([noteId]), results); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |             search(parentNoteId, tokens, path.concat([noteId]), results); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 20:22:41 -04:00
										 |  |  | function getNoteTitle(noteId, parentNoteId) { | 
					
						
							| 
									
										
										
										
											2018-04-20 00:12:01 -04:00
										 |  |  |     const prefix = prefixes[noteId + '-' + parentNoteId]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let title = noteTitles[noteId]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!title) { | 
					
						
							|  |  |  |         if (protectedSessionService.isProtectedSessionAvailable()) { | 
					
						
							|  |  |  |             title = protectedNoteTitles[noteId]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             title = '[protected]'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return (prefix ? (prefix + ' - ') : '') + title; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  | function getNoteTitleArrayForPath(path) { | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     const titles = []; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 21:43:13 +01:00
										 |  |  |     if (path[0] === hoistedNoteService.getHoistedNoteId() && path.length === 1) { | 
					
						
							|  |  |  |         return [ getNoteTitle(hoistedNoteService.getHoistedNoteId()) ]; | 
					
						
							| 
									
										
										
										
											2018-06-06 22:38:36 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     let parentNoteId = 'root'; | 
					
						
							| 
									
										
										
										
											2018-12-13 21:18:35 +01:00
										 |  |  |     let hoistedNotePassed = false; | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     for (const noteId of path) { | 
					
						
							| 
									
										
										
										
											2018-12-13 21:18:35 +01:00
										 |  |  |         // start collecting path segment titles only after hoisted note
 | 
					
						
							|  |  |  |         if (hoistedNotePassed) { | 
					
						
							|  |  |  |             const title = getNoteTitle(noteId, parentNoteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             titles.push(title); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (noteId === hoistedNoteService.getHoistedNoteId()) { | 
					
						
							|  |  |  |             hoistedNotePassed = true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         parentNoteId = noteId; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-20 21:22:20 +01:00
										 |  |  |     return titles; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getNoteTitleForPath(path) { | 
					
						
							|  |  |  |     const titles = getNoteTitleArrayForPath(path); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     return titles.join(' / '); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Returns notePath for noteId from cache. Note hoisting is respected. | 
					
						
							|  |  |  |  * Archived notes are also returned, but non-archived paths are preferred if available | 
					
						
							|  |  |  |  * - this means that archived paths is returned only if there's no non-archived path | 
					
						
							|  |  |  |  * - you can check whether returned path is archived using isArchived() | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getSomePath(noteId, path = []) { | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     if (noteId === 'root') { | 
					
						
							| 
									
										
										
										
											2018-12-13 21:43:13 +01:00
										 |  |  |         path.push(noteId); | 
					
						
							|  |  |  |         path.reverse(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 21:18:35 +01:00
										 |  |  |         if (!path.includes(hoistedNoteService.getHoistedNoteId())) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         return path; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     const parents = childToParent[noteId]; | 
					
						
							|  |  |  |     if (!parents || parents.length === 0) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const parentNoteId of parents) { | 
					
						
							|  |  |  |         const retPath = getSomePath(parentNoteId, path.concat([noteId])); | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (retPath) { | 
					
						
							|  |  |  |             return retPath; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 19:48:02 -04:00
										 |  |  | function getNotePath(noteId) { | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     const retPath = getSomePath(noteId); | 
					
						
							| 
									
										
										
										
											2018-06-03 20:42:25 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  |     if (retPath) { | 
					
						
							| 
									
										
										
										
											2018-06-03 20:42:25 -04:00
										 |  |  |         const noteTitle = getNoteTitleForPath(retPath); | 
					
						
							| 
									
										
										
										
											2018-06-05 22:47:47 -04:00
										 |  |  |         const parentNoteId = childToParent[noteId][0]; | 
					
						
							| 
									
										
										
										
											2018-06-03 20:42:25 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             noteId: noteId, | 
					
						
							| 
									
										
										
										
											2018-06-05 22:47:47 -04:00
										 |  |  |             branchId: childParentToBranchId[`${noteId}-${parentNoteId}`], | 
					
						
							| 
									
										
										
										
											2018-06-03 20:42:25 -04:00
										 |  |  |             title: noteTitle, | 
					
						
							| 
									
										
										
										
											2019-09-07 10:11:59 +02:00
										 |  |  |             notePath: retPath, | 
					
						
							| 
									
										
										
										
											2018-06-03 20:42:25 -04:00
										 |  |  |             path: retPath.join('/') | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | function evaluateSimilarity(text1, text2, noteId, results) { | 
					
						
							|  |  |  |     let coeff = stringSimilarity.compareTwoStrings(text1, text2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (coeff > 0.4) { | 
					
						
							|  |  |  |         const notePath = getSomePath(noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // this takes care of note hoisting
 | 
					
						
							|  |  |  |         if (!notePath) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  |         if (isNotePathArchived(notePath)) { | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |             coeff -= 0.2; // archived penalization
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-02 21:36:24 +02:00
										 |  |  |         results.push({coeff, notePath, noteId}); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 21:16:14 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Point of this is to break up long running sync process to avoid blocking | 
					
						
							|  |  |  |  * see https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function setImmediatePromise() { | 
					
						
							|  |  |  |     return new Promise((resolve) => { | 
					
						
							|  |  |  |         setImmediate(() => resolve()); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function evaluateSimilarityDict(title, dict, results) { | 
					
						
							|  |  |  |     let i = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const noteId in dict) { | 
					
						
							|  |  |  |         evaluateSimilarity(title, dict[noteId], noteId, results); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-03 21:16:14 +02:00
										 |  |  |         i++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (i % 200 === 0) { | 
					
						
							|  |  |  |             await setImmediatePromise(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-09-03 21:16:14 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function findSimilarNotes(title) { | 
					
						
							|  |  |  |     const results = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await evaluateSimilarityDict(title, noteTitles, results); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (protectedSessionService.isProtectedSessionAvailable()) { | 
					
						
							| 
									
										
										
										
											2019-09-03 21:16:14 +02:00
										 |  |  |         await evaluateSimilarityDict(title, protectedNoteTitles, results); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results.sort((a, b) => a.coeff > b.coeff ? -1 : 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return results.length > 50 ? results.slice(0, 50) : results; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-03 23:27:10 +01:00
										 |  |  | eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], async ({entityName, entity}) => { | 
					
						
							|  |  |  |     // note that entity can also be just POJO without methods if coming from sync
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-23 21:15:32 +02:00
										 |  |  |     if (!loaded) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-18 23:11:30 -04:00
										 |  |  |     if (entityName === 'notes') { | 
					
						
							| 
									
										
										
										
											2018-08-15 22:06:49 +02:00
										 |  |  |         const note = entity; | 
					
						
							| 
									
										
										
										
											2018-04-18 23:11:30 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (note.isDeleted) { | 
					
						
							|  |  |  |             delete noteTitles[note.noteId]; | 
					
						
							|  |  |  |             delete childToParent[note.noteId]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							| 
									
										
										
										
											2019-01-08 20:19:41 +01:00
										 |  |  |             if (note.isProtected) { | 
					
						
							|  |  |  |                 // we can assume we have protected session since we managed to update
 | 
					
						
							|  |  |  |                 // removing from the maps is important when switching between protected & unprotected
 | 
					
						
							|  |  |  |                 protectedNoteTitles[note.noteId] = note.title; | 
					
						
							|  |  |  |                 delete noteTitles[note.noteId]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else { | 
					
						
							|  |  |  |                 noteTitles[note.noteId] = note.title; | 
					
						
							|  |  |  |                 delete protectedNoteTitles[note.noteId]; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-04-18 23:11:30 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |     else if (entityName === 'branches') { | 
					
						
							| 
									
										
										
										
											2018-08-15 22:06:49 +02:00
										 |  |  |         const branch = entity; | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-31 19:55:56 +02:00
										 |  |  |         // first we remove records for original placement (if they exist)
 | 
					
						
							|  |  |  |         childToParent[branch.noteId] = childToParent[branch.noteId] || []; | 
					
						
							|  |  |  |         childToParent[branch.noteId] = childToParent[branch.noteId].filter(noteId => noteId !== branch.origParentNoteId); | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-31 19:55:56 +02:00
										 |  |  |         delete prefixes[branch.noteId + '-' + branch.origParentNoteId]; | 
					
						
							|  |  |  |         delete childParentToBranchId[branch.noteId + '-' + branch.origParentNoteId]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!branch.isDeleted) { | 
					
						
							|  |  |  |             // ... and then we create new records
 | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |             if (branch.prefix) { | 
					
						
							|  |  |  |                 prefixes[branch.noteId + '-' + branch.parentNoteId] = branch.prefix; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             childToParent[branch.noteId].push(branch.parentNoteId); | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |             resortChildToParent(branch.noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 23:21:45 -04:00
										 |  |  |             childParentToBranchId[branch.noteId + '-' + branch.parentNoteId] = branch.branchId; | 
					
						
							| 
									
										
										
										
											2018-04-19 20:59:44 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-08-07 13:44:51 +02:00
										 |  |  |     else if (entityName === 'attributes') { | 
					
						
							| 
									
										
										
										
											2018-08-15 22:06:49 +02:00
										 |  |  |         const attribute = entity; | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-07 13:44:51 +02:00
										 |  |  |         if (attribute.type === 'label' && attribute.name === 'archived') { | 
					
						
							| 
									
										
										
										
											2018-06-07 19:26:28 -04:00
										 |  |  |             // we're not using label object directly, since there might be other non-deleted archived label
 | 
					
						
							| 
									
										
										
										
											2018-11-26 22:22:16 +01:00
										 |  |  |             const archivedLabel = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' 
 | 
					
						
							| 
									
										
										
										
											2018-08-07 13:44:51 +02:00
										 |  |  |                                  AND name = 'archived' AND noteId = ?`, [attribute.noteId]);
 | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-26 22:22:16 +01:00
										 |  |  |             if (archivedLabel) { | 
					
						
							|  |  |  |                 archived[attribute.noteId] = archivedLabel.isInheritable ? 1 : 0; | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  |             } | 
					
						
							|  |  |  |             else { | 
					
						
							| 
									
										
										
										
											2018-08-07 13:44:51 +02:00
										 |  |  |                 delete archived[attribute.noteId]; | 
					
						
							| 
									
										
										
										
											2018-04-19 00:13:55 -04:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-18 23:11:30 -04:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  | // will sort the childs so that non-archived are first and archived at the end
 | 
					
						
							|  |  |  | // this is done so that non-archived paths are always explored as first when searching for note path
 | 
					
						
							|  |  |  | function resortChildToParent(noteId) { | 
					
						
							|  |  |  |     if (!childToParent[noteId]) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     childToParent[noteId].sort((a, b) => archived[a] === 1 ? 1 : -1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-22 18:08:33 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param noteId | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  |  * @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting | 
					
						
							| 
									
										
										
										
											2019-04-22 18:08:33 +02:00
										 |  |  |  */ | 
					
						
							|  |  |  | function isAvailable(noteId) { | 
					
						
							|  |  |  |     const notePath = getNotePath(noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return !!notePath; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-08 20:19:41 +01:00
										 |  |  | eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | 
					
						
							|  |  |  |     if (loaded) { | 
					
						
							|  |  |  |         loadProtectedNotes(); | 
					
						
							| 
									
										
										
										
											2018-04-20 00:12:01 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-25 21:57:08 +02:00
										 |  |  | sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load)); | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							| 
									
										
										
										
											2019-10-25 21:57:08 +02:00
										 |  |  |     loadedPromise, | 
					
						
							| 
									
										
										
										
											2018-06-04 19:48:02 -04:00
										 |  |  |     findNotes, | 
					
						
							| 
									
										
										
										
											2018-06-06 22:38:36 -04:00
										 |  |  |     getNotePath, | 
					
						
							| 
									
										
										
										
											2019-01-08 20:19:41 +01:00
										 |  |  |     getNoteTitleForPath, | 
					
						
							| 
									
										
										
										
											2019-04-22 18:08:33 +02:00
										 |  |  |     isAvailable, | 
					
						
							| 
									
										
										
										
											2019-09-03 20:16:38 +02:00
										 |  |  |     isArchived, | 
					
						
							| 
									
										
										
										
											2019-09-01 11:33:45 +02:00
										 |  |  |     load, | 
					
						
							|  |  |  |     findSimilarNotes | 
					
						
							| 
									
										
										
										
											2018-04-18 00:26:42 -04:00
										 |  |  | }; |