mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge pull request #47 from TriliumNext/feature/typescript_backend_10
Convert backend to TypeScript (84% -> 89%)
This commit is contained in:
		
							
								
								
									
										26
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										26
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -92,6 +92,7 @@ | |||||||
|         "@types/better-sqlite3": "^7.6.9", |         "@types/better-sqlite3": "^7.6.9", | ||||||
|         "@types/cls-hooked": "^4.3.8", |         "@types/cls-hooked": "^4.3.8", | ||||||
|         "@types/csurf": "^1.11.5", |         "@types/csurf": "^1.11.5", | ||||||
|  |         "@types/ejs": "^3.1.5", | ||||||
|         "@types/escape-html": "^1.0.4", |         "@types/escape-html": "^1.0.4", | ||||||
|         "@types/express": "^4.17.21", |         "@types/express": "^4.17.21", | ||||||
|         "@types/express-session": "^1.18.0", |         "@types/express-session": "^1.18.0", | ||||||
| @@ -101,6 +102,7 @@ | |||||||
|         "@types/mime-types": "^2.1.4", |         "@types/mime-types": "^2.1.4", | ||||||
|         "@types/multer": "^1.4.11", |         "@types/multer": "^1.4.11", | ||||||
|         "@types/node": "^20.11.19", |         "@types/node": "^20.11.19", | ||||||
|  |         "@types/safe-compare": "^1.1.2", | ||||||
|         "@types/sanitize-html": "^2.11.0", |         "@types/sanitize-html": "^2.11.0", | ||||||
|         "@types/sax": "^1.2.7", |         "@types/sax": "^1.2.7", | ||||||
|         "@types/stream-throttle": "^0.1.4", |         "@types/stream-throttle": "^0.1.4", | ||||||
| @@ -1271,6 +1273,12 @@ | |||||||
|         "@types/ms": "*" |         "@types/ms": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@types/ejs": { | ||||||
|  |       "version": "3.1.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", | ||||||
|  |       "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "node_modules/@types/escape-html": { |     "node_modules/@types/escape-html": { | ||||||
|       "version": "1.0.4", |       "version": "1.0.4", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", |       "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", | ||||||
| @@ -1537,6 +1545,12 @@ | |||||||
|         "@types/node": "*" |         "@types/node": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@types/safe-compare": { | ||||||
|  |       "version": "1.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/safe-compare/-/safe-compare-1.1.2.tgz", | ||||||
|  |       "integrity": "sha512-kK/IM1+pvwCMom+Kezt/UlP8LMEwm8rP6UgGbRc6zUnhU/csoBQ5rWgmD2CJuHxiMiX+H1VqPGpo0kDluJGXYA==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "node_modules/@types/sanitize-html": { |     "node_modules/@types/sanitize-html": { | ||||||
|       "version": "2.11.0", |       "version": "2.11.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", |       "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", | ||||||
| @@ -14276,6 +14290,12 @@ | |||||||
|         "@types/ms": "*" |         "@types/ms": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@types/ejs": { | ||||||
|  |       "version": "3.1.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", | ||||||
|  |       "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "@types/escape-html": { |     "@types/escape-html": { | ||||||
|       "version": "1.0.4", |       "version": "1.0.4", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", |       "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", | ||||||
| @@ -14535,6 +14555,12 @@ | |||||||
|         "@types/node": "*" |         "@types/node": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@types/safe-compare": { | ||||||
|  |       "version": "1.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/safe-compare/-/safe-compare-1.1.2.tgz", | ||||||
|  |       "integrity": "sha512-kK/IM1+pvwCMom+Kezt/UlP8LMEwm8rP6UgGbRc6zUnhU/csoBQ5rWgmD2CJuHxiMiX+H1VqPGpo0kDluJGXYA==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "@types/sanitize-html": { |     "@types/sanitize-html": { | ||||||
|       "version": "2.11.0", |       "version": "2.11.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", |       "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", | ||||||
|   | |||||||
| @@ -113,6 +113,7 @@ | |||||||
|     "@types/better-sqlite3": "^7.6.9", |     "@types/better-sqlite3": "^7.6.9", | ||||||
|     "@types/cls-hooked": "^4.3.8", |     "@types/cls-hooked": "^4.3.8", | ||||||
|     "@types/csurf": "^1.11.5", |     "@types/csurf": "^1.11.5", | ||||||
|  |     "@types/ejs": "^3.1.5", | ||||||
|     "@types/escape-html": "^1.0.4", |     "@types/escape-html": "^1.0.4", | ||||||
|     "@types/express": "^4.17.21", |     "@types/express": "^4.17.21", | ||||||
|     "@types/express-session": "^1.18.0", |     "@types/express-session": "^1.18.0", | ||||||
| @@ -122,6 +123,7 @@ | |||||||
|     "@types/mime-types": "^2.1.4", |     "@types/mime-types": "^2.1.4", | ||||||
|     "@types/multer": "^1.4.11", |     "@types/multer": "^1.4.11", | ||||||
|     "@types/node": "^20.11.19", |     "@types/node": "^20.11.19", | ||||||
|  |     "@types/safe-compare": "^1.1.2", | ||||||
|     "@types/sanitize-html": "^2.11.0", |     "@types/sanitize-html": "^2.11.0", | ||||||
|     "@types/sax": "^1.2.7", |     "@types/sax": "^1.2.7", | ||||||
|     "@types/stream-throttle": "^0.1.4", |     "@types/stream-throttle": "^0.1.4", | ||||||
|   | |||||||
| @@ -125,9 +125,6 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @returns {BNote|null} |  | ||||||
|      */ |  | ||||||
|     getNote() { |     getNote() { | ||||||
|         const note = this.becca.getNote(this.noteId); |         const note = this.becca.getNote(this.noteId); | ||||||
|  |  | ||||||
| @@ -138,9 +135,6 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> { | |||||||
|         return note; |         return note; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @returns {BNote|null} |  | ||||||
|      */ |  | ||||||
|     getTargetNote() { |     getTargetNote() { | ||||||
|         if (this.type !== 'relation') { |         if (this.type !== 'relation') { | ||||||
|             throw new Error(`Attribute '${this.attributeId}' is not a relation.`); |             throw new Error(`Attribute '${this.attributeId}' is not a relation.`); | ||||||
| @@ -153,9 +147,6 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> { | |||||||
|         return this.becca.getNote(this.value); |         return this.becca.getNote(this.value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @returns {boolean} |  | ||||||
|      */ |  | ||||||
|     isDefinition() { |     isDefinition() { | ||||||
|         return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:')); |         return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:')); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -127,8 +127,6 @@ class BBranch extends AbstractBeccaEntity<BBranch> { | |||||||
|      * An example is shared or bookmarked clones - they are created automatically and exist for technical reasons, |      * An example is shared or bookmarked clones - they are created automatically and exist for technical reasons, | ||||||
|      * not as user-intended actions. From user perspective, they don't count as real clones and for the purpose |      * not as user-intended actions. From user perspective, they don't count as real clones and for the purpose | ||||||
|      * of deletion should not act as a clone. |      * of deletion should not act as a clone. | ||||||
|      * |  | ||||||
|      * @returns {boolean} |  | ||||||
|      */ |      */ | ||||||
|     get isWeak() { |     get isWeak() { | ||||||
|         return ['_share', '_lbBookmarks'].includes(this.parentNoteId); |         return ['_share', '_lbBookmarks'].includes(this.parentNoteId); | ||||||
|   | |||||||
| @@ -167,39 +167,32 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return this.isContentAvailable() ? this.title : '[protected]'; |         return this.isContentAvailable() ? this.title : '[protected]'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BBranch[]} */ |  | ||||||
|     getParentBranches() { |     getParentBranches() { | ||||||
|         return this.parentBranches; |         return this.parentBranches; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Returns <i>strong</i> (as opposed to <i>weak</i>) parent branches. See isWeak for details. |      * Returns <i>strong</i> (as opposed to <i>weak</i>) parent branches. See isWeak for details. | ||||||
|      * |  | ||||||
|      * @returns {BBranch[]} |  | ||||||
|      */ |      */ | ||||||
|     getStrongParentBranches() { |     getStrongParentBranches() { | ||||||
|         return this.getParentBranches().filter(branch => !branch.isWeak); |         return this.getParentBranches().filter(branch => !branch.isWeak); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @returns {BBranch[]} |  | ||||||
|      * @deprecated use getParentBranches() instead |      * @deprecated use getParentBranches() instead | ||||||
|      */ |      */ | ||||||
|     getBranches() { |     getBranches() { | ||||||
|         return this.parentBranches; |         return this.parentBranches; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BNote[]} */ |  | ||||||
|     getParentNotes() { |     getParentNotes() { | ||||||
|         return this.parents; |         return this.parents; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BNote[]} */ |  | ||||||
|     getChildNotes() { |     getChildNotes() { | ||||||
|         return this.children; |         return this.children; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     hasChildren() { |     hasChildren() { | ||||||
|         return this.children && this.children.length > 0; |         return this.children && this.children.length > 0; | ||||||
|     } |     } | ||||||
| @@ -209,7 +202,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|             .map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId)) as BBranch[]; |             .map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId)) as BBranch[]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* |     /** | ||||||
|      * Note content has quite special handling - it's not a separate entity, but a lazily loaded |      * Note content has quite special handling - it's not a separate entity, but a lazily loaded | ||||||
|      * part of Note entity with its own sync. Reasons behind this hybrid design has been: |      * part of Note entity with its own sync. Reasons behind this hybrid design has been: | ||||||
|      * |      * | ||||||
| @@ -222,7 +215,8 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @throws Error in case of invalid JSON */ |      * @throws Error in case of invalid JSON | ||||||
|  |      */ | ||||||
|     getJsonContent(): any | null { |     getJsonContent(): any | null { | ||||||
|         const content = this.getContent(); |         const content = this.getContent(); | ||||||
|  |  | ||||||
| @@ -233,7 +227,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return JSON.parse(content); |         return JSON.parse(content); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */ |     /** @returns valid object or null if the content cannot be parsed as JSON */ | ||||||
|     getJsonContentSafely() { |     getJsonContentSafely() { | ||||||
|         try { |         try { | ||||||
|             return this.getJsonContent(); |             return this.getJsonContent(); | ||||||
| @@ -269,17 +263,17 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified); |         return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */ |     /** @returns true if this note is the root of the note tree. Root note has "root" noteId */ | ||||||
|     isRoot() { |     isRoot() { | ||||||
|         return this.noteId === 'root'; |         return this.noteId === 'root'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if this note is of application/json content type */ |     /** @returns true if this note is of application/json content type */ | ||||||
|     isJson() { |     isJson() { | ||||||
|         return this.mime === "application/json"; |         return this.mime === "application/json"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if this note is JavaScript (code or attachment) */ |     /** @returns true if this note is JavaScript (code or attachment) */ | ||||||
|     isJavaScript() { |     isJavaScript() { | ||||||
|         return (this.type === "code" || this.type === "file" || this.type === 'launcher') |         return (this.type === "code" || this.type === "file" || this.type === 'launcher') | ||||||
|             && (this.mime.startsWith("application/javascript") |             && (this.mime.startsWith("application/javascript") | ||||||
| @@ -287,13 +281,13 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|                 || this.mime === "text/javascript"); |                 || this.mime === "text/javascript"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if this note is HTML */ |     /** @returns true if this note is HTML */ | ||||||
|     isHtml() { |     isHtml() { | ||||||
|         return ["code", "file", "render"].includes(this.type) |         return ["code", "file", "render"].includes(this.type) | ||||||
|             && this.mime === "text/html"; |             && this.mime === "text/html"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if this note is an image */ |     /** @returns true if this note is an image */ | ||||||
|     isImage() { |     isImage() { | ||||||
|         return this.type === 'image' |         return this.type === 'image' | ||||||
|             || (this.type === 'file' && this.mime?.startsWith('image/')); |             || (this.type === 'file' && this.mime?.startsWith('image/')); | ||||||
| @@ -304,12 +298,12 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return this.hasStringContent(); |         return this.hasStringContent(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if the note has string content (not binary) */ |     /** @returns true if the note has string content (not binary) */ | ||||||
|     hasStringContent() { |     hasStringContent() { | ||||||
|         return utils.isStringNote(this.type, this.mime); |         return utils.isStringNote(this.type, this.mime); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {string|null} JS script environment - either "frontend" or "backend" */ |     /** @returns JS script environment - either "frontend" or "backend" */ | ||||||
|     getScriptEnv() { |     getScriptEnv() { | ||||||
|         if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) { |         if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) { | ||||||
|             return "frontend"; |             return "frontend"; | ||||||
| @@ -518,8 +512,8 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {BAttribute|null} label if it exists, null otherwise |      * @returns label if it exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getLabel(name: string): BAttribute | null { |     getLabel(name: string): BAttribute | null { | ||||||
|         return this.getAttribute(LABEL, name); |         return this.getAttribute(LABEL, name); | ||||||
| @@ -680,7 +674,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|      * @param type - (optional) attribute type to filter |      * @param type - (optional) attribute type to filter | ||||||
|      * @param name - (optional) attribute name to filter |      * @param name - (optional) attribute name to filter | ||||||
|      * @param value - (optional) attribute value to filter |      * @param value - (optional) attribute value to filter | ||||||
|      * @returns {BAttribute[]} note's "owned" attributes - excluding inherited ones |      * @returns note's "owned" attributes - excluding inherited ones | ||||||
|      */ |      */ | ||||||
|     getOwnedAttributes(type: string | null = null, name: string | null = null, value: string | null = null) { |     getOwnedAttributes(type: string | null = null, name: string | null = null, value: string | null = null) { | ||||||
|         this.__validateTypeName(type, name); |         this.__validateTypeName(type, name); | ||||||
| @@ -703,7 +697,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @returns {BAttribute} attribute belonging to this specific note (excludes inherited attributes) |      * @returns attribute belonging to this specific note (excludes inherited attributes) | ||||||
|      * |      * | ||||||
|      * This method can be significantly faster than the getAttribute() |      * This method can be significantly faster than the getAttribute() | ||||||
|      */ |      */ | ||||||
| @@ -780,7 +774,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|      * - fast searching |      * - fast searching | ||||||
|      * - note similarity evaluation |      * - note similarity evaluation | ||||||
|      * |      * | ||||||
|      * @returns {string} - returns flattened textual representation of note, prefixes and attributes |      * @returns - returns flattened textual representation of note, prefixes and attributes | ||||||
|      */ |      */ | ||||||
|     getFlatText() { |     getFlatText() { | ||||||
|         if (!this.__flatTextCache) { |         if (!this.__flatTextCache) { | ||||||
| @@ -971,7 +965,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {string[]} - includes the subtree root note as well */ |     /** @returns includes the subtree root note as well */ | ||||||
|     getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) { |     getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) { | ||||||
|         return this.getSubtree({includeArchived, includeHidden, resolveSearch}) |         return this.getSubtree({includeArchived, includeHidden, resolveSearch}) | ||||||
|             .notes |             .notes | ||||||
| @@ -1031,7 +1025,6 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return this.getOwnedAttributes().length; |         return this.getOwnedAttributes().length; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BNote[]} */ |  | ||||||
|     getAncestors() { |     getAncestors() { | ||||||
|         if (!this.__ancestorCache) { |         if (!this.__ancestorCache) { | ||||||
|             const noteIds = new Set(); |             const noteIds = new Set(); | ||||||
| @@ -1075,7 +1068,6 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return this.noteId === '_hidden' || this.hasAncestor('_hidden'); |         return this.noteId === '_hidden' || this.hasAncestor('_hidden'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BAttribute[]} */ |  | ||||||
|     getTargetRelations() { |     getTargetRelations() { | ||||||
|         return this.targetRelations; |         return this.targetRelations; | ||||||
|     } |     } | ||||||
| @@ -1117,7 +1109,6 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|             .map(row => new BRevision(row)); |             .map(row => new BRevision(row)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BAttachment[]} */ |  | ||||||
|     getAttachments(opts: AttachmentOpts = {}) { |     getAttachments(opts: AttachmentOpts = {}) { | ||||||
|         opts.includeContentLength = !!opts.includeContentLength; |         opts.includeContentLength = !!opts.includeContentLength; | ||||||
|         // from testing, it looks like calculating length does not make a difference in performance even on large-ish DB |         // from testing, it looks like calculating length does not make a difference in performance even on large-ish DB | ||||||
| @@ -1135,7 +1126,6 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|             .map(row => new BAttachment(row)); |             .map(row => new BAttachment(row)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {BAttachment|null} */ |  | ||||||
|     getAttachmentById(attachmentId: string, opts: AttachmentOpts = {}) { |     getAttachmentById(attachmentId: string, opts: AttachmentOpts = {}) { | ||||||
|         opts.includeContentLength = !!opts.includeContentLength; |         opts.includeContentLength = !!opts.includeContentLength; | ||||||
|  |  | ||||||
| @@ -1582,10 +1572,7 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|         return !(this.noteId in this.becca.notes) || this.isBeingDeleted; |         return !(this.noteId in this.becca.notes) || this.isBeingDeleted; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     saveRevision(): BRevision { | ||||||
|      * @returns {BRevision|null} |  | ||||||
|      */ |  | ||||||
|     saveRevision() { |  | ||||||
|         return sql.transactional(() => { |         return sql.transactional(() => { | ||||||
|             let noteContent = this.getContent(); |             let noteContent = this.getContent(); | ||||||
|  |  | ||||||
| @@ -1632,9 +1619,8 @@ class BNote extends AbstractBeccaEntity<BNote> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param {string} matchBy - choose by which property we detect if to update an existing attachment. |      * @param matchBy - choose by which property we detect if to update an existing attachment. | ||||||
|  *                      Supported values are either 'attachmentId' (default) or 'title' |  *                      Supported values are either 'attachmentId' (default) or 'title' | ||||||
|      * @returns {BAttachment} |  | ||||||
|      */ |      */ | ||||||
|     saveAttachment({attachmentId, role, mime, title, content, position}: AttachmentRow, matchBy = 'attachmentId') { |     saveAttachment({attachmentId, role, mime, title, content, position}: AttachmentRow, matchBy = 'attachmentId') { | ||||||
|         if (!['attachmentId', 'title'].includes(matchBy)) { |         if (!['attachmentId', 'title'].includes(matchBy)) { | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ const fontsRoute = require('./api/fonts'); | |||||||
| const etapiTokensApiRoutes = require('./api/etapi_tokens'); | const etapiTokensApiRoutes = require('./api/etapi_tokens'); | ||||||
| const relationMapApiRoute = require('./api/relation-map'); | const relationMapApiRoute = require('./api/relation-map'); | ||||||
| const otherRoute = require('./api/other'); | const otherRoute = require('./api/other'); | ||||||
| const shareRoutes = require('../share/routes.js'); | const shareRoutes = require('../share/routes'); | ||||||
|  |  | ||||||
| const etapiAuthRoutes = require('../etapi/auth'); | const etapiAuthRoutes = require('../etapi/auth'); | ||||||
| const etapiAppInfoRoutes = require('../etapi/app_info'); | const etapiAppInfoRoutes = require('../etapi/app_info'); | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ class ConsistencyChecks { | |||||||
|             childToParents[childNoteId].push(parentNoteId); |             childToParents[childNoteId].push(parentNoteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /** @returns {boolean} true if cycle was found and we should try again */ |         /** @returns true if cycle was found and we should try again */ | ||||||
|         const checkTreeCycle = (noteId: string, path: string[]) => { |         const checkTreeCycle = (noteId: string, path: string[]) => { | ||||||
|             if (noteId === 'root') { |             if (noteId === 'root') { | ||||||
|                 return false; |                 return false; | ||||||
|   | |||||||
| @@ -17,8 +17,7 @@ type EventListener = (data: any) => void; | |||||||
| const eventListeners: Record<string, EventListener[]> = {}; | const eventListeners: Record<string, EventListener[]> = {}; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {string|string[]}eventTypes - can be either single event or an array of events |  * @param eventTypes - can be either single event or an array of events | ||||||
|  * @param listener |  | ||||||
|  */ |  */ | ||||||
| function subscribe(eventTypes: EventType, listener: EventListener) { | function subscribe(eventTypes: EventType, listener: EventListener) { | ||||||
|     if (!Array.isArray(eventTypes)) { |     if (!Array.isArray(eventTypes)) { | ||||||
|   | |||||||
| @@ -323,9 +323,9 @@ export = { | |||||||
|      * Get single value from the given query - first column from first returned row. |      * Get single value from the given query - first column from first returned row. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      * @returns [object] - single value |      * @returns single value | ||||||
|      */ |      */ | ||||||
|     getValue, |     getValue, | ||||||
|  |  | ||||||
| @@ -333,9 +333,9 @@ export = { | |||||||
|      * Get first returned row. |      * Get first returned row. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      * @returns {object} - map of column name to column value |      * @returns - map of column name to column value | ||||||
|      */ |      */ | ||||||
|     getRow, |     getRow, | ||||||
|     getRowOrNull, |     getRowOrNull, | ||||||
| @@ -344,9 +344,9 @@ export = { | |||||||
|      * Get all returned rows. |      * Get all returned rows. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      * @returns {object[]} - array of all rows, each row is a map of column name to column value |      * @returns - array of all rows, each row is a map of column name to column value | ||||||
|      */ |      */ | ||||||
|     getRows, |     getRows, | ||||||
|     getRawRows, |     getRawRows, | ||||||
| @@ -357,9 +357,9 @@ export = { | |||||||
|      * Get a map of first column mapping to second column. |      * Get a map of first column mapping to second column. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      * @returns {object} - map of first column to second column |      * @returns - map of first column to second column | ||||||
|      */ |      */ | ||||||
|     getMap, |     getMap, | ||||||
|  |  | ||||||
| @@ -367,9 +367,9 @@ export = { | |||||||
|      * Get a first column in an array. |      * Get a first column in an array. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      * @returns {object[]} - array of first column of all returned rows |      * @returns array of first column of all returned rows | ||||||
|      */ |      */ | ||||||
|     getColumn, |     getColumn, | ||||||
|  |  | ||||||
| @@ -377,8 +377,8 @@ export = { | |||||||
|      * Execute SQL |      * Execute SQL | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param {string} query - SQL query with ? used as parameter placeholder |      * @param query - SQL query with ? used as parameter placeholder | ||||||
|      * @param {object[]} [params] - array of params if needed |      * @param params - array of params if needed | ||||||
|      */ |      */ | ||||||
|     execute, |     execute, | ||||||
|     executeMany, |     executeMany, | ||||||
|   | |||||||
| @@ -156,9 +156,9 @@ const STRING_MIME_TYPES = [ | |||||||
|     "image/svg+xml" |     "image/svg+xml" | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| function isStringNote(type: string, mime: string) { | function isStringNote(type: string | null, mime: string) { | ||||||
|     // render and book are string note in the sense that they are expected to contain empty string |     // render and book are string note in the sense that they are expected to contain empty string | ||||||
|     return ["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"].includes(type) |     return (type && ["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"].includes(type)) | ||||||
|         || mime.startsWith('text/') |         || mime.startsWith('text/') | ||||||
|         || STRING_MIME_TYPES.includes(mime); |         || STRING_MIME_TYPES.includes(mime); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,17 @@ | |||||||
| const {JSDOM} = require("jsdom"); | import { JSDOM } from "jsdom"; | ||||||
| const shaca = require('./shaca/shaca.js'); | import shaca = require('./shaca/shaca'); | ||||||
| const assetPath = require('../services/asset_path'); | import assetPath = require('../services/asset_path'); | ||||||
| const shareRoot = require('./share_root.js'); | import shareRoot = require('./share_root'); | ||||||
| const escapeHtml = require('escape-html'); | import escapeHtml = require('escape-html'); | ||||||
|  | import SNote = require("./shaca/entities/snote"); | ||||||
| 
 | 
 | ||||||
| function getContent(note) { | interface Result { | ||||||
|  |     header: string; | ||||||
|  |     content: string | Buffer | undefined; | ||||||
|  |     isEmpty: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getContent(note: SNote) { | ||||||
|     if (note.isProtected) { |     if (note.isProtected) { | ||||||
|         return { |         return { | ||||||
|             header: '', |             header: '', | ||||||
| @@ -13,7 +20,7 @@ function getContent(note) { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const result = { |     const result: Result = { | ||||||
|         content: note.getContent(), |         content: note.getContent(), | ||||||
|         header: '', |         header: '', | ||||||
|         isEmpty: false |         isEmpty: false | ||||||
| @@ -38,7 +45,7 @@ function getContent(note) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderIndex(result) { | function renderIndex(result: Result) { | ||||||
|     result.content += '<ul id="index">'; |     result.content += '<ul id="index">'; | ||||||
| 
 | 
 | ||||||
|     const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID); |     const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID); | ||||||
| @@ -53,10 +60,10 @@ function renderIndex(result) { | |||||||
|     result.content += '</ul>'; |     result.content += '</ul>'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderText(result, note) { | function renderText(result: Result, note: SNote) { | ||||||
|     const document = new JSDOM(result.content || "").window.document; |     const document = new JSDOM(result.content || "").window.document; | ||||||
| 
 | 
 | ||||||
|     result.isEmpty = document.body.textContent.trim().length === 0 |     result.isEmpty = document.body.textContent?.trim().length === 0 | ||||||
|         && document.querySelectorAll("img").length === 0; |         && document.querySelectorAll("img").length === 0; | ||||||
| 
 | 
 | ||||||
|     if (!result.isEmpty) { |     if (!result.isEmpty) { | ||||||
| @@ -89,7 +96,9 @@ function renderText(result, note) { | |||||||
|                 if (linkedNote) { |                 if (linkedNote) { | ||||||
|                     const isExternalLink = linkedNote.hasLabel("shareExternalLink"); |                     const isExternalLink = linkedNote.hasLabel("shareExternalLink"); | ||||||
|                     const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `./${linkedNote.shareId}`; |                     const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `./${linkedNote.shareId}`; | ||||||
|  |                     if (href) { | ||||||
|                         linkEl.setAttribute("href", href); |                         linkEl.setAttribute("href", href); | ||||||
|  |                     } | ||||||
|                     if (isExternalLink) { |                     if (isExternalLink) { | ||||||
|                         linkEl.setAttribute("target", "_blank"); |                         linkEl.setAttribute("target", "_blank"); | ||||||
|                         linkEl.setAttribute("rel", "noopener noreferrer"); |                         linkEl.setAttribute("rel", "noopener noreferrer"); | ||||||
| @@ -122,8 +131,8 @@ document.addEventListener("DOMContentLoaded", function() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderCode(result) { | function renderCode(result: Result) { | ||||||
|     if (!result.content?.trim()) { |     if (typeof result.content !== "string" || !result.content?.trim()) { | ||||||
|         result.isEmpty = true; |         result.isEmpty = true; | ||||||
|     } else { |     } else { | ||||||
|         const document = new JSDOM().window.document; |         const document = new JSDOM().window.document; | ||||||
| @@ -135,7 +144,11 @@ function renderCode(result) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderMermaid(result, note) { | function renderMermaid(result: Result, note: SNote) { | ||||||
|  |     if (typeof result.content !== "string") { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     result.content = ` |     result.content = ` | ||||||
| <img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}"> | <img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}"> | ||||||
| <hr> | <hr> | ||||||
| @@ -145,11 +158,11 @@ function renderMermaid(result, note) { | |||||||
| </details>` | </details>` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderImage(result, note) { | function renderImage(result: Result, note: SNote) { | ||||||
|     result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`; |     result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderFile(note, result) { | function renderFile(note: SNote, result: Result) { | ||||||
|     if (note.mime === 'application/pdf') { |     if (note.mime === 'application/pdf') { | ||||||
|         result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>` |         result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>` | ||||||
|     } else { |     } else { | ||||||
| @@ -157,6 +170,6 @@ function renderFile(note, result) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | export = { | ||||||
|     getContent |     getContent | ||||||
| }; | }; | ||||||
| @@ -1,23 +1,22 @@ | |||||||
| const express = require('express'); | import safeCompare = require('safe-compare'); | ||||||
| const path = require('path'); | import ejs = require("ejs"); | ||||||
| const safeCompare = require('safe-compare'); |  | ||||||
| const ejs = require("ejs"); |  | ||||||
| 
 | 
 | ||||||
| const shaca = require('./shaca/shaca.js'); | import type { Request, Response, Router } from "express"; | ||||||
| const shacaLoader = require('./shaca/shaca_loader.js'); |  | ||||||
| const shareRoot = require('./share_root.js'); |  | ||||||
| const contentRenderer = require('./content_renderer.js'); |  | ||||||
| const assetPath = require('../services/asset_path'); |  | ||||||
| const appPath = require('../services/app_path'); |  | ||||||
| const searchService = require('../services/search/services/search'); |  | ||||||
| const SearchContext = require('../services/search/search_context'); |  | ||||||
| const log = require('../services/log'); |  | ||||||
| 
 | 
 | ||||||
| /** | import shaca = require('./shaca/shaca'); | ||||||
|  * @param {SNote} note | import shacaLoader = require('./shaca/shaca_loader'); | ||||||
|  * @return {{note: SNote, branch: SBranch}|{}} | import shareRoot = require('./share_root'); | ||||||
|  */ | import contentRenderer = require('./content_renderer'); | ||||||
| function getSharedSubTreeRoot(note) { | import assetPath = require('../services/asset_path'); | ||||||
|  | import appPath = require('../services/app_path'); | ||||||
|  | import searchService = require('../services/search/services/search'); | ||||||
|  | import SearchContext = require('../services/search/search_context'); | ||||||
|  | import log = require('../services/log'); | ||||||
|  | import SNote = require('./shaca/entities/snote'); | ||||||
|  | import SBranch = require('./shaca/entities/sbranch'); | ||||||
|  | import SAttachment = require('./shaca/entities/sattachment'); | ||||||
|  | 
 | ||||||
|  | function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { | ||||||
|     if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { |     if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { | ||||||
|         // share root itself is not shared
 |         // share root itself is not shared
 | ||||||
|         return {}; |         return {}; | ||||||
| @@ -37,19 +36,18 @@ function getSharedSubTreeRoot(note) { | |||||||
|     return getSharedSubTreeRoot(parentBranch.getParentNote()); |     return getSharedSubTreeRoot(parentBranch.getParentNote()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function addNoIndexHeader(note, res) { | function addNoIndexHeader(note: SNote, res: Response) { | ||||||
|     if (note.isLabelTruthy('shareDisallowRobotIndexing')) { |     if (note.isLabelTruthy('shareDisallowRobotIndexing')) { | ||||||
|         res.setHeader('X-Robots-Tag', 'noindex'); |         res.setHeader('X-Robots-Tag', 'noindex'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function requestCredentials(res) { | function requestCredentials(res: Response) { | ||||||
|     res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm", charset="UTF-8"') |     res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm", charset="UTF-8"') | ||||||
|         .sendStatus(401); |         .sendStatus(401); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** @returns {SAttachment|boolean} */ | function checkAttachmentAccess(attachmentId: string, req: Request, res: Response) { | ||||||
| function checkAttachmentAccess(attachmentId, req, res) { |  | ||||||
|     const attachment = shaca.getAttachment(attachmentId); |     const attachment = shaca.getAttachment(attachmentId); | ||||||
| 
 | 
 | ||||||
|     if (!attachment) { |     if (!attachment) { | ||||||
| @@ -65,8 +63,7 @@ function checkAttachmentAccess(attachmentId, req, res) { | |||||||
|     return note ? attachment : false; |     return note ? attachment : false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** @returns {SNote|boolean} */ | function checkNoteAccess(noteId: string, req: Request, res: Response) { | ||||||
| function checkNoteAccess(noteId, req, res) { |  | ||||||
|     const note = shaca.getNote(noteId); |     const note = shaca.getNote(noteId); | ||||||
| 
 | 
 | ||||||
|     if (!note) { |     if (!note) { | ||||||
| @@ -109,12 +106,16 @@ function checkNoteAccess(noteId, req, res) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function renderImageAttachment(image, res, attachmentName) { | function renderImageAttachment(image: SNote, res: Response, attachmentName: string) { | ||||||
|     let svgString = '<svg/>' |     let svgString = '<svg/>' | ||||||
|     const attachment = image.getAttachmentByTitle(attachmentName); |     const attachment = image.getAttachmentByTitle(attachmentName); | ||||||
| 
 |     if (!attachment) { | ||||||
|     if (attachment) { |         res.status(404).render("share/404"); | ||||||
|         svgString = attachment.getContent(); |         return; | ||||||
|  |     } | ||||||
|  |     const content = attachment.getContent(); | ||||||
|  |     if (typeof content === "string") { | ||||||
|  |         svgString = content; | ||||||
|     } else { |     } else { | ||||||
|         // backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
 |         // backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
 | ||||||
|         const contentSvg = image.getJsonContentSafely()?.svg; |         const contentSvg = image.getJsonContentSafely()?.svg; | ||||||
| @@ -130,8 +131,8 @@ function renderImageAttachment(image, res, attachmentName) { | |||||||
|     res.send(svg); |     res.send(svg); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function register(router) { | function register(router: Router) { | ||||||
|     function renderNote(note, req, res) { |     function renderNote(note: SNote, req: Request, res: Response) { | ||||||
|         if (!note) { |         if (!note) { | ||||||
|             res.status(404).render("share/404"); |             res.status(404).render("share/404"); | ||||||
|             return; |             return; | ||||||
| @@ -152,35 +153,42 @@ function register(router) { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const {header, content, isEmpty} = contentRenderer.getContent(note); |         const { header, content, isEmpty } = contentRenderer.getContent(note); | ||||||
|         const subRoot = getSharedSubTreeRoot(note); |         const subRoot = getSharedSubTreeRoot(note); | ||||||
|         const opts = {note, header, content, isEmpty, subRoot, assetPath, appPath}; |         const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath }; | ||||||
|         let useDefaultView = true; |         let useDefaultView = true; | ||||||
| 
 | 
 | ||||||
|         // Check if the user has their own template
 |         // Check if the user has their own template
 | ||||||
|         if (note.hasRelation('shareTemplate')) { |         if (note.hasRelation('shareTemplate')) { | ||||||
|             // Get the template note and content
 |             // Get the template note and content
 | ||||||
|             const templateId = note.getRelation('shareTemplate').value; |             const templateId = note.getRelation('shareTemplate')?.value; | ||||||
|             const templateNote = shaca.getNote(templateId); |             const templateNote = templateId && shaca.getNote(templateId); | ||||||
| 
 | 
 | ||||||
|             // Make sure the note type is correct
 |             // Make sure the note type is correct
 | ||||||
|             if (templateNote.type === 'code' && templateNote.mime === 'application/x-ejs') { |             if (templateNote && templateNote.type === 'code' && templateNote.mime === 'application/x-ejs') { | ||||||
| 
 | 
 | ||||||
|                 // EJS caches the result of this so we don't need to pre-cache
 |                 // EJS caches the result of this so we don't need to pre-cache
 | ||||||
|                 const includer = (path) => { |                 const includer = (path: string) => { | ||||||
|                     const childNote = templateNote.children.find(n => path === n.title); |                     const childNote = templateNote.children.find(n => path === n.title); | ||||||
|                     if (!childNote) return null; |                     if (!childNote) throw new Error("Unable to find child note."); | ||||||
|                     if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') return null; |                     if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') throw new Error("Incorrect child note type."); | ||||||
|                     return { template: childNote.getContent() }; | 
 | ||||||
|  |                     const template = childNote.getContent(); | ||||||
|  |                     if (typeof template !== "string") throw new Error("Invalid template content type."); | ||||||
|  | 
 | ||||||
|  |                     return { template }; | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 // Try to render user's template, w/ fallback to default view
 |                 // Try to render user's template, w/ fallback to default view
 | ||||||
|                 try { |                 try { | ||||||
|                     const ejsResult = ejs.render(templateNote.getContent(), opts, {includer}); |                     const content = templateNote.getContent(); | ||||||
|  |                     if (typeof content === "string") { | ||||||
|  |                         const ejsResult = ejs.render(content, opts, { includer }); | ||||||
|                         res.send(ejsResult); |                         res.send(ejsResult); | ||||||
|                         useDefaultView = false; // Rendering went okay, don't use default view
 |                         useDefaultView = false; // Rendering went okay, don't use default view
 | ||||||
|                     } |                     } | ||||||
|                 catch (e) { |                 } | ||||||
|  |                 catch (e: any) { | ||||||
|                     log.error(`Rendering user provided share template (${templateId}) threw exception ${e.message} with stacktrace: ${e.stack}`); |                     log.error(`Rendering user provided share template (${templateId}) threw exception ${e.message} with stacktrace: ${e.stack}`); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -199,13 +207,18 @@ function register(router) { | |||||||
| 
 | 
 | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|  |         if (!shaca.shareRootNote) { | ||||||
|  |             return res.status(404) | ||||||
|  |                 .json({ message: "Share root note not found" }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         renderNote(shaca.shareRootNote, req, res); |         renderNote(shaca.shareRootNote, req, res); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     router.get('/share/:shareId', (req, res, next) => { |     router.get('/share/:shareId', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         const {shareId} = req.params; |         const { shareId } = req.params; | ||||||
| 
 | 
 | ||||||
|         const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; |         const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; | ||||||
| 
 | 
 | ||||||
| @@ -214,7 +227,7 @@ function register(router) { | |||||||
| 
 | 
 | ||||||
|     router.get('/share/api/notes/:noteId', (req, res, next) => { |     router.get('/share/api/notes/:noteId', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
|         let note; |         let note: SNote | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { |         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -228,7 +241,7 @@ function register(router) { | |||||||
|     router.get('/share/api/notes/:noteId/download', (req, res, next) => { |     router.get('/share/api/notes/:noteId/download', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         let note; |         let note: SNote | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { |         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -252,7 +265,7 @@ function register(router) { | |||||||
|     router.get('/share/api/images/:noteId/:filename', (req, res, next) => { |     router.get('/share/api/images/:noteId/:filename', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         let image; |         let image: SNote | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(image = checkNoteAccess(req.params.noteId, req, res))) { |         if (!(image = checkNoteAccess(req.params.noteId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -277,7 +290,7 @@ function register(router) { | |||||||
|     router.get('/share/api/attachments/:attachmentId/image/:filename', (req, res, next) => { |     router.get('/share/api/attachments/:attachmentId/image/:filename', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         let attachment; |         let attachment: SAttachment | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { |         if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -296,7 +309,7 @@ function register(router) { | |||||||
|     router.get('/share/api/attachments/:attachmentId/download', (req, res, next) => { |     router.get('/share/api/attachments/:attachmentId/download', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         let attachment; |         let attachment: SAttachment | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { |         if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -320,7 +333,7 @@ function register(router) { | |||||||
|     router.get('/share/api/notes/:noteId/view', (req, res, next) => { |     router.get('/share/api/notes/:noteId/view', (req, res, next) => { | ||||||
|         shacaLoader.ensureLoad(); |         shacaLoader.ensureLoad(); | ||||||
| 
 | 
 | ||||||
|         let note; |         let note: SNote | boolean; | ||||||
| 
 | 
 | ||||||
|         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { |         if (!(note = checkNoteAccess(req.params.noteId, req, res))) { | ||||||
|             return; |             return; | ||||||
| @@ -341,18 +354,22 @@ function register(router) { | |||||||
|         const ancestorNoteId = req.query.ancestorNoteId ?? "_share"; |         const ancestorNoteId = req.query.ancestorNoteId ?? "_share"; | ||||||
|         let note; |         let note; | ||||||
| 
 | 
 | ||||||
|  |         if (typeof ancestorNoteId !== "string") { | ||||||
|  |             return res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // This will automatically return if no ancestorNoteId is provided and there is no shareIndex
 |         // This will automatically return if no ancestorNoteId is provided and there is no shareIndex
 | ||||||
|         if (!(note = checkNoteAccess(ancestorNoteId, req, res))) { |         if (!(note = checkNoteAccess(ancestorNoteId, req, res))) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const {search} = req.query; |         const { search } = req.query; | ||||||
| 
 | 
 | ||||||
|         if (!search?.trim()) { |         if (typeof search !== "string" || !search?.trim()) { | ||||||
|             return res.status(400).json({ message: "'search' parameter is mandatory." }); |             return res.status(400).json({ message: "'search' parameter is mandatory." }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const searchContext = new SearchContext({ancestorNoteId: ancestorNoteId}); |         const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId }); | ||||||
|         const searchResults = searchService.findResultsWithQuery(search, searchContext); |         const searchResults = searchService.findResultsWithQuery(search, searchContext); | ||||||
|         const filteredResults = searchResults.map(sr => { |         const filteredResults = searchResults.map(sr => { | ||||||
|             const fullNote = shaca.notes[sr.noteId]; |             const fullNote = shaca.notes[sr.noteId]; | ||||||
| @@ -366,6 +383,6 @@ function register(router) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | export = { | ||||||
|     register |     register | ||||||
| } | } | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| let shaca; |  | ||||||
|  |  | ||||||
| class AbstractShacaEntity { |  | ||||||
|     /** @return {Shaca} */ |  | ||||||
|     get shaca() { |  | ||||||
|         if (!shaca) { |  | ||||||
|             shaca = require('../shaca.js'); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return shaca; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = AbstractShacaEntity; |  | ||||||
							
								
								
									
										15
									
								
								src/share/shaca/entities/abstract_shaca_entity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/share/shaca/entities/abstract_shaca_entity.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import Shaca from "../shaca-interface"; | ||||||
|  |  | ||||||
|  | let shaca: Shaca; | ||||||
|  |  | ||||||
|  | class AbstractShacaEntity { | ||||||
|  |     get shaca(): Shaca { | ||||||
|  |         if (!shaca) { | ||||||
|  |             shaca = require('../shaca'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return shaca; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export = AbstractShacaEntity; | ||||||
							
								
								
									
										4
									
								
								src/share/shaca/entities/rows.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/share/shaca/entities/rows.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | type SNoteRow = [ string, string, string, string, string, string, boolean ]; | ||||||
|  | type SBranchRow = [ string, string, string, string, string, boolean ]; | ||||||
|  | type SAttributeRow = [ string, string, string, string, string, boolean, number ]; | ||||||
|  | type SAttachmentRow = [ string, string, string, string, string, string, string ]; | ||||||
| @@ -1,39 +1,42 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const sql = require('../../sql'); | import sql = require('../../sql'); | ||||||
| const utils = require('../../../services/utils'); | import utils = require('../../../services/utils'); | ||||||
| const AbstractShacaEntity = require('./abstract_shaca_entity.js'); | import AbstractShacaEntity = require('./abstract_shaca_entity'); | ||||||
|  | import SNote = require('./snote'); | ||||||
|  | import { Blob } from '../../../services/blob-interface'; | ||||||
| 
 | 
 | ||||||
| class SAttachment extends AbstractShacaEntity { | class SAttachment extends AbstractShacaEntity { | ||||||
|     constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]) { |     private attachmentId: string; | ||||||
|  |     ownerId: string; | ||||||
|  |     title: string; | ||||||
|  |     role: string; | ||||||
|  |     mime: string; | ||||||
|  |     private blobId: string; | ||||||
|  |     /** used for caching of images */ | ||||||
|  |     private utcDateModified: string; | ||||||
|  | 
 | ||||||
|  |     constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]: SAttachmentRow) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         /** @param {string} */ |  | ||||||
|         this.attachmentId = attachmentId; |         this.attachmentId = attachmentId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.ownerId = ownerId; |         this.ownerId = ownerId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.title = title; |         this.title = title; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.role = role; |         this.role = role; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.mime = mime; |         this.mime = mime; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.blobId = blobId; |         this.blobId = blobId; | ||||||
|         /** @param {string} */ |         this.utcDateModified = utcDateModified; | ||||||
|         this.utcDateModified = utcDateModified; // used for caching of images
 |  | ||||||
| 
 | 
 | ||||||
|         this.shaca.attachments[this.attachmentId] = this; |         this.shaca.attachments[this.attachmentId] = this; | ||||||
|         this.shaca.notes[this.ownerId].attachments.push(this); |         this.shaca.notes[this.ownerId].attachments.push(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |     get note(): SNote { | ||||||
|     get note() { |  | ||||||
|         return this.shaca.notes[this.ownerId]; |         return this.shaca.notes[this.ownerId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getContent(silentNotFoundError = false) { |     getContent(silentNotFoundError = false) { | ||||||
|         const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); |         const row = sql.getRow<Pick<Blob, "content">>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); | ||||||
| 
 | 
 | ||||||
|         if (!row) { |         if (!row) { | ||||||
|             if (silentNotFoundError) { |             if (silentNotFoundError) { | ||||||
| @@ -56,7 +59,7 @@ class SAttachment extends AbstractShacaEntity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} true if the attachment has string content (not binary) */ |     /** @returns true if the attachment has string content (not binary) */ | ||||||
|     hasStringContent() { |     hasStringContent() { | ||||||
|         return utils.isStringNote(null, this.mime); |         return utils.isStringNote(null, this.mime); | ||||||
|     } |     } | ||||||
| @@ -67,11 +70,10 @@ class SAttachment extends AbstractShacaEntity { | |||||||
|             role: this.role, |             role: this.role, | ||||||
|             mime: this.mime, |             mime: this.mime, | ||||||
|             title: this.title, |             title: this.title, | ||||||
|             position: this.position, |  | ||||||
|             blobId: this.blobId, |             blobId: this.blobId, | ||||||
|             utcDateModified: this.utcDateModified |             utcDateModified: this.utcDateModified | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = SAttachment; | export = SAttachment; | ||||||
| @@ -1,24 +1,28 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const AbstractShacaEntity = require('./abstract_shaca_entity.js'); | import SNote = require("./snote"); | ||||||
|  | 
 | ||||||
|  | const AbstractShacaEntity = require('./abstract_shaca_entity'); | ||||||
| 
 | 
 | ||||||
| class SAttribute extends AbstractShacaEntity { | class SAttribute extends AbstractShacaEntity { | ||||||
|     constructor([attributeId, noteId, type, name, value, isInheritable, position]) { | 
 | ||||||
|  |     attributeId: string; | ||||||
|  |     private noteId: string; | ||||||
|  |     type: string; | ||||||
|  |     name: string; | ||||||
|  |     private position: number; | ||||||
|  |     value: string; | ||||||
|  |     isInheritable: boolean; | ||||||
|  | 
 | ||||||
|  |     constructor([attributeId, noteId, type, name, value, isInheritable, position]: SAttributeRow) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         /** @param {string} */ |  | ||||||
|         this.attributeId = attributeId; |         this.attributeId = attributeId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.noteId = noteId; |         this.noteId = noteId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.type = type; |         this.type = type; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.name = name; |         this.name = name; | ||||||
|         /** @param {int} */ |  | ||||||
|         this.position = position; |         this.position = position; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.value = value; |         this.value = value; | ||||||
|         /** @param {boolean} */ |  | ||||||
|         this.isInheritable = !!isInheritable; |         this.isInheritable = !!isInheritable; | ||||||
| 
 | 
 | ||||||
|         this.shaca.attributes[this.attributeId] = this; |         this.shaca.attributes[this.attributeId] = this; | ||||||
| @@ -53,41 +57,34 @@ class SAttribute extends AbstractShacaEntity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     get isAffectingSubtree() { |     get isAffectingSubtree() { | ||||||
|         return this.isInheritable |         return this.isInheritable | ||||||
|             || (this.type === 'relation' && ['template', 'inherit'].includes(this.name)); |             || (this.type === 'relation' && ['template', 'inherit'].includes(this.name)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {string} */ |  | ||||||
|     get targetNoteId() { // alias
 |     get targetNoteId() { // alias
 | ||||||
|         return this.type === 'relation' ? this.value : undefined; |         return this.type === 'relation' ? this.value : undefined; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     isAutoLink() { |     isAutoLink() { | ||||||
|         return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); |         return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |     get note(): SNote { | ||||||
|     get note() { |  | ||||||
|         return this.shaca.notes[this.noteId]; |         return this.shaca.notes[this.noteId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote|null} */ |     get targetNote(): SNote | null | undefined { | ||||||
|     get targetNote() { |  | ||||||
|         if (this.type === 'relation') { |         if (this.type === 'relation') { | ||||||
|             return this.shaca.notes[this.value]; |             return this.shaca.notes[this.value]; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote|null} */ |     getNote(): SNote | null { | ||||||
|     getNote() { |  | ||||||
|         return this.shaca.getNote(this.noteId); |         return this.shaca.getNote(this.noteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote|null} */ |     getTargetNote(): SNote | null { | ||||||
|     getTargetNote() { |  | ||||||
|         if (this.type !== 'relation') { |         if (this.type !== 'relation') { | ||||||
|             throw new Error(`Attribute '${this.attributeId}' is not relation`); |             throw new Error(`Attribute '${this.attributeId}' is not relation`); | ||||||
|         } |         } | ||||||
| @@ -112,4 +109,4 @@ class SAttribute extends AbstractShacaEntity { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = SAttribute; | export = SAttribute; | ||||||
| @@ -1,22 +1,25 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const AbstractShacaEntity = require('./abstract_shaca_entity.js'); | import AbstractShacaEntity = require('./abstract_shaca_entity'); | ||||||
|  | import SNote = require('./snote'); | ||||||
| 
 | 
 | ||||||
| class SBranch extends AbstractShacaEntity { | class SBranch extends AbstractShacaEntity { | ||||||
|     constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) { | 
 | ||||||
|  |     private branchId: string; | ||||||
|  |     private noteId: string; | ||||||
|  |     parentNoteId: string; | ||||||
|  |     private prefix: string; | ||||||
|  |     private isExpanded: boolean; | ||||||
|  |     isHidden: boolean; | ||||||
|  | 
 | ||||||
|  |     constructor([branchId, noteId, parentNoteId, prefix, isExpanded]: SBranchRow) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         /** @param {string} */ |  | ||||||
|         this.branchId = branchId; |         this.branchId = branchId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.noteId = noteId; |         this.noteId = noteId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.parentNoteId = parentNoteId; |         this.parentNoteId = parentNoteId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.prefix = prefix; |         this.prefix = prefix; | ||||||
|         /** @param {boolean} */ |  | ||||||
|         this.isExpanded = !!isExpanded; |         this.isExpanded = !!isExpanded; | ||||||
|         /** @param {boolean} */ |  | ||||||
|         this.isHidden = false; |         this.isHidden = false; | ||||||
| 
 | 
 | ||||||
|         const childNote = this.childNote; |         const childNote = this.childNote; | ||||||
| @@ -38,25 +41,21 @@ class SBranch extends AbstractShacaEntity { | |||||||
|         this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; |         this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |     get childNote(): SNote { | ||||||
|     get childNote() { |  | ||||||
|         return this.shaca.notes[this.noteId]; |         return this.shaca.notes[this.noteId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |  | ||||||
|     getNote() { |     getNote() { | ||||||
|         return this.childNote; |         return this.childNote; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |     get parentNote(): SNote { | ||||||
|     get parentNote() { |  | ||||||
|         return this.shaca.notes[this.parentNoteId]; |         return this.shaca.notes[this.parentNoteId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote} */ |  | ||||||
|     getParentNote() { |     getParentNote() { | ||||||
|         return this.parentNote; |         return this.parentNote; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = SBranch; | export = SBranch; | ||||||
| @@ -1,108 +1,103 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const sql = require('../../sql'); | import sql = require('../../sql'); | ||||||
| const utils = require('../../../services/utils'); | import utils = require('../../../services/utils'); | ||||||
| const AbstractShacaEntity = require('./abstract_shaca_entity.js'); | import AbstractShacaEntity = require('./abstract_shaca_entity'); | ||||||
| const escape = require('escape-html'); | import escape = require('escape-html'); | ||||||
|  | import { Blob } from '../../../services/blob-interface'; | ||||||
|  | import SAttachment = require('./sattachment'); | ||||||
|  | import SAttribute = require('./sattribute'); | ||||||
|  | import SBranch = require('./sbranch'); | ||||||
| 
 | 
 | ||||||
| const LABEL = 'label'; | const LABEL = 'label'; | ||||||
| const RELATION = 'relation'; | const RELATION = 'relation'; | ||||||
| const CREDENTIALS = 'shareCredentials'; | const CREDENTIALS = 'shareCredentials'; | ||||||
| 
 | 
 | ||||||
| const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS; | const isCredentials = (attr: SAttribute) => attr.type === 'label' && attr.name === CREDENTIALS; | ||||||
| 
 | 
 | ||||||
| class SNote extends AbstractShacaEntity { | class SNote extends AbstractShacaEntity { | ||||||
|     constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]) { |     noteId: string; | ||||||
|  |     title: string; | ||||||
|  |     type: string; | ||||||
|  |     mime: string; | ||||||
|  |     private blobId: string; | ||||||
|  |     utcDateModified: string; | ||||||
|  |     isProtected: boolean; | ||||||
|  |     parentBranches: SBranch[]; | ||||||
|  |     parents: SNote[]; | ||||||
|  |     children: SNote[]; | ||||||
|  |     private ownedAttributes: SAttribute[]; | ||||||
|  |     private __attributeCache: SAttribute[] | null; | ||||||
|  |     private __inheritableAttributeCache: SAttribute[] | null; | ||||||
|  |     targetRelations: SAttribute[]; | ||||||
|  |     attachments: SAttachment[]; | ||||||
|  | 
 | ||||||
|  |     constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: SNoteRow) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         /** @param {string} */ |  | ||||||
|         this.noteId = noteId; |         this.noteId = noteId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.title = isProtected ? "[protected]" : title; |         this.title = isProtected ? "[protected]" : title; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.type = type; |         this.type = type; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.mime = mime; |         this.mime = mime; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.blobId = blobId; |         this.blobId = blobId; | ||||||
|         /** @param {string} */ |  | ||||||
|         this.utcDateModified = utcDateModified; // used for caching of images
 |         this.utcDateModified = utcDateModified; // used for caching of images
 | ||||||
|         /** @param {boolean} */ |  | ||||||
|         this.isProtected = isProtected; |         this.isProtected = isProtected; | ||||||
| 
 | 
 | ||||||
|         /** @param {SBranch[]} */ |  | ||||||
|         this.parentBranches = []; |         this.parentBranches = []; | ||||||
|         /** @param {SNote[]} */ |  | ||||||
|         this.parents = []; |         this.parents = []; | ||||||
|         /** @param {SNote[]} */ |  | ||||||
|         this.children = []; |         this.children = []; | ||||||
|         /** @param {SAttribute[]} */ |  | ||||||
|         this.ownedAttributes = []; |         this.ownedAttributes = []; | ||||||
| 
 | 
 | ||||||
|         /** @param {SAttribute[]|null} */ |  | ||||||
|         this.__attributeCache = null; |         this.__attributeCache = null; | ||||||
|         /** @param {SAttribute[]|null} */ |  | ||||||
|         this.__inheritableAttributeCache = null; |         this.__inheritableAttributeCache = null; | ||||||
| 
 | 
 | ||||||
|         /** @param {SAttribute[]} */ |  | ||||||
|         this.targetRelations = []; |         this.targetRelations = []; | ||||||
| 
 |  | ||||||
|         /** @param {SAttachment[]} */ |  | ||||||
|         this.attachments = []; |         this.attachments = []; | ||||||
| 
 | 
 | ||||||
|         this.shaca.notes[this.noteId] = this; |         this.shaca.notes[this.noteId] = this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch[]} */ |  | ||||||
|     getParentBranches() { |     getParentBranches() { | ||||||
|         return this.parentBranches; |         return this.parentBranches; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch[]} */ |  | ||||||
|     getBranches() { |     getBranches() { | ||||||
|         return this.parentBranches; |         return this.parentBranches; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch[]} */ |     getChildBranches(): SBranch[] { | ||||||
|     getChildBranches() { |  | ||||||
|         return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); |         return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch[]} */ |  | ||||||
|     getVisibleChildBranches() { |     getVisibleChildBranches() { | ||||||
|         return this.getChildBranches() |         return this.getChildBranches() | ||||||
|             .filter(branch => !branch.isHidden |             .filter(branch => !branch.isHidden | ||||||
|                 && !branch.getNote().isLabelTruthy('shareHiddenFromTree')); |                 && !branch.getNote().isLabelTruthy('shareHiddenFromTree')); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote[]} */ |  | ||||||
|     getParentNotes() { |     getParentNotes() { | ||||||
|         return this.parents; |         return this.parents; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote[]} */ |  | ||||||
|     getChildNotes() { |     getChildNotes() { | ||||||
|         return this.children; |         return this.children; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote[]} */ |  | ||||||
|     getVisibleChildNotes() { |     getVisibleChildNotes() { | ||||||
|         return this.getVisibleChildBranches() |         return this.getVisibleChildBranches() | ||||||
|             .map(branch => branch.getNote()); |             .map(branch => branch.getNote()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     hasChildren() { |     hasChildren() { | ||||||
|         return this.children && this.children.length > 0; |         return this.children && this.children.length > 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     hasVisibleChildren() { |     hasVisibleChildren() { | ||||||
|         return this.getVisibleChildNotes().length > 0; |         return this.getVisibleChildNotes().length > 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getContent(silentNotFoundError = false) { |     getContent(silentNotFoundError = false) { | ||||||
|         const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); |         const row = sql.getRow<Pick<Blob, "content">>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); | ||||||
| 
 | 
 | ||||||
|         if (!row) { |         if (!row) { | ||||||
|             if (silentNotFoundError) { |             if (silentNotFoundError) { | ||||||
| @@ -125,43 +120,41 @@ class SNote extends AbstractShacaEntity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} true if the note has string content (not binary) */ |     /** @returns true if the note has string content (not binary) */ | ||||||
|     hasStringContent() { |     hasStringContent() { | ||||||
|         return utils.isStringNote(this.type, this.mime); |         return utils.isStringNote(this.type, this.mime); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [type] - (optional) attribute type to filter |      * @param type - (optional) attribute type to filter | ||||||
|      * @param {string} [name] - (optional) attribute name to filter |      * @param name - (optional) attribute name to filter | ||||||
|      * @returns {SAttribute[]} all note's attributes, including inherited ones |      * @returns all note's attributes, including inherited ones | ||||||
|      */ |      */ | ||||||
|     getAttributes(type, name) { |     getAttributes(type?: string, name?: string) { | ||||||
|         if (!this.__attributeCache) { |         let attributeCache = this.__attributeCache; | ||||||
|             this.__getAttributes([]); |         if (!attributeCache) { | ||||||
|  |             attributeCache = this.__getAttributes([]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (type && name) { |         if (type && name) { | ||||||
|             return this.__attributeCache.filter(attr => attr.type === type && attr.name === name && !isCredentials(attr)); |             return attributeCache.filter(attr => attr.type === type && attr.name === name && !isCredentials(attr)); | ||||||
|         } |         } | ||||||
|         else if (type) { |         else if (type) { | ||||||
|             return this.__attributeCache.filter(attr => attr.type === type && !isCredentials(attr)); |             return attributeCache.filter(attr => attr.type === type && !isCredentials(attr)); | ||||||
|         } |         } | ||||||
|         else if (name) { |         else if (name) { | ||||||
|             return this.__attributeCache.filter(attr => attr.name === name && !isCredentials(attr)); |             return attributeCache.filter(attr => attr.name === name && !isCredentials(attr)); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             return this.__attributeCache.filter(attr => !isCredentials(attr)); |             return attributeCache.filter(attr => !isCredentials(attr)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttribute[]} */ |  | ||||||
|     getCredentials() { |     getCredentials() { | ||||||
|         this.__getAttributes([]); |         return this.__getAttributes([]).filter(isCredentials); | ||||||
| 
 |  | ||||||
|         return this.__attributeCache.filter(isCredentials); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     __getAttributes(path) { |     __getAttributes(path: string[]) { | ||||||
|         if (path.includes(this.noteId)) { |         if (path.includes(this.noteId)) { | ||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
| @@ -176,7 +169,7 @@ class SNote extends AbstractShacaEntity { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const templateAttributes = []; |             const templateAttributes: SAttribute[] = []; | ||||||
| 
 | 
 | ||||||
|             for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
 |             for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates
 | ||||||
|                 if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) { |                 if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) { | ||||||
| @@ -212,8 +205,7 @@ class SNote extends AbstractShacaEntity { | |||||||
|         return this.__attributeCache; |         return this.__attributeCache; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttribute[]} */ |     __getInheritableAttributes(path: string[]) { | ||||||
|     __getInheritableAttributes(path) { |  | ||||||
|         if (path.includes(this.noteId)) { |         if (path.includes(this.noteId)) { | ||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
| @@ -222,204 +214,225 @@ class SNote extends AbstractShacaEntity { | |||||||
|             this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
 |             this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.__inheritableAttributeCache; |         return this.__inheritableAttributeCache || []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |     /** | ||||||
|     hasAttribute(type, name) { |      * @throws Error in case of invalid JSON | ||||||
|  |      */ | ||||||
|  |     getJsonContent(): any | null { | ||||||
|  |         const content = this.getContent(); | ||||||
|  | 
 | ||||||
|  |         if (typeof content !== "string" || !content || !content.trim()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return JSON.parse(content); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** @returns valid object or null if the content cannot be parsed as JSON */ | ||||||
|  |     getJsonContentSafely() { | ||||||
|  |         try { | ||||||
|  |             return this.getJsonContent(); | ||||||
|  |         } | ||||||
|  |         catch (e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     hasAttribute(type: string, name: string) { | ||||||
|         return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); |         return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote|null} */ |     getRelationTarget(name: string) { | ||||||
|     getRelationTarget(name) { |  | ||||||
|         const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); |         const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); | ||||||
| 
 | 
 | ||||||
|         return relation ? relation.targetNote : null; |         return relation ? relation.targetNote : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {boolean} true if label exists (including inherited) |      * @returns true if label exists (including inherited) | ||||||
|      */ |      */ | ||||||
|     hasLabel(name) { return this.hasAttribute(LABEL, name); } |     hasLabel(name: string) { return this.hasAttribute(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {boolean} true if label exists (including inherited) and does not have "false" value. |      * @returns true if label exists (including inherited) and does not have "false" value. | ||||||
|      */ |      */ | ||||||
|     isLabelTruthy(name) { |     isLabelTruthy(name: string) { | ||||||
|         const label = this.getLabel(name); |         const label = this.getLabel(name); | ||||||
| 
 | 
 | ||||||
|         if (!label) { |         if (!label) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return label && label.value !== 'false'; |         return !!label && label.value !== 'false'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {boolean} true if label exists (excluding inherited) |      * @returns true if label exists (excluding inherited) | ||||||
|      */ |      */ | ||||||
|     hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); } |     hasOwnedLabel(name: string) { return this.hasOwnedAttribute(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {boolean} true if relation exists (including inherited) |      * @returns true if relation exists (including inherited) | ||||||
|      */ |      */ | ||||||
|     hasRelation(name) { return this.hasAttribute(RELATION, name); } |     hasRelation(name: string) { return this.hasAttribute(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {boolean} true if relation exists (excluding inherited) |      * @returns true if relation exists (excluding inherited) | ||||||
|      */ |      */ | ||||||
|     hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); } |     hasOwnedRelation(name: string) { return this.hasOwnedAttribute(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {SAttribute|null} label if it exists, null otherwise |      * @returns label if it exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getLabel(name) { return this.getAttribute(LABEL, name); } |     getLabel(name: string) { return this.getAttribute(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {SAttribute|null} label if it exists, null otherwise |      * @returns label if it exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); } |     getOwnedLabel(name: string) { return this.getOwnedAttribute(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {SAttribute|null} relation if it exists, null otherwise |      * @returns relation if it exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getRelation(name) { return this.getAttribute(RELATION, name); } |     getRelation(name: string) { return this.getAttribute(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {SAttribute|null} relation if it exists, null otherwise |      * @returns relation if it exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); } |     getOwnedRelation(name: string) { return this.getOwnedAttribute(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {string|null} label value if label exists, null otherwise |      * @returns label value if label exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getLabelValue(name) { return this.getAttributeValue(LABEL, name); } |     getLabelValue(name: string) { return this.getAttributeValue(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param name - label name | ||||||
|      * @returns {string|null} label value if label exists, null otherwise |      * @returns label value if label exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); } |     getOwnedLabelValue(name: string) { return this.getOwnedAttributeValue(LABEL, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {string|null} relation value if relation exists, null otherwise |      * @returns relation value if relation exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getRelationValue(name) { return this.getAttributeValue(RELATION, name); } |     getRelationValue(name: string) { return this.getAttributeValue(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - relation name |      * @param name - relation name | ||||||
|      * @returns {string|null} relation value if relation exists, null otherwise |      * @returns relation value if relation exists, null otherwise | ||||||
|      */ |      */ | ||||||
|     getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); } |     getOwnedRelationValue(name: string) { return this.getOwnedAttributeValue(RELATION, name); } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} type - attribute type (label, relation, etc.) |      * @param type - attribute type (label, relation, etc.) | ||||||
|      * @param {string} name - attribute name |      * @param name - attribute name | ||||||
|      * @returns {boolean} true if note has an attribute with given type and name (excluding inherited) |      * @returns true if note has an attribute with given type and name (excluding inherited) | ||||||
|      */ |      */ | ||||||
|     hasOwnedAttribute(type, name) { |     hasOwnedAttribute(type: string, name: string) { | ||||||
|         return !!this.getOwnedAttribute(type, name); |         return !!this.getOwnedAttribute(type, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} type - attribute type (label, relation, etc.) |      * @param type - attribute type (label, relation, etc.) | ||||||
|      * @param {string} name - attribute name |      * @param name - attribute name | ||||||
|      * @returns {SAttribute} attribute of the given type and name. If there are more such attributes, first is  returned. |      * @returns attribute of the given type and name. If there are more such attributes, first is  returned. | ||||||
|      * Returns null if there's no such attribute belonging to this note. |      * Returns null if there's no such attribute belonging to this note. | ||||||
|      */ |      */ | ||||||
|     getAttribute(type, name) { |     getAttribute(type: string, name: string) { | ||||||
|         const attributes = this.getAttributes(); |         const attributes = this.getAttributes(); | ||||||
| 
 | 
 | ||||||
|         return attributes.find(attr => attr.type === type && attr.name === name); |         return attributes.find(attr => attr.type === type && attr.name === name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} type - attribute type (label, relation, etc.) |      * @param type - attribute type (label, relation, etc.) | ||||||
|      * @param {string} name - attribute name |      * @param name - attribute name | ||||||
|      * @returns {string|null} attribute value of the given type and name or null if no such attribute exists. |      * @returns attribute value of the given type and name or null if no such attribute exists. | ||||||
|      */ |      */ | ||||||
|     getAttributeValue(type, name) { |     getAttributeValue(type: string, name: string) { | ||||||
|         const attr = this.getAttribute(type, name); |         const attr = this.getAttribute(type, name); | ||||||
| 
 | 
 | ||||||
|         return attr ? attr.value : null; |         return attr ? attr.value : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} type - attribute type (label, relation, etc.) |      * @param type - attribute type (label, relation, etc.) | ||||||
|      * @param {string} name - attribute name |      * @param name - attribute name | ||||||
|      * @returns {string|null} attribute value of the given type and name or null if no such attribute exists. |      * @returns attribute value of the given type and name or null if no such attribute exists. | ||||||
|      */ |      */ | ||||||
|     getOwnedAttributeValue(type, name) { |     getOwnedAttributeValue(type: string, name: string) { | ||||||
|         const attr = this.getOwnedAttribute(type, name); |         const attr = this.getOwnedAttribute(type, name); | ||||||
| 
 | 
 | ||||||
|         return attr ? attr.value : null; |         return attr ? attr.value as string : null; // FIXME
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - label name to filter |      * @param name - label name to filter | ||||||
|      * @returns {SAttribute[]} all note's labels (attributes with type label), including inherited ones |      * @returns all note's labels (attributes with type label), including inherited ones | ||||||
|      */ |      */ | ||||||
|     getLabels(name) { |     getLabels(name: string) { | ||||||
|         return this.getAttributes(LABEL, name); |         return this.getAttributes(LABEL, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - label name to filter |      * @param name - label name to filter | ||||||
|      * @returns {string[]} all note's label values, including inherited ones |      * @returns all note's label values, including inherited ones | ||||||
|      */ |      */ | ||||||
|     getLabelValues(name) { |     getLabelValues(name: string) { | ||||||
|         return this.getLabels(name).map(l => l.value); |         return this.getLabels(name).map(l => l.value) as string[]; // FIXME
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - label name to filter |      * @param name - label name to filter | ||||||
|      * @returns {SAttribute[]} all note's labels (attributes with type label), excluding inherited ones |      * @returns all note's labels (attributes with type label), excluding inherited ones | ||||||
|      */ |      */ | ||||||
|     getOwnedLabels(name) { |     getOwnedLabels(name: string) { | ||||||
|         return this.getOwnedAttributes(LABEL, name); |         return this.getOwnedAttributes(LABEL, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - label name to filter |      * @param name - label name to filter | ||||||
|      * @returns {string[]} all note's label values, excluding inherited ones |      * @returns all note's label values, excluding inherited ones | ||||||
|      */ |      */ | ||||||
|     getOwnedLabelValues(name) { |     getOwnedLabelValues(name: string) { | ||||||
|         return this.getOwnedAttributes(LABEL, name).map(l => l.value); |         return this.getOwnedAttributes(LABEL, name).map(l => l.value); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - relation name to filter |      * @param name - relation name to filter | ||||||
|      * @returns {SAttribute[]} all note's relations (attributes with type relation), including inherited ones |      * @returns all note's relations (attributes with type relation), including inherited ones | ||||||
|      */ |      */ | ||||||
|     getRelations(name) { |     getRelations(name: string) { | ||||||
|         return this.getAttributes(RELATION, name); |         return this.getAttributes(RELATION, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [name] - relation name to filter |      * @param name - relation name to filter | ||||||
|      * @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones |      * @returns all note's relations (attributes with type relation), excluding inherited ones | ||||||
|      */ |      */ | ||||||
|     getOwnedRelations(name) { |     getOwnedRelations(name: string) { | ||||||
|         return this.getOwnedAttributes(RELATION, name); |         return this.getOwnedAttributes(RELATION, name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param {string} [type] - (optional) attribute type to filter |      * @param type - (optional) attribute type to filter | ||||||
|      * @param {string} [name] - (optional) attribute name to filter |      * @param name - (optional) attribute name to filter | ||||||
|      * @returns {SAttribute[]} note's "owned" attributes - excluding inherited ones |      * @returns note's "owned" attributes - excluding inherited ones | ||||||
|      */ |      */ | ||||||
|     getOwnedAttributes(type, name) { |     getOwnedAttributes(type: string, name: string) { | ||||||
|         // it's a common mistake to include # or ~ into attribute name
 |         // it's a common mistake to include # or ~ into attribute name
 | ||||||
|         if (name && ["#", "~"].includes(name[0])) { |         if (name && ["#", "~"].includes(name[0])) { | ||||||
|             name = name.substr(1); |             name = name.substr(1); | ||||||
| @@ -440,42 +453,36 @@ class SNote extends AbstractShacaEntity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @returns {SAttribute} attribute belonging to this specific note (excludes inherited attributes) |      * @returns attribute belonging to this specific note (excludes inherited attributes) | ||||||
|      * |      * | ||||||
|      * This method can be significantly faster than the getAttribute() |      * This method can be significantly faster than the getAttribute() | ||||||
|      */ |      */ | ||||||
|     getOwnedAttribute(type, name) { |     getOwnedAttribute(type: string, name: string) { | ||||||
|         const attrs = this.getOwnedAttributes(type, name); |         const attrs = this.getOwnedAttributes(type, name); | ||||||
| 
 | 
 | ||||||
|         return attrs.length > 0 ? attrs[0] : null; |         return attrs.length > 0 ? attrs[0] : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     get isArchived() { |     get isArchived() { | ||||||
|         return this.hasAttribute('label', 'archived'); |         return this.hasAttribute('label', 'archived'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |  | ||||||
|     isInherited() { |     isInherited() { | ||||||
|         return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); |         return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttribute[]} */ |  | ||||||
|     getTargetRelations() { |     getTargetRelations() { | ||||||
|         return this.targetRelations; |         return this.targetRelations; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttachment[]} */ |  | ||||||
|     getAttachments() { |     getAttachments() { | ||||||
|         return this.attachments; |         return this.attachments; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttachment} */ |     getAttachmentByTitle(title: string) { | ||||||
|     getAttachmentByTitle(title) { |  | ||||||
|         return this.attachments.find(attachment => attachment.title === title); |         return this.attachments.find(attachment => attachment.title === title); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {string} */ |  | ||||||
|     get shareId() { |     get shareId() { | ||||||
|         if (this.hasOwnedLabel('shareRoot')) { |         if (this.hasOwnedLabel('shareRoot')) { | ||||||
|             return ""; |             return ""; | ||||||
| @@ -514,4 +521,4 @@ class SNote extends AbstractShacaEntity { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = SNote; | export = SNote; | ||||||
| @@ -1,45 +1,49 @@ | |||||||
| "use strict"; | import SAttachment = require("./entities/sattachment"); | ||||||
|  | import SAttribute = require("./entities/sattribute"); | ||||||
|  | import SBranch = require("./entities/sbranch"); | ||||||
|  | import SNote = require("./entities/snote"); | ||||||
|  | 
 | ||||||
|  | export default class Shaca { | ||||||
|  | 
 | ||||||
|  |     notes!: Record<string, SNote>; | ||||||
|  |     branches!: Record<string, SBranch>; | ||||||
|  |     childParentToBranch!: Record<string, SBranch>; | ||||||
|  |     private attributes!: Record<string, SAttribute>; | ||||||
|  |     attachments!: Record<string, SAttachment>; | ||||||
|  |     aliasToNote!: Record<string, SNote>; | ||||||
|  |     shareRootNote!: SNote | null; | ||||||
|  |     /** true if the index of all shared subtrees is enabled */ | ||||||
|  |     shareIndexEnabled!: boolean; | ||||||
|  |     loaded!: boolean; | ||||||
| 
 | 
 | ||||||
| class Shaca { |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.reset(); |         this.reset(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     reset() { |     reset() { | ||||||
|         /** @type {Object.<String, SNote>} */ |  | ||||||
|         this.notes = {}; |         this.notes = {}; | ||||||
|         /** @type {Object.<String, SBranch>} */ |  | ||||||
|         this.branches = {}; |         this.branches = {}; | ||||||
|         /** @type {Object.<String, SBranch>} */ |  | ||||||
|         this.childParentToBranch = {}; |         this.childParentToBranch = {}; | ||||||
|         /** @type {Object.<String, SAttribute>} */ |  | ||||||
|         this.attributes = {}; |         this.attributes = {}; | ||||||
|         /** @type {Object.<String, SAttachment>} */ |  | ||||||
|         this.attachments = {}; |         this.attachments = {}; | ||||||
|         /** @type {Object.<String, SNote>} */ |  | ||||||
|         this.aliasToNote = {}; |         this.aliasToNote = {}; | ||||||
| 
 | 
 | ||||||
|         /** @type {SNote|null} */ |  | ||||||
|         this.shareRootNote = null; |         this.shareRootNote = null; | ||||||
| 
 | 
 | ||||||
|         /** @type {boolean} true if the index of all shared subtrees is enabled */ |  | ||||||
|         this.shareIndexEnabled = false; |         this.shareIndexEnabled = false; | ||||||
| 
 | 
 | ||||||
|         this.loaded = false; |         this.loaded = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote|null} */ |     getNote(noteId: string) { | ||||||
|     getNote(noteId) { |  | ||||||
|         return this.notes[noteId]; |         return this.notes[noteId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {boolean} */ |     hasNote(noteId: string) { | ||||||
|     hasNote(noteId) { |  | ||||||
|         return noteId in this.notes; |         return noteId in this.notes; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SNote[]} */ |     getNotes(noteIds: string[], ignoreMissing = false) { | ||||||
|     getNotes(noteIds, ignoreMissing = false) { |  | ||||||
|         const filteredNotes = []; |         const filteredNotes = []; | ||||||
| 
 | 
 | ||||||
|         for (const noteId of noteIds) { |         for (const noteId of noteIds) { | ||||||
| @@ -59,27 +63,23 @@ class Shaca { | |||||||
|         return filteredNotes; |         return filteredNotes; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch|null} */ |     getBranch(branchId: string) { | ||||||
|     getBranch(branchId) { |  | ||||||
|         return this.branches[branchId]; |         return this.branches[branchId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SBranch|null} */ |     getBranchFromChildAndParent(childNoteId: string, parentNoteId: string) { | ||||||
|     getBranchFromChildAndParent(childNoteId, parentNoteId) { |  | ||||||
|         return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; |         return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttribute|null} */ |     getAttribute(attributeId: string) { | ||||||
|     getAttribute(attributeId) { |  | ||||||
|         return this.attributes[attributeId]; |         return this.attributes[attributeId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** @returns {SAttachment|null} */ |     getAttachment(attachmentId: string) { | ||||||
|     getAttachment(attachmentId) { |  | ||||||
|         return this.attachments[attachmentId]; |         return this.attachments[attachmentId]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getEntity(entityName, entityId) { |     getEntity(entityName: string, entityId: string) { | ||||||
|         if (!entityName || !entityId) { |         if (!entityName || !entityId) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
| @@ -91,10 +91,6 @@ class Shaca { | |||||||
|                     .replace('_', '') |                     .replace('_', '') | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         return this[camelCaseEntityName][entityId]; |         return (this as any)[camelCaseEntityName][entityId]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| const shaca = new Shaca(); |  | ||||||
| 
 |  | ||||||
| module.exports = shaca; |  | ||||||
							
								
								
									
										7
									
								
								src/share/shaca/shaca.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/share/shaca/shaca.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | import Shaca from "./shaca-interface"; | ||||||
|  |  | ||||||
|  | const shaca = new Shaca(); | ||||||
|  |  | ||||||
|  | export = shaca; | ||||||
| @@ -1,14 +1,14 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const sql = require('../sql'); | import sql = require('../sql'); | ||||||
| const shaca = require('./shaca.js'); | import shaca = require('./shaca'); | ||||||
| const log = require('../../services/log'); | import log = require('../../services/log'); | ||||||
| const SNote = require('./entities/snote.js'); | import SNote = require('./entities/snote'); | ||||||
| const SBranch = require('./entities/sbranch.js'); | import SBranch = require('./entities/sbranch'); | ||||||
| const SAttribute = require('./entities/sattribute.js'); | import SAttribute = require('./entities/sattribute'); | ||||||
| const SAttachment = require('./entities/sattachment.js'); | import SAttachment = require('./entities/sattachment'); | ||||||
| const shareRoot = require('../share_root.js'); | import shareRoot = require('../share_root'); | ||||||
| const eventService = require('../../services/events'); | import eventService = require('../../services/events'); | ||||||
| 
 | 
 | ||||||
| function load() { | function load() { | ||||||
|     const start = Date.now(); |     const start = Date.now(); | ||||||
| @@ -35,7 +35,7 @@ function load() { | |||||||
| 
 | 
 | ||||||
|     const noteIdStr = noteIds.map(noteId => `'${noteId}'`).join(","); |     const noteIdStr = noteIds.map(noteId => `'${noteId}'`).join(","); | ||||||
| 
 | 
 | ||||||
|     const rawNoteRows = sql.getRawRows(` |     const rawNoteRows = sql.getRawRows<SNoteRow>(` | ||||||
|         SELECT noteId, title, type, mime, blobId, utcDateModified, isProtected |         SELECT noteId, title, type, mime, blobId, utcDateModified, isProtected | ||||||
|         FROM notes  |         FROM notes  | ||||||
|         WHERE isDeleted = 0  |         WHERE isDeleted = 0  | ||||||
| @@ -45,7 +45,7 @@ function load() { | |||||||
|         new SNote(row); |         new SNote(row); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const rawBranchRows = sql.getRawRows(` |     const rawBranchRows = sql.getRawRows<SBranchRow>(` | ||||||
|         SELECT branchId, noteId, parentNoteId, prefix, isExpanded, utcDateModified  |         SELECT branchId, noteId, parentNoteId, prefix, isExpanded, utcDateModified  | ||||||
|         FROM branches  |         FROM branches  | ||||||
|         WHERE isDeleted = 0  |         WHERE isDeleted = 0  | ||||||
| @@ -56,7 +56,7 @@ function load() { | |||||||
|         new SBranch(row); |         new SBranch(row); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const rawAttributeRows = sql.getRawRows(` |     const rawAttributeRows = sql.getRawRows<SAttributeRow>(` | ||||||
|         SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified  |         SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified  | ||||||
|         FROM attributes  |         FROM attributes  | ||||||
|         WHERE isDeleted = 0  |         WHERE isDeleted = 0  | ||||||
| @@ -66,14 +66,12 @@ function load() { | |||||||
|         new SAttribute(row); |         new SAttribute(row); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const rawAttachmentRows = sql.getRawRows(` |     const rawAttachmentRows = sql.getRawRows<SAttachmentRow>(` | ||||||
|         SELECT attachmentId, ownerId, role, mime, title, blobId, utcDateModified  |         SELECT attachmentId, ownerId, role, mime, title, blobId, utcDateModified  | ||||||
|         FROM attachments  |         FROM attachments  | ||||||
|         WHERE isDeleted = 0  |         WHERE isDeleted = 0  | ||||||
|           AND ownerId IN (${noteIdStr})`);
 |           AND ownerId IN (${noteIdStr})`);
 | ||||||
| 
 | 
 | ||||||
|     rawAttachmentRows.sort((a, b) => a.position < b.position ? -1 : 1); |  | ||||||
| 
 |  | ||||||
|     for (const row of rawAttachmentRows) { |     for (const row of rawAttachmentRows) { | ||||||
|         new SAttachment(row); |         new SAttachment(row); | ||||||
|     } |     } | ||||||
| @@ -89,11 +87,11 @@ function ensureLoad() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| eventService.subscribe([ eventService.ENTITY_CREATED, eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_CHANGE_SYNCED, eventService.ENTITY_DELETE_SYNCED ], ({ entityName, entity }) => { | eventService.subscribe([eventService.ENTITY_CREATED, eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_CHANGE_SYNCED, eventService.ENTITY_DELETE_SYNCED], ({ entityName, entity }) => { | ||||||
|     shaca.reset(); |     shaca.reset(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| module.exports = { | export = { | ||||||
|     load, |     load, | ||||||
|     ensureLoad |     ensureLoad | ||||||
| }; | }; | ||||||
| @@ -1,3 +1,3 @@ | |||||||
| module.exports = { | export = { | ||||||
|     SHARE_ROOT_NOTE_ID: '_share' |     SHARE_ROOT_NOTE_ID: '_share' | ||||||
| } | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const Database = require('better-sqlite3'); | import Database = require('better-sqlite3'); | ||||||
| const dataDir = require('../services/data_dir'); | import dataDir = require('../services/data_dir'); | ||||||
| 
 | 
 | ||||||
| const dbConnection = new Database(dataDir.DOCUMENT_PATH, { readonly: true }); | const dbConnection = new Database(dataDir.DOCUMENT_PATH, { readonly: true }); | ||||||
| 
 | 
 | ||||||
| @@ -15,19 +15,19 @@ const dbConnection = new Database(dataDir.DOCUMENT_PATH, { readonly: true }); | |||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function getRawRows(query, params = []) { | function getRawRows<T>(query: string, params = []): T[] { | ||||||
|     return dbConnection.prepare(query).raw().all(params); |     return dbConnection.prepare(query).raw().all(params) as T[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getRow(query, params = []) { | function getRow<T>(query: string, params: string[] = []): T { | ||||||
|     return dbConnection.prepare(query).get(params); |     return dbConnection.prepare(query).get(params) as T; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getColumn(query, params = []) { | function getColumn<T>(query: string, params: string[] = []): T[] { | ||||||
|     return dbConnection.prepare(query).pluck().all(params); |     return dbConnection.prepare(query).pluck().all(params) as T[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | export = { | ||||||
|     getRawRows, |     getRawRows, | ||||||
|     getRow, |     getRow, | ||||||
|     getColumn |     getColumn | ||||||
		Reference in New Issue
	
	Block a user