diff --git a/_check_ts_progress.sh b/_check_ts_progress.sh index 85b71add8..ac74a7205 100755 --- a/_check_ts_progress.sh +++ b/_check_ts_progress.sh @@ -4,4 +4,7 @@ cloc HEAD \ --git --md \ --include-lang=javascript,typescript \ --found=filelist.txt \ - --exclude-dir=public,libraries \ No newline at end of file + --exclude-dir=public,libraries + +grep -R \.js$ filelist.txt +rm filelist.txt \ No newline at end of file diff --git a/docker_healthcheck.js b/docker_healthcheck.js index 88a8f3509..9761aebe2 100755 --- a/docker_healthcheck.js +++ b/docker_healthcheck.js @@ -10,8 +10,8 @@ if (config.Network.https) { process.exit(0); } -const port = require('./src/services/port.ts'); -const host = require('./src/services/host.js'); +const port = require('./src/services/port'); +const host = require('./src/services/host'); const options = { timeout: 2000 }; diff --git a/electron.js b/electron.js index 13ba5acc5..69f403dd8 100644 --- a/electron.js +++ b/electron.js @@ -3,8 +3,8 @@ const {app, globalShortcut, BrowserWindow} = require('electron'); const sqlInit = require('./src/services/sql_init'); const appIconService = require('./src/services/app_icon.js'); -const windowService = require('./src/services/window.js'); -const tray = require('./src/services/tray.js'); +const windowService = require('./src/services/window'); +const tray = require('./src/services/tray'); // Adds debug features like hotkeys for triggering dev tools and reload require('electron-debug')(); diff --git a/spec/search/lexer.spec.js b/spec/search/lexer.spec.js index 20221e9a0..1533bf56a 100644 --- a/spec/search/lexer.spec.js +++ b/spec/search/lexer.spec.js @@ -1,4 +1,4 @@ -const lex = require('../../src/services/search/services/lex.js'); +const lex = require('../../src/services/search/services/lex'); describe("Lexer fulltext", () => { it("simple lexing", () => { diff --git a/spec/search/parens.spec.js b/spec/search/parens.spec.js index d79cfd6ea..ae1ac5b2f 100644 --- a/spec/search/parens.spec.js +++ b/spec/search/parens.spec.js @@ -1,4 +1,4 @@ -const handleParens = require('../../src/services/search/services/handle_parens.js'); +const handleParens = require('../../src/services/search/services/handle_parens'); describe("Parens handler", () => { it("handles parens", () => { diff --git a/spec/search/parser.spec.js b/spec/search/parser.spec.js index 000948d47..be2e2bcaf 100644 --- a/spec/search/parser.spec.js +++ b/spec/search/parser.spec.js @@ -1,5 +1,5 @@ -const SearchContext = require('../../src/services/search/search_context.js'); -const parse = require('../../src/services/search/services/parse.js'); +const SearchContext = require('../../src/services/search/search_context'); +const parse = require('../../src/services/search/services/parse'); function tokens(toks, cur = 0) { return toks.map(arg => { diff --git a/spec/search/search.spec.js b/spec/search/search.spec.js index 14714cb6e..c628ac2a0 100644 --- a/spec/search/search.spec.js +++ b/spec/search/search.spec.js @@ -1,7 +1,7 @@ -const searchService = require('../../src/services/search/services/search.js'); +const searchService = require('../../src/services/search/services/search'); const BNote = require('../../src/becca/entities/bnote.js'); const BBranch = require('../../src/becca/entities/bbranch.js'); -const SearchContext = require('../../src/services/search/search_context.js'); +const SearchContext = require('../../src/services/search/search_context'); const dateUtils = require('../../src/services/date_utils'); const becca = require('../../src/becca/becca.js'); const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js'); diff --git a/spec/search/value_extractor.spec.js b/spec/search/value_extractor.spec.js index 4e05380c2..08508a4bf 100644 --- a/spec/search/value_extractor.spec.js +++ b/spec/search/value_extractor.spec.js @@ -1,7 +1,7 @@ const {note} = require('./becca_mocking.js'); -const ValueExtractor = require('../../src/services/search/value_extractor.js'); +const ValueExtractor = require('../../src/services/search/value_extractor'); const becca = require('../../src/becca/becca.js'); -const SearchContext = require('../../src/services/search/search_context.js'); +const SearchContext = require('../../src/services/search/search_context'); const dsc = new SearchContext(); diff --git a/src/app.js b/src/app.js index c6afc27f8..2dad38c7d 100644 --- a/src/app.js +++ b/src/app.js @@ -43,7 +43,7 @@ require('./routes/custom.js').register(app); require('./routes/error_handlers.js').register(app); // triggers sync timer -require('./services/sync.js'); +require('./services/sync'); // triggers backup timer require('./services/backup'); diff --git a/src/becca/becca_loader.ts b/src/becca/becca_loader.ts index 507828ad5..b7ea941c8 100644 --- a/src/becca/becca_loader.ts +++ b/src/becca/becca_loader.ts @@ -20,7 +20,7 @@ const beccaLoaded = new Promise((res, rej) => { cls.init(() => { load(); - require('../services/options_init.js').initStartupOptions(); + require('../services/options_init').initStartupOptions(); res(); }); diff --git a/src/becca/entities/battribute.ts b/src/becca/entities/battribute.ts index b29d3e237..ebd9e74cf 100644 --- a/src/becca/entities/battribute.ts +++ b/src/becca/entities/battribute.ts @@ -222,7 +222,7 @@ class BAttribute extends AbstractBeccaEntity { }; } - createClone(type: AttributeType, name: string, value: string, isInheritable: boolean) { + createClone(type: AttributeType, name: string, value: string, isInheritable?: boolean) { return new BAttribute({ noteId: this.noteId, type: type, diff --git a/src/becca/entities/bbranch.ts b/src/becca/entities/bbranch.ts index 6b45a7e7a..677bc75ef 100644 --- a/src/becca/entities/bbranch.ts +++ b/src/becca/entities/bbranch.ts @@ -267,17 +267,19 @@ class BBranch extends AbstractBeccaEntity { }; } - createClone(parentNoteId: string, notePosition: number) { + createClone(parentNoteId: string, notePosition?: number) { const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId); if (existingBranch) { - existingBranch.notePosition = notePosition; + if (notePosition) { + existingBranch.notePosition = notePosition; + } return existingBranch; } else { return new BBranch({ noteId: this.noteId, parentNoteId: parentNoteId, - notePosition: notePosition, + notePosition: notePosition || null, prefix: this.prefix, isExpanded: this.isExpanded }); diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index cc251ffd0..c3f8339e9 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -78,13 +78,13 @@ class BNote extends AbstractBeccaEntity { // following attributes are filled during searching in the database /** size of the content in bytes */ - private contentSize!: number | null; + contentSize!: number | null; /** size of the note content, attachment contents in bytes */ - private contentAndAttachmentsSize!: number | null; + contentAndAttachmentsSize!: number | null; /** size of the note content, attachment contents and revision contents in bytes */ - private contentAndAttachmentsAndRevisionsSize!: number | null; + contentAndAttachmentsAndRevisionsSize!: number | null; /** number of note revisions for this note */ - private revisionCount!: number | null; + revisionCount!: number | null; constructor(row?: Partial) { super(); @@ -450,7 +450,7 @@ class BNote extends AbstractBeccaEntity { ); } - getAttributeCaseInsensitive(type: string, name: string, value: string | null) { + getAttributeCaseInsensitive(type: string, name: string, value?: string | null) { name = name.toLowerCase(); value = value ? value.toLowerCase() : null; diff --git a/src/becca/entities/rows.ts b/src/becca/entities/rows.ts index 4f2113f2e..a10c471e4 100644 --- a/src/becca/entities/rows.ts +++ b/src/becca/entities/rows.ts @@ -66,7 +66,7 @@ export type AttributeType = "label" | "relation"; export interface AttributeRow { attributeId?: string; - noteId: string; + noteId?: string; type: AttributeType; name: string; position?: number; @@ -80,7 +80,7 @@ export interface BranchRow { noteId: string; parentNoteId: string; prefix?: string | null; - notePosition: number | null; + notePosition?: number | null; isExpanded?: boolean; isDeleted?: boolean; utcDateModified?: string; @@ -106,4 +106,5 @@ export interface NoteRow { dateModified: string; utcDateCreated: string; utcDateModified: string; + content?: string; } diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index dca82b0ea..17ca00c07 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -1,7 +1,7 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); -const attributeService = require('../services/attributes.js'); +const attributeService = require('../services/attributes'); const v = require('./validators.js'); function register(router) { diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 0d96468d4..ebb12910b 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -5,8 +5,8 @@ const mappers = require('./mappers.js'); const noteService = require('../services/notes'); const TaskContext = require('../services/task_context'); const v = require('./validators.js'); -const searchService = require('../services/search/services/search.js'); -const SearchContext = require('../services/search/search_context.js'); +const searchService = require('../services/search/services/search'); +const SearchContext = require('../services/search/search_context'); const zipExportService = require('../services/export/zip.js'); const zipImportService = require('../services/import/zip.js'); diff --git a/src/etapi/special_notes.js b/src/etapi/special_notes.js index e6408b251..ac605ec67 100644 --- a/src/etapi/special_notes.js +++ b/src/etapi/special_notes.js @@ -1,5 +1,5 @@ const specialNotesService = require('../services/special_notes.js'); -const dateNotesService = require('../services/date_notes.js'); +const dateNotesService = require('../services/date_notes'); const eu = require('./etapi_utils'); const mappers = require('./mappers.js'); diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index de906a225..2c151fccf 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -2,7 +2,7 @@ const sql = require('../../services/sql'); const log = require('../../services/log'); -const attributeService = require('../../services/attributes.js'); +const attributeService = require('../../services/attributes'); const BAttribute = require('../../becca/entities/battribute'); const becca = require('../../becca/becca'); const ValidationError = require('../../errors/validation_error'); diff --git a/src/routes/api/autocomplete.js b/src/routes/api/autocomplete.js index 4d48709a0..9ed36f89e 100644 --- a/src/routes/api/autocomplete.js +++ b/src/routes/api/autocomplete.js @@ -1,7 +1,7 @@ "use strict"; const beccaService = require('../../becca/becca_service'); -const searchService = require('../../services/search/services/search.js'); +const searchService = require('../../services/search/services/search'); const log = require('../../services/log'); const utils = require('../../services/utils'); const cls = require('../../services/cls'); diff --git a/src/routes/api/branches.js b/src/routes/api/branches.js index ae06efdda..4638fb63e 100644 --- a/src/routes/api/branches.js +++ b/src/routes/api/branches.js @@ -3,11 +3,11 @@ const sql = require('../../services/sql'); const utils = require('../../services/utils'); const entityChangesService = require('../../services/entity_changes'); -const treeService = require('../../services/tree.js'); +const treeService = require('../../services/tree'); const eraseService = require('../../services/erase'); const becca = require('../../becca/becca'); const TaskContext = require('../../services/task_context'); -const branchService = require('../../services/branches.js'); +const branchService = require('../../services/branches'); const log = require('../../services/log'); const ValidationError = require('../../errors/validation_error'); const eventService = require("../../services/events"); diff --git a/src/routes/api/bulk_action.js b/src/routes/api/bulk_action.js index f04b32d61..27955cd35 100644 --- a/src/routes/api/bulk_action.js +++ b/src/routes/api/bulk_action.js @@ -1,5 +1,5 @@ const becca = require('../../becca/becca'); -const bulkActionService = require('../../services/bulk_actions.js'); +const bulkActionService = require('../../services/bulk_actions'); function execute(req) { const {noteIds, includeDescendants} = req.body; diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index b5cda8c31..54532a039 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -1,9 +1,9 @@ "use strict"; -const attributeService = require('../../services/attributes.js'); -const cloneService = require('../../services/cloning.js'); +const attributeService = require('../../services/attributes'); +const cloneService = require('../../services/cloning'); const noteService = require('../../services/notes'); -const dateNoteService = require('../../services/date_notes.js'); +const dateNoteService = require('../../services/date_notes'); const dateUtils = require('../../services/date_utils'); const imageService = require('../../services/image.js'); const appInfo = require('../../services/app_info'); diff --git a/src/routes/api/cloning.js b/src/routes/api/cloning.js index 384e76990..75a42e675 100644 --- a/src/routes/api/cloning.js +++ b/src/routes/api/cloning.js @@ -1,6 +1,6 @@ "use strict"; -const cloningService = require('../../services/cloning.js'); +const cloningService = require('../../services/cloning'); function cloneNoteToBranch(req) { const {noteId, parentBranchId} = req.params; diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 4f3f89176..8787acbf8 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -2,7 +2,7 @@ const noteService = require('../../services/notes'); const eraseService = require('../../services/erase'); -const treeService = require('../../services/tree.js'); +const treeService = require('../../services/tree'); const sql = require('../../services/sql'); const utils = require('../../services/utils'); const log = require('../../services/log'); diff --git a/src/routes/api/options.js b/src/routes/api/options.js index fd24422dc..88f72ae71 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -2,7 +2,7 @@ const optionService = require('../../services/options'); const log = require('../../services/log'); -const searchService = require('../../services/search/services/search.js'); +const searchService = require('../../services/search/services/search'); const ValidationError = require('../../errors/validation_error'); // options allowed to be updated directly in the Options dialog diff --git a/src/routes/api/script.js b/src/routes/api/script.js index 0dbb72f2c..f7903f411 100644 --- a/src/routes/api/script.js +++ b/src/routes/api/script.js @@ -1,9 +1,9 @@ "use strict"; const scriptService = require('../../services/script.js'); -const attributeService = require('../../services/attributes.js'); +const attributeService = require('../../services/attributes'); const becca = require('../../becca/becca'); -const syncService = require('../../services/sync.js'); +const syncService = require('../../services/sync'); const sql = require('../../services/sql'); // The async/await here is very confusing, because the body.script may, but may not be async. If it is async, then we diff --git a/src/routes/api/search.js b/src/routes/api/search.js index a582dafcd..96428c6af 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -1,9 +1,9 @@ "use strict"; const becca = require('../../becca/becca'); -const SearchContext = require('../../services/search/search_context.js'); -const searchService = require('../../services/search/services/search.js'); -const bulkActionService = require('../../services/bulk_actions.js'); +const SearchContext = require('../../services/search/search_context'); +const searchService = require('../../services/search/services/search'); +const bulkActionService = require('../../services/bulk_actions'); const cls = require('../../services/cls'); const {formatAttrForSearch} = require('../../services/attribute_formatter'); const ValidationError = require('../../errors/validation_error'); diff --git a/src/routes/api/special_notes.js b/src/routes/api/special_notes.js index 9c80f507f..76f41a01f 100644 --- a/src/routes/api/special_notes.js +++ b/src/routes/api/special_notes.js @@ -1,6 +1,6 @@ "use strict"; -const dateNoteService = require('../../services/date_notes.js'); +const dateNoteService = require('../../services/date_notes'); const sql = require('../../services/sql'); const cls = require('../../services/cls'); const specialNotesService = require('../../services/special_notes.js'); diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js index 68ac0a6e8..c1939552e 100644 --- a/src/routes/api/sync.js +++ b/src/routes/api/sync.js @@ -1,12 +1,12 @@ "use strict"; -const syncService = require('../../services/sync.js'); -const syncUpdateService = require('../../services/sync_update.js'); +const syncService = require('../../services/sync'); +const syncUpdateService = require('../../services/sync_update'); const entityChangesService = require('../../services/entity_changes'); const sql = require('../../services/sql'); const sqlInit = require('../../services/sql_init'); const optionService = require('../../services/options'); -const contentHashService = require('../../services/content_hash.js'); +const contentHashService = require('../../services/content_hash'); const log = require('../../services/log'); const syncOptions = require('../../services/sync_options'); const utils = require('../../services/utils'); diff --git a/src/routes/index.js b/src/routes/index.js index c9c0a33de..44c609834 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,7 +1,7 @@ "use strict"; const sql = require('../services/sql'); -const attributeService = require('../services/attributes.js'); +const attributeService = require('../services/attributes'); const config = require('../services/config'); const optionService = require('../services/options'); const log = require('../services/log'); diff --git a/src/routes/routes.js b/src/routes/routes.js index 2f2bdcb7d..430c2bb25 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -27,12 +27,12 @@ const notesApiRoute = require('./api/notes.js'); const branchesApiRoute = require('./api/branches.js'); const attachmentsApiRoute = require('./api/attachments.js'); const autocompleteApiRoute = require('./api/autocomplete.js'); -const cloningApiRoute = require('./api/cloning.js'); +const cloningApiRoute = require('./api/cloning'); const revisionsApiRoute = require('./api/revisions'); const recentChangesApiRoute = require('./api/recent_changes.js'); const optionsApiRoute = require('./api/options.js'); const passwordApiRoute = require('./api/password'); -const syncApiRoute = require('./api/sync.js'); +const syncApiRoute = require('./api/sync'); const loginApiRoute = require('./api/login.js'); const recentNotesRoute = require('./api/recent_notes.js'); const appInfoRoute = require('./api/app_info'); @@ -42,11 +42,11 @@ const setupApiRoute = require('./api/setup.js'); const sqlRoute = require('./api/sql'); const databaseRoute = require('./api/database.js'); const imageRoute = require('./api/image.js'); -const attributesRoute = require('./api/attributes.js'); +const attributesRoute = require('./api/attributes'); const scriptRoute = require('./api/script.js'); const senderRoute = require('./api/sender.js'); const filesRoute = require('./api/files.js'); -const searchRoute = require('./api/search.js'); +const searchRoute = require('./api/search'); const bulkActionRoute = require('./api/bulk_action.js'); const specialNotesRoute = require('./api/special_notes.js'); const noteMapRoute = require('./api/note_map.js'); @@ -64,7 +64,7 @@ const shareRoutes = require('../share/routes.js'); const etapiAuthRoutes = require('../etapi/auth.js'); const etapiAppInfoRoutes = require('../etapi/app_info'); const etapiAttachmentRoutes = require('../etapi/attachments.js'); -const etapiAttributeRoutes = require('../etapi/attributes.js'); +const etapiAttributeRoutes = require('../etapi/attributes'); const etapiBranchRoutes = require('../etapi/branches.js'); const etapiNoteRoutes = require('../etapi/notes.js'); const etapiSpecialNoteRoutes = require('../etapi/special_notes.js'); diff --git a/src/routes/session_parser.js b/src/routes/session_parser.js index a2e62e851..afe99fa2f 100644 --- a/src/routes/session_parser.js +++ b/src/routes/session_parser.js @@ -1,5 +1,5 @@ const session = require("express-session"); -const sessionSecret = require('../services/session_secret.js'); +const sessionSecret = require('../services/session_secret'); const dataDir = require('../services/data_dir'); const FileStore = require('session-file-store')(session); diff --git a/src/routes/setup.js b/src/routes/setup.js index d3af71bef..0a1dca715 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -9,7 +9,7 @@ const appPath = require('../services/app_path'); function setupPage(req, res) { if (sqlInit.isDbInitialized()) { if (utils.isElectron()) { - const windowService = require('../services/window.js'); + const windowService = require('../services/window'); const {app} = require('electron'); windowService.createMainWindow(app); windowService.closeSetupWindow(); diff --git a/src/services/attribute_formatter.ts b/src/services/attribute_formatter.ts index 846450f36..a7b4b7500 100644 --- a/src/services/attribute_formatter.ts +++ b/src/services/attribute_formatter.ts @@ -1,8 +1,8 @@ "use strict"; -import BAttribute = require("../becca/entities/battribute"); +import { AttributeRow } from "../becca/entities/rows"; -function formatAttrForSearch(attr: BAttribute, searchWithValue: string) { +function formatAttrForSearch(attr: AttributeRow, searchWithValue: boolean) { let searchStr = ''; if (attr.type === 'label') { diff --git a/src/services/attributes.js b/src/services/attributes.ts similarity index 67% rename from src/services/attributes.js rename to src/services/attributes.ts index fc527429c..0ae77dc36 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.ts @@ -1,17 +1,18 @@ "use strict"; -const searchService = require('./search/services/search.js'); -const sql = require('./sql'); -const becca = require('../becca/becca'); -const BAttribute = require('../becca/entities/battribute'); -const {formatAttrForSearch} = require('./attribute_formatter'); -const BUILTIN_ATTRIBUTES = require('./builtin_attributes'); +import searchService = require('./search/services/search'); +import sql = require('./sql'); +import becca = require('../becca/becca'); +import BAttribute = require('../becca/entities/battribute'); +import attributeFormatter = require('./attribute_formatter'); +import BUILTIN_ATTRIBUTES = require('./builtin_attributes'); +import BNote = require('../becca/entities/bnote'); +import { AttributeRow } from '../becca/entities/rows'; const ATTRIBUTE_TYPES = ['label', 'relation']; -/** @returns {BNote[]} */ -function getNotesWithLabel(name, value = undefined) { - const query = formatAttrForSearch({type: 'label', name, value}, value !== undefined); +function getNotesWithLabel(name: string, value?: string): BNote[] { + const query = attributeFormatter.formatAttrForSearch({type: 'label', name, value}, value !== undefined); return searchService.searchNotes(query, { includeArchivedNotes: true, ignoreHoistedNote: true @@ -19,8 +20,7 @@ function getNotesWithLabel(name, value = undefined) { } // TODO: should be in search service -/** @returns {BNote|null} */ -function getNoteWithLabel(name, value = undefined) { +function getNoteWithLabel(name: string, value?: string): BNote | null { // optimized version (~20 times faster) without using normal search, useful for e.g., finding date notes const attrs = becca.findAttributes('label', name); @@ -39,7 +39,7 @@ function getNoteWithLabel(name, value = undefined) { return null; } -function createLabel(noteId, name, value = "") { +function createLabel(noteId: string, name: string, value: string = "") { return createAttribute({ noteId: noteId, type: 'label', @@ -48,7 +48,7 @@ function createLabel(noteId, name, value = "") { }); } -function createRelation(noteId, name, targetNoteId) { +function createRelation(noteId: string, name: string, targetNoteId: string) { return createAttribute({ noteId: noteId, type: 'relation', @@ -57,14 +57,14 @@ function createRelation(noteId, name, targetNoteId) { }); } -function createAttribute(attribute) { +function createAttribute(attribute: AttributeRow) { return new BAttribute(attribute).save(); } -function getAttributeNames(type, nameLike) { +function getAttributeNames(type: string, nameLike: string) { nameLike = nameLike.toLowerCase(); - let names = sql.getColumn( + let names = sql.getColumn( `SELECT DISTINCT name FROM attributes WHERE isDeleted = 0 @@ -98,11 +98,11 @@ function getAttributeNames(type, nameLike) { return names; } -function isAttributeType(type) { +function isAttributeType(type: string): boolean { return ATTRIBUTE_TYPES.includes(type); } -function isAttributeDangerous(type, name) { +function isAttributeDangerous(type: string, name: string): boolean { return BUILTIN_ATTRIBUTES.some(attr => attr.type === type && attr.name.toLowerCase() === name.trim().toLowerCase() && @@ -110,7 +110,7 @@ function isAttributeDangerous(type, name) { ); } -module.exports = { +export = { getNotesWithLabel, getNoteWithLabel, createLabel, diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index d8687b944..e86efa8a9 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -2,22 +2,22 @@ const log = require('./log'); const noteService = require('./notes'); const sql = require('./sql'); const utils = require('./utils'); -const attributeService = require('./attributes.js'); -const dateNoteService = require('./date_notes.js'); -const treeService = require('./tree.js'); +const attributeService = require('./attributes'); +const dateNoteService = require('./date_notes'); +const treeService = require('./tree'); const config = require('./config'); const axios = require('axios'); const dayjs = require('dayjs'); const xml2js = require('xml2js'); -const cloningService = require('./cloning.js'); +const cloningService = require('./cloning'); const appInfo = require('./app_info'); -const searchService = require('./search/services/search.js'); -const SearchContext = require('./search/search_context.js'); +const searchService = require('./search/services/search'); +const SearchContext = require('./search/search_context'); const becca = require('../becca/becca'); const ws = require('./ws'); const SpacedUpdate = require('./spaced_update.js'); const specialNotesService = require('./special_notes.js'); -const branchService = require('./branches.js'); +const branchService = require('./branches'); const exportService = require('./export/zip.js'); const syncMutex = require('./sync_mutex'); const backupService = require('./backup'); diff --git a/src/services/branches.js b/src/services/branches.ts similarity index 61% rename from src/services/branches.js rename to src/services/branches.ts index ec0632745..7ee32c949 100644 --- a/src/services/branches.js +++ b/src/services/branches.ts @@ -1,7 +1,8 @@ -const treeService = require('./tree.js'); -const sql = require('./sql'); +import treeService = require('./tree'); +import sql = require('./sql'); +import BBranch = require('../becca/entities/bbranch.js'); -function moveBranchToNote(branchToMove, targetParentNoteId) { +function moveBranchToNote(branchToMove: BBranch, targetParentNoteId: string) { if (branchToMove.parentNoteId === targetParentNoteId) { return {success: true}; // no-op } @@ -12,8 +13,8 @@ function moveBranchToNote(branchToMove, targetParentNoteId) { return [200, validationResult]; } - const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [targetParentNoteId]); - const newNotePos = maxNotePos === null ? 0 : maxNotePos + 10; + const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [targetParentNoteId]); + const newNotePos = !maxNotePos ? 0 : maxNotePos + 10; const newBranch = branchToMove.createClone(targetParentNoteId, newNotePos); newBranch.save(); @@ -26,10 +27,10 @@ function moveBranchToNote(branchToMove, targetParentNoteId) { }; } -function moveBranchToBranch(branchToMove, targetParentBranch) { +function moveBranchToBranch(branchToMove: BBranch, targetParentBranch: BBranch) { const res = moveBranchToNote(branchToMove, targetParentBranch.noteId); - if (!res.success) { + if (!("success" in res) || !res.success) { return res; } @@ -42,7 +43,7 @@ function moveBranchToBranch(branchToMove, targetParentBranch) { return res; } -module.exports = { +export = { moveBranchToBranch, moveBranchToNote }; diff --git a/src/services/bulk_actions.js b/src/services/bulk_actions.ts similarity index 80% rename from src/services/bulk_actions.js rename to src/services/bulk_actions.ts index 136d1cccf..c91b1c0c9 100644 --- a/src/services/bulk_actions.js +++ b/src/services/bulk_actions.ts @@ -1,12 +1,30 @@ -const log = require('./log'); -const revisionService = require('./revisions'); -const becca = require('../becca/becca'); -const cloningService = require('./cloning.js'); -const branchService = require('./branches.js'); -const utils = require('./utils'); -const eraseService = require("./erase"); +import log = require('./log'); +import becca = require('../becca/becca'); +import cloningService = require('./cloning'); +import branchService = require('./branches'); +import utils = require('./utils'); +import eraseService = require("./erase"); +import BNote = require('../becca/entities/bnote'); -const ACTION_HANDLERS = { +interface Action { + labelName: string; + labelValue: string; + oldLabelName: string; + newLabelName: string; + + + relationName: string; + oldRelationName: string; + newRelationName: string; + + targetNoteId: string; + targetParentNoteId: string; + newTitle: string; + script: string; +} +type ActionHandler = (action: Action, note: BNote) => void; + +const ACTION_HANDLERS: Record = { addLabel: (action, note) => { note.addLabel(action.labelName, action.labelValue); }, @@ -19,7 +37,10 @@ const ACTION_HANDLERS = { note.deleteNote(deleteId); }, deleteRevisions: (action, note) => { - eraseService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId)); + const revisionIds = note.getRevisions() + .map(rev => rev.revisionId) + .filter((rev) => !!rev) as string[]; + eraseService.eraseRevisions(revisionIds); }, deleteLabel: (action, note) => { for (const label of note.getOwnedLabels(action.labelName)) { @@ -107,7 +128,7 @@ const ACTION_HANDLERS = { } }; -function getActions(note) { +function getActions(note: BNote) { return note.getLabels('action') .map(actionLabel => { let action; @@ -129,7 +150,7 @@ function getActions(note) { .filter(a => !!a); } -function executeActions(note, searchResultNoteIds) { +function executeActions(note: BNote, searchResultNoteIds: string[]) { const actions = getActions(note); for (const resultNoteId of searchResultNoteIds) { @@ -144,13 +165,13 @@ function executeActions(note, searchResultNoteIds) { log.info(`Applying action handler to note ${resultNote.noteId}: ${JSON.stringify(action)}`); ACTION_HANDLERS[action.name](action, resultNote); - } catch (e) { + } catch (e: any) { log.error(`ExecuteScript search action failed with ${e.message}`); } } } } -module.exports = { +export = { executeActions }; diff --git a/src/services/cloning.js b/src/services/cloning.ts similarity index 90% rename from src/services/cloning.js rename to src/services/cloning.ts index a42df4ba8..bb21425bc 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.ts @@ -2,12 +2,12 @@ const sql = require('./sql'); const eventChangesService = require('./entity_changes'); -const treeService = require('./tree.js'); +const treeService = require('./tree'); const BBranch = require('../becca/entities/bbranch'); const becca = require('../becca/becca'); const log = require('./log'); -function cloneNoteToParentNote(noteId, parentNoteId, prefix = null) { +function cloneNoteToParentNote(noteId: string, parentNoteId: string, prefix: string | null = null) { if (!(noteId in becca.notes) || !(parentNoteId in becca.notes)) { return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; } @@ -43,7 +43,7 @@ function cloneNoteToParentNote(noteId, parentNoteId, prefix = null) { }; } -function cloneNoteToBranch(noteId, parentBranchId, prefix) { +function cloneNoteToBranch(noteId: string, parentBranchId: string, prefix: string) { const parentBranch = becca.getBranch(parentBranchId); if (!parentBranch) { @@ -58,7 +58,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { return ret; } -function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { +function ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string) { if (!(noteId in becca.notes)) { return { branch: null, success: false, message: `Note '${noteId}' is deleted.` }; } else if (!(parentNoteId in becca.notes)) { @@ -89,7 +89,7 @@ function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { return { branch: branch, success: true }; } -function ensureNoteIsAbsentFromParent(noteId, parentNoteId) { +function ensureNoteIsAbsentFromParent(noteId: string, parentNoteId: string) { const branchId = sql.getValue(`SELECT branchId FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]); const branch = becca.getBranch(branchId); @@ -109,7 +109,7 @@ function ensureNoteIsAbsentFromParent(noteId, parentNoteId) { } } -function toggleNoteInParent(present, noteId, parentNoteId, prefix) { +function toggleNoteInParent(present: boolean, noteId: string, parentNoteId: string, prefix: string) { if (present) { return ensureNoteIsPresentInParent(noteId, parentNoteId, prefix); } @@ -118,7 +118,7 @@ function toggleNoteInParent(present, noteId, parentNoteId, prefix) { } } -function cloneNoteAfter(noteId, afterBranchId) { +function cloneNoteAfter(noteId: string, afterBranchId: string) { if (['_hidden', 'root'].includes(noteId)) { return { success: false, message: `Cloning the note '${noteId}' is forbidden.` }; } @@ -175,7 +175,7 @@ function cloneNoteAfter(noteId, afterBranchId) { return { success: true, branchId: branch.branchId }; } -module.exports = { +export = { cloneNoteToBranch, cloneNoteToParentNote, ensureNoteIsPresentInParent, diff --git a/src/services/content_hash.js b/src/services/content_hash.ts similarity index 79% rename from src/services/content_hash.js rename to src/services/content_hash.ts index 24fbfcfaa..2067e177f 100644 --- a/src/services/content_hash.js +++ b/src/services/content_hash.ts @@ -1,9 +1,11 @@ "use strict"; -const sql = require('./sql'); -const utils = require('./utils'); -const log = require('./log'); -const eraseService = require('./erase'); +import sql = require('./sql'); +import utils = require('./utils'); +import log = require('./log'); +import eraseService = require('./erase'); + +type SectorHash = Record; function getEntityHashes() { // blob erasure is not synced, we should check before each sync if there's some blob to erase @@ -12,8 +14,9 @@ function getEntityHashes() { const startTime = new Date(); // we know this is slow and the total content hash calculation time is logged + type HashRow = [ string, string, string, boolean ]; const hashRows = sql.disableSlowQueryLogging( - () => sql.getRawRows(` + () => sql.getRawRows(` SELECT entityName, entityId, hash, @@ -27,7 +30,7 @@ function getEntityHashes() { // sorting by entityId is enough, hashes will be segmented by entityName later on anyway hashRows.sort((a, b) => a[1] < b[1] ? -1 : 1); - const hashMap = {}; + const hashMap: Record = {}; for (const [entityName, entityId, hash, isErased] of hashRows) { const entityHashMap = hashMap[entityName] = hashMap[entityName] || {}; @@ -51,13 +54,13 @@ function getEntityHashes() { return hashMap; } -function checkContentHashes(otherHashes) { +function checkContentHashes(otherHashes: Record) { const entityHashes = getEntityHashes(); const failedChecks = []; for (const entityName in entityHashes) { - const thisSectorHashes = entityHashes[entityName] || {}; - const otherSectorHashes = otherHashes[entityName] || {}; + const thisSectorHashes: SectorHash = entityHashes[entityName] || {}; + const otherSectorHashes: SectorHash = otherHashes[entityName] || {}; const sectors = new Set(Object.keys(thisSectorHashes).concat(Object.keys(otherSectorHashes))); @@ -77,7 +80,7 @@ function checkContentHashes(otherHashes) { return failedChecks; } -module.exports = { +export = { getEntityHashes, checkContentHashes }; diff --git a/src/services/date_notes.js b/src/services/date_notes.ts similarity index 78% rename from src/services/date_notes.js rename to src/services/date_notes.ts index 628ed886f..5ae1f8366 100644 --- a/src/services/date_notes.js +++ b/src/services/date_notes.ts @@ -1,13 +1,14 @@ "use strict"; -const noteService = require('./notes'); -const attributeService = require('./attributes.js'); -const dateUtils = require('./date_utils'); -const sql = require('./sql'); -const protectedSessionService = require('./protected_session'); -const searchService = require('../services/search/services/search.js'); -const SearchContext = require('../services/search/search_context.js'); -const hoistedNoteService = require('./hoisted_note.js'); +import noteService = require('./notes'); +import attributeService = require('./attributes'); +import dateUtils = require('./date_utils'); +import sql = require('./sql'); +import protectedSessionService = require('./protected_session'); +import searchService = require('../services/search/services/search'); +import SearchContext = require('../services/search/search_context'); +import hoistedNoteService = require('./hoisted_note'); +import BNote = require('../becca/entities/bnote'); const CALENDAR_ROOT_LABEL = 'calendarRoot'; const YEAR_LABEL = 'yearNote'; @@ -17,7 +18,9 @@ const DATE_LABEL = 'dateNote'; const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; -function createNote(parentNote, noteTitle) { +type StartOfWeek = "monday" | "sunday"; + +function createNote(parentNote: BNote, noteTitle: string) { return noteService.createNewNote({ parentNoteId: parentNote.noteId, title: noteTitle, @@ -27,13 +30,12 @@ function createNote(parentNote, noteTitle) { }).note; } -/** @returns {BNote} */ -function getRootCalendarNote() { +function getRootCalendarNote(): BNote { let rootNote; const workspaceNote = hoistedNoteService.getWorkspaceNote(); - if (!workspaceNote.isRoot()) { + if (!workspaceNote || !workspaceNote.isRoot()) { rootNote = searchService.findFirstNoteWithQuery('#workspaceCalendarRoot', new SearchContext({ignoreHoistedNote: false})); } @@ -57,14 +59,11 @@ function getRootCalendarNote() { }); } - return rootNote; + return rootNote as BNote; } -/** @returns {BNote} */ -function getYearNote(dateStr, rootNote = null) { - if (!rootNote) { - rootNote = getRootCalendarNote(); - } +function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote { + const rootNote = _rootNote || getRootCalendarNote(); const yearStr = dateStr.trim().substr(0, 4); @@ -88,10 +87,10 @@ function getYearNote(dateStr, rootNote = null) { } }); - return yearNote; + return yearNote as unknown as BNote; } -function getMonthNoteTitle(rootNote, monthNumber, dateObj) { +function getMonthNoteTitle(rootNote: BNote, monthNumber: string, dateObj: Date) { const pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}"; const monthName = MONTHS[dateObj.getMonth()]; @@ -102,11 +101,8 @@ function getMonthNoteTitle(rootNote, monthNumber, dateObj) { .replace(/{month}/g, monthName); } -/** @returns {BNote} */ -function getMonthNote(dateStr, rootNote = null) { - if (!rootNote) { - rootNote = getRootCalendarNote(); - } +function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote { + const rootNote = _rootNote || getRootCalendarNote(); const monthStr = dateStr.substr(0, 7); const monthNumber = dateStr.substr(5, 2); @@ -137,10 +133,10 @@ function getMonthNote(dateStr, rootNote = null) { } }); - return monthNote; + return monthNote as unknown as BNote; } -function getDayNoteTitle(rootNote, dayNumber, dateObj) { +function getDayNoteTitle(rootNote: BNote, dayNumber: string, dateObj: Date) { const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}"; const weekDay = DAYS[dateObj.getDay()]; @@ -154,18 +150,15 @@ function getDayNoteTitle(rootNote, dayNumber, dateObj) { } /** produces 1st, 2nd, 3rd, 4th, 21st, 31st for 1, 2, 3, 4, 21, 31 */ -function ordinal(dayNumber) { +function ordinal(dayNumber: number) { const suffixes = ["th", "st", "nd", "rd"]; const suffix = suffixes[(dayNumber - 20) % 10] || suffixes[dayNumber] || suffixes[0]; return `${dayNumber}${suffix}`; } -/** @returns {BNote} */ -function getDayNote(dateStr, rootNote = null) { - if (!rootNote) { - rootNote = getRootCalendarNote(); - } +function getDayNote(dateStr: string, _rootNote: BNote | null = null): BNote { + const rootNote = _rootNote || getRootCalendarNote(); dateStr = dateStr.trim().substr(0, 10); @@ -195,14 +188,14 @@ function getDayNote(dateStr, rootNote = null) { } }); - return dateNote; + return dateNote as unknown as BNote; } function getTodayNote(rootNote = null) { return getDayNote(dateUtils.localNowDate(), rootNote); } -function getStartOfTheWeek(date, startOfTheWeek) { +function getStartOfTheWeek(date: Date, startOfTheWeek: StartOfWeek) { const day = date.getDay(); let diff; @@ -219,7 +212,11 @@ function getStartOfTheWeek(date, startOfTheWeek) { return new Date(date.setDate(diff)); } -function getWeekNote(dateStr, options = {}, rootNote = null) { +interface WeekNoteOpts { + startOfTheWeek?: StartOfWeek +} + +function getWeekNote(dateStr: string, options: WeekNoteOpts = {}, rootNote = null) { const startOfTheWeek = options.startOfTheWeek || "monday"; const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek); @@ -229,7 +226,7 @@ function getWeekNote(dateStr, options = {}, rootNote = null) { return getDayNote(dateStr, rootNote); } -module.exports = { +export = { getRootCalendarNote, getYearNote, getMonthNote, diff --git a/src/services/entity_changes_interface.ts b/src/services/entity_changes_interface.ts index 745a8a8fa..0276e24a0 100644 --- a/src/services/entity_changes_interface.ts +++ b/src/services/entity_changes_interface.ts @@ -12,4 +12,14 @@ export interface EntityChange { componentId?: string | null; changeId?: string | null; instanceId?: string | null; -} \ No newline at end of file +} + +export interface EntityRow { + isDeleted?: boolean; + content?: Buffer | string; +} + +export interface EntityChangeRecord { + entityChange: EntityChange; + entity?: EntityRow; +} diff --git a/src/services/handlers.js b/src/services/handlers.js index a24e28dd6..11c9ef5fe 100644 --- a/src/services/handlers.js +++ b/src/services/handlers.js @@ -1,11 +1,11 @@ const eventService = require('./events'); const scriptService = require('./script.js'); -const treeService = require('./tree.js'); +const treeService = require('./tree'); const noteService = require('./notes'); const becca = require('../becca/becca'); const BAttribute = require('../becca/entities/battribute'); const hiddenSubtreeService = require('./hidden_subtree'); -const oneTimeTimer = require('./one_time_timer.js'); +const oneTimeTimer = require('./one_time_timer'); function runAttachedRelations(note, relationName, originEntity) { if (!note) { diff --git a/src/services/hoisted_note.js b/src/services/hoisted_note.ts similarity index 81% rename from src/services/hoisted_note.js rename to src/services/hoisted_note.ts index a75d4addc..fca423269 100644 --- a/src/services/hoisted_note.js +++ b/src/services/hoisted_note.ts @@ -1,5 +1,5 @@ -const cls = require('./cls'); -const becca = require('../becca/becca'); +import cls = require('./cls'); +import becca = require('../becca/becca'); function getHoistedNoteId() { return cls.getHoistedNoteId(); @@ -26,14 +26,14 @@ function isHoistedInHiddenSubtree() { function getWorkspaceNote() { const hoistedNote = becca.getNote(cls.getHoistedNoteId()); - if (hoistedNote.isRoot() || hoistedNote.hasLabel('workspace')) { + if (hoistedNote && (hoistedNote.isRoot() || hoistedNote.hasLabel('workspace'))) { return hoistedNote; } else { return becca.getRoot(); } } -module.exports = { +export = { getHoistedNoteId, getWorkspaceNote, isHoistedInHiddenSubtree diff --git a/src/services/host.js b/src/services/host.js deleted file mode 100644 index 389203e99..000000000 --- a/src/services/host.js +++ /dev/null @@ -1,3 +0,0 @@ -const config = require('./config'); - -module.exports = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0'; diff --git a/src/services/host.ts b/src/services/host.ts new file mode 100644 index 000000000..3daef1504 --- /dev/null +++ b/src/services/host.ts @@ -0,0 +1,3 @@ +import config = require('./config'); + +export = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0'; diff --git a/src/services/import/zip.js b/src/services/import/zip.js index ee7a828d1..01bc8b50c 100644 --- a/src/services/import/zip.js +++ b/src/services/import/zip.js @@ -4,12 +4,12 @@ const BAttribute = require('../../becca/entities/battribute'); const utils = require('../../services/utils'); const log = require('../../services/log'); const noteService = require('../../services/notes'); -const attributeService = require('../../services/attributes.js'); +const attributeService = require('../../services/attributes'); const BBranch = require('../../becca/entities/bbranch'); const path = require('path'); const protectedSessionService = require('../protected_session'); const mimeService = require('./mime.js'); -const treeService = require('../tree.js'); +const treeService = require('../tree'); const yauzl = require("yauzl"); const htmlSanitizer = require('../html_sanitizer'); const becca = require('../../becca/becca'); diff --git a/src/services/keyboard_actions.ts b/src/services/keyboard_actions.ts index b2cbaad29..715c62684 100644 --- a/src/services/keyboard_actions.ts +++ b/src/services/keyboard_actions.ts @@ -3,19 +3,11 @@ import optionService = require('./options'); import log = require('./log'); import utils = require('./utils'); +import { KeyboardShortcut } from './keyboard_actions_interface'; const isMac = process.platform === "darwin"; const isElectron = utils.isElectron(); -interface KeyboardShortcut { - separator?: string; - actionName?: string; - description?: string; - defaultShortcuts?: string[]; - effectiveShortcuts?: string[]; - scope?: string; -} - /** * Scope here means on which element the keyboard shortcuts are attached - this means that for the shortcut to work, * the focus has to be inside the element. diff --git a/src/services/keyboard_actions_interface.ts b/src/services/keyboard_actions_interface.ts new file mode 100644 index 000000000..3e3621ecd --- /dev/null +++ b/src/services/keyboard_actions_interface.ts @@ -0,0 +1,12 @@ +export interface KeyboardShortcut { + separator?: string; + actionName?: string; + description?: string; + defaultShortcuts?: string[]; + effectiveShortcuts?: string[]; + scope?: string; +} + +export interface KeyboardShortcutWithRequiredActionName extends KeyboardShortcut { + actionName: string; +} \ No newline at end of file diff --git a/src/services/notes.ts b/src/services/notes.ts index a402a2f16..f9c80031d 100644 --- a/src/services/notes.ts +++ b/src/services/notes.ts @@ -173,6 +173,7 @@ interface NoteParams { dateCreated?: string; utcDateCreated?: string; ignoreForbiddenParents?: boolean; + target?: "into"; } function createNewNote(params: NoteParams): { diff --git a/src/services/one_time_timer.js b/src/services/one_time_timer.ts similarity index 77% rename from src/services/one_time_timer.js rename to src/services/one_time_timer.ts index 648c250bb..033a8b3c1 100644 --- a/src/services/one_time_timer.js +++ b/src/services/one_time_timer.ts @@ -1,4 +1,4 @@ -const scheduledExecutions = {}; +const scheduledExecutions: Record = {}; /** * Subsequent calls will not move the timer to the future. The first caller determines the time of execution. @@ -6,7 +6,7 @@ const scheduledExecutions = {}; * The good thing about synchronous better-sqlite3 is that this cannot interrupt transaction. The execution will be called * only outside of a transaction. */ -function scheduleExecution(name, milliseconds, cb) { +function scheduleExecution(name: string, milliseconds: number, cb: () => void) { if (name in scheduledExecutions) { return; } @@ -20,6 +20,6 @@ function scheduleExecution(name, milliseconds, cb) { }, milliseconds); } -module.exports = { +export = { scheduleExecution }; diff --git a/src/services/options_init.js b/src/services/options_init.ts similarity index 89% rename from src/services/options_init.js rename to src/services/options_init.ts index d601353af..e2ba1520a 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.ts @@ -1,16 +1,22 @@ -const optionService = require('./options'); -const appInfo = require('./app_info'); -const utils = require('./utils'); -const log = require('./log'); -const dateUtils = require('./date_utils'); -const keyboardActions = require('./keyboard_actions'); +import optionService = require('./options'); +import appInfo = require('./app_info'); +import utils = require('./utils'); +import log = require('./log'); +import dateUtils = require('./date_utils'); +import keyboardActions = require('./keyboard_actions'); +import { KeyboardShortcutWithRequiredActionName } from './keyboard_actions_interface'; function initDocumentOptions() { optionService.createOption('documentId', utils.randomSecureToken(16), false); optionService.createOption('documentSecret', utils.randomSecureToken(16), false); } -function initNotSyncedOptions(initialized, opts = {}) { +interface NotSyncedOpts { + syncServerHost?: string; + syncProxy?: string; +} + +function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = {}) { optionService.createOption('openNoteContexts', JSON.stringify([ { notePath: 'root', @@ -21,7 +27,7 @@ function initNotSyncedOptions(initialized, opts = {}) { optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false); optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false); optionService.createOption('lastMonthlyBackupDate', dateUtils.utcNowDateTime(), false); - optionService.createOption('dbVersion', appInfo.dbVersion, false); + optionService.createOption('dbVersion', appInfo.dbVersion.toString(), false); optionService.createOption('initialized', initialized ? 'true' : 'false', false); @@ -117,8 +123,8 @@ function initStartupOptions() { } function getKeyboardDefaultOptions() { - return keyboardActions.DEFAULT_KEYBOARD_ACTIONS - .filter(ka => !!ka.actionName) + return (keyboardActions.DEFAULT_KEYBOARD_ACTIONS + .filter(ka => !!ka.actionName) as KeyboardShortcutWithRequiredActionName[]) .map(ka => ({ name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`, value: JSON.stringify(ka.defaultShortcuts), @@ -126,7 +132,7 @@ function getKeyboardDefaultOptions() { })); } -module.exports = { +export = { initDocumentOptions, initNotSyncedOptions, initStartupOptions diff --git a/src/services/request.ts b/src/services/request.ts index ad1e6b5c8..9922e4b2d 100644 --- a/src/services/request.ts +++ b/src/services/request.ts @@ -4,29 +4,11 @@ import utils = require('./utils'); import log = require('./log'); import url = require('url'); import syncOptions = require('./sync_options'); +import { ExecOpts } from './request_interface'; // this service provides abstraction over node's HTTP/HTTPS and electron net.client APIs // this allows supporting system proxy -interface ExecOpts { - proxy: "noproxy" | null; - method: string; - url: string; - paging?: { - pageCount: number; - pageIndex: number; - requestId: string; - }; - cookieJar?: { - header?: string; - }; - auth?: { - password?: string; - }, - timeout: number; - body: string; -} - interface ClientOpts { method: string; url: string; @@ -230,7 +212,7 @@ function getClient(opts: ClientOpts): Client { // it's not clear how to explicitly configure proxy (as opposed to system proxy), // so in that case, we always use node's modules if (utils.isElectron() && !opts.proxy) { - return require('electron').net; + return require('electron').net as Client; } else { const {protocol} = url.parse(opts.url); diff --git a/src/services/request_interface.ts b/src/services/request_interface.ts new file mode 100644 index 000000000..4ba1da0a7 --- /dev/null +++ b/src/services/request_interface.ts @@ -0,0 +1,20 @@ +export interface CookieJar { + header?: string; +} + +export interface ExecOpts { + proxy: "noproxy" | null; + method: string; + url: string; + paging?: { + pageCount: number; + pageIndex: number; + requestId: string; + }; + cookieJar?: CookieJar; + auth?: { + password?: string; + }, + timeout: number; + body: string; +} \ No newline at end of file diff --git a/src/services/scheduler.js b/src/services/scheduler.js index d4f40cfd7..aa0a3dfcd 100644 --- a/src/services/scheduler.js +++ b/src/services/scheduler.js @@ -3,7 +3,7 @@ const cls = require('./cls'); const sqlInit = require('./sql_init'); const config = require('./config'); const log = require('./log'); -const attributeService = require('../services/attributes.js'); +const attributeService = require('../services/attributes'); const protectedSessionService = require('../services/protected_session'); const hiddenSubtreeService = require('./hidden_subtree'); diff --git a/src/services/search/expressions/ancestor.js b/src/services/search/expressions/ancestor.ts similarity index 74% rename from src/services/search/expressions/ancestor.js rename to src/services/search/expressions/ancestor.ts index 5275492a1..4e7380795 100644 --- a/src/services/search/expressions/ancestor.js +++ b/src/services/search/expressions/ancestor.ts @@ -1,12 +1,19 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const log = require('../../log'); -const becca = require('../../../becca/becca'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import log = require('../../log'); +import becca = require('../../../becca/becca'); +import SearchContext = require('../search_context'); class AncestorExp extends Expression { - constructor(ancestorNoteId, ancestorDepth) { + + private ancestorNoteId: string; + private ancestorDepthComparator; + + ancestorDepth?: string; + + constructor(ancestorNoteId: string, ancestorDepth?: string) { super(); this.ancestorNoteId = ancestorNoteId; @@ -14,7 +21,7 @@ class AncestorExp extends Expression { this.ancestorDepthComparator = this.getComparator(ancestorDepth); } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const ancestorNote = becca.notes[this.ancestorNoteId]; if (!ancestorNote) { @@ -44,7 +51,7 @@ class AncestorExp extends Expression { return depthConformingNoteSet; } - getComparator(depthCondition) { + getComparator(depthCondition?: string): ((depth: number) => boolean) | null { if (!depthCondition) { return null; } @@ -67,4 +74,4 @@ class AncestorExp extends Expression { } } -module.exports = AncestorExp; +export = AncestorExp; diff --git a/src/services/search/expressions/and.js b/src/services/search/expressions/and.ts similarity index 51% rename from src/services/search/expressions/and.js rename to src/services/search/expressions/and.ts index a0dd35047..82c73fe70 100644 --- a/src/services/search/expressions/and.js +++ b/src/services/search/expressions/and.ts @@ -1,11 +1,15 @@ "use strict"; -const Expression = require('./expression.js'); -const TrueExp = require('./true.js'); +import NoteSet = require('../note_set'); +import SearchContext = require('../search_context'); +import Expression = require('./expression'); +import TrueExp = require('./true'); class AndExp extends Expression { - static of(subExpressions) { - subExpressions = subExpressions.filter(exp => !!exp); + private subExpressions: Expression[]; + + static of(_subExpressions: (Expression | null | undefined)[]) { + const subExpressions = _subExpressions.filter((exp) => !!exp) as Expression[]; if (subExpressions.length === 1) { return subExpressions[0]; @@ -16,12 +20,12 @@ class AndExp extends Expression { } } - constructor(subExpressions) { + constructor(subExpressions: Expression[]) { super(); this.subExpressions = subExpressions; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { for (const subExpression of this.subExpressions) { inputNoteSet = subExpression.execute(inputNoteSet, executionContext, searchContext); } @@ -30,4 +34,4 @@ class AndExp extends Expression { } } -module.exports = AndExp; +export = AndExp; diff --git a/src/services/search/expressions/attribute_exists.js b/src/services/search/expressions/attribute_exists.ts similarity index 69% rename from src/services/search/expressions/attribute_exists.js rename to src/services/search/expressions/attribute_exists.ts index 4c723a433..b1f01a1c0 100644 --- a/src/services/search/expressions/attribute_exists.js +++ b/src/services/search/expressions/attribute_exists.ts @@ -1,11 +1,19 @@ "use strict"; -const NoteSet = require('../note_set'); -const becca = require('../../../becca/becca'); -const Expression = require('./expression.js'); +import NoteSet = require("../note_set"); +import SearchContext = require("../search_context"); + +import becca = require('../../../becca/becca'); +import Expression = require('./expression'); class AttributeExistsExp extends Expression { - constructor(attributeType, attributeName, prefixMatch) { + + private attributeType: string; + private attributeName: string; + private isTemplateLabel: boolean; + private prefixMatch: boolean; + + constructor(attributeType: string, attributeName: string, prefixMatch: boolean) { super(); this.attributeType = attributeType; @@ -15,7 +23,7 @@ class AttributeExistsExp extends Expression { this.prefixMatch = prefixMatch; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const attrs = this.prefixMatch ? becca.findAttributesWithPrefix(this.attributeType, this.attributeName) : becca.findAttributes(this.attributeType, this.attributeName); @@ -40,4 +48,4 @@ class AttributeExistsExp extends Expression { } } -module.exports = AttributeExistsExp; +export = AttributeExistsExp; diff --git a/src/services/search/expressions/child_of.js b/src/services/search/expressions/child_of.ts similarity index 68% rename from src/services/search/expressions/child_of.js rename to src/services/search/expressions/child_of.ts index fde480a83..10d31c00a 100644 --- a/src/services/search/expressions/child_of.js +++ b/src/services/search/expressions/child_of.ts @@ -1,16 +1,20 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import SearchContext = require('../search_context'); class ChildOfExp extends Expression { - constructor(subExpression) { + + private subExpression: Expression; + + constructor(subExpression: Expression) { super(); this.subExpression = subExpression; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const subInputNoteSet = new NoteSet(); for (const note of inputNoteSet.notes) { @@ -33,4 +37,4 @@ class ChildOfExp extends Expression { } } -module.exports = ChildOfExp; +export = ChildOfExp; diff --git a/src/services/search/expressions/descendant_of.js b/src/services/search/expressions/descendant_of.ts similarity index 58% rename from src/services/search/expressions/descendant_of.js rename to src/services/search/expressions/descendant_of.ts index 11ad011e8..6994056c8 100644 --- a/src/services/search/expressions/descendant_of.js +++ b/src/services/search/expressions/descendant_of.ts @@ -1,17 +1,20 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const becca = require('../../../becca/becca'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import becca = require('../../../becca/becca'); +import SearchContext = require('../search_context'); class DescendantOfExp extends Expression { - constructor(subExpression) { + private subExpression: Expression; + + constructor(subExpression: Expression) { super(); this.subExpression = subExpression; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const subInputNoteSet = new NoteSet(Object.values(becca.notes)); const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext); @@ -25,4 +28,4 @@ class DescendantOfExp extends Expression { } } -module.exports = DescendantOfExp; +export = DescendantOfExp; diff --git a/src/services/search/expressions/expression.js b/src/services/search/expressions/expression.js deleted file mode 100644 index 6ffbc290e..000000000 --- a/src/services/search/expressions/expression.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; - -class Expression { - constructor() { - this.name = this.constructor.name; // for DEBUG mode to have expression name as part of dumped JSON - } - - /** - * @param {NoteSet} inputNoteSet - * @param {object} executionContext - * @param {SearchContext} searchContext - * @returns {NoteSet} - */ - execute(inputNoteSet, executionContext, searchContext) {} -} - -module.exports = Expression; diff --git a/src/services/search/expressions/expression.ts b/src/services/search/expressions/expression.ts new file mode 100644 index 000000000..c74341ba2 --- /dev/null +++ b/src/services/search/expressions/expression.ts @@ -0,0 +1,16 @@ +"use strict"; + +import NoteSet = require("../note_set"); +import SearchContext = require("../search_context"); + +abstract class Expression { + name: string; + + constructor() { + this.name = this.constructor.name; // for DEBUG mode to have expression name as part of dumped JSON + } + + abstract execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext): NoteSet; +} + +export = Expression; diff --git a/src/services/search/expressions/is_hidden.js b/src/services/search/expressions/is_hidden.ts similarity index 62% rename from src/services/search/expressions/is_hidden.js rename to src/services/search/expressions/is_hidden.ts index e5ff48536..81bef22b3 100644 --- a/src/services/search/expressions/is_hidden.js +++ b/src/services/search/expressions/is_hidden.ts @@ -1,13 +1,14 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import SearchContext = require('../search_context'); /** * Note is hidden when all its note paths start in hidden subtree (i.e., the note is not cloned into visible tree) */ class IsHiddenExp extends Expression { - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const resultNoteSet = new NoteSet(); for (const note of inputNoteSet.notes) { @@ -20,4 +21,4 @@ class IsHiddenExp extends Expression { } } -module.exports = IsHiddenExp; +export = IsHiddenExp; diff --git a/src/services/search/expressions/label_comparison.js b/src/services/search/expressions/label_comparison.ts similarity index 64% rename from src/services/search/expressions/label_comparison.js rename to src/services/search/expressions/label_comparison.ts index 961bf13f5..b455f82ca 100644 --- a/src/services/search/expressions/label_comparison.js +++ b/src/services/search/expressions/label_comparison.ts @@ -1,11 +1,19 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const becca = require('../../../becca/becca'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import becca = require('../../../becca/becca'); +import SearchContext = require('../search_context'); + +type Comparator = (value: string) => boolean; class LabelComparisonExp extends Expression { - constructor(attributeType, attributeName, comparator) { + + private attributeType: string; + private attributeName: string; + private comparator: Comparator; + + constructor(attributeType: string, attributeName: string, comparator: Comparator) { super(); this.attributeType = attributeType; @@ -13,7 +21,7 @@ class LabelComparisonExp extends Expression { this.comparator = comparator; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const attrs = becca.findAttributes(this.attributeType, this.attributeName); const resultNoteSet = new NoteSet(); @@ -38,4 +46,4 @@ class LabelComparisonExp extends Expression { } } -module.exports = LabelComparisonExp; +export = LabelComparisonExp; diff --git a/src/services/search/expressions/not.js b/src/services/search/expressions/not.js deleted file mode 100644 index 2efd741e2..000000000 --- a/src/services/search/expressions/not.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict"; - -const Expression = require('./expression.js'); - -class NotExp extends Expression { - constructor(subExpression) { - super(); - - this.subExpression = subExpression; - } - - execute(inputNoteSet, executionContext, searchContext) { - const subNoteSet = this.subExpression.execute(inputNoteSet, executionContext, searchContext); - - return inputNoteSet.minus(subNoteSet); - } -} - -module.exports = NotExp; diff --git a/src/services/search/expressions/not.ts b/src/services/search/expressions/not.ts new file mode 100644 index 000000000..e1b31c84e --- /dev/null +++ b/src/services/search/expressions/not.ts @@ -0,0 +1,23 @@ +"use strict"; + +import NoteSet = require('../note_set'); +import SearchContext = require('../search_context'); +import Expression = require('./expression'); + +class NotExp extends Expression { + private subExpression: Expression; + + constructor(subExpression: Expression) { + super(); + + this.subExpression = subExpression; + } + + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { + const subNoteSet = this.subExpression.execute(inputNoteSet, executionContext, searchContext); + + return inputNoteSet.minus(subNoteSet); + } +} + +export = NotExp; diff --git a/src/services/search/expressions/note_content_fulltext.js b/src/services/search/expressions/note_content_fulltext.ts similarity index 73% rename from src/services/search/expressions/note_content_fulltext.js rename to src/services/search/expressions/note_content_fulltext.ts index 98b8a804f..ada9705a2 100644 --- a/src/services/search/expressions/note_content_fulltext.js +++ b/src/services/search/expressions/note_content_fulltext.ts @@ -1,18 +1,22 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const log = require('../../log'); -const becca = require('../../../becca/becca'); -const protectedSessionService = require('../../protected_session'); -const striptags = require('striptags'); -const utils = require('../../utils'); +import { NoteRow } from "../../../becca/entities/rows"; +import SearchContext = require("../search_context"); + +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import log = require('../../log'); +import becca = require('../../../becca/becca'); +import protectedSessionService = require('../../protected_session'); +import striptags = require('striptags'); +import utils = require('../../utils'); +import sql = require("../../sql"); const ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%=']; -const cachedRegexes = {}; +const cachedRegexes: Record = {}; -function getRegex(str) { +function getRegex(str: string): RegExp { if (!(str in cachedRegexes)) { cachedRegexes[str] = new RegExp(str, 'ms'); // multiline, dot-all } @@ -20,8 +24,22 @@ function getRegex(str) { return cachedRegexes[str]; } +interface ConstructorOpts { + tokens: string[]; + raw?: boolean; + flatText?: boolean; +} + +type SearchRow = Pick; + class NoteContentFulltextExp extends Expression { - constructor(operator, {tokens, raw, flatText}) { + + private operator: string; + private tokens: string[]; + private raw: boolean; + private flatText: boolean; + + constructor(operator: string, {tokens, raw, flatText}: ConstructorOpts) { super(); this.operator = operator; @@ -30,7 +48,7 @@ class NoteContentFulltextExp extends Expression { this.flatText = !!flatText; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { if (!ALLOWED_OPERATORS.includes(this.operator)) { searchContext.addError(`Note content can be searched only with operators: ${ALLOWED_OPERATORS.join(", ")}, operator ${this.operator} given.`); @@ -38,9 +56,8 @@ class NoteContentFulltextExp extends Expression { } const resultNoteSet = new NoteSet(); - const sql = require('../../sql'); - - for (const row of sql.iterateRows(` + + for (const row of sql.iterateRows(` SELECT noteId, type, mime, content, isProtected FROM notes JOIN blobs USING (blobId) WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) { @@ -51,18 +68,18 @@ class NoteContentFulltextExp extends Expression { return resultNoteSet; } - findInText({noteId, isProtected, content, type, mime}, inputNoteSet, resultNoteSet) { + findInText({noteId, isProtected, content, type, mime}: SearchRow, inputNoteSet: NoteSet, resultNoteSet: NoteSet) { if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) { return; } if (isProtected) { - if (!protectedSessionService.isProtectedSessionAvailable()) { + if (!protectedSessionService.isProtectedSessionAvailable() || !content) { return; } try { - content = protectedSessionService.decryptString(content); + content = protectedSessionService.decryptString(content) || undefined; } catch (e) { log.info(`Cannot decrypt content of note ${noteId}`); return; @@ -89,7 +106,7 @@ class NoteContentFulltextExp extends Expression { } } else { const nonMatchingToken = this.tokens.find(token => - !content.includes(token) && + !content?.includes(token) && ( // in case of default fulltext search, we should consider both title, attrs and content // so e.g. "hello world" should match when "hello" is in title and "world" in content @@ -106,7 +123,7 @@ class NoteContentFulltextExp extends Expression { return content; } - preprocessContent(content, type, mime) { + preprocessContent(content: string, type: string, mime: string) { content = utils.normalize(content.toString()); if (type === 'text' && mime === 'text/html') { @@ -120,7 +137,7 @@ class NoteContentFulltextExp extends Expression { return content.trim(); } - stripTags(content) { + stripTags(content: string) { // we want to allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412 // we want to insert space in place of block tags (because they imply text separation) // but we don't want to insert text for typical formatting inline tags which can occur within one word @@ -138,4 +155,4 @@ class NoteContentFulltextExp extends Expression { } } -module.exports = NoteContentFulltextExp; +export = NoteContentFulltextExp; diff --git a/src/services/search/expressions/note_flat_text.js b/src/services/search/expressions/note_flat_text.ts similarity index 84% rename from src/services/search/expressions/note_flat_text.js rename to src/services/search/expressions/note_flat_text.ts index cf523bfbb..f163e8a7a 100644 --- a/src/services/search/expressions/note_flat_text.js +++ b/src/services/search/expressions/note_flat_text.ts @@ -1,29 +1,34 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const becca = require('../../../becca/becca'); -const utils = require('../../utils'); +import BNote = require("../../../becca/entities/bnote"); +import SearchContext = require("../search_context"); + +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import becca = require('../../../becca/becca'); +import utils = require('../../utils'); class NoteFlatTextExp extends Expression { - constructor(tokens) { + private tokens: string[]; + + constructor(tokens: string[]) { super(); this.tokens = tokens; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: any, searchContext: SearchContext) { // has deps on SQL which breaks unit test so needs to be dynamically required const beccaService = require('../../../becca/becca_service'); const resultNoteSet = new NoteSet(); /** - * @param {BNote} note - * @param {string[]} remainingTokens - tokens still needed to be found in the path towards root - * @param {string[]} takenPath - path so far taken towards from candidate note towards the root. - * It contains the suffix fragment of the full note path. + * @param note + * @param remainingTokens - tokens still needed to be found in the path towards root + * @param takenPath - path so far taken towards from candidate note towards the root. + * It contains the suffix fragment of the full note path. */ - const searchPathTowardsRoot = (note, remainingTokens, takenPath) => { + const searchPathTowardsRoot = (note: BNote, remainingTokens: string[], takenPath: string[]) => { if (remainingTokens.length === 0) { // we're done, just build the result const resultPath = this.getNotePath(note, takenPath); @@ -134,12 +139,7 @@ class NoteFlatTextExp extends Expression { return resultNoteSet; } - /** - * @param {BNote} note - * @param {string[]} takenPath - * @returns {string[]} - */ - getNotePath(note, takenPath) { + getNotePath(note: BNote, takenPath: string[]): string[] { if (takenPath.length === 0) { throw new Error("Path is not expected to be empty."); } else if (takenPath.length === 1 && takenPath[0] === note.noteId) { @@ -147,7 +147,7 @@ class NoteFlatTextExp extends Expression { } else { // this note is the closest to root containing the last matching token(s), thus completing the requirements // what's in this note's predecessors does not matter, thus we'll choose the best note path - const topMostMatchingTokenNotePath = becca.getNote(takenPath[0]).getBestNotePath(); + const topMostMatchingTokenNotePath = becca.getNote(takenPath[0])?.getBestNotePath() || []; return [...topMostMatchingTokenNotePath, ...takenPath.slice(1)]; } @@ -155,11 +155,8 @@ class NoteFlatTextExp extends Expression { /** * Returns noteIds which have at least one matching tokens - * - * @param {NoteSet} noteSet - * @returns {BNote[]} */ - getCandidateNotes(noteSet) { + getCandidateNotes(noteSet: NoteSet): BNote[] { const candidateNotes = []; for (const note of noteSet.notes) { @@ -175,4 +172,4 @@ class NoteFlatTextExp extends Expression { } } -module.exports = NoteFlatTextExp; +export = NoteFlatTextExp; diff --git a/src/services/search/expressions/or.js b/src/services/search/expressions/or.ts similarity index 62% rename from src/services/search/expressions/or.js rename to src/services/search/expressions/or.ts index c5a9c64c4..f89e9070e 100644 --- a/src/services/search/expressions/or.js +++ b/src/services/search/expressions/or.ts @@ -1,11 +1,14 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const TrueExp = require('./true.js'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import TrueExp = require('./true'); +import SearchContext = require('../search_context'); class OrExp extends Expression { - static of(subExpressions) { + private subExpressions: Expression[]; + + static of(subExpressions: Expression[]) { subExpressions = subExpressions.filter(exp => !!exp); if (subExpressions.length === 1) { @@ -19,13 +22,13 @@ class OrExp extends Expression { } } - constructor(subExpressions) { + constructor(subExpressions: Expression[]) { super(); this.subExpressions = subExpressions; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const resultNoteSet = new NoteSet(); for (const subExpression of this.subExpressions) { @@ -36,4 +39,4 @@ class OrExp extends Expression { } } -module.exports = OrExp; +export = OrExp; diff --git a/src/services/search/expressions/order_by_and_limit.js b/src/services/search/expressions/order_by_and_limit.ts similarity index 66% rename from src/services/search/expressions/order_by_and_limit.js rename to src/services/search/expressions/order_by_and_limit.ts index 9a68f9eb2..a3a37496f 100644 --- a/src/services/search/expressions/order_by_and_limit.js +++ b/src/services/search/expressions/order_by_and_limit.ts @@ -1,13 +1,31 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); +import BNote = require("../../../becca/entities/bnote"); +import NoteSet = require("../note_set"); +import SearchContext = require("../search_context"); +import Expression = require("./expression"); + +interface ValueExtractor { + extract: (note: BNote) => number | string | null; +} + +interface OrderDefinition { + direction?: string; + smaller: number; + larger: number; + valueExtractor: ValueExtractor; +} class OrderByAndLimitExp extends Expression { - constructor(orderDefinitions, limit) { + + private orderDefinitions: OrderDefinition[]; + private limit: number; + subExpression: Expression | null; + + constructor(orderDefinitions: Pick[], limit?: number) { super(); - this.orderDefinitions = orderDefinitions; + this.orderDefinitions = orderDefinitions as OrderDefinition[]; for (const od of this.orderDefinitions) { od.smaller = od.direction === "asc" ? -1 : 1; @@ -16,11 +34,14 @@ class OrderByAndLimitExp extends Expression { this.limit = limit || 0; - /** @type {Expression} */ this.subExpression = null; // it's expected to be set after construction } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { + if (!this.subExpression) { + throw new Error("Missing subexpression"); + } + let {notes} = this.subExpression.execute(inputNoteSet, executionContext, searchContext); notes.sort((a, b) => { @@ -48,7 +69,8 @@ class OrderByAndLimitExp extends Expression { } // if both are numbers, then parse them for numerical comparison - if (this.isNumber(valA) && this.isNumber(valB)) { + if (typeof valA === "string" && this.isNumber(valA) && + typeof valB === "string" && this.isNumber(valB)) { valA = parseFloat(valA); valB = parseFloat(valB); } @@ -77,16 +99,16 @@ class OrderByAndLimitExp extends Expression { return noteSet; } - isNumber(x) { + isNumber(x: number | string) { if (typeof x === 'number') { return true; } else if (typeof x === 'string') { // isNaN will return false for blank string - return x.trim() !== "" && !isNaN(x); + return x.trim() !== "" && !isNaN(parseInt(x, 10)); } else { return false; } } } -module.exports = OrderByAndLimitExp; +export = OrderByAndLimitExp; diff --git a/src/services/search/expressions/parent_of.js b/src/services/search/expressions/parent_of.ts similarity index 68% rename from src/services/search/expressions/parent_of.js rename to src/services/search/expressions/parent_of.ts index 5f388696b..bd7b9d304 100644 --- a/src/services/search/expressions/parent_of.js +++ b/src/services/search/expressions/parent_of.ts @@ -1,16 +1,19 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import SearchContext = require('../search_context'); class ParentOfExp extends Expression { - constructor(subExpression) { + private subExpression: Expression; + + constructor(subExpression: Expression) { super(); this.subExpression = subExpression; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const subInputNoteSet = new NoteSet(); for (const note of inputNoteSet.notes) { @@ -33,4 +36,4 @@ class ParentOfExp extends Expression { } } -module.exports = ParentOfExp; +export = ParentOfExp; diff --git a/src/services/search/expressions/property_comparison.js b/src/services/search/expressions/property_comparison.ts similarity index 74% rename from src/services/search/expressions/property_comparison.js rename to src/services/search/expressions/property_comparison.ts index 5f8ac14b3..843b5a862 100644 --- a/src/services/search/expressions/property_comparison.js +++ b/src/services/search/expressions/property_comparison.ts @@ -1,14 +1,14 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const buildComparator = require('../services/build_comparator.js'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import buildComparator = require('../services/build_comparator'); /** * Search string is lower cased for case-insensitive comparison. But when retrieving properties, * we need the case-sensitive form, so we have this translation object. */ -const PROP_MAPPING = { +const PROP_MAPPING: Record = { "noteid": "noteId", "title": "title", "type": "type", @@ -36,12 +36,22 @@ const PROP_MAPPING = { "revisioncount": "revisionCount" }; +interface SearchContext { + dbLoadNeeded?: boolean; +} + class PropertyComparisonExp extends Expression { - static isProperty(name) { + + private propertyName: string; + private operator: string; + private comparedValue: string; + private comparator; + + static isProperty(name: string) { return name in PROP_MAPPING; } - constructor(searchContext, propertyName, operator, comparedValue) { + constructor(searchContext: SearchContext, propertyName: string, operator: string, comparedValue: string) { super(); this.propertyName = PROP_MAPPING[propertyName]; @@ -54,11 +64,11 @@ class PropertyComparisonExp extends Expression { } } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const resNoteSet = new NoteSet(); for (const note of inputNoteSet.notes) { - let value = note[this.propertyName]; + let value = (note as any)[this.propertyName]; if (value !== undefined && value !== null && typeof value !== 'string') { value = value.toString(); @@ -68,7 +78,7 @@ class PropertyComparisonExp extends Expression { value = value.toLowerCase(); } - if (this.comparator(value)) { + if (this.comparator && this.comparator(value)) { resNoteSet.add(note); } } @@ -77,4 +87,4 @@ class PropertyComparisonExp extends Expression { } } -module.exports = PropertyComparisonExp; +export = PropertyComparisonExp; diff --git a/src/services/search/expressions/relation_where.js b/src/services/search/expressions/relation_where.ts similarity index 72% rename from src/services/search/expressions/relation_where.js rename to src/services/search/expressions/relation_where.ts index 77283b45c..9f33dcecf 100644 --- a/src/services/search/expressions/relation_where.js +++ b/src/services/search/expressions/relation_where.ts @@ -1,18 +1,22 @@ "use strict"; -const Expression = require('./expression.js'); -const NoteSet = require('../note_set'); -const becca = require('../../../becca/becca'); +import Expression = require('./expression'); +import NoteSet = require('../note_set'); +import becca = require('../../../becca/becca'); +import SearchContext = require('../search_context'); class RelationWhereExp extends Expression { - constructor(relationName, subExpression) { + private relationName: string; + private subExpression: Expression; + + constructor(relationName: string, subExpression: Expression) { super(); this.relationName = relationName; this.subExpression = subExpression; } - execute(inputNoteSet, executionContext, searchContext) { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) { const candidateNoteSet = new NoteSet(); for (const attr of becca.findAttributes('relation', this.relationName)) { @@ -38,4 +42,4 @@ class RelationWhereExp extends Expression { } } -module.exports = RelationWhereExp; +export = RelationWhereExp; diff --git a/src/services/search/expressions/true.js b/src/services/search/expressions/true.js deleted file mode 100644 index 7dbf35c27..000000000 --- a/src/services/search/expressions/true.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const Expression = require('./expression.js'); - -class TrueExp extends Expression { - execute(inputNoteSet, executionContext, searchContext) { - return inputNoteSet; - } -} - -module.exports = TrueExp; diff --git a/src/services/search/expressions/true.ts b/src/services/search/expressions/true.ts new file mode 100644 index 000000000..a53d1afde --- /dev/null +++ b/src/services/search/expressions/true.ts @@ -0,0 +1,14 @@ +"use strict"; + +import NoteSet = require("../note_set"); +import SearchContext = require("../search_context"); + +import Expression = require('./expression'); + +class TrueExp extends Expression { + execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext): NoteSet { + return inputNoteSet; + } +} + +export = TrueExp; diff --git a/src/services/search/search_context.js b/src/services/search/search_context.ts similarity index 69% rename from src/services/search/search_context.js rename to src/services/search/search_context.ts index 6827e3841..f7c1e2198 100644 --- a/src/services/search/search_context.js +++ b/src/services/search/search_context.ts @@ -1,9 +1,29 @@ "use strict"; -const hoistedNoteService = require('../hoisted_note.js'); +import hoistedNoteService = require('../hoisted_note'); +import { SearchParams } from './services/types'; class SearchContext { - constructor(params = {}) { + + fastSearch: boolean; + includeArchivedNotes: boolean; + includeHiddenNotes: boolean; + ignoreHoistedNote: boolean; + ancestorNoteId?: string; + ancestorDepth?: string; + orderBy?: string; + orderDirection?: string; + limit?: number | null; + debug?: boolean; + debugInfo: {} | null; + fuzzyAttributeSearch: boolean; + highlightedTokens: string[]; + originalQuery: string; + fulltextQuery: string; + dbLoadNeeded: boolean; + private error: string | null; + + constructor(params: SearchParams = {}) { this.fastSearch = !!params.fastSearch; this.includeArchivedNotes = !!params.includeArchivedNotes; this.includeHiddenNotes = !!params.includeHiddenNotes; @@ -32,7 +52,7 @@ class SearchContext { this.error = null; } - addError(error) { + addError(error: string) { // we record only the first error, subsequent ones are usually a consequence of the first if (!this.error) { this.error = error; @@ -48,4 +68,4 @@ class SearchContext { } } -module.exports = SearchContext; +export = SearchContext; diff --git a/src/services/search/search_result.js b/src/services/search/search_result.ts similarity index 77% rename from src/services/search/search_result.js rename to src/services/search/search_result.ts index ca3811f8e..cf651d58e 100644 --- a/src/services/search/search_result.js +++ b/src/services/search/search_result.ts @@ -1,12 +1,18 @@ "use strict"; -const beccaService = require('../../becca/becca_service'); -const becca = require('../../becca/becca'); +import beccaService = require('../../becca/becca_service'); +import becca = require('../../becca/becca'); class SearchResult { - constructor(notePathArray) { + notePathArray: string[]; + score: number; + notePathTitle: string; + highlightedNotePathTitle?: string; + + constructor(notePathArray: string[]) { this.notePathArray = notePathArray; this.notePathTitle = beccaService.getNoteTitleForPath(notePathArray); + this.score = 0; } get notePath() { @@ -17,7 +23,7 @@ class SearchResult { return this.notePathArray[this.notePathArray.length - 1]; } - computeScore(fulltextQuery, tokens) { + computeScore(fulltextQuery: string, tokens: string[]) { this.score = 0; const note = becca.notes[this.noteId]; @@ -42,9 +48,11 @@ class SearchResult { } } - addScoreForStrings(tokens, str, factor) { + addScoreForStrings(tokens: string[], str: string, factor: number) { const chunks = str.toLowerCase().split(" "); + this.score = 0; + for (const chunk of chunks) { for (const token of tokens) { if (chunk === token) { @@ -59,4 +67,4 @@ class SearchResult { } } -module.exports = SearchResult; +export = SearchResult; diff --git a/src/services/search/services/build_comparator.js b/src/services/search/services/build_comparator.ts similarity index 51% rename from src/services/search/services/build_comparator.js rename to src/services/search/services/build_comparator.ts index 6d3ba463a..426bfa195 100644 --- a/src/services/search/services/build_comparator.js +++ b/src/services/search/services/build_comparator.ts @@ -1,6 +1,6 @@ -const cachedRegexes = {}; +const cachedRegexes: Record = {}; -function getRegex(str) { +function getRegex(str: string) { if (!(str in cachedRegexes)) { cachedRegexes[str] = new RegExp(str); } @@ -8,31 +8,36 @@ function getRegex(str) { return cachedRegexes[str]; } -const stringComparators = { +type Comparator = (comparedValue: T) => ((val: string) => boolean); + +const stringComparators: Record> = { "=": comparedValue => (val => val === comparedValue), "!=": comparedValue => (val => val !== comparedValue), ">": comparedValue => (val => val > comparedValue), ">=": comparedValue => (val => val >= comparedValue), "<": comparedValue => (val => val < comparedValue), "<=": comparedValue => (val => val <= comparedValue), - "*=": comparedValue => (val => val && val.endsWith(comparedValue)), - "=*": comparedValue => (val => val && val.startsWith(comparedValue)), - "*=*": comparedValue => (val => val && val.includes(comparedValue)), - "%=": comparedValue => (val => val && !!getRegex(comparedValue).test(val)), + "*=": comparedValue => (val => !!val && val.endsWith(comparedValue)), + "=*": comparedValue => (val => !!val && val.startsWith(comparedValue)), + "*=*": comparedValue => (val => !!val && val.includes(comparedValue)), + "%=": comparedValue => (val => !!val && !!getRegex(comparedValue).test(val)), }; -const numericComparators = { +const numericComparators: Record> = { ">": comparedValue => (val => parseFloat(val) > comparedValue), ">=": comparedValue => (val => parseFloat(val) >= comparedValue), "<": comparedValue => (val => parseFloat(val) < comparedValue), "<=": comparedValue => (val => parseFloat(val) <= comparedValue) }; -function buildComparator(operator, comparedValue) { +function buildComparator(operator: string, comparedValue: string) { comparedValue = comparedValue.toLowerCase(); - if (operator in numericComparators && !isNaN(comparedValue)) { - return numericComparators[operator](parseFloat(comparedValue)); + if (operator in numericComparators) { + const floatValue = parseFloat(comparedValue); + if (!isNaN(floatValue)) { + return numericComparators[operator](floatValue); + } } if (operator in stringComparators) { @@ -40,4 +45,4 @@ function buildComparator(operator, comparedValue) { } } -module.exports = buildComparator; +export = buildComparator; diff --git a/src/services/search/services/handle_parens.js b/src/services/search/services/handle_parens.ts similarity index 64% rename from src/services/search/services/handle_parens.js rename to src/services/search/services/handle_parens.ts index 14ff58b0a..30d7e03fc 100644 --- a/src/services/search/services/handle_parens.js +++ b/src/services/search/services/handle_parens.ts @@ -1,13 +1,15 @@ +import { TokenData } from "./types"; + /** * This will create a recursive object from a list of tokens - tokens between parenthesis are grouped in a single array */ -function handleParens(tokens) { +function handleParens(tokens: (TokenData | TokenData[])[]) { if (tokens.length === 0) { return []; } while (true) { - const leftIdx = tokens.findIndex(token => token.token === '('); + const leftIdx = tokens.findIndex(token => "token" in token && token.token === '('); if (leftIdx === -1) { return tokens; @@ -17,13 +19,18 @@ function handleParens(tokens) { let parensLevel = 0 for (rightIdx = leftIdx; rightIdx < tokens.length; rightIdx++) { - if (tokens[rightIdx].token === ')') { + const token = tokens[rightIdx]; + if (!("token" in token)) { + continue; + } + + if (token.token === ')') { parensLevel--; if (parensLevel === 0) { break; } - } else if (tokens[rightIdx].token === '(') { + } else if (token.token === '(') { parensLevel++; } } @@ -36,8 +43,8 @@ function handleParens(tokens) { ...tokens.slice(0, leftIdx), handleParens(tokens.slice(leftIdx + 1, rightIdx)), ...tokens.slice(rightIdx + 1) - ]; + ] as (TokenData | TokenData[])[]; } } -module.exports = handleParens; +export = handleParens; diff --git a/src/services/search/services/lex.js b/src/services/search/services/lex.ts similarity index 89% rename from src/services/search/services/lex.js rename to src/services/search/services/lex.ts index ddee0840f..516cf71df 100644 --- a/src/services/search/services/lex.js +++ b/src/services/search/services/lex.ts @@ -1,16 +1,17 @@ -function lex(str) { +import { TokenData } from "./types"; + +function lex(str: string) { str = str.toLowerCase(); let fulltextQuery = ""; - const fulltextTokens = []; - const expressionTokens = []; + const fulltextTokens: TokenData[] = []; + const expressionTokens: TokenData[] = []; - /** @type {boolean|string} */ - let quotes = false; // otherwise contains used quote - ', " or ` + let quotes: boolean | string = false; // otherwise contains used quote - ', " or ` let fulltextEnded = false; let currentWord = ''; - function isSymbolAnOperator(chr) { + function isSymbolAnOperator(chr: string) { return ['=', '*', '>', '<', '!', "-", "+", '%', ','].includes(chr); } @@ -23,12 +24,12 @@ function lex(str) { } } - function finishWord(endIndex, createAlsoForEmptyWords = false) { + function finishWord(endIndex: number, createAlsoForEmptyWords = false) { if (currentWord === '' && !createAlsoForEmptyWords) { return; } - const rec = { + const rec: TokenData = { token: currentWord, inQuotes: !!quotes, startIndex: endIndex - currentWord.length + 1, @@ -146,4 +147,4 @@ function lex(str) { } } -module.exports = lex; +export = lex; diff --git a/src/services/search/services/parse.js b/src/services/search/services/parse.ts similarity index 72% rename from src/services/search/services/parse.js rename to src/services/search/services/parse.ts index 84c717f6e..de943fdd2 100644 --- a/src/services/search/services/parse.js +++ b/src/services/search/services/parse.ts @@ -1,28 +1,31 @@ "use strict"; -const dayjs = require("dayjs"); -const AndExp = require('../expressions/and.js'); -const OrExp = require('../expressions/or.js'); -const NotExp = require('../expressions/not.js'); -const ChildOfExp = require('../expressions/child_of.js'); -const DescendantOfExp = require('../expressions/descendant_of.js'); -const ParentOfExp = require('../expressions/parent_of.js'); -const RelationWhereExp = require('../expressions/relation_where.js'); -const PropertyComparisonExp = require('../expressions/property_comparison.js'); -const AttributeExistsExp = require('../expressions/attribute_exists.js'); -const LabelComparisonExp = require('../expressions/label_comparison.js'); -const NoteFlatTextExp = require('../expressions/note_flat_text.js'); -const NoteContentFulltextExp = require('../expressions/note_content_fulltext.js'); -const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js'); -const AncestorExp = require('../expressions/ancestor.js'); -const buildComparator = require('./build_comparator.js'); -const ValueExtractor = require('../value_extractor.js'); -const utils = require('../../utils'); -const TrueExp = require('../expressions/true.js'); -const IsHiddenExp = require('../expressions/is_hidden.js'); +import dayjs = require("dayjs"); +import AndExp = require('../expressions/and'); +import OrExp = require('../expressions/or'); +import NotExp = require('../expressions/not'); +import ChildOfExp = require('../expressions/child_of'); +import DescendantOfExp = require('../expressions/descendant_of'); +import ParentOfExp = require('../expressions/parent_of'); +import RelationWhereExp = require('../expressions/relation_where'); +import PropertyComparisonExp = require('../expressions/property_comparison'); +import AttributeExistsExp = require('../expressions/attribute_exists'); +import LabelComparisonExp = require('../expressions/label_comparison'); +import NoteFlatTextExp = require('../expressions/note_flat_text'); +import NoteContentFulltextExp = require('../expressions/note_content_fulltext'); +import OrderByAndLimitExp = require('../expressions/order_by_and_limit'); +import AncestorExp = require('../expressions/ancestor'); +import buildComparator = require('./build_comparator'); +import ValueExtractor = require('../value_extractor'); +import utils = require('../../utils'); +import TrueExp = require('../expressions/true'); +import IsHiddenExp = require('../expressions/is_hidden'); +import SearchContext = require("../search_context"); +import { TokenData } from "./types"; +import Expression = require("../expressions/expression"); -function getFulltext(tokens, searchContext) { - tokens = tokens.map(t => utils.removeDiacritic(t.token)); +function getFulltext(_tokens: TokenData[], searchContext: SearchContext) { + const tokens: string[] = _tokens.map(t => utils.removeDiacritic(t.token)); searchContext.highlightedTokens.push(...tokens); @@ -54,7 +57,7 @@ const OPERATORS = [ "%=" ]; -function isOperator(token) { +function isOperator(token: TokenData) { if (Array.isArray(token)) { return false; } @@ -62,20 +65,20 @@ function isOperator(token) { return OPERATORS.includes(token.token); } -function getExpression(tokens, searchContext, level = 0) { +function getExpression(tokens: TokenData[], searchContext: SearchContext, level = 0) { if (tokens.length === 0) { return null; } - const expressions = []; - let op = null; + const expressions: Expression[] = []; + let op: string | null = null; - let i; + let i: number; - function context(i) { + function context(i: number) { let {startIndex, endIndex} = tokens[i]; - startIndex = Math.max(0, startIndex - 20); - endIndex = Math.min(searchContext.originalQuery.length, endIndex + 20); + startIndex = Math.max(0, (startIndex || 0) - 20); + endIndex = Math.min(searchContext.originalQuery.length, (endIndex || Number.MAX_SAFE_INTEGER) + 20); return `"${startIndex !== 0 ? "..." : ""}${searchContext.originalQuery.substr(startIndex, endIndex - startIndex)}${endIndex !== searchContext.originalQuery.length ? "..." : ""}"`; } @@ -133,7 +136,7 @@ function getExpression(tokens, searchContext, level = 0) { return date.format(format); } - function parseNoteProperty() { + function parseNoteProperty(): Expression | undefined | null { if (tokens[i].token !== '.') { searchContext.addError('Expected "." to separate field path'); return; @@ -161,19 +164,25 @@ function getExpression(tokens, searchContext, level = 0) { if (tokens[i].token === 'parents') { i += 1; - return new ChildOfExp(parseNoteProperty()); + const expression = parseNoteProperty(); + if (!expression) { return; } + return new ChildOfExp(expression); } if (tokens[i].token === 'children') { i += 1; - return new ParentOfExp(parseNoteProperty()); + const expression = parseNoteProperty(); + if (!expression) { return; } + return new ParentOfExp(expression); } if (tokens[i].token === 'ancestors') { i += 1; - return new DescendantOfExp(parseNoteProperty()); + const expression = parseNoteProperty(); + if (!expression) { return; } + return new DescendantOfExp(expression); } if (tokens[i].token === 'labels') { @@ -219,6 +228,10 @@ function getExpression(tokens, searchContext, level = 0) { i += 2; const comparedValue = resolveConstantOperand(); + if (!comparedValue) { + searchContext.addError(`Unresolved constant operand.`); + return; + } return new PropertyComparisonExp(searchContext, propertyName, operator, comparedValue); } @@ -226,7 +239,7 @@ function getExpression(tokens, searchContext, level = 0) { searchContext.addError(`Unrecognized note property "${tokens[i].token}" in ${context(i)}`); } - function parseAttribute(name) { + function parseAttribute(name: string) { const isLabel = name.startsWith('#'); name = name.substr(1); @@ -239,10 +252,10 @@ function getExpression(tokens, searchContext, level = 0) { const subExp = isLabel ? parseLabel(name) : parseRelation(name); - return isNegated ? new NotExp(subExp) : subExp; + return subExp && isNegated ? new NotExp(subExp) : subExp; } - function parseLabel(labelName) { + function parseLabel(labelName: string) { searchContext.highlightedTokens.push(labelName); if (i < tokens.length - 2 && isOperator(tokens[i + 1])) { @@ -274,13 +287,15 @@ function getExpression(tokens, searchContext, level = 0) { } } - function parseRelation(relationName) { + function parseRelation(relationName: string) { searchContext.highlightedTokens.push(relationName); if (i < tokens.length - 2 && tokens[i + 1].token === '.') { i += 1; - return new RelationWhereExp(relationName, parseNoteProperty()); + const expression = parseNoteProperty(); + if (!expression) { return; } + return new RelationWhereExp(relationName, expression); } else if (i < tokens.length - 2 && isOperator(tokens[i + 1])) { searchContext.addError(`Relation can be compared only with property, e.g. ~relation.title=hello in ${context(i)}`); @@ -293,7 +308,10 @@ function getExpression(tokens, searchContext, level = 0) { } function parseOrderByAndLimit() { - const orderDefinitions = []; + const orderDefinitions: { + valueExtractor: ValueExtractor, + direction: string + }[] = []; let limit; if (tokens[i].token === 'orderby') { @@ -316,8 +334,9 @@ function getExpression(tokens, searchContext, level = 0) { const valueExtractor = new ValueExtractor(searchContext, propertyPath); - if (valueExtractor.validate()) { - searchContext.addError(valueExtractor.validate()); + const validationError = valueExtractor.validate(); + if (validationError) { + searchContext.addError(validationError); } orderDefinitions.push({ @@ -348,7 +367,10 @@ function getExpression(tokens, searchContext, level = 0) { for (i = 0; i < tokens.length; i++) { if (Array.isArray(tokens[i])) { - expressions.push(getExpression(tokens[i], searchContext, level++)); + const expression = getExpression(tokens[i] as unknown as TokenData[], searchContext, level++); + if (expression) { + expressions.push(expression); + } continue; } @@ -359,7 +381,10 @@ function getExpression(tokens, searchContext, level = 0) { } if (token.startsWith('#') || token.startsWith('~')) { - expressions.push(parseAttribute(token)); + const attribute = parseAttribute(token); + if (attribute) { + expressions.push(attribute); + } } else if (['orderby', 'limit'].includes(token)) { if (level !== 0) { @@ -384,12 +409,17 @@ function getExpression(tokens, searchContext, level = 0) { continue; } - expressions.push(new NotExp(getExpression(tokens[i], searchContext, level++))); + const tokenArray = tokens[i] as unknown as TokenData[]; + const expression = getExpression(tokenArray, searchContext, level++); + if (!expression) { return; } + expressions.push(new NotExp(expression)); } else if (token === 'note') { i++; - expressions.push(parseNoteProperty()); + const expression = parseNoteProperty(); + if (!expression) { return; } + expressions.push(expression); continue; } @@ -416,13 +446,18 @@ function getExpression(tokens, searchContext, level = 0) { return getAggregateExpression(); } -function parse({fulltextTokens, expressionTokens, searchContext}) { - let expression; +function parse({fulltextTokens, expressionTokens, searchContext}: { + fulltextTokens: TokenData[], + expressionTokens: (TokenData | TokenData[])[], + searchContext: SearchContext, + originalQuery: string +}) { + let expression: Expression | undefined | null; try { - expression = getExpression(expressionTokens, searchContext); + expression = getExpression(expressionTokens as TokenData[], searchContext); } - catch (e) { + catch (e: any) { searchContext.addError(e.message); expression = new TrueExp(); @@ -441,15 +476,15 @@ function parse({fulltextTokens, expressionTokens, searchContext}) { exp = new OrderByAndLimitExp([{ valueExtractor: new ValueExtractor(searchContext, ['note', searchContext.orderBy]), direction: searchContext.orderDirection - }], searchContext.limit); + }], searchContext.limit || undefined); - exp.subExpression = filterExp; + (exp as any).subExpression = filterExp; } return exp; } -function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) { +function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}: SearchContext) { if (ancestorNoteId && ancestorNoteId !== 'root') { return new AncestorExp(ancestorNoteId, ancestorDepth); } else if (!includeHiddenNotes) { @@ -459,4 +494,4 @@ function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) { } } -module.exports = parse; +export = parse; diff --git a/src/services/search/services/search.js b/src/services/search/services/search.ts similarity index 78% rename from src/services/search/services/search.js rename to src/services/search/services/search.ts index 828c624a8..c986b80bb 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.ts @@ -1,22 +1,28 @@ "use strict"; -const normalizeString = require("normalize-strings"); -const lex = require('./lex.js'); -const handleParens = require('./handle_parens.js'); -const parse = require('./parse.js'); -const SearchResult = require('../search_result.js'); -const SearchContext = require('../search_context.js'); -const becca = require('../../../becca/becca'); -const beccaService = require('../../../becca/becca_service'); -const utils = require('../../utils'); -const log = require('../../log'); -const hoistedNoteService = require('../../hoisted_note.js'); +import normalizeString = require("normalize-strings"); +import lex = require('./lex'); +import handleParens = require('./handle_parens'); +import parse = require('./parse'); +import SearchResult = require('../search_result'); +import SearchContext = require('../search_context'); +import becca = require('../../../becca/becca'); +import beccaService = require('../../../becca/becca_service'); +import utils = require('../../utils'); +import log = require('../../log'); +import hoistedNoteService = require('../../hoisted_note'); +import BNote = require("../../../becca/entities/bnote"); +import BAttribute = require("../../../becca/entities/battribute"); +import { SearchParams, TokenData } from "./types"; +import Expression = require("../expressions/expression"); +import sql = require("../../sql"); -function searchFromNote(note) { - let searchResultNoteIds, highlightedTokens; +function searchFromNote(note: BNote) { + let searchResultNoteIds; + let highlightedTokens: string[]; const searchScript = note.getRelationValue('searchScript'); - const searchString = note.getLabelValue('searchString'); + const searchString = note.getLabelValue('searchString') || ""; let error = null; if (searchScript) { @@ -25,12 +31,12 @@ function searchFromNote(note) { } else { const searchContext = new SearchContext({ fastSearch: note.hasLabel('fastSearch'), - ancestorNoteId: note.getRelationValue('ancestor'), - ancestorDepth: note.getLabelValue('ancestorDepth'), + ancestorNoteId: note.getRelationValue('ancestor') || undefined, + ancestorDepth: note.getLabelValue('ancestorDepth') || undefined, includeArchivedNotes: note.hasLabel('includeArchivedNotes'), - orderBy: note.getLabelValue('orderBy'), - orderDirection: note.getLabelValue('orderDirection'), - limit: note.getLabelValue('limit'), + orderBy: note.getLabelValue('orderBy') || undefined, + orderDirection: note.getLabelValue('orderDirection') || undefined, + limit: parseInt(note.getLabelValue('limit') || "0", 10), debug: note.hasLabel('debug'), fuzzyAttributeSearch: false }); @@ -51,7 +57,7 @@ function searchFromNote(note) { }; } -function searchFromRelation(note, relationName) { +function searchFromRelation(note: BNote, relationName: string) { const scriptNote = note.getRelationTarget(relationName); if (!scriptNote) { @@ -90,18 +96,21 @@ function searchFromRelation(note, relationName) { } function loadNeededInfoFromDatabase() { - const sql = require('../../sql'); - /** * This complex structure is needed to calculate total occupied space by a note. Several object instances * (note, revisions, attachments) can point to a single blobId, and thus the blob size should count towards the total * only once. * - * @var {Object.>} - noteId => { blobId => blobSize } + * noteId => { blobId => blobSize } */ - const noteBlobs = {}; + const noteBlobs: Record> = {}; - const noteContentLengths = sql.getRows(` + type NoteContentLengthsRow = { + noteId: string; + blobId: string; + length: number; + }; + const noteContentLengths = sql.getRows(` SELECT noteId, blobId, @@ -122,7 +131,12 @@ function loadNeededInfoFromDatabase() { noteBlobs[noteId] = { [blobId]: length }; } - const attachmentContentLengths = sql.getRows(` + type AttachmentContentLengthsRow = { + noteId: string; + blobId: string; + length: number; + }; + const attachmentContentLengths = sql.getRows(` SELECT ownerId AS noteId, attachments.blobId, @@ -151,7 +165,13 @@ function loadNeededInfoFromDatabase() { becca.notes[noteId].contentAndAttachmentsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0); } - const revisionContentLengths = sql.getRows(` + type RevisionRow = { + noteId: string; + blobId: string; + length: number; + isNoteRevision: true; + }; + const revisionContentLengths = sql.getRows(` SELECT noteId, revisions.blobId, @@ -186,8 +206,11 @@ function loadNeededInfoFromDatabase() { noteBlobs[noteId][blobId] = length; - if (isNoteRevision) { - becca.notes[noteId].revisionCount++; + if (isNoteRevision) { + const noteRevision = becca.notes[noteId]; + if (noteRevision && noteRevision.revisionCount) { + noteRevision.revisionCount++; + } } } @@ -196,20 +219,16 @@ function loadNeededInfoFromDatabase() { } } -/** - * @param {Expression} expression - * @param {SearchContext} searchContext - * @returns {SearchResult[]} - */ -function findResultsWithExpression(expression, searchContext) { +function findResultsWithExpression(expression: Expression, searchContext: SearchContext): SearchResult[] { if (searchContext.dbLoadNeeded) { loadNeededInfoFromDatabase(); } const allNoteSet = becca.getAllNoteSet(); + const noteIdToNotePath: Record = {}; const executionContext = { - noteIdToNotePath: {} + noteIdToNotePath }; const noteSet = expression.execute(allNoteSet, executionContext, searchContext); @@ -250,16 +269,16 @@ function findResultsWithExpression(expression, searchContext) { return searchResults; } -function parseQueryToExpression(query, searchContext) { +function parseQueryToExpression(query: string, searchContext: SearchContext) { const {fulltextQuery, fulltextTokens, expressionTokens} = lex(query); searchContext.fulltextQuery = fulltextQuery; - let structuredExpressionTokens; + let structuredExpressionTokens: (TokenData | TokenData[])[]; try { structuredExpressionTokens = handleParens(expressionTokens); } - catch (e) { + catch (e: any) { structuredExpressionTokens = []; searchContext.addError(e.message); } @@ -284,23 +303,13 @@ function parseQueryToExpression(query, searchContext) { return expression; } -/** - * @param {string} query - * @param {object} params - see SearchContext - * @returns {BNote[]} - */ -function searchNotes(query, params = {}) { +function searchNotes(query: string, params: SearchParams = {}): BNote[] { const searchResults = findResultsWithQuery(query, new SearchContext(params)); return searchResults.map(sr => becca.notes[sr.noteId]); } -/** - * @param {string} query - * @param {SearchContext} searchContext - * @returns {SearchResult[]} - */ -function findResultsWithQuery(query, searchContext) { +function findResultsWithQuery(query: string, searchContext: SearchContext): SearchResult[] { query = query || ""; searchContext.originalQuery = query; @@ -313,18 +322,13 @@ function findResultsWithQuery(query, searchContext) { return findResultsWithExpression(expression, searchContext); } -/** - * @param {string} query - * @param {SearchContext} searchContext - * @returns {BNote|null} - */ -function findFirstNoteWithQuery(query, searchContext) { +function findFirstNoteWithQuery(query: string, searchContext: SearchContext): BNote | null { const searchResults = findResultsWithQuery(query, searchContext); return searchResults.length > 0 ? becca.notes[searchResults[0].noteId] : null; } -function searchNotesForAutocomplete(query) { +function searchNotesForAutocomplete(query: string) { const searchContext = new SearchContext({ fastSearch: true, includeArchivedNotes: false, @@ -351,7 +355,7 @@ function searchNotesForAutocomplete(query) { }); } -function highlightSearchResults(searchResults, highlightedTokens) { +function highlightSearchResults(searchResults: SearchResult[], highlightedTokens: string[]) { highlightedTokens = Array.from(new Set(highlightedTokens)); // we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks @@ -387,7 +391,7 @@ function highlightSearchResults(searchResults, highlightedTokens) { } } - function wrapText(text, start, length, prefix, suffix) { + function wrapText(text: string, start: number, length: number, prefix: string, suffix: string) { return text.substring(0, start) + prefix + text.substr(start, length) + suffix + text.substring(start + length); } @@ -403,6 +407,7 @@ function highlightSearchResults(searchResults, highlightedTokens) { let match; // Find all matches + if (!result.highlightedNotePathTitle) { continue; } while ((match = tokenRegex.exec(normalizeString(result.highlightedNotePathTitle))) !== null) { result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}"); @@ -413,6 +418,7 @@ function highlightSearchResults(searchResults, highlightedTokens) { } for (const result of searchResults) { + if (!result.highlightedNotePathTitle) { continue; } result.highlightedNotePathTitle = result.highlightedNotePathTitle .replace(/"/g, "") .replace(/'/g, "") @@ -421,7 +427,7 @@ function highlightSearchResults(searchResults, highlightedTokens) { } } -function formatAttribute(attr) { +function formatAttribute(attr: BAttribute) { if (attr.type === 'relation') { return `~${utils.escapeHtml(attr.name)}=…`; } @@ -438,7 +444,7 @@ function formatAttribute(attr) { } } -module.exports = { +export = { searchFromNote, searchNotesForAutocomplete, findResultsWithQuery, diff --git a/src/services/search/services/types.ts b/src/services/search/services/types.ts new file mode 100644 index 000000000..09450f760 --- /dev/null +++ b/src/services/search/services/types.ts @@ -0,0 +1,20 @@ +export interface TokenData { + token: string; + inQuotes?: boolean; + startIndex?: number; + endIndex?: number; +} + +export interface SearchParams { + fastSearch?: boolean; + includeArchivedNotes?: boolean; + includeHiddenNotes?: boolean; + ignoreHoistedNote?: boolean; + ancestorNoteId?: string; + ancestorDepth?: string; + orderBy?: string; + orderDirection?: string; + limit?: number | null; + debug?: boolean; + fuzzyAttributeSearch?: boolean; +} \ No newline at end of file diff --git a/src/services/search/value_extractor.js b/src/services/search/value_extractor.ts similarity index 89% rename from src/services/search/value_extractor.js rename to src/services/search/value_extractor.ts index 27aff0e6c..4f2466f11 100644 --- a/src/services/search/value_extractor.js +++ b/src/services/search/value_extractor.ts @@ -1,10 +1,12 @@ "use strict"; +import BNote = require("../../becca/entities/bnote"); + /** * Search string is lower cased for case-insensitive comparison. But when retrieving properties, * we need a case-sensitive form, so we have this translation object. */ -const PROP_MAPPING = { +const PROP_MAPPING: Record = { "noteid": "noteId", "title": "title", "type": "type", @@ -32,8 +34,14 @@ const PROP_MAPPING = { "revisioncount": "revisionCount" }; +interface SearchContext { + dbLoadNeeded: boolean; +} + class ValueExtractor { - constructor(searchContext, propertyPath) { + private propertyPath: string[]; + + constructor(searchContext: SearchContext, propertyPath: string[]) { this.propertyPath = propertyPath.map(pathEl => pathEl.toLowerCase()); if (this.propertyPath[0].startsWith('#')) { @@ -81,10 +89,10 @@ class ValueExtractor { } } - extract(note) { - let cursor = note; + extract(note: BNote) { + let cursor: BNote | null = note; - let i; + let i: number = 0; const cur = () => this.propertyPath[i]; @@ -105,8 +113,7 @@ class ValueExtractor { i++; const attr = cursor.getAttributeCaseInsensitive('relation', cur()); - - cursor = attr ? attr.targetNote : null; + cursor = attr?.targetNote || null; } else if (cur() === 'parents') { cursor = cursor.parents[0]; @@ -118,7 +125,7 @@ class ValueExtractor { return Math.random().toString(); // string is expected for comparison } else if (cur() in PROP_MAPPING) { - return cursor[PROP_MAPPING[cur()]]; + return (cursor as any)[PROP_MAPPING[cur()]]; } else { // FIXME @@ -127,4 +134,4 @@ class ValueExtractor { } } -module.exports = ValueExtractor; +export = ValueExtractor; diff --git a/src/services/session_secret.js b/src/services/session_secret.ts similarity index 55% rename from src/services/session_secret.js rename to src/services/session_secret.ts index 16c1ed8c3..3721cf63f 100644 --- a/src/services/session_secret.js +++ b/src/services/session_secret.ts @@ -1,15 +1,17 @@ "use strict"; -const fs = require('fs'); -const crypto = require('crypto'); -const dataDir = require('./data_dir'); -const log = require('./log'); +import fs = require('fs'); +import crypto = require('crypto'); +import dataDir = require('./data_dir'); +import log = require('./log'); const sessionSecretPath = `${dataDir.TRILIUM_DATA_DIR}/session_secret.txt`; let sessionSecret; -function randomValueHex(len) { +const ENCODING = "ascii"; + +function randomValueHex(len: number) { return crypto.randomBytes(Math.ceil(len / 2)) .toString('hex') // convert to hexadecimal format .slice(0, len).toUpperCase(); // return required number of characters @@ -20,10 +22,10 @@ if (!fs.existsSync(sessionSecretPath)) { log.info("Generated session secret"); - fs.writeFileSync(sessionSecretPath, sessionSecret, 'ASCII'); + fs.writeFileSync(sessionSecretPath, sessionSecret, ENCODING); } else { - sessionSecret = fs.readFileSync(sessionSecretPath, 'ASCII'); + sessionSecret = fs.readFileSync(sessionSecretPath, ENCODING); } -module.exports = sessionSecret; +export = sessionSecret; diff --git a/src/services/setup.js b/src/services/setup.js index 55e559e87..9c5d0c0c4 100644 --- a/src/services/setup.js +++ b/src/services/setup.js @@ -1,4 +1,4 @@ -const syncService = require('./sync.js'); +const syncService = require('./sync'); const log = require('./log'); const sqlInit = require('./sql_init'); const optionService = require('./options'); diff --git a/src/services/special_notes.js b/src/services/special_notes.js index 749f43325..cb4108cc4 100644 --- a/src/services/special_notes.js +++ b/src/services/special_notes.js @@ -1,12 +1,12 @@ -const attributeService = require('./attributes.js'); -const dateNoteService = require('./date_notes.js'); +const attributeService = require('./attributes'); +const dateNoteService = require('./date_notes'); const becca = require('../becca/becca'); const noteService = require('./notes'); const dateUtils = require('./date_utils'); const log = require('./log'); -const hoistedNoteService = require('./hoisted_note.js'); -const searchService = require('./search/services/search.js'); -const SearchContext = require('./search/search_context.js'); +const hoistedNoteService = require('./hoisted_note'); +const searchService = require('./search/services/search'); +const SearchContext = require('./search/search_context'); const {LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT} = require('./hidden_subtree'); function getInboxNote(date) { diff --git a/src/services/sql.ts b/src/services/sql.ts index 671b136d2..4aa367fbe 100644 --- a/src/services/sql.ts +++ b/src/services/sql.ts @@ -147,12 +147,12 @@ function getRawRows(query: string, params: Params = [] return (wrap(query, s => s.raw().all(params)) as T[]) || []; } -function iterateRows(query: string, params: Params = []) { +function iterateRows(query: string, params: Params = []): IterableIterator { if (LOG_ALL_QUERIES) { console.log(query); } - return stmt(query).iterate(params); + return stmt(query).iterate(params) as IterableIterator; } function getMap(query: string, params: Params = []) { diff --git a/src/services/sql_init.ts b/src/services/sql_init.ts index af193d236..c8cae5ad2 100644 --- a/src/services/sql_init.ts +++ b/src/services/sql_init.ts @@ -84,7 +84,7 @@ async function createInitialDatabase() { notePosition: 10 }).save(); - const optionsInitService = require('./options_init.js'); + const optionsInitService = require('./options_init'); optionsInitService.initDocumentOptions(); optionsInitService.initNotSyncedOptions(true, {}); @@ -132,7 +132,7 @@ function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncPr sql.transactional(() => { sql.executeScript(schema); - require('./options_init.js').initNotSyncedOptions(false, { syncServerHost, syncProxy }); + require('./options_init').initNotSyncedOptions(false, { syncServerHost, syncProxy }); // document options required for sync to kick off for (const opt of options) { diff --git a/src/services/sync.js b/src/services/sync.ts similarity index 75% rename from src/services/sync.js rename to src/services/sync.ts index 347554528..da559e36a 100644 --- a/src/services/sync.js +++ b/src/services/sync.ts @@ -1,27 +1,50 @@ "use strict"; -const log = require('./log'); -const sql = require('./sql'); -const optionService = require('./options'); -const utils = require('./utils'); -const instanceId = require('./instance_id'); -const dateUtils = require('./date_utils'); -const syncUpdateService = require('./sync_update.js'); -const contentHashService = require('./content_hash.js'); -const appInfo = require('./app_info'); -const syncOptions = require('./sync_options'); -const syncMutexService = require('./sync_mutex'); -const cls = require('./cls'); -const request = require('./request'); -const ws = require('./ws'); -const entityChangesService = require('./entity_changes'); -const entityConstructor = require('../becca/entity_constructor'); -const becca = require('../becca/becca'); +import log = require('./log'); +import sql = require('./sql'); +import optionService = require('./options'); +import utils = require('./utils'); +import instanceId = require('./instance_id'); +import dateUtils = require('./date_utils'); +import syncUpdateService = require('./sync_update'); +import contentHashService = require('./content_hash'); +import appInfo = require('./app_info'); +import syncOptions = require('./sync_options'); +import syncMutexService = require('./sync_mutex'); +import cls = require('./cls'); +import request = require('./request'); +import ws = require('./ws'); +import entityChangesService = require('./entity_changes'); +import entityConstructor = require('../becca/entity_constructor'); +import becca = require('../becca/becca'); +import { EntityChange, EntityChangeRecord, EntityRow } from './entity_changes_interface'; +import { CookieJar, ExecOpts } from './request_interface'; let proxyToggle = true; let outstandingPullCount = 0; +interface CheckResponse { + maxEntityChangeId: number; + entityHashes: Record> +} + +interface SyncResponse { + instanceId: string; + maxEntityChangeId: number; +} + +interface ChangesResponse { + entityChanges: EntityChangeRecord[]; + lastEntityChangeId: number; + outstandingPullCount: number; +} + +interface SyncContext { + cookieJar: CookieJar; + instanceId?: string; +} + async function sync() { try { return await syncMutexService.doExclusively(async () => { @@ -53,7 +76,7 @@ async function sync() { }; }); } - catch (e) { + catch (e: any) { // we're dynamically switching whether we're using proxy or not based on whether we encountered error with the current method proxyToggle = !proxyToggle; @@ -93,19 +116,23 @@ async function login() { return await doLogin(); } -async function doLogin() { +async function doLogin(): Promise { const timestamp = dateUtils.utcNowDateTime(); const documentSecret = optionService.getOption('documentSecret'); const hash = utils.hmac(documentSecret, timestamp); - const syncContext = { cookieJar: {} }; - const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { + const syncContext: SyncContext = { cookieJar: {} }; + const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { timestamp: timestamp, syncVersion: appInfo.syncVersion, hash: hash }); + if (!resp) { + throw new Error("Got no response."); + } + if (resp.instanceId === instanceId) { throw new Error(`Sync server has instance ID '${resp.instanceId}' which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`); } @@ -125,7 +152,7 @@ async function doLogin() { return syncContext; } -async function pullChanges(syncContext) { +async function pullChanges(syncContext: SyncContext) { while (true) { const lastSyncedPull = getLastSyncedPull(); const logMarkerId = utils.randomString(10); // to easily pair sync events between client and server logs @@ -133,7 +160,10 @@ async function pullChanges(syncContext) { const startDate = Date.now(); - const resp = await syncRequest(syncContext, 'GET', changesUri); + const resp = await syncRequest(syncContext, 'GET', changesUri); + if (!resp) { + throw new Error("Request failed."); + } const {entityChanges, lastEntityChangeId} = resp; outstandingPullCount = resp.outstandingPullCount; @@ -141,7 +171,9 @@ async function pullChanges(syncContext) { const pulledDate = Date.now(); sql.transactional(() => { - syncUpdateService.updateEntities(entityChanges, syncContext.instanceId); + if (syncContext.instanceId) { + syncUpdateService.updateEntities(entityChanges, syncContext.instanceId); + } if (lastSyncedPull !== lastEntityChangeId) { setLastSyncedPull(lastEntityChangeId); @@ -156,7 +188,7 @@ async function pullChanges(syncContext) { log.info(`Sync ${logMarkerId}: Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`); } - catch (e) { + catch (e: any) { log.error(`Error occurred ${e.message} ${e.stack}`); } } @@ -165,11 +197,11 @@ async function pullChanges(syncContext) { log.info("Finished pull"); } -async function pushChanges(syncContext) { - let lastSyncedPush = getLastSyncedPush(); +async function pushChanges(syncContext: SyncContext) { + let lastSyncedPush: number | null | undefined = getLastSyncedPush(); while (true) { - const entityChanges = sql.getRows('SELECT * FROM entity_changes WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]); + const entityChanges = sql.getRows('SELECT * FROM entity_changes WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]); if (entityChanges.length === 0) { log.info("Nothing to push"); @@ -190,7 +222,7 @@ async function pushChanges(syncContext) { } }); - if (filteredEntityChanges.length === 0) { + if (filteredEntityChanges.length === 0 && lastSyncedPush) { // there still might be more sync changes (because of batch limit), just all the current batch // has been filtered out setLastSyncedPush(lastSyncedPush); @@ -214,16 +246,22 @@ async function pushChanges(syncContext) { lastSyncedPush = entityChangesRecords[entityChangesRecords.length - 1].entityChange.id; - setLastSyncedPush(lastSyncedPush); + if (lastSyncedPush) { + setLastSyncedPush(lastSyncedPush); + } } } -async function syncFinished(syncContext) { +async function syncFinished(syncContext: SyncContext) { await syncRequest(syncContext, 'POST', '/api/sync/finished'); } -async function checkContentHash(syncContext) { - const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); +async function checkContentHash(syncContext: SyncContext) { + const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); + if (!resp) { + throw new Error("Got no response."); + } + const lastSyncedPullId = getLastSyncedPull(); if (lastSyncedPullId < resp.maxEntityChangeId) { @@ -261,8 +299,12 @@ async function checkContentHash(syncContext) { const PAGE_SIZE = 1000000; -async function syncRequest(syncContext, method, requestPath, body) { - body = body ? JSON.stringify(body) : ''; +interface SyncContext { + cookieJar: CookieJar +} + +async function syncRequest(syncContext: SyncContext, method: string, requestPath: string, _body?: {}) { + const body = _body ? JSON.stringify(_body) : ''; const timeout = syncOptions.getSyncTimeout(); @@ -272,7 +314,7 @@ async function syncRequest(syncContext, method, requestPath, body) { const pageCount = Math.max(1, Math.ceil(body.length / PAGE_SIZE)); for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) { - const opts = { + const opts: ExecOpts = { method, url: syncOptions.getSyncServerHost() + requestPath, cookieJar: syncContext.cookieJar, @@ -286,13 +328,13 @@ async function syncRequest(syncContext, method, requestPath, body) { proxy: proxyToggle ? syncOptions.getSyncProxy() : null }; - response = await utils.timeLimit(request.exec(opts), timeout); + response = await utils.timeLimit(request.exec(opts), timeout) as T; } return response; } -function getEntityChangeRow(entityChange) { +function getEntityChangeRow(entityChange: EntityChange) { const {entityName, entityId} = entityChange; if (entityName === 'note_reordering') { @@ -305,7 +347,7 @@ function getEntityChangeRow(entityChange) { throw new Error(`Unknown entity for entity change ${JSON.stringify(entityChange)}`); } - const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); + const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); if (!entityRow) { log.error(`Cannot find entity for entity change ${JSON.stringify(entityChange)}`); @@ -317,15 +359,17 @@ function getEntityChangeRow(entityChange) { entityRow.content = Buffer.from(entityRow.content, 'utf-8'); } - entityRow.content = entityRow.content.toString("base64"); + if (entityRow.content) { + entityRow.content = entityRow.content.toString("base64"); + } } return entityRow; } } -function getEntityChangeRecords(entityChanges) { - const records = []; +function getEntityChangeRecords(entityChanges: EntityChange[]) { + const records: EntityChangeRecord[] = []; let length = 0; for (const entityChange of entityChanges) { @@ -340,7 +384,7 @@ function getEntityChangeRecords(entityChanges) { continue; } - const record = { entityChange, entity }; + const record: EntityChangeRecord = { entityChange, entity }; records.push(record); @@ -359,7 +403,7 @@ function getLastSyncedPull() { return parseInt(optionService.getOption('lastSyncedPull')); } -function setLastSyncedPull(entityChangeId) { +function setLastSyncedPull(entityChangeId: number) { const lastSyncedPullOption = becca.getOption('lastSyncedPull'); if (lastSyncedPullOption) { // might be null in initial sync when becca is not loaded @@ -378,7 +422,7 @@ function getLastSyncedPush() { return lastSyncedPush; } -function setLastSyncedPush(entityChangeId) { +function setLastSyncedPush(entityChangeId: number) { ws.setLastSyncedPush(entityChangeId); const lastSyncedPushOption = becca.getOption('lastSyncedPush'); @@ -409,7 +453,7 @@ require('../becca/becca_loader').beccaLoaded.then(() => { getLastSyncedPush(); }); -module.exports = { +export = { sync, login, getEntityChangeRecords, diff --git a/src/services/sync_update.js b/src/services/sync_update.ts similarity index 69% rename from src/services/sync_update.js rename to src/services/sync_update.ts index af7b87e11..888947b8b 100644 --- a/src/services/sync_update.js +++ b/src/services/sync_update.ts @@ -1,11 +1,18 @@ -const sql = require('./sql'); -const log = require('./log'); -const entityChangesService = require('./entity_changes'); -const eventService = require('./events'); -const entityConstructor = require('../becca/entity_constructor'); -const ws = require('./ws'); +import sql = require('./sql'); +import log = require('./log'); +import entityChangesService = require('./entity_changes'); +import eventService = require('./events'); +import entityConstructor = require('../becca/entity_constructor'); +import ws = require('./ws'); +import { EntityChange, EntityChangeRecord, EntityRow } from './entity_changes_interface'; -function updateEntities(entityChanges, instanceId) { +interface UpdateContext { + alreadyErased: number; + erased: number; + updated: Record +} + +function updateEntities(entityChanges: EntityChangeRecord[], instanceId: string) { if (entityChanges.length === 0) { return; } @@ -34,13 +41,15 @@ function updateEntities(entityChanges, instanceId) { atLeastOnePullApplied = true; } - updateEntity(entityChange, entity, instanceId, updateContext); + if (entity) { + updateEntity(entityChange, entity, instanceId, updateContext); + } } logUpdateContext(updateContext); } -function updateEntity(remoteEC, remoteEntityRow, instanceId, updateContext) { +function updateEntity(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string, updateContext: UpdateContext) { if (!remoteEntityRow && remoteEC.entityName === 'options') { return; // can be undefined for options with isSynced=false } @@ -65,8 +74,12 @@ function updateEntity(remoteEC, remoteEntityRow, instanceId, updateContext) { } } -function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext) { - const localEC = sql.getRow(`SELECT * FROM entity_changes WHERE entityName = ? AND entityId = ?`, [remoteEC.entityName, remoteEC.entityId]); +function updateNormalEntity(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string, updateContext: UpdateContext) { + const localEC = sql.getRow(`SELECT * FROM entity_changes WHERE entityName = ? AND entityId = ?`, [remoteEC.entityName, remoteEC.entityId]); + + if (!localEC.utcDateChanged || !remoteEC.utcDateChanged) { + throw new Error("Missing date changed."); + } if (!localEC || localEC.utcDateChanged <= remoteEC.utcDateChanged) { if (remoteEC.isErased) { @@ -110,28 +123,30 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext return false; } -function preProcessContent(remoteEC, remoteEntityRow) { +function preProcessContent(remoteEC: EntityChange, remoteEntityRow: EntityRow) { if (remoteEC.entityName === 'blobs' && remoteEntityRow.content !== null) { // we always use a Buffer object which is different from normal saving - there we use a simple string type for // "string notes". The problem is that in general, it's not possible to detect whether a blob content // is string note or note (syncs can arrive out of order) - remoteEntityRow.content = Buffer.from(remoteEntityRow.content, 'base64'); + if (typeof remoteEntityRow.content === "string") { + remoteEntityRow.content = Buffer.from(remoteEntityRow.content, 'base64'); - if (remoteEntityRow.content.byteLength === 0) { - // there seems to be a bug which causes empty buffer to be stored as NULL which is then picked up as inconsistency - // (possibly not a problem anymore with the newer better-sqlite3) - remoteEntityRow.content = ""; + if (remoteEntityRow.content.byteLength === 0) { + // there seems to be a bug which causes empty buffer to be stored as NULL which is then picked up as inconsistency + // (possibly not a problem anymore with the newer better-sqlite3) + remoteEntityRow.content = ""; + } } } } -function updateNoteReordering(remoteEC, remoteEntityRow, instanceId) { +function updateNoteReordering(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string) { if (!remoteEntityRow) { throw new Error(`Empty note_reordering body for: ${JSON.stringify(remoteEC)}`); } for (const key in remoteEntityRow) { - sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [remoteEntityRow[key], key]); + sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [remoteEntityRow[key as keyof EntityRow], key]); } entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId); @@ -139,7 +154,7 @@ function updateNoteReordering(remoteEC, remoteEntityRow, instanceId) { return true; } -function eraseEntity(entityChange) { +function eraseEntity(entityChange: EntityChange) { const {entityName, entityId} = entityChange; const entityNames = [ @@ -161,7 +176,7 @@ function eraseEntity(entityChange) { sql.execute(`DELETE FROM ${entityName} WHERE ${primaryKeyName} = ?`, [entityId]); } -function logUpdateContext(updateContext) { +function logUpdateContext(updateContext: UpdateContext) { const message = JSON.stringify(updateContext) .replaceAll('"', '') .replaceAll(":", ": ") @@ -170,6 +185,6 @@ function logUpdateContext(updateContext) { log.info(message.substr(1, message.length - 2)); } -module.exports = { +export = { updateEntities }; diff --git a/src/services/tray.js b/src/services/tray.ts similarity index 84% rename from src/services/tray.js rename to src/services/tray.ts index 7d8e34e1e..48a8ebaae 100644 --- a/src/services/tray.js +++ b/src/services/tray.ts @@ -1,13 +1,9 @@ -const { Menu, Tray } = require('electron'); -const path = require('path'); -const windowService = require('./window.js'); -const optionService = require('./options'); +import { Menu, Tray } from 'electron'; +import path = require('path'); +import windowService = require('./window'); +import optionService = require('./options'); -const UPDATE_TRAY_EVENTS = [ - 'minimize', 'maximize', 'show', 'hide' -] - -let tray = null; +let tray: Tray; // `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window // is minimized let isVisible = true; @@ -37,22 +33,25 @@ const getIconPath = () => { } const registerVisibilityListener = () => { const mainWindow = windowService.getMainWindow(); + if (!mainWindow) { return; } // They need to be registered before the tray updater is registered mainWindow.on('show', () => { isVisible = true; + updateTrayMenu(); }); mainWindow.on('hide', () => { isVisible = false; + updateTrayMenu(); }); - UPDATE_TRAY_EVENTS.forEach(eventName => { - mainWindow.on(eventName, updateTrayMenu) - }); + mainWindow.on("minimize", updateTrayMenu); + mainWindow.on("maximize", updateTrayMenu); } const updateTrayMenu = () => { const mainWindow = windowService.getMainWindow(); + if (!mainWindow) { return; } const contextMenu = Menu.buildFromTemplate([ { @@ -83,6 +82,7 @@ const updateTrayMenu = () => { } const changeVisibility = () => { const window = windowService.getMainWindow(); + if (!window) { return; } if (isVisible) { window.hide(); @@ -106,6 +106,6 @@ function createTray() { registerVisibilityListener(); } -module.exports = { +export = { createTray } diff --git a/src/services/tree.js b/src/services/tree.ts similarity index 79% rename from src/services/tree.js rename to src/services/tree.ts index 66276ab2f..d731fe6b0 100644 --- a/src/services/tree.js +++ b/src/services/tree.ts @@ -1,12 +1,13 @@ "use strict"; -const sql = require('./sql'); -const log = require('./log'); -const BBranch = require('../becca/entities/bbranch'); -const entityChangesService = require('./entity_changes'); -const becca = require('../becca/becca'); +import sql = require('./sql'); +import log = require('./log'); +import BBranch = require('../becca/entities/bbranch'); +import entityChangesService = require('./entity_changes'); +import becca = require('../becca/becca'); +import BNote = require('../becca/entities/bnote'); -function validateParentChild(parentNoteId, childNoteId, branchId = null) { +function validateParentChild(parentNoteId: string, childNoteId: string, branchId: string | null = null) { if (['root', '_hidden', '_share', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(childNoteId)) { return { branch: null, success: false, message: `Cannot change this note's location.` }; } @@ -25,7 +26,7 @@ function validateParentChild(parentNoteId, childNoteId, branchId = null) { return { branch: existingBranch, success: false, - message: `Note "${childNote.title}" note already exists in the "${parentNote.title}".` + message: `Note "${childNote?.title}" note already exists in the "${parentNote?.title}".` }; } @@ -37,7 +38,7 @@ function validateParentChild(parentNoteId, childNoteId, branchId = null) { }; } - if (parentNoteId !== '_lbBookmarks' && becca.getNote(parentNoteId).type === 'launcher') { + if (parentNoteId !== '_lbBookmarks' && becca.getNote(parentNoteId)?.type === 'launcher') { return { branch: null, success: false, @@ -51,7 +52,7 @@ function validateParentChild(parentNoteId, childNoteId, branchId = null) { /** * Tree cycle can be created when cloning or when moving existing clone. This method should detect both cases. */ -function wouldAddingBranchCreateCycle(parentNoteId, childNoteId) { +function wouldAddingBranchCreateCycle(parentNoteId: string, childNoteId: string) { if (parentNoteId === childNoteId) { return true; } @@ -70,20 +71,22 @@ function wouldAddingBranchCreateCycle(parentNoteId, childNoteId) { return parentAncestorNoteIds.some(parentAncestorNoteId => childSubtreeNoteIds.has(parentAncestorNoteId)); } -function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, foldersFirst = false, sortNatural = false, sortLocale) { +function sortNotes(parentNoteId: string, customSortBy: string = 'title', reverse = false, foldersFirst = false, sortNatural = false, _sortLocale?: string | null) { if (!customSortBy) { customSortBy = 'title'; } - if (!sortLocale) { - // sortLocale can not be empty string or null value, default value must be set to undefined. - sortLocale = undefined; - } + // sortLocale can not be empty string or null value, default value must be set to undefined. + const sortLocale = (_sortLocale || undefined); sql.transactional(() => { - const notes = becca.getNote(parentNoteId).getChildNotes(); + const note = becca.getNote(parentNoteId); + if (!note) { + throw new Error("Unable to find note"); + } - const normalize = obj => (obj && typeof obj === 'string') ? obj.toLowerCase() : obj; + const notes = note.getChildNotes(); + const normalize = (obj: any) => (obj && typeof obj === 'string') ? obj.toLowerCase() : obj; notes.sort((a, b) => { if (foldersFirst) { @@ -96,7 +99,7 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder } } - function fetchValue(note, key) { + function fetchValue(note: BNote, key: string) { let rawValue; if (key === 'title') { @@ -105,14 +108,14 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder rawValue = prefix ? `${prefix} - ${note.title}` : note.title; } else { rawValue = ['dateCreated', 'dateModified'].includes(key) - ? note[key] + ? (note as any)[key] : note.getLabelValue(key); } return normalize(rawValue); } - function compare(a, b) { + function compare(a: string, b: string) { if (!sortNatural) { // alphabetical sort return b === null || b === undefined || a < b ? -1 : 1; @@ -160,6 +163,7 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder for (const note of notes) { const branch = note.getParentBranches().find(b => b.parentNoteId === parentNoteId); + if (!branch) { continue; } if (branch.noteId === '_hidden') { position = 999_999_999; @@ -182,9 +186,8 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder }); } -function sortNotesIfNeeded(parentNoteId) { +function sortNotesIfNeeded(parentNoteId: string) { const parentNote = becca.getNote(parentNoteId); - if (!parentNote) { return; } @@ -206,7 +209,7 @@ function sortNotesIfNeeded(parentNoteId) { /** * @deprecated this will be removed in the future */ -function setNoteToParent(noteId, prefix, parentNoteId) { +function setNoteToParent(noteId: string, prefix: string, parentNoteId: string) { const parentNote = becca.getNote(parentNoteId); if (parentNoteId && !parentNote) { @@ -215,7 +218,7 @@ function setNoteToParent(noteId, prefix, parentNoteId) { } // case where there might be more such branches is ignored. It's expected there should be just one - const branchId = sql.getValue("SELECT branchId FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]); + const branchId = sql.getValue("SELECT branchId FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]); const branch = becca.getBranch(branchId); if (branch) { @@ -233,12 +236,15 @@ function setNoteToParent(noteId, prefix, parentNoteId) { } else if (parentNoteId) { const note = becca.getNote(noteId); + if (!note) { + throw new Error(`Cannot find note '${noteId}.`); + } if (note.isDeleted) { throw new Error(`Cannot create a branch for '${noteId}' which is deleted.`); } - const branchId = sql.getValue('SELECT branchId FROM branches WHERE isDeleted = 0 AND noteId = ? AND parentNoteId = ?', [noteId, parentNoteId]); + const branchId = sql.getValue('SELECT branchId FROM branches WHERE isDeleted = 0 AND noteId = ? AND parentNoteId = ?', [noteId, parentNoteId]); const branch = becca.getBranch(branchId); if (branch) { @@ -255,7 +261,7 @@ function setNoteToParent(noteId, prefix, parentNoteId) { } } -module.exports = { +export = { validateParentChild, sortNotes, sortNotesIfNeeded, diff --git a/src/services/utils.ts b/src/services/utils.ts index dc6129770..947dfac9c 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -238,7 +238,7 @@ function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, n } } -function timeLimit(promise: Promise, limitMs: number, errorMessage: string): Promise { +function timeLimit(promise: Promise, limitMs: number, errorMessage?: string): Promise { if (!promise || !promise.then) { // it's not actually a promise return promise; } diff --git a/src/services/window.js b/src/services/window.ts similarity index 84% rename from src/services/window.js rename to src/services/window.ts index 31bf373c5..80f7f7e83 100644 --- a/src/services/window.js +++ b/src/services/window.ts @@ -1,21 +1,20 @@ -const path = require('path'); -const url = require("url"); -const port = require('./port'); -const optionService = require('./options'); -const env = require('./env'); -const log = require('./log'); -const sqlInit = require('./sql_init'); -const cls = require('./cls'); -const keyboardActionsService = require('./keyboard_actions'); -const { ipcMain } = require('electron'); +import path = require('path'); +import url = require("url"); +import port = require('./port'); +import optionService = require('./options'); +import env = require('./env'); +import log = require('./log'); +import sqlInit = require('./sql_init'); +import cls = require('./cls'); +import keyboardActionsService = require('./keyboard_actions'); +import remoteMain = require("@electron/remote/main") +import { App, BrowserWindow, WebContents, ipcMain } from 'electron'; // Prevent the window being garbage collected -/** @type {Electron.BrowserWindow} */ -let mainWindow; -/** @type {Electron.BrowserWindow} */ -let setupWindow; +let mainWindow: BrowserWindow | null; +let setupWindow: BrowserWindow | null; -async function createExtraWindow(extraWindowHash) { +async function createExtraWindow(extraWindowHash: string) { const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled'); const { BrowserWindow } = require('electron'); @@ -25,7 +24,6 @@ async function createExtraWindow(extraWindowHash) { height: 800, title: 'Trilium Notes', webPreferences: { - enableRemoteModule: true, nodeIntegration: true, contextIsolation: false, spellcheck: spellcheckEnabled @@ -44,7 +42,7 @@ ipcMain.on('create-extra-window', (event, arg) => { createExtraWindow(arg.extraWindowHash); }); -async function createMainWindow(app) { +async function createMainWindow(app: App) { const windowStateKeeper = require('electron-window-state'); // should not be statically imported const mainWindowState = windowStateKeeper({ @@ -64,7 +62,6 @@ async function createMainWindow(app) { height: mainWindowState.height, title: 'Trilium Notes', webPreferences: { - enableRemoteModule: true, nodeIntegration: true, contextIsolation: false, spellcheck: spellcheckEnabled, @@ -95,8 +92,12 @@ async function createMainWindow(app) { }); } -function configureWebContents(webContents, spellcheckEnabled) { - require("@electron/remote/main").enable(webContents); +function configureWebContents(webContents: WebContents, spellcheckEnabled: boolean) { + if (!mainWindow) { + return; + } + + remoteMain.enable(webContents); mainWindow.webContents.setWindowOpenHandler((details) => { require("electron").shell.openExternal(details.url); @@ -108,8 +109,7 @@ function configureWebContents(webContents, spellcheckEnabled) { const parsedUrl = url.parse(targetUrl); // we still need to allow internal redirects from setup and migration pages - if (!['localhost', '127.0.0.1'].includes(parsedUrl.hostname) || (parsedUrl.path && parsedUrl.path !== '/' && parsedUrl.path !== '/?')) { - + if (!['localhost', '127.0.0.1'].includes(parsedUrl.hostname || "") || (parsedUrl.path && parsedUrl.path !== '/' && parsedUrl.path !== '/?')) { ev.preventDefault(); } }); @@ -168,6 +168,10 @@ async function registerGlobalShortcuts() { const translatedShortcut = shortcut.substr(7); const result = globalShortcut.register(translatedShortcut, cls.wrap(() => { + if (!mainWindow) { + return; + } + // window may be hidden / not in focus mainWindow.focus(); @@ -189,8 +193,7 @@ function getMainWindow() { return mainWindow; } - -module.exports = { +export = { createMainWindow, createSetupWindow, closeSetupWindow, diff --git a/src/share/routes.js b/src/share/routes.js index b40048797..4ad5a15c6 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -9,8 +9,8 @@ const shareRoot = require('./share_root.js'); const contentRenderer = require('./content_renderer.js'); const assetPath = require('../services/asset_path'); const appPath = require('../services/app_path'); -const searchService = require('../services/search/services/search.js'); -const SearchContext = require('../services/search/search_context.js'); +const searchService = require('../services/search/services/search'); +const SearchContext = require('../services/search/search_context'); const log = require('../services/log'); /** diff --git a/src/tools/generate_document.js b/src/tools/generate_document.js index 503fdc6ee..e716e7e01 100644 --- a/src/tools/generate_document.js +++ b/src/tools/generate_document.js @@ -6,9 +6,9 @@ require('../becca/entity_constructor'); const sqlInit = require('../services/sql_init'); const noteService = require('../services/notes'); -const attributeService = require('../services/attributes.js'); +const attributeService = require('../services/attributes'); const cls = require('../services/cls'); -const cloningService = require('../services/cloning.js'); +const cloningService = require('../services/cloning'); const loremIpsum = require('lorem-ipsum').loremIpsum; const noteCount = parseInt(process.argv[2]); diff --git a/src/types.d.ts b/src/types.d.ts index c4ccea844..602d291c4 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -11,4 +11,9 @@ declare module 'unescape' { declare module 'html2plaintext' { function html2plaintext(htmlText: string): string; export = html2plaintext; +} + +declare module 'normalize-strings' { + function normalizeString(string: string): string; + export = normalizeString; } \ No newline at end of file diff --git a/src/www.js b/src/www.js index 84f3abaa9..2059a3a7c 100644 --- a/src/www.js +++ b/src/www.js @@ -45,7 +45,7 @@ function startTrilium() { * instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium * if port and data dir are configured separately. This complication is the source of the following weird usage. * - * The line below makes sure that the "second-instance" (process in window.js) is fired. Normally it returns a boolean + * The line below makes sure that the "second-instance" (process in window.ts) is fired. Normally it returns a boolean * indicating whether another instance is running or not, but we ignore that and kill the app only based on the port conflict. * * A bit weird is that "second-instance" is triggered also on the valid usecases (different port/data dir) and @@ -126,26 +126,26 @@ function startHttpServer() { } httpServer.on('error', error => { - if (!listenOnTcp || error.syscall !== 'listen') { - throw error; - } - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(`Port ${port} requires elevated privileges. It's recommended to use port above 1024.`); - process.exit(1); - break; - - case 'EADDRINUSE': - console.error(`Port ${port} is already in use. Most likely, another Trilium process is already running. You might try to find it, kill it, and try again.`); - process.exit(1); - break; - - default: - throw error; - } + if (!listenOnTcp || error.syscall !== 'listen') { + throw error; } + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(`Port ${port} requires elevated privileges. It's recommended to use port above 1024.`); + process.exit(1); + break; + + case 'EADDRINUSE': + console.error(`Port ${port} is already in use. Most likely, another Trilium process is already running. You might try to find it, kill it, and try again.`); + process.exit(1); + break; + + default: + throw error; + } + } ) httpServer.on('listening', () => {