mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-01 05:21:32 +08:00 
			
		
		
		
	Merge branch 'master' into eslint-format
This commit is contained in:
		
						commit
						3eafdadd1f
					
				
							
								
								
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							| @ -46,7 +46,7 @@ | |||||||
|         const errors = new eslint().verify(text, { |         const errors = new eslint().verify(text, { | ||||||
|             root: true, |             root: true, | ||||||
|             parserOptions: { |             parserOptions: { | ||||||
|                 ecmaVersion: 2019 |                 ecmaVersion: 2022 | ||||||
|             }, |             }, | ||||||
|             extends: ['eslint:recommended', 'airbnb-base'], |             extends: ['eslint:recommended', 'airbnb-base'], | ||||||
|             env: { |             env: { | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								nodemon.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								nodemon.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "restartable": "rs", | ||||||
|  |     "ignore": [".git", "node_modules/**/node_modules", "src/public/"], | ||||||
|  |     "verbose": false, | ||||||
|  |     "execMap": { | ||||||
|  |         "js": "node --harmony" | ||||||
|  |     }, | ||||||
|  |     "watch": ["src/"], | ||||||
|  |     "env": { | ||||||
|  |         "NODE_ENV": "development" | ||||||
|  |     }, | ||||||
|  |     "ext": "js,json" | ||||||
|  | } | ||||||
| @ -13,16 +13,16 @@ | |||||||
|     "url": "https://github.com/zadam/trilium.git" |     "url": "https://github.com/zadam/trilium.git" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www", |     "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||||
|     "start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www", |     "start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||||
|     "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", |     "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", | ||||||
|     "start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", |     "start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", | ||||||
|     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", |     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", | ||||||
|     "switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild", |     "switch-electron": "./node_modules/.bin/electron-rebuild", | ||||||
|     "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", |     "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", | ||||||
|     "build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/right_panel_widget.js", |     "build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/right_panel_widget.js", | ||||||
|     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", |     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||||
|     "webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js", |     "webpack": "webpack -c webpack.config.js", | ||||||
|     "test-jasmine": "jasmine", |     "test-jasmine": "jasmine", | ||||||
|     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", |     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", | ||||||
|     "test": "npm run test-jasmine && npm run test-es6", |     "test": "npm run test-jasmine && npm run test-es6", | ||||||
| @ -115,6 +115,7 @@ | |||||||
|     "jsdoc": "4.0.2", |     "jsdoc": "4.0.2", | ||||||
|     "lorem-ipsum": "2.0.8", |     "lorem-ipsum": "2.0.8", | ||||||
|     "prettier": "2.8.7", |     "prettier": "2.8.7", | ||||||
|  |     "nodemon": "^2.0.22", | ||||||
|     "rcedit": "3.0.1", |     "rcedit": "3.0.1", | ||||||
|     "webpack": "5.78.0", |     "webpack": "5.78.0", | ||||||
|     "webpack-cli": "5.0.1" |     "webpack-cli": "5.0.1" | ||||||
|  | |||||||
| @ -24,49 +24,12 @@ function isNotePathArchived(notePath) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * 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); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param {string} noteId |  | ||||||
|  * @param {string} ancestorNoteId |  | ||||||
|  * @returns {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived) |  | ||||||
|  */ |  | ||||||
| function isInAncestor(noteId, ancestorNoteId) { |  | ||||||
|     if (ancestorNoteId === 'root' || ancestorNoteId === noteId) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const note = becca.notes[noteId]; |  | ||||||
| 
 |  | ||||||
|     if (!note) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (const parentNote of note.parents) { |  | ||||||
|         if (isInAncestor(parentNote.noteId, ancestorNoteId)) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getNoteTitle(childNoteId, parentNoteId) { | function getNoteTitle(childNoteId, parentNoteId) { | ||||||
|     const childNote = becca.notes[childNoteId]; |     const childNote = becca.notes[childNoteId]; | ||||||
|     const parentNote = becca.notes[parentNoteId]; |     const parentNote = becca.notes[parentNoteId]; | ||||||
| 
 | 
 | ||||||
|     if (!childNote) { |     if (!childNote) { | ||||||
|         log.info(`Cannot find note in cache for noteId '${childNoteId}'`); |         log.info(`Cannot find note '${childNoteId}'`); | ||||||
|         return "[error fetching title]"; |         return "[error fetching title]"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -119,107 +82,8 @@ function getNoteTitleForPath(notePathArray) { | |||||||
|     return titles.join(' / '); |     return titles.join(' / '); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Returns notePath for noteId from cache. Note hoisting is respected. |  | ||||||
|  * Archived (and hidden) 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 |  | ||||||
|  * |  | ||||||
|  * @param {BNote} note |  | ||||||
|  * @param {string[]} path |  | ||||||
|  */ |  | ||||||
| function getSomePath(note, path = []) { |  | ||||||
|     // first try to find note within hoisted note, otherwise take any existing note path
 |  | ||||||
|     return getSomePathInner(note, path, true) |  | ||||||
|         || getSomePathInner(note, path, false); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param {BNote} note |  | ||||||
|  * @param {string[]} path |  | ||||||
|  * @param {boolean}respectHoisting |  | ||||||
|  * @returns {string[]|false} |  | ||||||
|  */ |  | ||||||
| function getSomePathInner(note, path, respectHoisting) { |  | ||||||
|     if (note.isRoot()) { |  | ||||||
|         const foundPath = [...path, note.noteId]; |  | ||||||
|         foundPath.reverse(); |  | ||||||
| 
 |  | ||||||
|         if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return foundPath; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const parents = note.parents; |  | ||||||
|     if (parents.length === 0) { |  | ||||||
|         console.log(`Note '${note.noteId}' - '${note.title}' has no parents.`); |  | ||||||
| 
 |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for (const parentNote of parents) { |  | ||||||
|         const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting); |  | ||||||
| 
 |  | ||||||
|         if (retPath) { |  | ||||||
|             return retPath; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getNotePath(noteId) { |  | ||||||
|     const note = becca.notes[noteId]; |  | ||||||
| 
 |  | ||||||
|     if (!note) { |  | ||||||
|         console.trace(`Cannot find note '${noteId}' in cache.`); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const retPath = getSomePath(note); |  | ||||||
| 
 |  | ||||||
|     if (retPath) { |  | ||||||
|         const noteTitle = getNoteTitleForPath(retPath); |  | ||||||
| 
 |  | ||||||
|         let branchId; |  | ||||||
| 
 |  | ||||||
|         if (note.isRoot()) { |  | ||||||
|             branchId = 'none_root'; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             const parentNote = note.parents[0]; |  | ||||||
|             branchId = becca.getBranchFromChildAndParent(noteId, parentNote.noteId).branchId; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return { |  | ||||||
|             noteId: noteId, |  | ||||||
|             branchId: branchId, |  | ||||||
|             title: noteTitle, |  | ||||||
|             notePath: retPath, |  | ||||||
|             path: retPath.join('/') |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * @param noteId |  | ||||||
|  * @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting |  | ||||||
|  */ |  | ||||||
| function isAvailable(noteId) { |  | ||||||
|     const notePath = getNotePath(noteId); |  | ||||||
| 
 |  | ||||||
|     return !!notePath; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getSomePath, |  | ||||||
|     getNotePath, |  | ||||||
|     getNoteTitle, |     getNoteTitle, | ||||||
|     getNoteTitleForPath, |     getNoteTitleForPath, | ||||||
|     isAvailable, |  | ||||||
|     isArchived, |  | ||||||
|     isInAncestor, |  | ||||||
|     isNotePathArchived |     isNotePathArchived | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -747,6 +747,21 @@ class BNote extends AbstractBeccaEntity { | |||||||
|         return this.hasAttribute('label', 'archived'); |         return this.hasAttribute('label', 'archived'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     areAllNotePathsArchived() { | ||||||
|  |         // there's a slight difference between note being itself archived and all its note paths being archived
 | ||||||
|  |         // - note is archived when it itself has an archived label or inherits it
 | ||||||
|  |         // - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable)
 | ||||||
|  |         //   archived label
 | ||||||
|  | 
 | ||||||
|  |         const bestNotePathRecord = this.getSortedNotePathRecords()[0]; | ||||||
|  | 
 | ||||||
|  |         if (!bestNotePathRecord) { | ||||||
|  |             throw new Error(`No note path available for note '${this.noteId}'`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return bestNotePathRecord.isArchived; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     hasInheritableArchivedLabel() { |     hasInheritableArchivedLabel() { | ||||||
|         for (const attr of this.getAttributes()) { |         for (const attr of this.getAttributes()) { | ||||||
|             if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { |             if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { | ||||||
| @ -1150,6 +1165,8 @@ class BNote extends AbstractBeccaEntity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|      */ |      */ | ||||||
|     getAllNotePaths() { |     getAllNotePaths() { | ||||||
| @ -1157,18 +1174,73 @@ class BNote extends AbstractBeccaEntity { | |||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const notePaths = []; |         const parentNotes = this.getParentNotes(); | ||||||
|  |         let notePaths = []; | ||||||
| 
 | 
 | ||||||
|         for (const parentNote of this.getParentNotes()) { |         if (parentNotes.length === 1) { // optimization for most common case
 | ||||||
|             for (const parentPath of parentNote.getAllNotePaths()) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 parentPath.push(this.noteId); |         } else { | ||||||
|                 notePaths.push(parentPath); |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         for (const notePath of notePaths) { | ||||||
|  |             notePath.push(this.noteId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
|  | 
 | ||||||
|  |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|  |             notePath: path, | ||||||
|  |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|  |             isArchived: path.some(noteId => this.becca.notes[noteId].isArchived), | ||||||
|  |             isHidden: path.includes('_hidden') | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         notePaths.sort((a, b) => { | ||||||
|  |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|  |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|  |             } else if (a.isArchived !== b.isArchived) { | ||||||
|  |                 return a.isArchived ? 1 : -1; | ||||||
|  |             } else if (a.isHidden !== b.isHidden) { | ||||||
|  |                 return a.isHidden ? 1 : -1; | ||||||
|  |             } else { | ||||||
|  |                 return a.notePath.length - b.notePath.length; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return notePaths; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  | 
 | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -404,7 +404,7 @@ async function findSimilarNotes(noteId) { | |||||||
|         let score = computeScore(candidateNote); |         let score = computeScore(candidateNote); | ||||||
| 
 | 
 | ||||||
|         if (score >= 1.5) { |         if (score >= 1.5) { | ||||||
|             const notePath = beccaService.getSomePath(candidateNote); |             const notePath = candidateNote.getBestNotePath(); | ||||||
| 
 | 
 | ||||||
|             // this takes care of note hoisting
 |             // this takes care of note hoisting
 | ||||||
|             if (!notePath) { |             if (!notePath) { | ||||||
|  | |||||||
| @ -388,7 +388,12 @@ export default class TabManager extends Component { | |||||||
|             await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove }); |             await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove }); | ||||||
| 
 | 
 | ||||||
|             if (!noteContextToRemove.isMainContext()) { |             if (!noteContextToRemove.isMainContext()) { | ||||||
|                 await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId); |                 const siblings = noteContextToRemove.getMainContext().getSubContexts(); | ||||||
|  |                 const idx = siblings.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId); | ||||||
|  |                 const contextToActivateIdx = idx === siblings.length - 1 ? idx - 1 : idx + 1; | ||||||
|  |                 const contextToActivate = siblings[contextToActivateIdx]; | ||||||
|  | 
 | ||||||
|  |                 await this.activateNoteContext(contextToActivate.ntxId); | ||||||
|             } |             } | ||||||
|             else if (this.mainNoteContexts.length <= 1) { |             else if (this.mainNoteContexts.length <= 1) { | ||||||
|                 await this.openAndActivateEmptyTab(); |                 await this.openAndActivateEmptyTab(); | ||||||
|  | |||||||
| @ -247,6 +247,11 @@ class FNote { | |||||||
|         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); |         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @param {string[]} path | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __getCachedAttributes(path) { |     __getCachedAttributes(path) { | ||||||
|         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
 |         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
 | ||||||
|         // when template instance is a parent of template itself
 |         // when template instance is a parent of template itself
 | ||||||
| @ -299,63 +304,49 @@ class FNote { | |||||||
|         return this.noteId === 'root'; |         return this.noteId === 'root'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getAllNotePaths(encounteredNoteIds = null) { |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|  |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|  |      */ | ||||||
|  |     getAllNotePaths() { | ||||||
|         if (this.noteId === 'root') { |         if (this.noteId === 'root') { | ||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!encounteredNoteIds) { |  | ||||||
|             encounteredNoteIds = new Set(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         encounteredNoteIds.add(this.noteId); |  | ||||||
| 
 |  | ||||||
|         const parentNotes = this.getParentNotes(); |         const parentNotes = this.getParentNotes(); | ||||||
|         let paths; |         let notePaths = []; | ||||||
| 
 | 
 | ||||||
|         if (parentNotes.length === 1) { // optimization for the most common case
 |         if (parentNotes.length === 1) { // optimization for most common case
 | ||||||
|             if (encounteredNoteIds.has(parentNotes[0].noteId)) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 return []; |         } else { | ||||||
|             } |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|             else { |  | ||||||
|                 paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             paths = []; |  | ||||||
| 
 |  | ||||||
|             for (const parentNote of parentNotes) { |  | ||||||
|                 if (encounteredNoteIds.has(parentNote.noteId)) { |  | ||||||
|                     continue; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|                 const newSet = new Set(encounteredNoteIds); |         for (const notePath of notePaths) { | ||||||
| 
 |             notePath.push(this.noteId); | ||||||
|                 paths.push(...parentNote.getAllNotePaths(newSet)); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const path of paths) { |         return notePaths; | ||||||
|             path.push(this.noteId); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         return paths; |     /** | ||||||
|     } |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
| 
 | 
 | ||||||
|     getSortedNotePaths(hoistedNotePath = 'root') { |  | ||||||
|         const notePaths = this.getAllNotePaths().map(path => ({ |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|             notePath: path, |             notePath: path, | ||||||
|             isInHoistedSubTree: path.includes(hoistedNotePath), |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|             isArchived: path.find(noteId => froca.notes[noteId].isArchived), |             isArchived: path.some(noteId => froca.notes[noteId].isArchived), | ||||||
|             isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), |  | ||||||
|             isHidden: path.includes('_hidden') |             isHidden: path.includes('_hidden') | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         notePaths.sort((a, b) => { |         notePaths.sort((a, b) => { | ||||||
|             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|                 return a.isInHoistedSubTree ? -1 : 1; |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|             } else if (a.isSearch !== b.isSearch) { |  | ||||||
|                 return a.isSearch ? 1 : -1; |  | ||||||
|             } else if (a.isArchived !== b.isArchived) { |             } else if (a.isArchived !== b.isArchived) { | ||||||
|                 return a.isArchived ? 1 : -1; |                 return a.isArchived ? 1 : -1; | ||||||
|             } else if (a.isHidden !== b.isHidden) { |             } else if (a.isHidden !== b.isHidden) { | ||||||
| @ -368,6 +359,28 @@ class FNote { | |||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  | 
 | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
| @ -391,6 +404,13 @@ class FNote { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @param {FAttribute[]} attributes | ||||||
|  |      * @param {string} type | ||||||
|  |      * @param {string} name | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __filterAttrs(attributes, type, name) { |     __filterAttrs(attributes, type, name) { | ||||||
|         this.__validateTypeName(type, name); |         this.__validateTypeName(type, name); | ||||||
| 
 | 
 | ||||||
| @ -527,7 +547,9 @@ class FNote { | |||||||
|      * @returns {boolean} true if note has an attribute with given type and name (including inherited) |      * @returns {boolean} true if note has an attribute with given type and name (including inherited) | ||||||
|      */ |      */ | ||||||
|     hasAttribute(type, name) { |     hasAttribute(type, name) { | ||||||
|         return !!this.getAttribute(type, name); |         const attributes = this.getAttributes(); | ||||||
|  | 
 | ||||||
|  |         return attributes.some(attr => attr.name === name && attr.type === type); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -227,7 +227,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function cloneNoteToNote(childNoteId, parentNoteId, prefix) { | async function cloneNoteToParentNote(childNoteId, parentNoteId, prefix) { | ||||||
|     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { |     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { | ||||||
|         prefix: prefix |         prefix: prefix | ||||||
|     }); |     }); | ||||||
| @ -254,5 +254,5 @@ export default { | |||||||
|     moveNodeUpInHierarchy, |     moveNodeUpInHierarchy, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ import server from "./server.js"; | |||||||
| import appContext from "../components/app_context.js"; | import appContext from "../components/app_context.js"; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import noteCreateService from './note_create.js'; | import noteCreateService from './note_create.js'; | ||||||
| import treeService from './tree.js'; |  | ||||||
| import froca from "./froca.js"; | import froca from "./froca.js"; | ||||||
| 
 | 
 | ||||||
| // this key needs to have this value, so it's hit by the tooltip
 | // this key needs to have this value, so it's hit by the tooltip
 | ||||||
| @ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) { | |||||||
|                 templateNoteId: templateNoteId |                 templateNoteId: templateNoteId | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             suggestion.notePath = treeService.getSomeNotePath(note); |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |             suggestion.notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $el.setSelectedNotePath(suggestion.notePath); |         $el.setSelectedNotePath(suggestion.notePath); | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import froca from "./froca.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import attributeRenderer from "./attribute_renderer.js"; | import attributeRenderer from "./attribute_renderer.js"; | ||||||
| import noteContentRenderer from "./note_content_renderer.js"; | import noteContentRenderer from "./note_content_renderer.js"; | ||||||
|  | import appContext from "../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| function setupGlobalTooltip() { | function setupGlobalTooltip() { | ||||||
|     $(document).on("mouseenter", "a", mouseEnterHandler); |     $(document).on("mouseenter", "a", mouseEnterHandler); | ||||||
| @ -83,13 +84,14 @@ async function renderTooltip(note) { | |||||||
|         return '<div>Note has been deleted.</div>'; |         return '<div>Note has been deleted.</div>'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const someNotePath = treeService.getSomeNotePath(note); |     const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |     const bestNotePath = note.getBestNotePathString(hoistedNoteId); | ||||||
| 
 | 
 | ||||||
|     if (!someNotePath) { |     if (!bestNotePath) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}</h5>`; |     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}</h5>`; | ||||||
| 
 | 
 | ||||||
|     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); |     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|                         You can ignore this message as it is mostly harmless.`);
 |                         You can ignore this message as it is mostly harmless.`);
 | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 const someNotePath = getSomeNotePath(child, hoistedNoteId); |                 const bestNotePath = child.getBestNotePath(hoistedNoteId); | ||||||
| 
 | 
 | ||||||
|                 if (someNotePath) { // in case it's root the path may be empty
 |                 if (bestNotePath) { | ||||||
|                     const pathToRoot = someNotePath.split("/").reverse().slice(1); |                     const pathToRoot = bestNotePath.reverse().slice(1); | ||||||
| 
 |  | ||||||
|                     if (!pathToRoot.includes("root")) { |  | ||||||
|                         pathToRoot.push('root'); |  | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|                     for (const noteId of pathToRoot) { |                     for (const noteId of pathToRoot) { | ||||||
|                         effectivePathSegments.push(noteId); |                         effectivePathSegments.push(noteId); | ||||||
| @ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|     else { |     else { | ||||||
|         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); |         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); | ||||||
| 
 | 
 | ||||||
|         const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId); |         const bestNotePath = note.getBestNotePath(hoistedNoteId); | ||||||
| 
 | 
 | ||||||
|         if (!someNotePathSegments) { |         if (!bestNotePath) { | ||||||
|             throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`); |             throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // if there isn't actually any note path with hoisted note then return the original resolved note path
 |         // if there isn't actually any note path with hoisted note then return the original resolved note path
 | ||||||
|         return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments; |         return bestNotePath.includes(hoistedNoteId) ? bestNotePath : effectivePathSegments; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getSomeNotePathSegments(note, hoistedNotePath = 'root') { |  | ||||||
|     utils.assertArguments(note); |  | ||||||
| 
 |  | ||||||
|     const notePaths = note.getSortedNotePaths(hoistedNotePath); |  | ||||||
| 
 |  | ||||||
|     return notePaths.length > 0 ? notePaths[0].notePath : null; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getSomeNotePath(note, hoistedNotePath = 'root') { |  | ||||||
|     const notePath = getSomeNotePathSegments(note, hoistedNotePath); |  | ||||||
| 
 |  | ||||||
|     return notePath === null ? null : notePath.join('/'); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ws.subscribeToMessages(message => { | ws.subscribeToMessages(message => { | ||||||
|    if (message.type === 'openNote') { |    if (message.type === 'openNote') { | ||||||
|        appContext.tabManager.activateOrOpenNote(message.noteId); |        appContext.tabManager.activateOrOpenNote(message.noteId); | ||||||
| @ -311,16 +293,6 @@ function isNotePathInAddress() { | |||||||
|         || (notePath === '' && !!ntxId); |         || (notePath === '' && !!ntxId); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function parseNotePath(notePath) { |  | ||||||
|     let noteIds = notePath.split('/'); |  | ||||||
| 
 |  | ||||||
|     if (noteIds[0] !== 'root') { |  | ||||||
|         noteIds = ['root'].concat(noteIds); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return noteIds; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function isNotePathInHiddenSubtree(notePath) { | function isNotePathInHiddenSubtree(notePath) { | ||||||
|     return notePath?.includes("root/_hidden"); |     return notePath?.includes("root/_hidden"); | ||||||
| } | } | ||||||
| @ -328,8 +300,6 @@ function isNotePathInHiddenSubtree(notePath) { | |||||||
| export default { | export default { | ||||||
|     resolveNotePath, |     resolveNotePath, | ||||||
|     resolveNotePathToSegments, |     resolveNotePathToSegments, | ||||||
|     getSomeNotePath, |  | ||||||
|     getSomeNotePathSegments, |  | ||||||
|     getParentProtectedStatus, |     getParentProtectedStatus, | ||||||
|     getNotePath, |     getNotePath, | ||||||
|     getNoteIdFromNotePath, |     getNoteIdFromNotePath, | ||||||
| @ -340,6 +310,5 @@ export default { | |||||||
|     getNoteTitleWithPathAsSuffix, |     getNoteTitleWithPathAsSuffix, | ||||||
|     getHashValueFromAddress, |     getHashValueFromAddress, | ||||||
|     isNotePathInAddress, |     isNotePathInAddress, | ||||||
|     parseNotePath, |  | ||||||
|     isNotePathInHiddenSubtree |     isNotePathInHiddenSubtree | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import linkService from "../../services/link.js"; | import linkService from "../../services/link.js"; | ||||||
| import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | ||||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||||
| @ -9,6 +8,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; | |||||||
| import SpacedUpdate from "../../services/spaced_update.js"; | import SpacedUpdate from "../../services/spaced_update.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import shortcutService from "../../services/shortcuts.js"; | import shortcutService from "../../services/shortcuts.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="attr-detail"> | <div class="attr-detail"> | ||||||
| @ -598,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { | |||||||
| 
 | 
 | ||||||
|             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); |             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); | ||||||
|             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); |             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); | ||||||
|  |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
| 
 | 
 | ||||||
|             for (const note of displayedNotes) { |             for (const note of displayedNotes) { | ||||||
|                 const notePath = treeService.getSomeNotePath(note); |                 const notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); |                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); | ||||||
| 
 | 
 | ||||||
|                 this.$relatedNotesList.append( |                 this.$relatedNotesList.append( | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js"; | |||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import attributeRenderer from "../../services/attribute_renderer.js"; | import attributeRenderer from "../../services/attribute_renderer.js"; | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import attributeService from "../../services/attributes.js"; | import attributeService from "../../services/attributes.js"; | ||||||
| 
 | 
 | ||||||
| const HELP_TEXT = ` | const HELP_TEXT = ` | ||||||
| @ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget { | |||||||
|             title: title |             title: title | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return treeService.getSomeNotePath(note); |         return note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async updateAttributeList(attributes) { |     async updateAttributeList(attributes) { | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import linkService from '../../services/link.js'; | import linkService from '../../services/link.js'; | ||||||
| import utils from '../../services/utils.js'; | import utils from '../../services/utils.js'; | ||||||
| import server from '../../services/server.js'; | import server from '../../services/server.js'; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
| import hoistedNoteService from "../../services/hoisted_note.js"; | import hoistedNoteService from "../../services/hoisted_note.js"; | ||||||
| @ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget { | |||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     const note = await froca.getNote(change.noteId); |                     const note = await froca.getNote(change.noteId); | ||||||
|                     const notePath = treeService.getSomeNotePath(note); |                     const notePath = note.getBestNotePathString(); | ||||||
| 
 | 
 | ||||||
|                     if (notePath) { |                     if (notePath) { | ||||||
|                         $noteLink = await linkService.createNoteLink(notePath, { |                         $noteLink = await linkService.createNoteLink(notePath, { | ||||||
|  | |||||||
| @ -673,7 +673,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         return noteList; |         return noteList; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     updateNode(node) { |     async updateNode(node) { | ||||||
|         const note = froca.getNoteFromCache(node.data.noteId); |         const note = froca.getNoteFromCache(node.data.noteId); | ||||||
|         const branch = froca.getBranch(node.data.branchId); |         const branch = froca.getBranch(node.data.branchId); | ||||||
| 
 | 
 | ||||||
| @ -697,7 +697,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         node.title = utils.escapeHtml(title); |         node.title = utils.escapeHtml(title); | ||||||
| 
 | 
 | ||||||
|         if (node.isExpanded() !== branch.isExpanded) { |         if (node.isExpanded() !== branch.isExpanded) { | ||||||
|             node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true}); |             await node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true}); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         node.renderTitle(); |         node.renderTitle(); | ||||||
| @ -920,7 +920,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (expand) { |                 if (expand) { | ||||||
|  |                     if (!parentNode.isExpanded()) { | ||||||
|                         await parentNode.setExpanded(true, {noAnimation: true}); |                         await parentNode.setExpanded(true, {noAnimation: true}); | ||||||
|  |                     } | ||||||
| 
 | 
 | ||||||
|                     // although previous line should set the expanded status, it seems to happen asynchronously,
 |                     // although previous line should set the expanded status, it seems to happen asynchronously,
 | ||||||
|                     // so we need to make sure it is set properly before calling updateNode which uses this flag
 |                     // so we need to make sure it is set properly before calling updateNode which uses this flag
 | ||||||
| @ -928,7 +930,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                     branch.isExpanded = true; |                     branch.isExpanded = true; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 this.updateNode(parentNode); |                 await this.updateNode(parentNode); | ||||||
| 
 | 
 | ||||||
|                 let foundChildNode = this.findChildNode(parentNode, childNoteId); |                 let foundChildNode = this.findChildNode(parentNode, childNoteId); | ||||||
| 
 | 
 | ||||||
| @ -1096,10 +1098,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         const activeNode = this.getActiveNode(); |         const activeNode = this.getActiveNode(); | ||||||
|         const activeNodeFocused = activeNode && activeNode.hasFocus(); |         const activeNodeFocused = activeNode && activeNode.hasFocus(); | ||||||
|         const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null; |         const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null; | ||||||
|         const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; |         let activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; | ||||||
| 
 | 
 | ||||||
|         const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; |         const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; | ||||||
|         const activeNoteId = activeNode ? activeNode.data.noteId : null; |         let activeNoteId = activeNode ? activeNode.data.noteId : null; | ||||||
| 
 | 
 | ||||||
|         const noteIdsToUpdate = new Set(); |         const noteIdsToUpdate = new Set(); | ||||||
|         const noteIdsToReload = new Set(); |         const noteIdsToReload = new Set(); | ||||||
| @ -1142,7 +1144,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const ecBranch of loadResults.getBranches()) { |         // activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
 | ||||||
|  |         let movedActiveNode = null; | ||||||
|  |         let parentsOfAddedNodes = []; | ||||||
|  | 
 | ||||||
|  |         const allBranches = loadResults.getBranches(); | ||||||
|  |         const allBranchesDeleted = allBranches.every(branch => !!branch.isDeleted); | ||||||
|  | 
 | ||||||
|  |         for (const ecBranch of allBranches) { | ||||||
|             if (ecBranch.parentNoteId === '_share') { |             if (ecBranch.parentNoteId === '_share') { | ||||||
|                 // all shared notes have a sign in the tree, even the descendants of shared notes
 |                 // all shared notes have a sign in the tree, even the descendants of shared notes
 | ||||||
|                 noteIdsToReload.add(ecBranch.noteId); |                 noteIdsToReload.add(ecBranch.noteId); | ||||||
| @ -1155,6 +1164,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             for (const node of this.getNodesByBranch(ecBranch)) { |             for (const node of this.getNodesByBranch(ecBranch)) { | ||||||
|                 if (ecBranch.isDeleted) { |                 if (ecBranch.isDeleted) { | ||||||
|                     if (node.isActive()) { |                     if (node.isActive()) { | ||||||
|  |                         if (allBranchesDeleted) { | ||||||
|                             const newActiveNode = node.getNextSibling() |                             const newActiveNode = node.getNextSibling() | ||||||
|                                 || node.getPrevSibling() |                                 || node.getPrevSibling() | ||||||
|                                 || node.getParent(); |                                 || node.getParent(); | ||||||
| @ -1162,6 +1172,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                             if (newActiveNode) { |                             if (newActiveNode) { | ||||||
|                                 newActiveNode.setActive(true, {noEvents: true, noFocus: true}); |                                 newActiveNode.setActive(true, {noEvents: true, noFocus: true}); | ||||||
|                             } |                             } | ||||||
|  |                         } else { | ||||||
|  |                             movedActiveNode = node; | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     if (node.getParent()) { |                     if (node.getParent()) { | ||||||
| @ -1174,12 +1187,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
| 
 | 
 | ||||||
|             if (!ecBranch.isDeleted) { |             if (!ecBranch.isDeleted) { | ||||||
|                 for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) { |                 for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) { | ||||||
|  |                     parentsOfAddedNodes.push(parentNode) | ||||||
|  | 
 | ||||||
|                     if (parentNode.isFolder() && !parentNode.isLoaded()) { |                     if (parentNode.isFolder() && !parentNode.isLoaded()) { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId); |                     const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId); | ||||||
| 
 |  | ||||||
|                     if (!found) { |                     if (!found) { | ||||||
|                         // make sure it's loaded
 |                         // make sure it's loaded
 | ||||||
|                         await froca.getNote(ecBranch.noteId); |                         await froca.getNote(ecBranch.noteId); | ||||||
| @ -1222,7 +1236,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         // for some reason node update cannot be in the batchUpdate() block (node is not re-rendered)
 |         // for some reason node update cannot be in the batchUpdate() block (node is not re-rendered)
 | ||||||
|         for (const noteId of noteIdsToUpdate) { |         for (const noteId of noteIdsToUpdate) { | ||||||
|             for (const node of this.getNodesByNoteId(noteId)) { |             for (const node of this.getNodesByNoteId(noteId)) { | ||||||
|                 this.updateNode(node); |                 await this.updateNode(node); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (movedActiveNode) { | ||||||
|  |             for (const parentNode of parentsOfAddedNodes) { | ||||||
|  |                 const found = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId); | ||||||
|  |                 if (found) { | ||||||
|  |                     activeNotePath = treeService.getNotePath(found); | ||||||
|  |                     activeNoteId = found.data.noteId; | ||||||
|  |                     break | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId) |         const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId) | ||||||
|             .filter(notePath => !notePath.isHidden); |             .filter(notePath => !notePath.isHidden); | ||||||
| 
 | 
 | ||||||
|         if (sortedNotePaths.length > 0) { |         if (sortedNotePaths.length > 0) { | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async switchOn() { |     async switchOn() { | ||||||
|         await branchService.cloneNoteToNote(this.noteId, '_share'); |         await branchService.cloneNoteToParentNote(this.noteId, '_share'); | ||||||
| 
 | 
 | ||||||
|         syncService.syncNow(true); |         syncService.syncNow(true); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ export default class EditableCodeTypeWidget extends TypeWidget { | |||||||
|             matchBrackets: true, |             matchBrackets: true, | ||||||
|             keyMap: options.is('vimKeymapEnabled') ? "vim": "default", |             keyMap: options.is('vimKeymapEnabled') ? "vim": "default", | ||||||
|             matchTags: {bothTags: true}, |             matchTags: {bothTags: true}, | ||||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, |             highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, | ||||||
|             lint: true, |             lint: true, | ||||||
|             gutters: ["CodeMirror-lint-markers"], |             gutters: ["CodeMirror-lint-markers"], | ||||||
|             lineNumbers: true, |             lineNumbers: true, | ||||||
| @ -62,7 +62,7 @@ export default class EditableCodeTypeWidget extends TypeWidget { | |||||||
|             // all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
 |             // all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
 | ||||||
|             lineWrapping: options.is('codeLineWrapEnabled'), |             lineWrapping: options.is('codeLineWrapEnabled'), | ||||||
|             dragDrop: false, // with true the editor inlines dropped files which is not what we expect
 |             dragDrop: false, // with true the editor inlines dropped files which is not what we expect
 | ||||||
|             placeholder: "Type the content of your code note here..." |             placeholder: "Type the content of your code note here...", | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate()); |         this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate()); | ||||||
|  | |||||||
| @ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js'; | |||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | import keyboardActionService from "../../services/keyboard_actions.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | ||||||
| import link from "../../services/link.js"; | import link from "../../services/link.js"; | ||||||
| @ -378,7 +377,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return treeService.getSomeNotePath(resp.note); |         return resp.note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refreshIncludedNoteEvent({noteId}) { |     async refreshIncludedNoteEvent({noteId}) { | ||||||
|  | |||||||
| @ -9,11 +9,11 @@ function cloneNoteToBranch(req) { | |||||||
|     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); |     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function cloneNoteToNote(req) { | function cloneNoteToParentNote(req) { | ||||||
|     const {noteId, parentNoteId} = req.params; |     const {noteId, parentNoteId} = req.params; | ||||||
|     const {prefix} = req.body; |     const {prefix} = req.body; | ||||||
| 
 | 
 | ||||||
|     return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix); |     return cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function cloneNoteAfter(req) { | function cloneNoteAfter(req) { | ||||||
| @ -30,7 +30,7 @@ function toggleNoteInParent(req) { | |||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     toggleNoteInParent |     toggleNoteInParent | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -135,7 +135,7 @@ function getEditedNotesOnDate(req) { | |||||||
|     notes = notes.map(note => note.getPojo()); |     notes = notes.map(note => note.getPojo()); | ||||||
| 
 | 
 | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|         const notePath = note.isDeleted ? null : beccaService.getNotePath(note.noteId); |         const notePath = note.isDeleted ? null : getNotePathData(note); | ||||||
| 
 | 
 | ||||||
|         note.notePath = notePath ? notePath.notePath : null; |         note.notePath = notePath ? notePath.notePath : null; | ||||||
|     } |     } | ||||||
| @ -143,6 +143,32 @@ function getEditedNotesOnDate(req) { | |||||||
|     return notes; |     return notes; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function getNotePathData(note) { | ||||||
|  |     const retPath = note.getBestNotePath(); | ||||||
|  | 
 | ||||||
|  |     if (retPath) { | ||||||
|  |         const noteTitle = beccaService.getNoteTitleForPath(retPath); | ||||||
|  | 
 | ||||||
|  |         let branchId; | ||||||
|  | 
 | ||||||
|  |         if (note.isRoot()) { | ||||||
|  |             branchId = 'none_root'; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const parentNote = note.parents[0]; | ||||||
|  |             branchId = becca.getBranchFromChildAndParent(note.noteId, parentNote.noteId).branchId; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             noteId: note.noteId, | ||||||
|  |             branchId: branchId, | ||||||
|  |             title: noteTitle, | ||||||
|  |             notePath: retPath, | ||||||
|  |             path: retPath.join('/') | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNoteRevisions, |     getNoteRevisions, | ||||||
|     getNoteRevision, |     getNoteRevision, | ||||||
|  | |||||||
| @ -3,14 +3,14 @@ | |||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
| const beccaService = require('../../becca/becca_service'); | const becca = require("../../becca/becca"); | ||||||
| 
 | 
 | ||||||
| function getRecentChanges(req) { | function getRecentChanges(req) { | ||||||
|     const {ancestorNoteId} = req.params; |     const {ancestorNoteId} = req.params; | ||||||
| 
 | 
 | ||||||
|     let recentChanges = []; |     let recentChanges = []; | ||||||
| 
 | 
 | ||||||
|     const noteRevisions = sql.getRows(` |     const noteRevisionRows = sql.getRows(` | ||||||
|         SELECT  |         SELECT  | ||||||
|             notes.noteId, |             notes.noteId, | ||||||
|             notes.isDeleted AS current_isDeleted, |             notes.isDeleted AS current_isDeleted, | ||||||
| @ -24,16 +24,18 @@ function getRecentChanges(req) { | |||||||
|             note_revisions |             note_revisions | ||||||
|             JOIN notes USING(noteId)`);
 |             JOIN notes USING(noteId)`);
 | ||||||
| 
 | 
 | ||||||
|     for (const noteRevision of noteRevisions) { |     for (const noteRevisionRow of noteRevisionRows) { | ||||||
|         if (beccaService.isInAncestor(noteRevision.noteId, ancestorNoteId)) { |         const note = becca.getNote(noteRevisionRow.noteId); | ||||||
|             recentChanges.push(noteRevision); | 
 | ||||||
|  |         if (note?.hasAncestor(ancestorNoteId)) { | ||||||
|  |             recentChanges.push(noteRevisionRow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // now we need to also collect date points not represented in note revisions:
 |     // now we need to also collect date points not represented in note revisions:
 | ||||||
|     // 1. creation for all notes (dateCreated)
 |     // 1. creation for all notes (dateCreated)
 | ||||||
|     // 2. deletion for deleted notes (dateModified)
 |     // 2. deletion for deleted notes (dateModified)
 | ||||||
|     const notes = sql.getRows(` |     const noteRows = sql.getRows(` | ||||||
|             SELECT |             SELECT | ||||||
|                 notes.noteId, |                 notes.noteId, | ||||||
|                 notes.isDeleted AS current_isDeleted, |                 notes.isDeleted AS current_isDeleted, | ||||||
| @ -57,9 +59,11 @@ function getRecentChanges(req) { | |||||||
|             FROM notes |             FROM notes | ||||||
|             WHERE notes.isDeleted = 1`);
 |             WHERE notes.isDeleted = 1`);
 | ||||||
| 
 | 
 | ||||||
|     for (const note of notes) { |     for (const noteRow of noteRows) { | ||||||
|         if (beccaService.isInAncestor(note.noteId, ancestorNoteId)) { |         const note = becca.getNote(noteRow.noteId); | ||||||
|             recentChanges.push(note); | 
 | ||||||
|  |         if (note?.hasAncestor(ancestorNoteId)) { | ||||||
|  |             recentChanges.push(noteRow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ function register(app) { | |||||||
| 
 | 
 | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); |     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToParentNote); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); |     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||||
| 
 | 
 | ||||||
|     route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); |     route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); | ||||||
|  | |||||||
| @ -83,7 +83,7 @@ const ACTION_HANDLERS = { | |||||||
|         let res; |         let res; | ||||||
| 
 | 
 | ||||||
|         if (note.getParentBranches().length > 1) { |         if (note.getParentBranches().length > 1) { | ||||||
|             res = cloningService.cloneNoteToNote(note.noteId, action.targetParentNoteId); |             res = cloningService.cloneNoteToParentNote(note.noteId, action.targetParentNoteId); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); |             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ const becca = require("../becca/becca"); | |||||||
| const beccaService = require("../becca/becca_service"); | const beccaService = require("../becca/becca_service"); | ||||||
| const log = require("./log"); | const log = require("./log"); | ||||||
| 
 | 
 | ||||||
| function cloneNoteToNote(noteId, parentNoteId, prefix) { | function cloneNoteToParentNote(noteId, parentNoteId, prefix) { | ||||||
|     const parentNote = becca.getNote(parentNoteId); |     const parentNote = becca.getNote(parentNoteId); | ||||||
| 
 | 
 | ||||||
|     if (parentNote.type === 'search') { |     if (parentNote.type === 'search') { | ||||||
| @ -19,7 +19,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { |     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { | ||||||
|         return { success: false, message: 'Note is deleted.' }; |         return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const validationResult = treeService.validateParentChild(parentNoteId, noteId); |     const validationResult = treeService.validateParentChild(parentNoteId, noteId); | ||||||
| @ -35,12 +35,12 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).save(); | ||||||
| 
 | 
 | ||||||
|     log.info(`Cloned note '${noteId}' to new parent note '${parentNoteId}' with prefix '${prefix}'`); |     log.info(`Cloned note '${noteId}' to a new parent note '${parentNoteId}' with prefix '${prefix}'`); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|         success: true, |         success: true, | ||||||
|         branchId: branch.branchId, |         branchId: branch.branchId, | ||||||
|         notePath: `${beccaService.getNotePath(parentNoteId).path}/${noteId}` |         notePath: `${parentNote.getBestNotePathString()}/${noteId}` | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -51,7 +51,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { | |||||||
|         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; |         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const ret = cloneNoteToNote(noteId, parentBranch.noteId, prefix); |     const ret = cloneNoteToParentNote(noteId, parentBranch.noteId, prefix); | ||||||
| 
 | 
 | ||||||
|     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user
 |     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user
 | ||||||
|     parentBranch.save(); |     parentBranch.save(); | ||||||
| @ -182,7 +182,7 @@ function isNoteDeleted(noteId) { | |||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     ensureNoteIsPresentInParent, |     ensureNoteIsPresentInParent, | ||||||
|     ensureNoteIsAbsentFromParent, |     ensureNoteIsAbsentFromParent, | ||||||
|     toggleNoteInParent, |     toggleNoteInParent, | ||||||
|  | |||||||
| @ -22,9 +22,9 @@ class NoteFlatTextExp extends Expression { | |||||||
|          * @param {string[]} tokens |          * @param {string[]} tokens | ||||||
|          * @param {string[]} path |          * @param {string[]} path | ||||||
|          */ |          */ | ||||||
|         function searchDownThePath(note, tokens, path) { |         const searchDownThePath = (note, tokens, path) => { | ||||||
|             if (tokens.length === 0) { |             if (tokens.length === 0) { | ||||||
|                 const retPath = beccaService.getSomePath(note, path); |                 const retPath = this.getNotePath(note, path); | ||||||
| 
 | 
 | ||||||
|                 if (retPath) { |                 if (retPath) { | ||||||
|                     const noteId = retPath[retPath.length - 1]; |                     const noteId = retPath[retPath.length - 1]; | ||||||
| @ -131,6 +131,17 @@ class NoteFlatTextExp extends Expression { | |||||||
|         return resultNoteSet; |         return resultNoteSet; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     getNotePath(note, path) { | ||||||
|  |         if (path.length === 0) { | ||||||
|  |             return note.getBestNotePath(); | ||||||
|  |         } else { | ||||||
|  |             const closestNoteId = path[0]; | ||||||
|  |             const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath(); | ||||||
|  | 
 | ||||||
|  |             return [...closestNoteBestNotePath, ...path.slice(1)]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns noteIds which have at least one matching tokens |      * Returns noteIds which have at least one matching tokens | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -157,7 +157,7 @@ function findResultsWithExpression(expression, searchContext) { | |||||||
|     const searchResults = noteSet.notes |     const searchResults = noteSet.notes | ||||||
|         .filter(note => !note.isDeleted) |         .filter(note => !note.isDeleted) | ||||||
|         .map(note => { |         .map(note => { | ||||||
|             const notePathArray = executionContext.noteIdToNotePath[note.noteId] || beccaService.getSomePath(note); |             const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath(); | ||||||
| 
 | 
 | ||||||
|             if (!notePathArray) { |             if (!notePathArray) { | ||||||
|                 throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`); |                 throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`); | ||||||
|  | |||||||
| @ -9,6 +9,18 @@ const protectedSessionService = require('./protected_session'); | |||||||
| const becca = require("../becca/becca"); | const becca = require("../becca/becca"); | ||||||
| const AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity"); | const AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity"); | ||||||
| 
 | 
 | ||||||
|  | const env = require('./env'); | ||||||
|  | if (env.isDev()) { | ||||||
|  |     const chokidar = require('chokidar'); | ||||||
|  |     const debounce = require('debounce'); | ||||||
|  |     const debouncedReloadFrontend = debounce(reloadFrontend, 200); | ||||||
|  |     chokidar | ||||||
|  |         .watch('src/public') | ||||||
|  |         .on('add', debouncedReloadFrontend) | ||||||
|  |         .on('change', debouncedReloadFrontend) | ||||||
|  |         .on('unlink', debouncedReloadFrontend); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| let webSocketServer; | let webSocketServer; | ||||||
| let lastSyncedPush = null; | let lastSyncedPush = null; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,16 +0,0 @@ | |||||||
| const path = require('path'); |  | ||||||
| const assetPath = require('./src/services/asset_path'); |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|     mode: 'production', |  | ||||||
|     entry: { |  | ||||||
|         mobile: './src/public/app/desktop.js', |  | ||||||
|     }, |  | ||||||
|     output: { |  | ||||||
|         publicPath: `${assetPath}/app-dist/`, |  | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |  | ||||||
|         filename: 'desktop.js' |  | ||||||
|     }, |  | ||||||
|     devtool: 'source-map', |  | ||||||
|     target: 'electron-renderer' |  | ||||||
| }; |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| const path = require('path'); |  | ||||||
| const assetPath = require('./src/services/asset_path'); |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|     mode: 'production', |  | ||||||
|     entry: { |  | ||||||
|         mobile: './src/public/app/setup.js', |  | ||||||
|     }, |  | ||||||
|     output: { |  | ||||||
|         publicPath: `${assetPath}/app-dist/`, |  | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |  | ||||||
|         filename: 'setup.js' |  | ||||||
|     }, |  | ||||||
|     devtool: 'source-map', |  | ||||||
|     target: 'electron-renderer' |  | ||||||
| }; |  | ||||||
| @ -4,13 +4,15 @@ const assetPath = require('./src/services/asset_path'); | |||||||
| module.exports = { | module.exports = { | ||||||
|     mode: 'production', |     mode: 'production', | ||||||
|     entry: { |     entry: { | ||||||
|  |         setup: './src/public/app/setup.js', | ||||||
|         mobile: './src/public/app/mobile.js', |         mobile: './src/public/app/mobile.js', | ||||||
|  |         desktop: './src/public/app/desktop.js', | ||||||
|     }, |     }, | ||||||
|     output: { |     output: { | ||||||
|         publicPath: `${assetPath}/app-dist/`, |         publicPath: `${assetPath}/app-dist/`, | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |         path: path.resolve(__dirname, 'src/public/app-dist'), | ||||||
|         filename: 'mobile.js' |         filename: '[name].js', | ||||||
|     }, |     }, | ||||||
|     devtool: 'source-map', |     devtool: 'source-map', | ||||||
|     target: 'electron-renderer' |     target: 'electron-renderer', | ||||||
| }; | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam