mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
Merge pull request #25 from TriliumNext/feature/typescript_backend_3
Convert backend to TypeScript (35% -> 50%)
This commit is contained in:
commit
8acfb5b558
@ -5,3 +5,6 @@ cloc HEAD \
|
||||
--include-lang=javascript,typescript \
|
||||
--found=filelist.txt \
|
||||
--exclude-dir=public,libraries
|
||||
|
||||
grep -R \.js$ filelist.txt
|
||||
rm filelist.txt
|
@ -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 };
|
||||
|
||||
|
@ -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')();
|
||||
|
@ -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", () => {
|
||||
|
@ -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", () => {
|
||||
|
@ -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 => {
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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');
|
||||
|
@ -20,7 +20,7 @@ const beccaLoaded = new Promise<void>((res, rej) => {
|
||||
cls.init(() => {
|
||||
load();
|
||||
|
||||
require('../services/options_init.js').initStartupOptions();
|
||||
require('../services/options_init').initStartupOptions();
|
||||
|
||||
res();
|
||||
});
|
||||
|
@ -222,7 +222,7 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> {
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -267,17 +267,19 @@ class BBranch extends AbstractBeccaEntity<BBranch> {
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
@ -78,13 +78,13 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
// 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<NoteRow>) {
|
||||
super();
|
||||
@ -450,7 +450,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
);
|
||||
}
|
||||
|
||||
getAttributeCaseInsensitive(type: string, name: string, value: string | null) {
|
||||
getAttributeCaseInsensitive(type: string, name: string, value?: string | null) {
|
||||
name = name.toLowerCase();
|
||||
value = value ? value.toLowerCase() : null;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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') {
|
||||
|
@ -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<string>(
|
||||
`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,
|
@ -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');
|
||||
|
@ -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<number | null>('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
|
||||
};
|
@ -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<string, ActionHandler> = {
|
||||
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
|
||||
};
|
@ -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,
|
@ -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<string, string>;
|
||||
|
||||
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<HashRow>(`
|
||||
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<string, SectorHash> = {};
|
||||
|
||||
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<string, SectorHash>) {
|
||||
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
|
||||
};
|
@ -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,
|
@ -13,3 +13,13 @@ export interface EntityChange {
|
||||
changeId?: string | null;
|
||||
instanceId?: string | null;
|
||||
}
|
||||
|
||||
export interface EntityRow {
|
||||
isDeleted?: boolean;
|
||||
content?: Buffer | string;
|
||||
}
|
||||
|
||||
export interface EntityChangeRecord {
|
||||
entityChange: EntityChange;
|
||||
entity?: EntityRow;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
@ -1,3 +0,0 @@
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0';
|
3
src/services/host.ts
Normal file
3
src/services/host.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import config = require('./config');
|
||||
|
||||
export = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0';
|
@ -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');
|
||||
|
@ -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.
|
||||
|
12
src/services/keyboard_actions_interface.ts
Normal file
12
src/services/keyboard_actions_interface.ts
Normal file
@ -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;
|
||||
}
|
@ -173,6 +173,7 @@ interface NoteParams {
|
||||
dateCreated?: string;
|
||||
utcDateCreated?: string;
|
||||
ignoreForbiddenParents?: boolean;
|
||||
target?: "into";
|
||||
}
|
||||
|
||||
function createNewNote(params: NoteParams): {
|
||||
|
@ -1,4 +1,4 @@
|
||||
const scheduledExecutions = {};
|
||||
const scheduledExecutions: Record<string, boolean> = {};
|
||||
|
||||
/**
|
||||
* 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
|
||||
};
|
@ -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
|
@ -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);
|
||||
|
20
src/services/request_interface.ts
Normal file
20
src/services/request_interface.ts
Normal file
@ -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;
|
||||
}
|
@ -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');
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
16
src/services/search/expressions/expression.ts
Normal file
16
src/services/search/expressions/expression.ts
Normal file
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
23
src/services/search/expressions/not.ts
Normal file
23
src/services/search/expressions/not.ts
Normal file
@ -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;
|
@ -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<string, RegExp> = {};
|
||||
|
||||
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<NoteRow, "noteId" | "type" | "mime" | "content" | "isProtected">;
|
||||
|
||||
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<SearchRow>(`
|
||||
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;
|
@ -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;
|
@ -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;
|
@ -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<OrderDefinition, "direction" | "valueExtractor">[], 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;
|
@ -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;
|
@ -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<string, string> = {
|
||||
"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;
|
@ -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;
|
@ -1,11 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const Expression = require('./expression.js');
|
||||
|
||||
class TrueExp extends Expression {
|
||||
execute(inputNoteSet, executionContext, searchContext) {
|
||||
return inputNoteSet;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TrueExp;
|
14
src/services/search/expressions/true.ts
Normal file
14
src/services/search/expressions/true.ts
Normal file
@ -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;
|
@ -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;
|
@ -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;
|
@ -1,6 +1,6 @@
|
||||
const cachedRegexes = {};
|
||||
const cachedRegexes: Record<string, RegExp> = {};
|
||||
|
||||
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<T> = (comparedValue: T) => ((val: string) => boolean);
|
||||
|
||||
const stringComparators: Record<string, Comparator<string>> = {
|
||||
"=": 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<string, Comparator<number>> = {
|
||||
">": 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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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.<string, Object.<string, int>>} - noteId => { blobId => blobSize }
|
||||
* noteId => { blobId => blobSize }
|
||||
*/
|
||||
const noteBlobs = {};
|
||||
const noteBlobs: Record<string, Record<string, number>> = {};
|
||||
|
||||
const noteContentLengths = sql.getRows(`
|
||||
type NoteContentLengthsRow = {
|
||||
noteId: string;
|
||||
blobId: string;
|
||||
length: number;
|
||||
};
|
||||
const noteContentLengths = sql.getRows<NoteContentLengthsRow>(`
|
||||
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<AttachmentContentLengthsRow>(`
|
||||
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<RevisionRow>(`
|
||||
SELECT
|
||||
noteId,
|
||||
revisions.blobId,
|
||||
@ -187,7 +207,10 @@ function loadNeededInfoFromDatabase() {
|
||||
noteBlobs[noteId][blobId] = length;
|
||||
|
||||
if (isNoteRevision) {
|
||||
becca.notes[noteId].revisionCount++;
|
||||
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<string, string[]> = {};
|
||||
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, "<small>")
|
||||
.replace(/'/g, "</small>")
|
||||
@ -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,
|
20
src/services/search/services/types.ts
Normal file
20
src/services/search/services/types.ts
Normal file
@ -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;
|
||||
}
|
@ -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<string, string> = {
|
||||
"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;
|
@ -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;
|
@ -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');
|
||||
|
@ -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) {
|
||||
|
@ -147,12 +147,12 @@ function getRawRows<T extends {} | unknown[]>(query: string, params: Params = []
|
||||
return (wrap(query, s => s.raw().all(params)) as T[]) || [];
|
||||
}
|
||||
|
||||
function iterateRows(query: string, params: Params = []) {
|
||||
function iterateRows<T>(query: string, params: Params = []): IterableIterator<T> {
|
||||
if (LOG_ALL_QUERIES) {
|
||||
console.log(query);
|
||||
}
|
||||
|
||||
return stmt(query).iterate(params);
|
||||
return stmt(query).iterate(params) as IterableIterator<T>;
|
||||
}
|
||||
|
||||
function getMap<K extends string | number | symbol, V>(query: string, params: Params = []) {
|
||||
|
@ -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) {
|
||||
|
@ -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<string, Record<string, string>>
|
||||
}
|
||||
|
||||
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<SyncContext> {
|
||||
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<SyncResponse>(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<ChangesResponse>(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<EntityChange>('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<CheckResponse>(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<T extends {}>(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<EntityRow>(`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,
|
@ -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<string, string[]>
|
||||
}
|
||||
|
||||
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<EntityChange>(`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
|
||||
};
|
@ -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
|
||||
}
|
@ -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<string>("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<string>('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,
|
@ -238,7 +238,7 @@ function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, n
|
||||
}
|
||||
}
|
||||
|
||||
function timeLimit<T>(promise: Promise<T>, limitMs: number, errorMessage: string): Promise<T> {
|
||||
function timeLimit<T>(promise: Promise<T>, limitMs: number, errorMessage?: string): Promise<T> {
|
||||
if (!promise || !promise.then) { // it's not actually a promise
|
||||
return promise;
|
||||
}
|
||||
|
@ -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,
|
@ -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');
|
||||
|
||||
/**
|
||||
|
@ -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]);
|
||||
|
5
src/types.d.ts
vendored
5
src/types.d.ts
vendored
@ -12,3 +12,8 @@ declare module 'html2plaintext' {
|
||||
function html2plaintext(htmlText: string): string;
|
||||
export = html2plaintext;
|
||||
}
|
||||
|
||||
declare module 'normalize-strings' {
|
||||
function normalizeString(string: string): string;
|
||||
export = normalizeString;
|
||||
}
|
40
src/www.js
40
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', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user