From 45e3632c6eb8349bfa2a2476fbead8ac6a29af61 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 21:48:15 +0300 Subject: [PATCH 01/14] server-ts: Port share/shaca/abstract_shaca_entity --- ...{abstract_shaca_entity.js => abstract_shaca_entity.ts} | 8 ++++---- src/share/shaca/entities/sattachment.js | 2 +- src/share/shaca/entities/sattribute.js | 2 +- src/share/shaca/entities/sbranch.js | 2 +- src/share/shaca/entities/snote.js | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename src/share/shaca/entities/{abstract_shaca_entity.js => abstract_shaca_entity.ts} (50%) diff --git a/src/share/shaca/entities/abstract_shaca_entity.js b/src/share/shaca/entities/abstract_shaca_entity.ts similarity index 50% rename from src/share/shaca/entities/abstract_shaca_entity.js rename to src/share/shaca/entities/abstract_shaca_entity.ts index 772eb4d6f..707db9441 100644 --- a/src/share/shaca/entities/abstract_shaca_entity.js +++ b/src/share/shaca/entities/abstract_shaca_entity.ts @@ -1,8 +1,8 @@ -let shaca; +let shaca: any; class AbstractShacaEntity { - /** @return {Shaca} */ - get shaca() { + // FIXME: Use right data type once we convert Shaca as well. + get shaca(): any { if (!shaca) { shaca = require('../shaca.js'); } @@ -11,4 +11,4 @@ class AbstractShacaEntity { } } -module.exports = AbstractShacaEntity; +export = AbstractShacaEntity; diff --git a/src/share/shaca/entities/sattachment.js b/src/share/shaca/entities/sattachment.js index 4b76fea2c..998c5ac95 100644 --- a/src/share/shaca/entities/sattachment.js +++ b/src/share/shaca/entities/sattachment.js @@ -2,7 +2,7 @@ const sql = require('../../sql'); const utils = require('../../../services/utils'); -const AbstractShacaEntity = require('./abstract_shaca_entity.js'); +const AbstractShacaEntity = require('./abstract_shaca_entity'); class SAttachment extends AbstractShacaEntity { constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]) { diff --git a/src/share/shaca/entities/sattribute.js b/src/share/shaca/entities/sattribute.js index bc3b5437a..26b285ce8 100644 --- a/src/share/shaca/entities/sattribute.js +++ b/src/share/shaca/entities/sattribute.js @@ -1,6 +1,6 @@ "use strict"; -const AbstractShacaEntity = require('./abstract_shaca_entity.js'); +const AbstractShacaEntity = require('./abstract_shaca_entity'); class SAttribute extends AbstractShacaEntity { constructor([attributeId, noteId, type, name, value, isInheritable, position]) { diff --git a/src/share/shaca/entities/sbranch.js b/src/share/shaca/entities/sbranch.js index 1be0f424a..a6f2ac13a 100644 --- a/src/share/shaca/entities/sbranch.js +++ b/src/share/shaca/entities/sbranch.js @@ -1,6 +1,6 @@ "use strict"; -const AbstractShacaEntity = require('./abstract_shaca_entity.js'); +const AbstractShacaEntity = require('./abstract_shaca_entity'); class SBranch extends AbstractShacaEntity { constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) { diff --git a/src/share/shaca/entities/snote.js b/src/share/shaca/entities/snote.js index fd889c23f..b639d3829 100644 --- a/src/share/shaca/entities/snote.js +++ b/src/share/shaca/entities/snote.js @@ -2,7 +2,7 @@ const sql = require('../../sql'); const utils = require('../../../services/utils'); -const AbstractShacaEntity = require('./abstract_shaca_entity.js'); +const AbstractShacaEntity = require('./abstract_shaca_entity'); const escape = require('escape-html'); const LABEL = 'label'; From 80a1b8b44d0f387a1e989dcf23b457016a618119 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 21:50:47 +0300 Subject: [PATCH 02/14] server-ts: Port share/sql --- src/share/{sql.js => sql.ts} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename src/share/{sql.js => sql.ts} (52%) diff --git a/src/share/sql.js b/src/share/sql.ts similarity index 52% rename from src/share/sql.js rename to src/share/sql.ts index 485c87921..002b34622 100644 --- a/src/share/sql.js +++ b/src/share/sql.ts @@ -1,7 +1,7 @@ "use strict"; -const Database = require('better-sqlite3'); -const dataDir = require('../services/data_dir'); +import Database = require('better-sqlite3'); +import dataDir = require('../services/data_dir'); 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 = []) { - return dbConnection.prepare(query).raw().all(params); +function getRawRows(query: string, params = []): T[] { + return dbConnection.prepare(query).raw().all(params) as T[]; } -function getRow(query, params = []) { - return dbConnection.prepare(query).get(params); +function getRow(query: string, params = []): T { + return dbConnection.prepare(query).get(params) as T; } -function getColumn(query, params = []) { - return dbConnection.prepare(query).pluck().all(params); +function getColumn(query: string, params = []): T[] { + return dbConnection.prepare(query).pluck().all(params) as T[]; } -module.exports = { +export = { getRawRows, getRow, getColumn From b07df6061f032e7cb9dadc605e956777e0afe27c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 21:51:23 +0300 Subject: [PATCH 03/14] server-ts: Port share/share_root --- src/share/content_renderer.js | 4 ++-- src/share/routes.js | 14 +++++++------- src/share/shaca/shaca_loader.js | 4 ++-- src/share/{share_root.js => share_root.ts} | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename src/share/{share_root.js => share_root.ts} (64%) diff --git a/src/share/content_renderer.js b/src/share/content_renderer.js index 9065fec08..89b3cea4a 100644 --- a/src/share/content_renderer.js +++ b/src/share/content_renderer.js @@ -1,7 +1,7 @@ -const {JSDOM} = require("jsdom"); +const { JSDOM } = require("jsdom"); const shaca = require('./shaca/shaca.js'); const assetPath = require('../services/asset_path'); -const shareRoot = require('./share_root.js'); +const shareRoot = require('./share_root'); const escapeHtml = require('escape-html'); function getContent(note) { diff --git a/src/share/routes.js b/src/share/routes.js index 4ad5a15c6..e540bf9e1 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -5,7 +5,7 @@ const ejs = require("ejs"); const shaca = require('./shaca/shaca.js'); const shacaLoader = require('./shaca/shaca_loader.js'); -const shareRoot = require('./share_root.js'); +const shareRoot = require('./share_root'); const contentRenderer = require('./content_renderer.js'); const assetPath = require('../services/asset_path'); const appPath = require('../services/app_path'); @@ -152,9 +152,9 @@ function register(router) { return; } - const {header, content, isEmpty} = contentRenderer.getContent(note); + const { header, content, isEmpty } = contentRenderer.getContent(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; // Check if the user has their own template @@ -176,7 +176,7 @@ function register(router) { // Try to render user's template, w/ fallback to default view try { - const ejsResult = ejs.render(templateNote.getContent(), opts, {includer}); + const ejsResult = ejs.render(templateNote.getContent(), opts, { includer }); res.send(ejsResult); useDefaultView = false; // Rendering went okay, don't use default view } @@ -205,7 +205,7 @@ function register(router) { router.get('/share/:shareId', (req, res, next) => { shacaLoader.ensureLoad(); - const {shareId} = req.params; + const { shareId } = req.params; const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; @@ -346,13 +346,13 @@ function register(router) { return; } - const {search} = req.query; + const { search } = req.query; if (!search?.trim()) { 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 filteredResults = searchResults.map(sr => { const fullNote = shaca.notes[sr.noteId]; diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 5d1648f49..f68d6d4c9 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -7,7 +7,7 @@ const SNote = require('./entities/snote.js'); const SBranch = require('./entities/sbranch.js'); const SAttribute = require('./entities/sattribute.js'); const SAttachment = require('./entities/sattachment.js'); -const shareRoot = require('../share_root.js'); +const shareRoot = require('../share_root'); const eventService = require('../../services/events'); function load() { @@ -89,7 +89,7 @@ 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(); }); diff --git a/src/share/share_root.js b/src/share/share_root.ts similarity index 64% rename from src/share/share_root.js rename to src/share/share_root.ts index ec63597f2..a500d5ac4 100644 --- a/src/share/share_root.js +++ b/src/share/share_root.ts @@ -1,3 +1,3 @@ -module.exports = { +export = { SHARE_ROOT_NOTE_ID: '_share' } From 3e4b0d5f91de92a5b45a45be942dee178f44eec7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:13:01 +0300 Subject: [PATCH 04/14] server-ts: Port share/shaca/snote --- .../shaca/entities/{snote.js => snote.ts} | 237 +++++++++--------- src/share/shaca/shaca_loader.js | 2 +- src/share/sql.ts | 2 +- 3 files changed, 123 insertions(+), 118 deletions(-) rename src/share/shaca/entities/{snote.js => snote.ts} (62%) diff --git a/src/share/shaca/entities/snote.js b/src/share/shaca/entities/snote.ts similarity index 62% rename from src/share/shaca/entities/snote.js rename to src/share/shaca/entities/snote.ts index b639d3829..5883fdf88 100644 --- a/src/share/shaca/entities/snote.js +++ b/src/share/shaca/entities/snote.ts @@ -1,53 +1,57 @@ "use strict"; -const sql = require('../../sql'); -const utils = require('../../../services/utils'); -const AbstractShacaEntity = require('./abstract_shaca_entity'); -const escape = require('escape-html'); +import sql = require('../../sql'); +import utils = require('../../../services/utils'); +import AbstractShacaEntity = require('./abstract_shaca_entity'); +import escape = require('escape-html'); +import { AttributeRow } from '../../../becca/entities/rows'; +import { Blob } from '../../../services/blob-interface'; const LABEL = 'label'; const RELATION = 'relation'; const CREDENTIALS = 'shareCredentials'; -const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS; +const isCredentials = (attr: AttributeRow) => attr.type === 'label' && attr.name === CREDENTIALS; + +type NoteRow = [ string, string, string, string, string, string, boolean ]; class SNote extends AbstractShacaEntity { - constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]) { + private noteId: string; + private title: string; + private type: string; + private mime: string; + private blobId: string; + private utcDateModified: string; + private isProtected: boolean; + private parentBranches: any[]; // fixme: set right data type once SBranch is ported. + private parents: SNote[]; + private children: SNote[]; + private ownedAttributes: any[]; // fixme: set right data type once SAttribute is ported. + private __attributeCache: any[] | null; // fixme + private __inheritableAttributeCache: any[] | null; // fixme + private targetRelations: any[]; // fixme: SAttribute[] + private attachments: any[] ; // fixme: SAttachment[] + + constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) { super(); - /** @param {string} */ this.noteId = noteId; - /** @param {string} */ this.title = isProtected ? "[protected]" : title; - /** @param {string} */ this.type = type; - /** @param {string} */ this.mime = mime; - /** @param {string} */ this.blobId = blobId; - /** @param {string} */ this.utcDateModified = utcDateModified; // used for caching of images - /** @param {boolean} */ this.isProtected = isProtected; - /** @param {SBranch[]} */ this.parentBranches = []; - /** @param {SNote[]} */ this.parents = []; - /** @param {SNote[]} */ this.children = []; - /** @param {SAttribute[]} */ this.ownedAttributes = []; - /** @param {SAttribute[]|null} */ this.__attributeCache = null; - /** @param {SAttribute[]|null} */ this.__inheritableAttributeCache = null; - /** @param {SAttribute[]} */ this.targetRelations = []; - - /** @param {SAttachment[]} */ this.attachments = []; this.shaca.notes[this.noteId] = this; @@ -102,7 +106,7 @@ class SNote extends AbstractShacaEntity { } getContent(silentNotFoundError = false) { - const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); + const row = sql.getRow>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); if (!row) { if (silentNotFoundError) { @@ -116,52 +120,56 @@ class SNote extends AbstractShacaEntity { let content = row.content; if (this.hasStringContent()) { - return content === null - ? "" - : content.toString("utf-8"); + if (content == null) { + return ""; + } + + if (typeof content !== "string") { + return content.toString("utf-8"); + } + + return content; } else { return content; } } - /** @returns {boolean} true if the note has string content (not binary) */ + /** @returns true if the note has string content (not binary) */ hasStringContent() { return utils.isStringNote(this.type, this.mime); } /** - * @param {string} [type] - (optional) attribute type to filter - * @param {string} [name] - (optional) attribute name to filter - * @returns {SAttribute[]} all note's attributes, including inherited ones + * @param type - (optional) attribute type to filter + * @param name - (optional) attribute name to filter + * @returns all note's attributes, including inherited ones */ - getAttributes(type, name) { - if (!this.__attributeCache) { - this.__getAttributes([]); + getAttributes(type?: string, name?: string) { + let attributeCache = this.__attributeCache; + if (!attributeCache) { + attributeCache = this.__getAttributes([]); } 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) { - return this.__attributeCache.filter(attr => attr.type === type && !isCredentials(attr)); + return attributeCache.filter(attr => attr.type === type && !isCredentials(attr)); } else if (name) { - return this.__attributeCache.filter(attr => attr.name === name && !isCredentials(attr)); + return attributeCache.filter(attr => attr.name === name && !isCredentials(attr)); } else { - return this.__attributeCache.filter(attr => !isCredentials(attr)); + return attributeCache.filter(attr => !isCredentials(attr)); } } - /** @returns {SAttribute[]} */ getCredentials() { - this.__getAttributes([]); - - return this.__attributeCache.filter(isCredentials); + return this.__getAttributes([]).filter(isCredentials); } - __getAttributes(path) { + __getAttributes(path: string[]) { if (path.includes(this.noteId)) { return []; } @@ -213,213 +221,212 @@ class SNote extends AbstractShacaEntity { } /** @returns {SAttribute[]} */ - __getInheritableAttributes(path) { + __getInheritableAttributes(path: string[]) { if (path.includes(this.noteId)) { return []; } if (!this.__inheritableAttributeCache) { - this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache + return this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache + } else { + return this.__inheritableAttributeCache; } - - return this.__inheritableAttributeCache; } - /** @returns {boolean} */ - hasAttribute(type, name) { + hasAttribute(type: string, name: string) { return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); } /** @returns {SNote|null} */ - getRelationTarget(name) { + getRelationTarget(name: string) { const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); return relation ? relation.targetNote : null; } /** - * @param {string} name - label name - * @returns {boolean} true if label exists (including inherited) + * @param name - label name + * @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. */ - isLabelTruthy(name) { + isLabelTruthy(name: string) { const label = this.getLabel(name); if (!label) { return false; } - return label && label.value !== 'false'; + return !!label && label.value !== 'false'; } /** - * @param {string} name - label name - * @returns {boolean} true if label exists (excluding inherited) + * @param name - label name + * @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 - * @returns {boolean} true if relation exists (including inherited) + * @param name - relation name + * @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 - * @returns {boolean} true if relation exists (excluding inherited) + * @param name - relation name + * @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 */ - 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 */ - 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 */ - 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 */ - 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 */ - 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 */ - 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 */ - 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 */ - getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); } + getOwnedRelationValue(name: string) { return this.getOwnedAttributeValue(RELATION, name); } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @returns {boolean} true if note has an attribute with given type and name (excluding inherited) + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @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); } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name * @returns {SAttribute} 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. */ - getAttribute(type, name) { + getAttribute(type: string, name: string) { const attributes = this.getAttributes(); return attributes.find(attr => attr.type === type && attr.name === name); } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name * @returns {string|null} 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); return attr ? attr.value : null; } /** - * @param {string} type - attribute type (label, relation, etc.) - * @param {string} name - attribute name - * @returns {string|null} attribute value of the given type and name or null if no such attribute exists. + * @param type - attribute type (label, relation, etc.) + * @param name - attribute name + * @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); - 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 */ - getLabels(name) { + getLabels(name: string) { return this.getAttributes(LABEL, name); } /** - * @param {string} [name] - label name to filter - * @returns {string[]} all note's label values, including inherited ones + * @param name - label name to filter + * @returns all note's label values, including inherited ones */ - getLabelValues(name) { - return this.getLabels(name).map(l => l.value); + getLabelValues(name: string) { + 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 */ - getOwnedLabels(name) { + getOwnedLabels(name: string) { 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 */ - getOwnedLabelValues(name) { + getOwnedLabelValues(name: string) { 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 */ - getRelations(name) { + getRelations(name: string) { return this.getAttributes(RELATION, name); } /** - * @param {string} [name] - relation name to filter + * @param {string} name - relation name to filter * @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones */ - getOwnedRelations(name) { + getOwnedRelations(name: string) { return this.getOwnedAttributes(RELATION, name); } /** - * @param {string} [type] - (optional) attribute type to filter - * @param {string} [name] - (optional) attribute name to filter + * @param {string} type - (optional) attribute type to filter + * @param {string} name - (optional) attribute name to filter * @returns {SAttribute[]} 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 if (name && ["#", "~"].includes(name[0])) { name = name.substr(1); @@ -444,18 +451,16 @@ class SNote extends AbstractShacaEntity { * * This method can be significantly faster than the getAttribute() */ - getOwnedAttribute(type, name) { + getOwnedAttribute(type: string, name: string) { const attrs = this.getOwnedAttributes(type, name); return attrs.length > 0 ? attrs[0] : null; } - /** @returns {boolean} */ get isArchived() { return this.hasAttribute('label', 'archived'); } - /** @returns {boolean} */ isInherited() { return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); } @@ -471,7 +476,7 @@ class SNote extends AbstractShacaEntity { } /** @returns {SAttachment} */ - getAttachmentByTitle(title) { + getAttachmentByTitle(title: string) { return this.attachments.find(attachment => attachment.title === title); } diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index f68d6d4c9..77348e697 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -3,7 +3,7 @@ const sql = require('../sql'); const shaca = require('./shaca.js'); const log = require('../../services/log'); -const SNote = require('./entities/snote.js'); +const SNote = require('./entities/snote'); const SBranch = require('./entities/sbranch.js'); const SAttribute = require('./entities/sattribute.js'); const SAttachment = require('./entities/sattachment.js'); diff --git a/src/share/sql.ts b/src/share/sql.ts index 002b34622..3d07df65f 100644 --- a/src/share/sql.ts +++ b/src/share/sql.ts @@ -19,7 +19,7 @@ function getRawRows(query: string, params = []): T[] { return dbConnection.prepare(query).raw().all(params) as T[]; } -function getRow(query: string, params = []): T { +function getRow(query: string, params: string[] = []): T { return dbConnection.prepare(query).get(params) as T; } From c4c2259e69f93f4c766e58caf00f7ff2c78a1f89 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:19:54 +0300 Subject: [PATCH 05/14] server-ts: Port share/shaca/sattachment --- src/services/utils.ts | 4 +- .../{sattachment.js => sattachment.ts} | 40 ++++++++++--------- src/share/shaca/entities/snote.ts | 20 +++------- src/share/shaca/shaca_loader.js | 2 +- 4 files changed, 31 insertions(+), 35 deletions(-) rename src/share/shaca/entities/{sattachment.js => sattachment.ts} (61%) diff --git a/src/services/utils.ts b/src/services/utils.ts index 6b2974df0..aef261a6a 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -156,9 +156,9 @@ const STRING_MIME_TYPES = [ "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 - 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/') || STRING_MIME_TYPES.includes(mime); } diff --git a/src/share/shaca/entities/sattachment.js b/src/share/shaca/entities/sattachment.ts similarity index 61% rename from src/share/shaca/entities/sattachment.js rename to src/share/shaca/entities/sattachment.ts index 998c5ac95..352d34749 100644 --- a/src/share/shaca/entities/sattachment.js +++ b/src/share/shaca/entities/sattachment.ts @@ -1,39 +1,44 @@ "use strict"; -const sql = require('../../sql'); -const utils = require('../../../services/utils'); -const AbstractShacaEntity = require('./abstract_shaca_entity'); +import sql = require('../../sql'); +import utils = require('../../../services/utils'); +import AbstractShacaEntity = require('./abstract_shaca_entity'); +import SNote = require('./snote'); +import { Blob } from '../../../services/blob-interface'; + +type AttachmentRow = [ string, string, string, string, string, string, string ]; class SAttachment extends AbstractShacaEntity { - constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]) { + private attachmentId: string; + private ownerId: string; + title: string; + private role: string; + private mime: string; + private blobId: string; + /** used for caching of images */ + private utcDateModified: string; + + constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]: AttachmentRow) { super(); - /** @param {string} */ this.attachmentId = attachmentId; - /** @param {string} */ this.ownerId = ownerId; - /** @param {string} */ this.title = title; - /** @param {string} */ this.role = role; - /** @param {string} */ this.mime = mime; - /** @param {string} */ this.blobId = blobId; - /** @param {string} */ - this.utcDateModified = utcDateModified; // used for caching of images + this.utcDateModified = utcDateModified; this.shaca.attachments[this.attachmentId] = this; this.shaca.notes[this.ownerId].attachments.push(this); } - /** @returns {SNote} */ - get note() { + get note(): SNote { return this.shaca.notes[this.ownerId]; } getContent(silentNotFoundError = false) { - const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); + const row = sql.getRow>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]); if (!row) { if (silentNotFoundError) { @@ -56,7 +61,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() { return utils.isStringNote(null, this.mime); } @@ -67,11 +72,10 @@ class SAttachment extends AbstractShacaEntity { role: this.role, mime: this.mime, title: this.title, - position: this.position, blobId: this.blobId, utcDateModified: this.utcDateModified }; } } -module.exports = SAttachment; +export = SAttachment; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 5883fdf88..1574271be 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -6,6 +6,7 @@ import AbstractShacaEntity = require('./abstract_shaca_entity'); import escape = require('escape-html'); import { AttributeRow } from '../../../becca/entities/rows'; import { Blob } from '../../../services/blob-interface'; +import SAttachment = require('./sattachment'); const LABEL = 'label'; const RELATION = 'relation'; @@ -30,7 +31,7 @@ class SNote extends AbstractShacaEntity { private __attributeCache: any[] | null; // fixme private __inheritableAttributeCache: any[] | null; // fixme private targetRelations: any[]; // fixme: SAttribute[] - private attachments: any[] ; // fixme: SAttachment[] + private attachments: SAttachment[]; constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) { super(); @@ -120,15 +121,9 @@ class SNote extends AbstractShacaEntity { let content = row.content; if (this.hasStringContent()) { - if (content == null) { - return ""; - } - - if (typeof content !== "string") { - return content.toString("utf-8"); - } - - return content; + return content === null + ? "" + : content.toString("utf-8"); } else { return content; @@ -470,17 +465,14 @@ class SNote extends AbstractShacaEntity { return this.targetRelations; } - /** @returns {SAttachment[]} */ getAttachments() { return this.attachments; } - /** @returns {SAttachment} */ getAttachmentByTitle(title: string) { return this.attachments.find(attachment => attachment.title === title); } - /** @returns {string} */ get shareId() { if (this.hasOwnedLabel('shareRoot')) { return ""; @@ -519,4 +511,4 @@ class SNote extends AbstractShacaEntity { } } -module.exports = SNote; +export = SNote; diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 77348e697..418311a79 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -6,7 +6,7 @@ const log = require('../../services/log'); const SNote = require('./entities/snote'); const SBranch = require('./entities/sbranch.js'); const SAttribute = require('./entities/sattribute.js'); -const SAttachment = require('./entities/sattachment.js'); +const SAttachment = require('./entities/sattachment'); const shareRoot = require('../share_root'); const eventService = require('../../services/events'); From e1d74cd2f5efd65cdcd42e64347f131669ca2be6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:25:32 +0300 Subject: [PATCH 06/14] server-ts: Port share/shaca/sattribute --- .../entities/{sattribute.js => sattribute.ts} | 39 ++++++++-------- src/share/shaca/entities/snote.ts | 46 +++++++++---------- src/share/shaca/shaca_loader.js | 2 +- 3 files changed, 42 insertions(+), 45 deletions(-) rename src/share/shaca/entities/{sattribute.js => sattribute.ts} (82%) diff --git a/src/share/shaca/entities/sattribute.js b/src/share/shaca/entities/sattribute.ts similarity index 82% rename from src/share/shaca/entities/sattribute.js rename to src/share/shaca/entities/sattribute.ts index 26b285ce8..e12a9d121 100644 --- a/src/share/shaca/entities/sattribute.js +++ b/src/share/shaca/entities/sattribute.ts @@ -1,24 +1,30 @@ "use strict"; +import SNote = require("./snote"); + const AbstractShacaEntity = require('./abstract_shaca_entity'); +type AttributeRow = [ string, string, string, string, string, boolean, number ]; + 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]: AttributeRow) { super(); - /** @param {string} */ this.attributeId = attributeId; - /** @param {string} */ this.noteId = noteId; - /** @param {string} */ this.type = type; - /** @param {string} */ this.name = name; - /** @param {int} */ this.position = position; - /** @param {string} */ this.value = value; - /** @param {boolean} */ this.isInheritable = !!isInheritable; this.shaca.attributes[this.attributeId] = this; @@ -53,41 +59,34 @@ class SAttribute extends AbstractShacaEntity { } } - /** @returns {boolean} */ get isAffectingSubtree() { return this.isInheritable || (this.type === 'relation' && ['template', 'inherit'].includes(this.name)); } - /** @returns {string} */ get targetNoteId() { // alias return this.type === 'relation' ? this.value : undefined; } - /** @returns {boolean} */ isAutoLink() { return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); } - /** @returns {SNote} */ - get note() { + get note(): SNote { return this.shaca.notes[this.noteId]; } - /** @returns {SNote|null} */ - get targetNote() { + get targetNote(): SNote | null | undefined { if (this.type === 'relation') { return this.shaca.notes[this.value]; } } - /** @returns {SNote|null} */ - getNote() { + getNote(): SNote | null { return this.shaca.getNote(this.noteId); } - /** @returns {SNote|null} */ - getTargetNote() { + getTargetNote(): SNote | null { if (this.type !== 'relation') { throw new Error(`Attribute '${this.attributeId}' is not relation`); } @@ -112,4 +111,4 @@ class SAttribute extends AbstractShacaEntity { } } -module.exports = SAttribute; +export = SAttribute; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 1574271be..9f8205865 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -4,20 +4,20 @@ import sql = require('../../sql'); import utils = require('../../../services/utils'); import AbstractShacaEntity = require('./abstract_shaca_entity'); import escape = require('escape-html'); -import { AttributeRow } from '../../../becca/entities/rows'; import { Blob } from '../../../services/blob-interface'; import SAttachment = require('./sattachment'); +import SAttribute = require('./sattribute'); const LABEL = 'label'; const RELATION = 'relation'; const CREDENTIALS = 'shareCredentials'; -const isCredentials = (attr: AttributeRow) => attr.type === 'label' && attr.name === CREDENTIALS; +const isCredentials = (attr: SAttribute) => attr.type === 'label' && attr.name === CREDENTIALS; type NoteRow = [ string, string, string, string, string, string, boolean ]; class SNote extends AbstractShacaEntity { - private noteId: string; + noteId: string; private title: string; private type: string; private mime: string; @@ -27,10 +27,10 @@ class SNote extends AbstractShacaEntity { private parentBranches: any[]; // fixme: set right data type once SBranch is ported. private parents: SNote[]; private children: SNote[]; - private ownedAttributes: any[]; // fixme: set right data type once SAttribute is ported. - private __attributeCache: any[] | null; // fixme - private __inheritableAttributeCache: any[] | null; // fixme - private targetRelations: any[]; // fixme: SAttribute[] + private ownedAttributes: SAttribute[]; + private __attributeCache: SAttribute[] | null; + private __inheritableAttributeCache: SAttribute[] | null; + targetRelations: SAttribute[]; private attachments: SAttachment[]; constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) { @@ -215,7 +215,6 @@ class SNote extends AbstractShacaEntity { return this.__attributeCache; } - /** @returns {SAttribute[]} */ __getInheritableAttributes(path: string[]) { if (path.includes(this.noteId)) { return []; @@ -279,25 +278,25 @@ class SNote extends AbstractShacaEntity { /** * @param name - label name - * @returns {SAttribute|null} label if it exists, null otherwise + * @returns label if it exists, null otherwise */ getLabel(name: string) { return this.getAttribute(LABEL, name); } /** * @param name - label name - * @returns {SAttribute|null} label if it exists, null otherwise + * @returns label if it exists, null otherwise */ getOwnedLabel(name: string) { return this.getOwnedAttribute(LABEL, name); } /** * @param name - relation name - * @returns {SAttribute|null} relation if it exists, null otherwise + * @returns relation if it exists, null otherwise */ getRelation(name: string) { return this.getAttribute(RELATION, name); } /** * @param name - relation name - * @returns {SAttribute|null} relation if it exists, null otherwise + * @returns relation if it exists, null otherwise */ getOwnedRelation(name: string) { return this.getOwnedAttribute(RELATION, name); } @@ -337,8 +336,8 @@ class SNote extends AbstractShacaEntity { /** * @param type - attribute type (label, relation, etc.) * @param name - attribute name - * @returns {SAttribute} 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 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. */ getAttribute(type: string, name: string) { const attributes = this.getAttributes(); @@ -370,7 +369,7 @@ class SNote extends AbstractShacaEntity { /** * @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: string) { return this.getAttributes(LABEL, name); @@ -386,7 +385,7 @@ class SNote extends AbstractShacaEntity { /** * @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: string) { return this.getOwnedAttributes(LABEL, name); @@ -402,24 +401,24 @@ class SNote extends AbstractShacaEntity { /** * @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: string) { return this.getAttributes(RELATION, name); } /** - * @param {string} name - relation name to filter - * @returns {SAttribute[]} all note's relations (attributes with type relation), excluding inherited ones + * @param name - relation name to filter + * @returns all note's relations (attributes with type relation), excluding inherited ones */ getOwnedRelations(name: string) { return this.getOwnedAttributes(RELATION, name); } /** - * @param {string} type - (optional) attribute type to filter - * @param {string} name - (optional) attribute name to filter - * @returns {SAttribute[]} note's "owned" attributes - excluding inherited ones + * @param type - (optional) attribute type to filter + * @param name - (optional) attribute name to filter + * @returns note's "owned" attributes - excluding inherited ones */ getOwnedAttributes(type: string, name: string) { // it's a common mistake to include # or ~ into attribute name @@ -442,7 +441,7 @@ 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() */ @@ -460,7 +459,6 @@ class SNote extends AbstractShacaEntity { return !!this.targetRelations.find(rel => rel.name === 'template' || rel.name === 'inherit'); } - /** @returns {SAttribute[]} */ getTargetRelations() { return this.targetRelations; } diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 418311a79..9763c4718 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -5,7 +5,7 @@ const shaca = require('./shaca.js'); const log = require('../../services/log'); const SNote = require('./entities/snote'); const SBranch = require('./entities/sbranch.js'); -const SAttribute = require('./entities/sattribute.js'); +const SAttribute = require('./entities/sattribute'); const SAttachment = require('./entities/sattachment'); const shareRoot = require('../share_root'); const eventService = require('../../services/events'); From 0865e90cae87ccff5f5c19317958f4cf71f9eb3e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:32:06 +0300 Subject: [PATCH 07/14] server-ts: Port share/shaca/sbranch --- .../shaca/entities/{sbranch.js => sbranch.ts} | 31 ++++++++++--------- src/share/shaca/entities/snote.ts | 12 +++---- src/share/shaca/shaca_loader.js | 2 +- 3 files changed, 22 insertions(+), 23 deletions(-) rename src/share/shaca/entities/{sbranch.js => sbranch.ts} (72%) diff --git a/src/share/shaca/entities/sbranch.js b/src/share/shaca/entities/sbranch.ts similarity index 72% rename from src/share/shaca/entities/sbranch.js rename to src/share/shaca/entities/sbranch.ts index a6f2ac13a..4d906d750 100644 --- a/src/share/shaca/entities/sbranch.js +++ b/src/share/shaca/entities/sbranch.ts @@ -1,22 +1,27 @@ "use strict"; -const AbstractShacaEntity = require('./abstract_shaca_entity'); +import AbstractShacaEntity = require('./abstract_shaca_entity'); +import SNote = require('./snote'); + +type BranchRow = [ string, string, string, string, string, boolean ]; class SBranch extends AbstractShacaEntity { - constructor([branchId, noteId, parentNoteId, prefix, isExpanded]) { + + private branchId: string; + private noteId: string; + private parentNoteId: string; + private prefix: string; + private isExpanded: boolean; + isHidden: boolean; + + constructor([branchId, noteId, parentNoteId, prefix, isExpanded]: BranchRow) { super(); - /** @param {string} */ this.branchId = branchId; - /** @param {string} */ this.noteId = noteId; - /** @param {string} */ this.parentNoteId = parentNoteId; - /** @param {string} */ this.prefix = prefix; - /** @param {boolean} */ this.isExpanded = !!isExpanded; - /** @param {boolean} */ this.isHidden = false; const childNote = this.childNote; @@ -38,25 +43,21 @@ class SBranch extends AbstractShacaEntity { this.shaca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; } - /** @returns {SNote} */ - get childNote() { + get childNote(): SNote { return this.shaca.notes[this.noteId]; } - /** @returns {SNote} */ getNote() { return this.childNote; } - /** @returns {SNote} */ - get parentNote() { + get parentNote(): SNote { return this.shaca.notes[this.parentNoteId]; } - /** @returns {SNote} */ getParentNote() { return this.parentNote; } } -module.exports = SBranch; +export = SBranch; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 9f8205865..5f924938d 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -7,6 +7,7 @@ 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 RELATION = 'relation'; @@ -24,9 +25,9 @@ class SNote extends AbstractShacaEntity { private blobId: string; private utcDateModified: string; private isProtected: boolean; - private parentBranches: any[]; // fixme: set right data type once SBranch is ported. - private parents: SNote[]; - private children: SNote[]; + parentBranches: SBranch[]; + parents: SNote[]; + children: SNote[]; private ownedAttributes: SAttribute[]; private __attributeCache: SAttribute[] | null; private __inheritableAttributeCache: SAttribute[] | null; @@ -58,18 +59,15 @@ class SNote extends AbstractShacaEntity { this.shaca.notes[this.noteId] = this; } - /** @returns {SBranch[]} */ getParentBranches() { return this.parentBranches; } - /** @returns {SBranch[]} */ getBranches() { return this.parentBranches; } - /** @returns {SBranch[]} */ - getChildBranches() { + getChildBranches(): SBranch[] { return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); } diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 9763c4718..6c7c802a5 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -4,7 +4,7 @@ const sql = require('../sql'); const shaca = require('./shaca.js'); const log = require('../../services/log'); const SNote = require('./entities/snote'); -const SBranch = require('./entities/sbranch.js'); +const SBranch = require('./entities/sbranch'); const SAttribute = require('./entities/sattribute'); const SAttachment = require('./entities/sattachment'); const shareRoot = require('../share_root'); From b3c26026208e37d2c850a1c3948f8fa0feff00a3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:39:43 +0300 Subject: [PATCH 08/14] server-ts: Port share/shaca/shaca --- src/share/content_renderer.js | 2 +- src/share/routes.js | 2 +- .../shaca/entities/abstract_shaca_entity.ts | 9 +-- src/share/shaca/entities/snote.ts | 2 +- .../shaca/{shaca.js => shaca-interface.ts} | 58 +++++++++---------- src/share/shaca/shaca.ts | 7 +++ src/share/shaca/shaca_loader.js | 2 +- 7 files changed, 43 insertions(+), 39 deletions(-) rename src/share/shaca/{shaca.js => shaca-interface.ts} (55%) create mode 100644 src/share/shaca/shaca.ts diff --git a/src/share/content_renderer.js b/src/share/content_renderer.js index 89b3cea4a..20d8b0b7f 100644 --- a/src/share/content_renderer.js +++ b/src/share/content_renderer.js @@ -1,5 +1,5 @@ const { JSDOM } = require("jsdom"); -const shaca = require('./shaca/shaca.js'); +const shaca = require('./shaca/shaca'); const assetPath = require('../services/asset_path'); const shareRoot = require('./share_root'); const escapeHtml = require('escape-html'); diff --git a/src/share/routes.js b/src/share/routes.js index e540bf9e1..7c36d4b28 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -3,7 +3,7 @@ const path = require('path'); const safeCompare = require('safe-compare'); const ejs = require("ejs"); -const shaca = require('./shaca/shaca.js'); +const shaca = require('./shaca/shaca'); const shacaLoader = require('./shaca/shaca_loader.js'); const shareRoot = require('./share_root'); const contentRenderer = require('./content_renderer.js'); diff --git a/src/share/shaca/entities/abstract_shaca_entity.ts b/src/share/shaca/entities/abstract_shaca_entity.ts index 707db9441..20dba31ff 100644 --- a/src/share/shaca/entities/abstract_shaca_entity.ts +++ b/src/share/shaca/entities/abstract_shaca_entity.ts @@ -1,10 +1,11 @@ -let shaca: any; +import Shaca from "../shaca-interface"; + +let shaca: Shaca; class AbstractShacaEntity { - // FIXME: Use right data type once we convert Shaca as well. - get shaca(): any { + get shaca(): Shaca { if (!shaca) { - shaca = require('../shaca.js'); + shaca = require('../shaca'); } return shaca; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 5f924938d..f91949aef 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -32,7 +32,7 @@ class SNote extends AbstractShacaEntity { private __attributeCache: SAttribute[] | null; private __inheritableAttributeCache: SAttribute[] | null; targetRelations: SAttribute[]; - private attachments: SAttachment[]; + attachments: SAttachment[]; constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) { super(); diff --git a/src/share/shaca/shaca.js b/src/share/shaca/shaca-interface.ts similarity index 55% rename from src/share/shaca/shaca.js rename to src/share/shaca/shaca-interface.ts index 4e1c3a1ba..751412880 100644 --- a/src/share/shaca/shaca.js +++ b/src/share/shaca/shaca-interface.ts @@ -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; + branches!: Record; + childParentToBranch!: Record; + private attributes!: Record; + attachments!: Record; + private aliasToNote!: Record; + private shareRootNote!: SNote | null; + /** true if the index of all shared subtrees is enabled */ + private shareIndexEnabled!: boolean; + private loaded!: boolean; -class Shaca { constructor() { this.reset(); } reset() { - /** @type {Object.} */ this.notes = {}; - /** @type {Object.} */ this.branches = {}; - /** @type {Object.} */ this.childParentToBranch = {}; - /** @type {Object.} */ this.attributes = {}; - /** @type {Object.} */ this.attachments = {}; - /** @type {Object.} */ this.aliasToNote = {}; - /** @type {SNote|null} */ this.shareRootNote = null; - /** @type {boolean} true if the index of all shared subtrees is enabled */ this.shareIndexEnabled = false; this.loaded = false; } - /** @returns {SNote|null} */ - getNote(noteId) { + getNote(noteId: string) { return this.notes[noteId]; } - /** @returns {boolean} */ - hasNote(noteId) { + hasNote(noteId: string) { return noteId in this.notes; } - /** @returns {SNote[]} */ - getNotes(noteIds, ignoreMissing = false) { + getNotes(noteIds: string[], ignoreMissing = false) { const filteredNotes = []; for (const noteId of noteIds) { @@ -59,27 +63,23 @@ class Shaca { return filteredNotes; } - /** @returns {SBranch|null} */ - getBranch(branchId) { + getBranch(branchId: string) { return this.branches[branchId]; } - /** @returns {SBranch|null} */ - getBranchFromChildAndParent(childNoteId, parentNoteId) { + getBranchFromChildAndParent(childNoteId: string, parentNoteId: string) { return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; } - /** @returns {SAttribute|null} */ - getAttribute(attributeId) { + getAttribute(attributeId: string) { return this.attributes[attributeId]; } - /** @returns {SAttachment|null} */ - getAttachment(attachmentId) { + getAttachment(attachmentId: string) { return this.attachments[attachmentId]; } - getEntity(entityName, entityId) { + getEntity(entityName: string, entityId: string) { if (!entityName || !entityId) { return null; } @@ -91,10 +91,6 @@ class Shaca { .replace('_', '') ); - return this[camelCaseEntityName][entityId]; + return (this as any)[camelCaseEntityName][entityId]; } -} - -const shaca = new Shaca(); - -module.exports = shaca; +} \ No newline at end of file diff --git a/src/share/shaca/shaca.ts b/src/share/shaca/shaca.ts new file mode 100644 index 000000000..d256b17e1 --- /dev/null +++ b/src/share/shaca/shaca.ts @@ -0,0 +1,7 @@ +"use strict"; + +import Shaca from "./shaca-interface"; + +const shaca = new Shaca(); + +export = shaca; diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.js index 6c7c802a5..6b8e59e5e 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.js @@ -1,7 +1,7 @@ "use strict"; const sql = require('../sql'); -const shaca = require('./shaca.js'); +const shaca = require('./shaca'); const log = require('../../services/log'); const SNote = require('./entities/snote'); const SBranch = require('./entities/sbranch'); From 7c76d28f75f2082dfda64c8360228487468d800c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:49:05 +0300 Subject: [PATCH 09/14] server-ts: Port share/shaca/shaca_loader --- src/share/routes.js | 2 +- src/share/shaca/entities/rows.ts | 4 +++ src/share/shaca/entities/sattachment.ts | 4 +-- src/share/shaca/entities/sattribute.ts | 4 +-- src/share/shaca/entities/sbranch.ts | 4 +-- src/share/shaca/entities/snote.ts | 4 +-- src/share/shaca/shaca-interface.ts | 2 +- .../{shaca_loader.js => shaca_loader.ts} | 30 +++++++++---------- src/share/sql.ts | 2 +- 9 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 src/share/shaca/entities/rows.ts rename src/share/shaca/{shaca_loader.js => shaca_loader.ts} (77%) diff --git a/src/share/routes.js b/src/share/routes.js index 7c36d4b28..8bbb106c4 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -4,7 +4,7 @@ const safeCompare = require('safe-compare'); const ejs = require("ejs"); const shaca = require('./shaca/shaca'); -const shacaLoader = require('./shaca/shaca_loader.js'); +const shacaLoader = require('./shaca/shaca_loader'); const shareRoot = require('./share_root'); const contentRenderer = require('./content_renderer.js'); const assetPath = require('../services/asset_path'); diff --git a/src/share/shaca/entities/rows.ts b/src/share/shaca/entities/rows.ts new file mode 100644 index 000000000..d8b8ec84e --- /dev/null +++ b/src/share/shaca/entities/rows.ts @@ -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 ]; diff --git a/src/share/shaca/entities/sattachment.ts b/src/share/shaca/entities/sattachment.ts index 352d34749..cb86f1106 100644 --- a/src/share/shaca/entities/sattachment.ts +++ b/src/share/shaca/entities/sattachment.ts @@ -6,8 +6,6 @@ import AbstractShacaEntity = require('./abstract_shaca_entity'); import SNote = require('./snote'); import { Blob } from '../../../services/blob-interface'; -type AttachmentRow = [ string, string, string, string, string, string, string ]; - class SAttachment extends AbstractShacaEntity { private attachmentId: string; private ownerId: string; @@ -18,7 +16,7 @@ class SAttachment extends AbstractShacaEntity { /** used for caching of images */ private utcDateModified: string; - constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]: AttachmentRow) { + constructor([attachmentId, ownerId, role, mime, title, blobId, utcDateModified]: SAttachmentRow) { super(); this.attachmentId = attachmentId; diff --git a/src/share/shaca/entities/sattribute.ts b/src/share/shaca/entities/sattribute.ts index e12a9d121..390a85b8c 100644 --- a/src/share/shaca/entities/sattribute.ts +++ b/src/share/shaca/entities/sattribute.ts @@ -4,8 +4,6 @@ import SNote = require("./snote"); const AbstractShacaEntity = require('./abstract_shaca_entity'); -type AttributeRow = [ string, string, string, string, string, boolean, number ]; - class SAttribute extends AbstractShacaEntity { attributeId: string; @@ -16,7 +14,7 @@ class SAttribute extends AbstractShacaEntity { value: string; isInheritable: boolean; - constructor([attributeId, noteId, type, name, value, isInheritable, position]: AttributeRow) { + constructor([attributeId, noteId, type, name, value, isInheritable, position]: SAttributeRow) { super(); this.attributeId = attributeId; diff --git a/src/share/shaca/entities/sbranch.ts b/src/share/shaca/entities/sbranch.ts index 4d906d750..a4cf57c51 100644 --- a/src/share/shaca/entities/sbranch.ts +++ b/src/share/shaca/entities/sbranch.ts @@ -3,8 +3,6 @@ import AbstractShacaEntity = require('./abstract_shaca_entity'); import SNote = require('./snote'); -type BranchRow = [ string, string, string, string, string, boolean ]; - class SBranch extends AbstractShacaEntity { private branchId: string; @@ -14,7 +12,7 @@ class SBranch extends AbstractShacaEntity { private isExpanded: boolean; isHidden: boolean; - constructor([branchId, noteId, parentNoteId, prefix, isExpanded]: BranchRow) { + constructor([branchId, noteId, parentNoteId, prefix, isExpanded]: SBranchRow) { super(); this.branchId = branchId; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index f91949aef..50b03ec42 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -15,8 +15,6 @@ const CREDENTIALS = 'shareCredentials'; const isCredentials = (attr: SAttribute) => attr.type === 'label' && attr.name === CREDENTIALS; -type NoteRow = [ string, string, string, string, string, string, boolean ]; - class SNote extends AbstractShacaEntity { noteId: string; private title: string; @@ -34,7 +32,7 @@ class SNote extends AbstractShacaEntity { targetRelations: SAttribute[]; attachments: SAttachment[]; - constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: NoteRow) { + constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: SNoteRow) { super(); this.noteId = noteId; diff --git a/src/share/shaca/shaca-interface.ts b/src/share/shaca/shaca-interface.ts index 751412880..495741fcc 100644 --- a/src/share/shaca/shaca-interface.ts +++ b/src/share/shaca/shaca-interface.ts @@ -14,7 +14,7 @@ export default class Shaca { private shareRootNote!: SNote | null; /** true if the index of all shared subtrees is enabled */ private shareIndexEnabled!: boolean; - private loaded!: boolean; + loaded!: boolean; constructor() { this.reset(); diff --git a/src/share/shaca/shaca_loader.js b/src/share/shaca/shaca_loader.ts similarity index 77% rename from src/share/shaca/shaca_loader.js rename to src/share/shaca/shaca_loader.ts index 6b8e59e5e..c6a3fc4da 100644 --- a/src/share/shaca/shaca_loader.js +++ b/src/share/shaca/shaca_loader.ts @@ -1,14 +1,14 @@ "use strict"; -const sql = require('../sql'); -const shaca = require('./shaca'); -const log = require('../../services/log'); -const SNote = require('./entities/snote'); -const SBranch = require('./entities/sbranch'); -const SAttribute = require('./entities/sattribute'); -const SAttachment = require('./entities/sattachment'); -const shareRoot = require('../share_root'); -const eventService = require('../../services/events'); +import sql = require('../sql'); +import shaca = require('./shaca'); +import log = require('../../services/log'); +import SNote = require('./entities/snote'); +import SBranch = require('./entities/sbranch'); +import SAttribute = require('./entities/sattribute'); +import SAttachment = require('./entities/sattachment'); +import shareRoot = require('../share_root'); +import eventService = require('../../services/events'); function load() { const start = Date.now(); @@ -35,7 +35,7 @@ function load() { const noteIdStr = noteIds.map(noteId => `'${noteId}'`).join(","); - const rawNoteRows = sql.getRawRows(` + const rawNoteRows = sql.getRawRows(` SELECT noteId, title, type, mime, blobId, utcDateModified, isProtected FROM notes WHERE isDeleted = 0 @@ -45,7 +45,7 @@ function load() { new SNote(row); } - const rawBranchRows = sql.getRawRows(` + const rawBranchRows = sql.getRawRows(` SELECT branchId, noteId, parentNoteId, prefix, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0 @@ -56,7 +56,7 @@ function load() { new SBranch(row); } - const rawAttributeRows = sql.getRawRows(` + const rawAttributeRows = sql.getRawRows(` SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0 @@ -66,14 +66,12 @@ function load() { new SAttribute(row); } - const rawAttachmentRows = sql.getRawRows(` + const rawAttachmentRows = sql.getRawRows(` SELECT attachmentId, ownerId, role, mime, title, blobId, utcDateModified FROM attachments WHERE isDeleted = 0 AND ownerId IN (${noteIdStr})`); - rawAttachmentRows.sort((a, b) => a.position < b.position ? -1 : 1); - for (const row of rawAttachmentRows) { new SAttachment(row); } @@ -93,7 +91,7 @@ eventService.subscribe([eventService.ENTITY_CREATED, eventService.ENTITY_CHANGED shaca.reset(); }); -module.exports = { +export = { load, ensureLoad }; diff --git a/src/share/sql.ts b/src/share/sql.ts index 3d07df65f..a5465f4c2 100644 --- a/src/share/sql.ts +++ b/src/share/sql.ts @@ -23,7 +23,7 @@ function getRow(query: string, params: string[] = []): T { return dbConnection.prepare(query).get(params) as T; } -function getColumn(query: string, params = []): T[] { +function getColumn(query: string, params: string[] = []): T[] { return dbConnection.prepare(query).pluck().all(params) as T[]; } From 88aba1c8448afc50017cca250d88b0cc7ad451ec Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 9 Apr 2024 22:58:24 +0300 Subject: [PATCH 10/14] server-ts: Port share/shaca/content_renderer --- ...ontent_renderer.js => content_renderer.ts} | 47 ++++++++++++------- src/share/routes.js | 2 +- src/share/shaca/entities/sattachment.ts | 2 +- src/share/shaca/entities/snote.ts | 8 ++-- 4 files changed, 36 insertions(+), 23 deletions(-) rename src/share/{content_renderer.js => content_renderer.ts} (82%) diff --git a/src/share/content_renderer.js b/src/share/content_renderer.ts similarity index 82% rename from src/share/content_renderer.js rename to src/share/content_renderer.ts index 20d8b0b7f..cb111008a 100644 --- a/src/share/content_renderer.js +++ b/src/share/content_renderer.ts @@ -1,10 +1,17 @@ -const { JSDOM } = require("jsdom"); -const shaca = require('./shaca/shaca'); -const assetPath = require('../services/asset_path'); -const shareRoot = require('./share_root'); -const escapeHtml = require('escape-html'); +import { JSDOM } from "jsdom"; +import shaca = require('./shaca/shaca'); +import assetPath = require('../services/asset_path'); +import shareRoot = require('./share_root'); +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) { return { header: '', @@ -13,7 +20,7 @@ function getContent(note) { }; } - const result = { + const result: Result = { content: note.getContent(), header: '', isEmpty: false @@ -38,7 +45,7 @@ function getContent(note) { return result; } -function renderIndex(result) { +function renderIndex(result: Result) { result.content += '
    '; const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID); @@ -53,10 +60,10 @@ function renderIndex(result) { result.content += '
'; } -function renderText(result, note) { +function renderText(result: Result, note: SNote) { 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; if (!result.isEmpty) { @@ -89,7 +96,9 @@ function renderText(result, note) { if (linkedNote) { const isExternalLink = linkedNote.hasLabel("shareExternalLink"); const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `./${linkedNote.shareId}`; - linkEl.setAttribute("href", href); + if (href) { + linkEl.setAttribute("href", href); + } if (isExternalLink) { linkEl.setAttribute("target", "_blank"); linkEl.setAttribute("rel", "noopener noreferrer"); @@ -122,8 +131,8 @@ document.addEventListener("DOMContentLoaded", function() { } } -function renderCode(result) { - if (!result.content?.trim()) { +function renderCode(result: Result) { + if (typeof result.content !== "string" || !result.content?.trim()) { result.isEmpty = true; } else { 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 = `
@@ -145,11 +158,11 @@ function renderMermaid(result, note) { ` } -function renderImage(result, note) { +function renderImage(result: Result, note: SNote) { result.content = ``; } -function renderFile(note, result) { +function renderFile(note: SNote, result: Result) { if (note.mime === 'application/pdf') { result.content = `` } else { @@ -157,6 +170,6 @@ function renderFile(note, result) { } } -module.exports = { +export = { getContent }; diff --git a/src/share/routes.js b/src/share/routes.js index 8bbb106c4..719fa9368 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -6,7 +6,7 @@ const ejs = require("ejs"); const shaca = require('./shaca/shaca'); const shacaLoader = require('./shaca/shaca_loader'); const shareRoot = require('./share_root'); -const contentRenderer = require('./content_renderer.js'); +const contentRenderer = require('./content_renderer'); const assetPath = require('../services/asset_path'); const appPath = require('../services/app_path'); const searchService = require('../services/search/services/search'); diff --git a/src/share/shaca/entities/sattachment.ts b/src/share/shaca/entities/sattachment.ts index cb86f1106..c8d58cd17 100644 --- a/src/share/shaca/entities/sattachment.ts +++ b/src/share/shaca/entities/sattachment.ts @@ -10,7 +10,7 @@ class SAttachment extends AbstractShacaEntity { private attachmentId: string; private ownerId: string; title: string; - private role: string; + role: string; private mime: string; private blobId: string; /** used for caching of images */ diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 50b03ec42..5df224af5 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -18,11 +18,11 @@ const isCredentials = (attr: SAttribute) => attr.type === 'label' && attr.name = class SNote extends AbstractShacaEntity { noteId: string; private title: string; - private type: string; - private mime: string; + type: string; + mime: string; private blobId: string; - private utcDateModified: string; - private isProtected: boolean; + utcDateModified: string; + isProtected: boolean; parentBranches: SBranch[]; parents: SNote[]; children: SNote[]; From c08393f04b1552a34413d9795b2f4ddac3f2f301 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 10 Apr 2024 19:04:38 +0300 Subject: [PATCH 11/14] server-ts: Port share/routes --- package-lock.json | 26 ++++++ package.json | 2 + src/becca/entities/bnote.ts | 7 +- src/routes/routes.js | 2 +- src/share/{routes.js => routes.ts} | 117 ++++++++++++++---------- src/share/shaca/entities/sattachment.ts | 4 +- src/share/shaca/entities/sbranch.ts | 2 +- src/share/shaca/entities/snote.ts | 25 ++++- src/share/shaca/shaca-interface.ts | 6 +- 9 files changed, 131 insertions(+), 60 deletions(-) rename src/share/{routes.js => routes.ts} (74%) diff --git a/package-lock.json b/package-lock.json index e80b41f85..a0fdabf82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "@types/better-sqlite3": "^7.6.9", "@types/cls-hooked": "^4.3.8", "@types/csurf": "^1.11.5", + "@types/ejs": "^3.1.5", "@types/escape-html": "^1.0.4", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", @@ -101,6 +102,7 @@ "@types/mime-types": "^2.1.4", "@types/multer": "^1.4.11", "@types/node": "^20.11.19", + "@types/safe-compare": "^1.1.2", "@types/sanitize-html": "^2.11.0", "@types/sax": "^1.2.7", "@types/stream-throttle": "^0.1.4", @@ -1271,6 +1273,12 @@ "@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": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", @@ -1537,6 +1545,12 @@ "@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": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", @@ -14276,6 +14290,12 @@ "@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": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", @@ -14535,6 +14555,12 @@ "@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": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", diff --git a/package.json b/package.json index 89ccc993c..8cc9cf947 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "@types/better-sqlite3": "^7.6.9", "@types/cls-hooked": "^4.3.8", "@types/csurf": "^1.11.5", + "@types/ejs": "^3.1.5", "@types/escape-html": "^1.0.4", "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", @@ -122,6 +123,7 @@ "@types/mime-types": "^2.1.4", "@types/multer": "^1.4.11", "@types/node": "^20.11.19", + "@types/safe-compare": "^1.1.2", "@types/sanitize-html": "^2.11.0", "@types/sax": "^1.2.7", "@types/stream-throttle": "^0.1.4", diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index c29945a93..1b681d546 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -209,7 +209,7 @@ class BNote extends AbstractBeccaEntity { .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 * part of Note entity with its own sync. Reasons behind this hybrid design has been: * @@ -222,7 +222,8 @@ class BNote extends AbstractBeccaEntity { } /** - * @throws Error in case of invalid JSON */ + * @throws Error in case of invalid JSON + */ getJsonContent(): any | null { const content = this.getContent(); @@ -233,7 +234,7 @@ class BNote extends AbstractBeccaEntity { 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() { try { return this.getJsonContent(); diff --git a/src/routes/routes.js b/src/routes/routes.js index 00c803cb0..7aa6a64ef 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -59,7 +59,7 @@ const fontsRoute = require('./api/fonts'); const etapiTokensApiRoutes = require('./api/etapi_tokens'); const relationMapApiRoute = require('./api/relation-map'); const otherRoute = require('./api/other'); -const shareRoutes = require('../share/routes.js'); +const shareRoutes = require('../share/routes'); const etapiAuthRoutes = require('../etapi/auth'); const etapiAppInfoRoutes = require('../etapi/app_info'); diff --git a/src/share/routes.js b/src/share/routes.ts similarity index 74% rename from src/share/routes.js rename to src/share/routes.ts index 719fa9368..ccac51295 100644 --- a/src/share/routes.js +++ b/src/share/routes.ts @@ -1,23 +1,24 @@ -const express = require('express'); -const path = require('path'); -const safeCompare = require('safe-compare'); -const ejs = require("ejs"); +import express = require('express'); +import path = require('path'); +import safeCompare = require('safe-compare'); +import ejs = require("ejs"); -const shaca = require('./shaca/shaca'); -const shacaLoader = require('./shaca/shaca_loader'); -const shareRoot = require('./share_root'); -const contentRenderer = require('./content_renderer'); -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'); +import shacaLoader = require('./shaca/shaca_loader'); +import shareRoot = require('./share_root'); +import contentRenderer = require('./content_renderer'); +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'); +import BNote = require('../becca/entities/bnote'); +import BRevision = require('../becca/entities/brevision'); -/** - * @param {SNote} note - * @return {{note: SNote, branch: SBranch}|{}} - */ -function getSharedSubTreeRoot(note) { +function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { // share root itself is not shared return {}; @@ -37,19 +38,18 @@ function getSharedSubTreeRoot(note) { return getSharedSubTreeRoot(parentBranch.getParentNote()); } -function addNoIndexHeader(note, res) { +function addNoIndexHeader(note: SNote, res: express.Response) { if (note.isLabelTruthy('shareDisallowRobotIndexing')) { res.setHeader('X-Robots-Tag', 'noindex'); } } -function requestCredentials(res) { +function requestCredentials(res: express.Response) { res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm", charset="UTF-8"') .sendStatus(401); } -/** @returns {SAttachment|boolean} */ -function checkAttachmentAccess(attachmentId, req, res) { +function checkAttachmentAccess(attachmentId: string, req: express.Request, res: express.Response) { const attachment = shaca.getAttachment(attachmentId); if (!attachment) { @@ -65,8 +65,7 @@ function checkAttachmentAccess(attachmentId, req, res) { return note ? attachment : false; } -/** @returns {SNote|boolean} */ -function checkNoteAccess(noteId, req, res) { +function checkNoteAccess(noteId: string, req: express.Request, res: express.Response) { const note = shaca.getNote(noteId); if (!note) { @@ -109,12 +108,16 @@ function checkNoteAccess(noteId, req, res) { return false; } -function renderImageAttachment(image, res, attachmentName) { +function renderImageAttachment(image: SNote, res: express.Response, attachmentName: string) { let svgString = '' const attachment = image.getAttachmentByTitle(attachmentName); - - if (attachment) { - svgString = attachment.getContent(); + if (!attachment) { + res.status(404).render("share/404"); + return; + } + const content = attachment.getContent(); + if (typeof content === "string") { + svgString = content; } else { // backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key const contentSvg = image.getJsonContentSafely()?.svg; @@ -130,8 +133,8 @@ function renderImageAttachment(image, res, attachmentName) { res.send(svg); } -function register(router) { - function renderNote(note, req, res) { +function register(router: express.Router) { + function renderNote(note: SNote, req: express.Request, res: express.Response) { if (!note) { res.status(404).render("share/404"); return; @@ -160,27 +163,34 @@ function register(router) { // Check if the user has their own template if (note.hasRelation('shareTemplate')) { // Get the template note and content - const templateId = note.getRelation('shareTemplate').value; - const templateNote = shaca.getNote(templateId); + const templateId = note.getRelation('shareTemplate')?.value; + const templateNote = templateId && shaca.getNote(templateId); // 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 - const includer = (path) => { + const includer = (path: string) => { const childNote = templateNote.children.find(n => path === n.title); - if (!childNote) return null; - if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') return null; - return { template: childNote.getContent() }; + if (!childNote) throw new Error("Unable to find child note."); + if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') throw new Error("Incorrect child note type."); + + 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 { - const ejsResult = ejs.render(templateNote.getContent(), opts, { includer }); - res.send(ejsResult); - useDefaultView = false; // Rendering went okay, don't use default view + const content = templateNote.getContent(); + if (typeof content === "string") { + 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}`); } } @@ -199,6 +209,11 @@ function register(router) { shacaLoader.ensureLoad(); + if (!shaca.shareRootNote) { + return res.status(404) + .json({ message: "Share root note not found" }); + } + renderNote(shaca.shareRootNote, req, res); }); @@ -214,7 +229,7 @@ function register(router) { router.get('/share/api/notes/:noteId', (req, res, next) => { shacaLoader.ensureLoad(); - let note; + let note: SNote | boolean; if (!(note = checkNoteAccess(req.params.noteId, req, res))) { return; @@ -228,7 +243,7 @@ function register(router) { router.get('/share/api/notes/:noteId/download', (req, res, next) => { shacaLoader.ensureLoad(); - let note; + let note: SNote | boolean; if (!(note = checkNoteAccess(req.params.noteId, req, res))) { return; @@ -252,7 +267,7 @@ function register(router) { router.get('/share/api/images/:noteId/:filename', (req, res, next) => { shacaLoader.ensureLoad(); - let image; + let image: SNote | boolean; if (!(image = checkNoteAccess(req.params.noteId, req, res))) { return; @@ -277,7 +292,7 @@ function register(router) { router.get('/share/api/attachments/:attachmentId/image/:filename', (req, res, next) => { shacaLoader.ensureLoad(); - let attachment; + let attachment: SAttachment | boolean; if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { return; @@ -296,7 +311,7 @@ function register(router) { router.get('/share/api/attachments/:attachmentId/download', (req, res, next) => { shacaLoader.ensureLoad(); - let attachment; + let attachment: SAttachment | boolean; if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) { return; @@ -320,7 +335,7 @@ function register(router) { router.get('/share/api/notes/:noteId/view', (req, res, next) => { shacaLoader.ensureLoad(); - let note; + let note: SNote | boolean; if (!(note = checkNoteAccess(req.params.noteId, req, res))) { return; @@ -341,6 +356,10 @@ function register(router) { const ancestorNoteId = req.query.ancestorNoteId ?? "_share"; 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 if (!(note = checkNoteAccess(ancestorNoteId, req, res))) { return; @@ -348,7 +367,7 @@ function register(router) { const { search } = req.query; - if (!search?.trim()) { + if (typeof search !== "string" || !search?.trim()) { return res.status(400).json({ message: "'search' parameter is mandatory." }); } @@ -366,6 +385,6 @@ function register(router) { }); } -module.exports = { +export = { register } diff --git a/src/share/shaca/entities/sattachment.ts b/src/share/shaca/entities/sattachment.ts index c8d58cd17..1e9565ce3 100644 --- a/src/share/shaca/entities/sattachment.ts +++ b/src/share/shaca/entities/sattachment.ts @@ -8,10 +8,10 @@ import { Blob } from '../../../services/blob-interface'; class SAttachment extends AbstractShacaEntity { private attachmentId: string; - private ownerId: string; + ownerId: string; title: string; role: string; - private mime: string; + mime: string; private blobId: string; /** used for caching of images */ private utcDateModified: string; diff --git a/src/share/shaca/entities/sbranch.ts b/src/share/shaca/entities/sbranch.ts index a4cf57c51..0ff356922 100644 --- a/src/share/shaca/entities/sbranch.ts +++ b/src/share/shaca/entities/sbranch.ts @@ -7,7 +7,7 @@ class SBranch extends AbstractShacaEntity { private branchId: string; private noteId: string; - private parentNoteId: string; + parentNoteId: string; private prefix: string; private isExpanded: boolean; isHidden: boolean; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 5df224af5..89b6562b4 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -17,7 +17,7 @@ const isCredentials = (attr: SAttribute) => attr.type === 'label' && attr.name = class SNote extends AbstractShacaEntity { noteId: string; - private title: string; + title: string; type: string; mime: string; private blobId: string; @@ -223,6 +223,29 @@ class SNote extends AbstractShacaEntity { } } + /** + * @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); } diff --git a/src/share/shaca/shaca-interface.ts b/src/share/shaca/shaca-interface.ts index 495741fcc..9cff96eeb 100644 --- a/src/share/shaca/shaca-interface.ts +++ b/src/share/shaca/shaca-interface.ts @@ -10,10 +10,10 @@ export default class Shaca { childParentToBranch!: Record; private attributes!: Record; attachments!: Record; - private aliasToNote!: Record; - private shareRootNote!: SNote | null; + aliasToNote!: Record; + shareRootNote!: SNote | null; /** true if the index of all shared subtrees is enabled */ - private shareIndexEnabled!: boolean; + shareIndexEnabled!: boolean; loaded!: boolean; constructor() { From 262e4db0f2319ada7f136ea549106f5a5edfa144 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Apr 2024 22:35:38 +0300 Subject: [PATCH 12/14] server-ts: Remove unnecessary type comments --- src/becca/entities/battribute.ts | 9 ------ src/becca/entities/bbranch.ts | 2 -- src/becca/entities/bnote.ts | 47 ++++++++++-------------------- src/services/backend_script_api.ts | 7 ++--- src/services/consistency_checks.ts | 2 +- src/services/events.ts | 3 +- src/services/sql.ts | 34 ++++++++++----------- src/share/shaca/entities/snote.ts | 21 +++++-------- 8 files changed, 44 insertions(+), 81 deletions(-) diff --git a/src/becca/entities/battribute.ts b/src/becca/entities/battribute.ts index ebd9e74cf..6ad1965ed 100644 --- a/src/becca/entities/battribute.ts +++ b/src/becca/entities/battribute.ts @@ -125,9 +125,6 @@ class BAttribute extends AbstractBeccaEntity { } } - /** - * @returns {BNote|null} - */ getNote() { const note = this.becca.getNote(this.noteId); @@ -138,9 +135,6 @@ class BAttribute extends AbstractBeccaEntity { return note; } - /** - * @returns {BNote|null} - */ getTargetNote() { if (this.type !== 'relation') { throw new Error(`Attribute '${this.attributeId}' is not a relation.`); @@ -153,9 +147,6 @@ class BAttribute extends AbstractBeccaEntity { return this.becca.getNote(this.value); } - /** - * @returns {boolean} - */ isDefinition() { return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:')); } diff --git a/src/becca/entities/bbranch.ts b/src/becca/entities/bbranch.ts index 2b6dc8657..0f904de92 100644 --- a/src/becca/entities/bbranch.ts +++ b/src/becca/entities/bbranch.ts @@ -127,8 +127,6 @@ class BBranch extends AbstractBeccaEntity { * 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 * of deletion should not act as a clone. - * - * @returns {boolean} */ get isWeak() { return ['_share', '_lbBookmarks'].includes(this.parentNoteId); diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index 1b681d546..a007f5278 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -167,39 +167,32 @@ class BNote extends AbstractBeccaEntity { return this.isContentAvailable() ? this.title : '[protected]'; } - /** @returns {BBranch[]} */ getParentBranches() { return this.parentBranches; } /** * Returns strong (as opposed to weak) parent branches. See isWeak for details. - * - * @returns {BBranch[]} */ getStrongParentBranches() { return this.getParentBranches().filter(branch => !branch.isWeak); } /** - * @returns {BBranch[]} * @deprecated use getParentBranches() instead */ getBranches() { return this.parentBranches; } - /** @returns {BNote[]} */ getParentNotes() { return this.parents; } - /** @returns {BNote[]} */ getChildNotes() { return this.children; } - /** @returns {boolean} */ hasChildren() { return this.children && this.children.length > 0; } @@ -270,17 +263,17 @@ class BNote extends AbstractBeccaEntity { 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() { 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() { 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() { return (this.type === "code" || this.type === "file" || this.type === 'launcher') && (this.mime.startsWith("application/javascript") @@ -288,13 +281,13 @@ class BNote extends AbstractBeccaEntity { || this.mime === "text/javascript"); } - /** @returns {boolean} true if this note is HTML */ + /** @returns true if this note is HTML */ isHtml() { return ["code", "file", "render"].includes(this.type) && this.mime === "text/html"; } - /** @returns {boolean} true if this note is an image */ + /** @returns true if this note is an image */ isImage() { return this.type === 'image' || (this.type === 'file' && this.mime?.startsWith('image/')); @@ -305,12 +298,12 @@ class BNote extends AbstractBeccaEntity { 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() { 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() { if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith('env=frontend'))) { return "frontend"; @@ -519,8 +512,8 @@ class BNote extends AbstractBeccaEntity { } /** - * @param {string} name - label name - * @returns {BAttribute|null} label if it exists, null otherwise + * @param name - label name + * @returns label if it exists, null otherwise */ getLabel(name: string): BAttribute | null { return this.getAttribute(LABEL, name); @@ -681,7 +674,7 @@ class BNote extends AbstractBeccaEntity { * @param type - (optional) attribute type to filter * @param name - (optional) attribute name 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) { this.__validateTypeName(type, name); @@ -704,7 +697,7 @@ class BNote extends AbstractBeccaEntity { } /** - * @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() */ @@ -781,7 +774,7 @@ class BNote extends AbstractBeccaEntity { * - fast searching * - 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() { if (!this.__flatTextCache) { @@ -972,7 +965,7 @@ class BNote extends AbstractBeccaEntity { }; } - /** @returns {string[]} - includes the subtree root note as well */ + /** @returns includes the subtree root note as well */ getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) { return this.getSubtree({includeArchived, includeHidden, resolveSearch}) .notes @@ -1032,7 +1025,6 @@ class BNote extends AbstractBeccaEntity { return this.getOwnedAttributes().length; } - /** @returns {BNote[]} */ getAncestors() { if (!this.__ancestorCache) { const noteIds = new Set(); @@ -1076,7 +1068,6 @@ class BNote extends AbstractBeccaEntity { return this.noteId === '_hidden' || this.hasAncestor('_hidden'); } - /** @returns {BAttribute[]} */ getTargetRelations() { return this.targetRelations; } @@ -1118,7 +1109,6 @@ class BNote extends AbstractBeccaEntity { .map(row => new BRevision(row)); } - /** @returns {BAttachment[]} */ getAttachments(opts: AttachmentOpts = {}) { opts.includeContentLength = !!opts.includeContentLength; // from testing, it looks like calculating length does not make a difference in performance even on large-ish DB @@ -1136,7 +1126,6 @@ class BNote extends AbstractBeccaEntity { .map(row => new BAttachment(row)); } - /** @returns {BAttachment|null} */ getAttachmentById(attachmentId: string, opts: AttachmentOpts = {}) { opts.includeContentLength = !!opts.includeContentLength; @@ -1583,10 +1572,7 @@ class BNote extends AbstractBeccaEntity { return !(this.noteId in this.becca.notes) || this.isBeingDeleted; } - /** - * @returns {BRevision|null} - */ - saveRevision() { + saveRevision(): BRevision { return sql.transactional(() => { let noteContent = this.getContent(); @@ -1633,9 +1619,8 @@ class BNote extends AbstractBeccaEntity { } /** - * @param {string} matchBy - choose by which property we detect if to update an existing attachment. - * Supported values are either 'attachmentId' (default) or 'title' - * @returns {BAttachment} + * @param matchBy - choose by which property we detect if to update an existing attachment. + * Supported values are either 'attachmentId' (default) or 'title' */ saveAttachment({attachmentId, role, mime, title, content, position}: AttachmentRow, matchBy = 'attachmentId') { if (!['attachmentId', 'title'].includes(matchBy)) { diff --git a/src/services/backend_script_api.ts b/src/services/backend_script_api.ts index 34767ae75..4a6e89d83 100644 --- a/src/services/backend_script_api.ts +++ b/src/services/backend_script_api.ts @@ -122,17 +122,14 @@ interface Api { /** * This is a powerful search method - you can search by attributes and their values, e.g.: * "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options - * - * @param {string} query - * @param {Object} [searchParams] */ searchForNote(query: string, searchParams: SearchParams): BNote | null; /** * Retrieves notes with given label name & value * - * @param name - attribute name - * @param value - attribute value + * @param name - attribute name + * @param value - attribute value */ getNotesWithLabel(name: string, value?: string): BNote[]; diff --git a/src/services/consistency_checks.ts b/src/services/consistency_checks.ts index 36fdeef78..b4f615f53 100644 --- a/src/services/consistency_checks.ts +++ b/src/services/consistency_checks.ts @@ -69,7 +69,7 @@ class ConsistencyChecks { 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[]) => { if (noteId === 'root') { return false; diff --git a/src/services/events.ts b/src/services/events.ts index 327443a95..0eda326b9 100644 --- a/src/services/events.ts +++ b/src/services/events.ts @@ -17,8 +17,7 @@ type EventListener = (data: any) => void; const eventListeners: Record = {}; /** - * @param {string|string[]}eventTypes - can be either single event or an array of events - * @param listener + * @param eventTypes - can be either single event or an array of events */ function subscribe(eventTypes: EventType, listener: EventListener) { if (!Array.isArray(eventTypes)) { diff --git a/src/services/sql.ts b/src/services/sql.ts index 18d72912f..cb4a75df5 100644 --- a/src/services/sql.ts +++ b/src/services/sql.ts @@ -323,9 +323,9 @@ export = { * Get single value from the given query - first column from first returned row. * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed - * @returns [object] - single value + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed + * @returns single value */ getValue, @@ -333,9 +333,9 @@ export = { * Get first returned row. * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed - * @returns {object} - map of column name to column value + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed + * @returns - map of column name to column value */ getRow, getRowOrNull, @@ -344,9 +344,9 @@ export = { * Get all returned rows. * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed - * @returns {object[]} - array of all rows, each row is a map of column name to column value + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed + * @returns - array of all rows, each row is a map of column name to column value */ getRows, getRawRows, @@ -357,9 +357,9 @@ export = { * Get a map of first column mapping to second column. * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed - * @returns {object} - map of first column to second column + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed + * @returns - map of first column to second column */ getMap, @@ -367,9 +367,9 @@ export = { * Get a first column in an array. * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed - * @returns {object[]} - array of first column of all returned rows + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed + * @returns array of first column of all returned rows */ getColumn, @@ -377,8 +377,8 @@ export = { * Execute SQL * * @method - * @param {string} query - SQL query with ? used as parameter placeholder - * @param {object[]} [params] - array of params if needed + * @param query - SQL query with ? used as parameter placeholder + * @param params - array of params if needed */ execute, executeMany, diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 89b6562b4..37e0fb477 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -69,35 +69,29 @@ class SNote extends AbstractShacaEntity { return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); } - /** @returns {SBranch[]} */ getVisibleChildBranches() { return this.getChildBranches() .filter(branch => !branch.isHidden && !branch.getNote().isLabelTruthy('shareHiddenFromTree')); } - /** @returns {SNote[]} */ getParentNotes() { return this.parents; } - /** @returns {SNote[]} */ getChildNotes() { return this.children; } - /** @returns {SNote[]} */ getVisibleChildNotes() { return this.getVisibleChildBranches() .map(branch => branch.getNote()); } - /** @returns {boolean} */ hasChildren() { return this.children && this.children.length > 0; } - /** @returns {boolean} */ hasVisibleChildren() { return this.getVisibleChildNotes().length > 0; } @@ -250,7 +244,6 @@ class SNote extends AbstractShacaEntity { return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); } - /** @returns {SNote|null} */ getRelationTarget(name: string) { const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); @@ -265,7 +258,7 @@ class SNote extends AbstractShacaEntity { /** * @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: string) { const label = this.getLabel(name); @@ -321,25 +314,25 @@ class SNote extends AbstractShacaEntity { /** * @param name - label name - * @returns {string|null} label value if label exists, null otherwise + * @returns label value if label exists, null otherwise */ getLabelValue(name: string) { return this.getAttributeValue(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: string) { return this.getOwnedAttributeValue(LABEL, 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: string) { return this.getAttributeValue(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: string) { return this.getOwnedAttributeValue(RELATION, name); } @@ -367,7 +360,7 @@ class SNote extends AbstractShacaEntity { /** * @param type - attribute type (label, relation, etc.) * @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: string, name: string) { const attr = this.getAttribute(type, name); @@ -412,7 +405,7 @@ class SNote extends AbstractShacaEntity { /** * @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: string) { return this.getOwnedAttributes(LABEL, name).map(l => l.value); From 1d1ccc8d636ffa8604df82173c39871cdc83cb8a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Apr 2024 22:49:41 +0300 Subject: [PATCH 13/14] server-ts: Fix regression --- src/share/shaca/entities/snote.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 37e0fb477..3ca182167 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -211,10 +211,10 @@ class SNote extends AbstractShacaEntity { } if (!this.__inheritableAttributeCache) { - return this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache - } else { - return this.__inheritableAttributeCache; + this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache } + + return this.__inheritableAttributeCache || []; } /** From 67cb02ed922bce7b2621a79d1ab71037eba2018f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Apr 2024 22:54:05 +0300 Subject: [PATCH 14/14] server-ts: Address some more review comments --- src/share/routes.ts | 20 +++++++++----------- src/share/shaca/entities/snote.ts | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/share/routes.ts b/src/share/routes.ts index ccac51295..97a0dd8e1 100644 --- a/src/share/routes.ts +++ b/src/share/routes.ts @@ -1,8 +1,8 @@ -import express = require('express'); -import path = require('path'); import safeCompare = require('safe-compare'); import ejs = require("ejs"); +import type { Request, Response, Router } from "express"; + import shaca = require('./shaca/shaca'); import shacaLoader = require('./shaca/shaca_loader'); import shareRoot = require('./share_root'); @@ -15,8 +15,6 @@ import log = require('../services/log'); import SNote = require('./shaca/entities/snote'); import SBranch = require('./shaca/entities/sbranch'); import SAttachment = require('./shaca/entities/sattachment'); -import BNote = require('../becca/entities/bnote'); -import BRevision = require('../becca/entities/brevision'); function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { @@ -38,18 +36,18 @@ function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } { return getSharedSubTreeRoot(parentBranch.getParentNote()); } -function addNoIndexHeader(note: SNote, res: express.Response) { +function addNoIndexHeader(note: SNote, res: Response) { if (note.isLabelTruthy('shareDisallowRobotIndexing')) { res.setHeader('X-Robots-Tag', 'noindex'); } } -function requestCredentials(res: express.Response) { +function requestCredentials(res: Response) { res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm", charset="UTF-8"') .sendStatus(401); } -function checkAttachmentAccess(attachmentId: string, req: express.Request, res: express.Response) { +function checkAttachmentAccess(attachmentId: string, req: Request, res: Response) { const attachment = shaca.getAttachment(attachmentId); if (!attachment) { @@ -65,7 +63,7 @@ function checkAttachmentAccess(attachmentId: string, req: express.Request, res: return note ? attachment : false; } -function checkNoteAccess(noteId: string, req: express.Request, res: express.Response) { +function checkNoteAccess(noteId: string, req: Request, res: Response) { const note = shaca.getNote(noteId); if (!note) { @@ -108,7 +106,7 @@ function checkNoteAccess(noteId: string, req: express.Request, res: express.Resp return false; } -function renderImageAttachment(image: SNote, res: express.Response, attachmentName: string) { +function renderImageAttachment(image: SNote, res: Response, attachmentName: string) { let svgString = '' const attachment = image.getAttachmentByTitle(attachmentName); if (!attachment) { @@ -133,8 +131,8 @@ function renderImageAttachment(image: SNote, res: express.Response, attachmentNa res.send(svg); } -function register(router: express.Router) { - function renderNote(note: SNote, req: express.Request, res: express.Response) { +function register(router: Router) { + function renderNote(note: SNote, req: Request, res: Response) { if (!note) { res.status(404).render("share/404"); return; diff --git a/src/share/shaca/entities/snote.ts b/src/share/shaca/entities/snote.ts index 3ca182167..bae610886 100644 --- a/src/share/shaca/entities/snote.ts +++ b/src/share/shaca/entities/snote.ts @@ -169,7 +169,7 @@ class SNote extends AbstractShacaEntity { } } - const templateAttributes = []; + const templateAttributes: SAttribute[] = []; for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates if (ownedAttr.type === 'relation' && ['template', 'inherit'].includes(ownedAttr.name)) {