mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge pull request #47 from TriliumNext/feature/typescript_backend_10
Convert backend to TypeScript (84% -> 89%)
This commit is contained in:
commit
2771bd4ece
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');
|
||||||
|
@ -127,8 +127,8 @@ interface Api {
|
|||||||
/**
|
/**
|
||||||
* Retrieves notes with given label name & value
|
* Retrieves notes with given label name & value
|
||||||
*
|
*
|
||||||
* @param name - attribute name
|
* @param name - attribute name
|
||||||
* @param value - attribute value
|
* @param value - attribute value
|
||||||
*/
|
*/
|
||||||
getNotesWithLabel(name: string, value?: string): BNote[];
|
getNotesWithLabel(name: string, value?: string): BNote[];
|
||||||
|
|
||||||
|
@ -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}`;
|
||||||
linkEl.setAttribute("href", href);
|
if (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();
|
||||||
res.send(ejsResult);
|
if (typeof content === "string") {
|
||||||
useDefaultView = false; // Rendering went okay, don't use default view
|
const ejsResult = ejs.render(content, opts, { includer });
|
||||||
|
res.send(ejsResult);
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user