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 \
|
--include-lang=javascript,typescript \
|
||||||
--found=filelist.txt \
|
--found=filelist.txt \
|
||||||
--exclude-dir=public,libraries
|
--exclude-dir=public,libraries
|
||||||
|
|
||||||
|
grep -R \.js$ filelist.txt
|
||||||
|
rm filelist.txt
|
@ -10,8 +10,8 @@ if (config.Network.https) {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = require('./src/services/port.ts');
|
const port = require('./src/services/port');
|
||||||
const host = require('./src/services/host.js');
|
const host = require('./src/services/host');
|
||||||
|
|
||||||
const options = { timeout: 2000 };
|
const options = { timeout: 2000 };
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
const {app, globalShortcut, BrowserWindow} = require('electron');
|
const {app, globalShortcut, BrowserWindow} = require('electron');
|
||||||
const sqlInit = require('./src/services/sql_init');
|
const sqlInit = require('./src/services/sql_init');
|
||||||
const appIconService = require('./src/services/app_icon.js');
|
const appIconService = require('./src/services/app_icon.js');
|
||||||
const windowService = require('./src/services/window.js');
|
const windowService = require('./src/services/window');
|
||||||
const tray = require('./src/services/tray.js');
|
const tray = require('./src/services/tray');
|
||||||
|
|
||||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||||
require('electron-debug')();
|
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", () => {
|
describe("Lexer fulltext", () => {
|
||||||
it("simple lexing", () => {
|
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", () => {
|
describe("Parens handler", () => {
|
||||||
it("handles parens", () => {
|
it("handles parens", () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const SearchContext = require('../../src/services/search/search_context.js');
|
const SearchContext = require('../../src/services/search/search_context');
|
||||||
const parse = require('../../src/services/search/services/parse.js');
|
const parse = require('../../src/services/search/services/parse');
|
||||||
|
|
||||||
function tokens(toks, cur = 0) {
|
function tokens(toks, cur = 0) {
|
||||||
return toks.map(arg => {
|
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 BNote = require('../../src/becca/entities/bnote.js');
|
||||||
const BBranch = require('../../src/becca/entities/bbranch.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 dateUtils = require('../../src/services/date_utils');
|
||||||
const becca = require('../../src/becca/becca.js');
|
const becca = require('../../src/becca/becca.js');
|
||||||
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');
|
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const {note} = require('./becca_mocking.js');
|
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 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();
|
const dsc = new SearchContext();
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ require('./routes/custom.js').register(app);
|
|||||||
require('./routes/error_handlers.js').register(app);
|
require('./routes/error_handlers.js').register(app);
|
||||||
|
|
||||||
// triggers sync timer
|
// triggers sync timer
|
||||||
require('./services/sync.js');
|
require('./services/sync');
|
||||||
|
|
||||||
// triggers backup timer
|
// triggers backup timer
|
||||||
require('./services/backup');
|
require('./services/backup');
|
||||||
|
@ -20,7 +20,7 @@ const beccaLoaded = new Promise<void>((res, rej) => {
|
|||||||
cls.init(() => {
|
cls.init(() => {
|
||||||
load();
|
load();
|
||||||
|
|
||||||
require('../services/options_init.js').initStartupOptions();
|
require('../services/options_init').initStartupOptions();
|
||||||
|
|
||||||
res();
|
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({
|
return new BAttribute({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
type: type,
|
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);
|
const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
|
||||||
|
|
||||||
if (existingBranch) {
|
if (existingBranch) {
|
||||||
existingBranch.notePosition = notePosition;
|
if (notePosition) {
|
||||||
|
existingBranch.notePosition = notePosition;
|
||||||
|
}
|
||||||
return existingBranch;
|
return existingBranch;
|
||||||
} else {
|
} else {
|
||||||
return new BBranch({
|
return new BBranch({
|
||||||
noteId: this.noteId,
|
noteId: this.noteId,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
notePosition: notePosition,
|
notePosition: notePosition || null,
|
||||||
prefix: this.prefix,
|
prefix: this.prefix,
|
||||||
isExpanded: this.isExpanded
|
isExpanded: this.isExpanded
|
||||||
});
|
});
|
||||||
|
@ -78,13 +78,13 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
|||||||
|
|
||||||
// following attributes are filled during searching in the database
|
// following attributes are filled during searching in the database
|
||||||
/** size of the content in bytes */
|
/** size of the content in bytes */
|
||||||
private contentSize!: number | null;
|
contentSize!: number | null;
|
||||||
/** size of the note content, attachment contents in bytes */
|
/** 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 */
|
/** 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 */
|
/** number of note revisions for this note */
|
||||||
private revisionCount!: number | null;
|
revisionCount!: number | null;
|
||||||
|
|
||||||
constructor(row?: Partial<NoteRow>) {
|
constructor(row?: Partial<NoteRow>) {
|
||||||
super();
|
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();
|
name = name.toLowerCase();
|
||||||
value = value ? value.toLowerCase() : null;
|
value = value ? value.toLowerCase() : null;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export type AttributeType = "label" | "relation";
|
|||||||
|
|
||||||
export interface AttributeRow {
|
export interface AttributeRow {
|
||||||
attributeId?: string;
|
attributeId?: string;
|
||||||
noteId: string;
|
noteId?: string;
|
||||||
type: AttributeType;
|
type: AttributeType;
|
||||||
name: string;
|
name: string;
|
||||||
position?: number;
|
position?: number;
|
||||||
@ -80,7 +80,7 @@ export interface BranchRow {
|
|||||||
noteId: string;
|
noteId: string;
|
||||||
parentNoteId: string;
|
parentNoteId: string;
|
||||||
prefix?: string | null;
|
prefix?: string | null;
|
||||||
notePosition: number | null;
|
notePosition?: number | null;
|
||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
isDeleted?: boolean;
|
isDeleted?: boolean;
|
||||||
utcDateModified?: string;
|
utcDateModified?: string;
|
||||||
@ -106,4 +106,5 @@ export interface NoteRow {
|
|||||||
dateModified: string;
|
dateModified: string;
|
||||||
utcDateCreated: string;
|
utcDateCreated: string;
|
||||||
utcDateModified: string;
|
utcDateModified: string;
|
||||||
|
content?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const eu = require('./etapi_utils');
|
const eu = require('./etapi_utils');
|
||||||
const mappers = require('./mappers.js');
|
const mappers = require('./mappers.js');
|
||||||
const attributeService = require('../services/attributes.js');
|
const attributeService = require('../services/attributes');
|
||||||
const v = require('./validators.js');
|
const v = require('./validators.js');
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
|
@ -5,8 +5,8 @@ const mappers = require('./mappers.js');
|
|||||||
const noteService = require('../services/notes');
|
const noteService = require('../services/notes');
|
||||||
const TaskContext = require('../services/task_context');
|
const TaskContext = require('../services/task_context');
|
||||||
const v = require('./validators.js');
|
const v = require('./validators.js');
|
||||||
const searchService = require('../services/search/services/search.js');
|
const searchService = require('../services/search/services/search');
|
||||||
const SearchContext = require('../services/search/search_context.js');
|
const SearchContext = require('../services/search/search_context');
|
||||||
const zipExportService = require('../services/export/zip.js');
|
const zipExportService = require('../services/export/zip.js');
|
||||||
const zipImportService = require('../services/import/zip.js');
|
const zipImportService = require('../services/import/zip.js');
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const specialNotesService = require('../services/special_notes.js');
|
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 eu = require('./etapi_utils');
|
||||||
const mappers = require('./mappers.js');
|
const mappers = require('./mappers.js');
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const attributeService = require('../../services/attributes.js');
|
const attributeService = require('../../services/attributes');
|
||||||
const BAttribute = require('../../becca/entities/battribute');
|
const BAttribute = require('../../becca/entities/battribute');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const ValidationError = require('../../errors/validation_error');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const beccaService = require('../../becca/becca_service');
|
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 log = require('../../services/log');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const entityChangesService = require('../../services/entity_changes');
|
const entityChangesService = require('../../services/entity_changes');
|
||||||
const treeService = require('../../services/tree.js');
|
const treeService = require('../../services/tree');
|
||||||
const eraseService = require('../../services/erase');
|
const eraseService = require('../../services/erase');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const TaskContext = require('../../services/task_context');
|
const TaskContext = require('../../services/task_context');
|
||||||
const branchService = require('../../services/branches.js');
|
const branchService = require('../../services/branches');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const ValidationError = require('../../errors/validation_error');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
const eventService = require("../../services/events");
|
const eventService = require("../../services/events");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const bulkActionService = require('../../services/bulk_actions.js');
|
const bulkActionService = require('../../services/bulk_actions');
|
||||||
|
|
||||||
function execute(req) {
|
function execute(req) {
|
||||||
const {noteIds, includeDescendants} = req.body;
|
const {noteIds, includeDescendants} = req.body;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const attributeService = require('../../services/attributes.js');
|
const attributeService = require('../../services/attributes');
|
||||||
const cloneService = require('../../services/cloning.js');
|
const cloneService = require('../../services/cloning');
|
||||||
const noteService = require('../../services/notes');
|
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 dateUtils = require('../../services/date_utils');
|
||||||
const imageService = require('../../services/image.js');
|
const imageService = require('../../services/image.js');
|
||||||
const appInfo = require('../../services/app_info');
|
const appInfo = require('../../services/app_info');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const cloningService = require('../../services/cloning.js');
|
const cloningService = require('../../services/cloning');
|
||||||
|
|
||||||
function cloneNoteToBranch(req) {
|
function cloneNoteToBranch(req) {
|
||||||
const {noteId, parentBranchId} = req.params;
|
const {noteId, parentBranchId} = req.params;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const eraseService = require('../../services/erase');
|
const eraseService = require('../../services/erase');
|
||||||
const treeService = require('../../services/tree.js');
|
const treeService = require('../../services/tree');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const optionService = require('../../services/options');
|
const optionService = require('../../services/options');
|
||||||
const log = require('../../services/log');
|
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');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
|
|
||||||
// options allowed to be updated directly in the Options dialog
|
// options allowed to be updated directly in the Options dialog
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const scriptService = require('../../services/script.js');
|
const scriptService = require('../../services/script.js');
|
||||||
const attributeService = require('../../services/attributes.js');
|
const attributeService = require('../../services/attributes');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const syncService = require('../../services/sync.js');
|
const syncService = require('../../services/sync');
|
||||||
const sql = require('../../services/sql');
|
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
|
// 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";
|
"use strict";
|
||||||
|
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const SearchContext = require('../../services/search/search_context.js');
|
const SearchContext = require('../../services/search/search_context');
|
||||||
const searchService = require('../../services/search/services/search.js');
|
const searchService = require('../../services/search/services/search');
|
||||||
const bulkActionService = require('../../services/bulk_actions.js');
|
const bulkActionService = require('../../services/bulk_actions');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const {formatAttrForSearch} = require('../../services/attribute_formatter');
|
const {formatAttrForSearch} = require('../../services/attribute_formatter');
|
||||||
const ValidationError = require('../../errors/validation_error');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const dateNoteService = require('../../services/date_notes.js');
|
const dateNoteService = require('../../services/date_notes');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const specialNotesService = require('../../services/special_notes.js');
|
const specialNotesService = require('../../services/special_notes.js');
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const syncService = require('../../services/sync.js');
|
const syncService = require('../../services/sync');
|
||||||
const syncUpdateService = require('../../services/sync_update.js');
|
const syncUpdateService = require('../../services/sync_update');
|
||||||
const entityChangesService = require('../../services/entity_changes');
|
const entityChangesService = require('../../services/entity_changes');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
const optionService = require('../../services/options');
|
const optionService = require('../../services/options');
|
||||||
const contentHashService = require('../../services/content_hash.js');
|
const contentHashService = require('../../services/content_hash');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const syncOptions = require('../../services/sync_options');
|
const syncOptions = require('../../services/sync_options');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
const attributeService = require('../services/attributes.js');
|
const attributeService = require('../services/attributes');
|
||||||
const config = require('../services/config');
|
const config = require('../services/config');
|
||||||
const optionService = require('../services/options');
|
const optionService = require('../services/options');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
|
@ -27,12 +27,12 @@ const notesApiRoute = require('./api/notes.js');
|
|||||||
const branchesApiRoute = require('./api/branches.js');
|
const branchesApiRoute = require('./api/branches.js');
|
||||||
const attachmentsApiRoute = require('./api/attachments.js');
|
const attachmentsApiRoute = require('./api/attachments.js');
|
||||||
const autocompleteApiRoute = require('./api/autocomplete.js');
|
const autocompleteApiRoute = require('./api/autocomplete.js');
|
||||||
const cloningApiRoute = require('./api/cloning.js');
|
const cloningApiRoute = require('./api/cloning');
|
||||||
const revisionsApiRoute = require('./api/revisions');
|
const revisionsApiRoute = require('./api/revisions');
|
||||||
const recentChangesApiRoute = require('./api/recent_changes.js');
|
const recentChangesApiRoute = require('./api/recent_changes.js');
|
||||||
const optionsApiRoute = require('./api/options.js');
|
const optionsApiRoute = require('./api/options.js');
|
||||||
const passwordApiRoute = require('./api/password');
|
const passwordApiRoute = require('./api/password');
|
||||||
const syncApiRoute = require('./api/sync.js');
|
const syncApiRoute = require('./api/sync');
|
||||||
const loginApiRoute = require('./api/login.js');
|
const loginApiRoute = require('./api/login.js');
|
||||||
const recentNotesRoute = require('./api/recent_notes.js');
|
const recentNotesRoute = require('./api/recent_notes.js');
|
||||||
const appInfoRoute = require('./api/app_info');
|
const appInfoRoute = require('./api/app_info');
|
||||||
@ -42,11 +42,11 @@ const setupApiRoute = require('./api/setup.js');
|
|||||||
const sqlRoute = require('./api/sql');
|
const sqlRoute = require('./api/sql');
|
||||||
const databaseRoute = require('./api/database.js');
|
const databaseRoute = require('./api/database.js');
|
||||||
const imageRoute = require('./api/image.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 scriptRoute = require('./api/script.js');
|
||||||
const senderRoute = require('./api/sender.js');
|
const senderRoute = require('./api/sender.js');
|
||||||
const filesRoute = require('./api/files.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 bulkActionRoute = require('./api/bulk_action.js');
|
||||||
const specialNotesRoute = require('./api/special_notes.js');
|
const specialNotesRoute = require('./api/special_notes.js');
|
||||||
const noteMapRoute = require('./api/note_map.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 etapiAuthRoutes = require('../etapi/auth.js');
|
||||||
const etapiAppInfoRoutes = require('../etapi/app_info');
|
const etapiAppInfoRoutes = require('../etapi/app_info');
|
||||||
const etapiAttachmentRoutes = require('../etapi/attachments.js');
|
const etapiAttachmentRoutes = require('../etapi/attachments.js');
|
||||||
const etapiAttributeRoutes = require('../etapi/attributes.js');
|
const etapiAttributeRoutes = require('../etapi/attributes');
|
||||||
const etapiBranchRoutes = require('../etapi/branches.js');
|
const etapiBranchRoutes = require('../etapi/branches.js');
|
||||||
const etapiNoteRoutes = require('../etapi/notes.js');
|
const etapiNoteRoutes = require('../etapi/notes.js');
|
||||||
const etapiSpecialNoteRoutes = require('../etapi/special_notes.js');
|
const etapiSpecialNoteRoutes = require('../etapi/special_notes.js');
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const session = require("express-session");
|
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 dataDir = require('../services/data_dir');
|
||||||
const FileStore = require('session-file-store')(session);
|
const FileStore = require('session-file-store')(session);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ const appPath = require('../services/app_path');
|
|||||||
function setupPage(req, res) {
|
function setupPage(req, res) {
|
||||||
if (sqlInit.isDbInitialized()) {
|
if (sqlInit.isDbInitialized()) {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const windowService = require('../services/window.js');
|
const windowService = require('../services/window');
|
||||||
const {app} = require('electron');
|
const {app} = require('electron');
|
||||||
windowService.createMainWindow(app);
|
windowService.createMainWindow(app);
|
||||||
windowService.closeSetupWindow();
|
windowService.closeSetupWindow();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
"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 = '';
|
let searchStr = '';
|
||||||
|
|
||||||
if (attr.type === 'label') {
|
if (attr.type === 'label') {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const searchService = require('./search/services/search.js');
|
import searchService = require('./search/services/search');
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
const BAttribute = require('../becca/entities/battribute');
|
import BAttribute = require('../becca/entities/battribute');
|
||||||
const {formatAttrForSearch} = require('./attribute_formatter');
|
import attributeFormatter = require('./attribute_formatter');
|
||||||
const BUILTIN_ATTRIBUTES = require('./builtin_attributes');
|
import BUILTIN_ATTRIBUTES = require('./builtin_attributes');
|
||||||
|
import BNote = require('../becca/entities/bnote');
|
||||||
|
import { AttributeRow } from '../becca/entities/rows';
|
||||||
|
|
||||||
const ATTRIBUTE_TYPES = ['label', 'relation'];
|
const ATTRIBUTE_TYPES = ['label', 'relation'];
|
||||||
|
|
||||||
/** @returns {BNote[]} */
|
function getNotesWithLabel(name: string, value?: string): BNote[] {
|
||||||
function getNotesWithLabel(name, value = undefined) {
|
const query = attributeFormatter.formatAttrForSearch({type: 'label', name, value}, value !== undefined);
|
||||||
const query = formatAttrForSearch({type: 'label', name, value}, value !== undefined);
|
|
||||||
return searchService.searchNotes(query, {
|
return searchService.searchNotes(query, {
|
||||||
includeArchivedNotes: true,
|
includeArchivedNotes: true,
|
||||||
ignoreHoistedNote: true
|
ignoreHoistedNote: true
|
||||||
@ -19,8 +20,7 @@ function getNotesWithLabel(name, value = undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should be in search service
|
// TODO: should be in search service
|
||||||
/** @returns {BNote|null} */
|
function getNoteWithLabel(name: string, value?: string): BNote | null {
|
||||||
function getNoteWithLabel(name, value = undefined) {
|
|
||||||
// optimized version (~20 times faster) without using normal search, useful for e.g., finding date notes
|
// optimized version (~20 times faster) without using normal search, useful for e.g., finding date notes
|
||||||
const attrs = becca.findAttributes('label', name);
|
const attrs = becca.findAttributes('label', name);
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ function getNoteWithLabel(name, value = undefined) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLabel(noteId, name, value = "") {
|
function createLabel(noteId: string, name: string, value: string = "") {
|
||||||
return createAttribute({
|
return createAttribute({
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
type: 'label',
|
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({
|
return createAttribute({
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
type: 'relation',
|
type: 'relation',
|
||||||
@ -57,14 +57,14 @@ function createRelation(noteId, name, targetNoteId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAttribute(attribute) {
|
function createAttribute(attribute: AttributeRow) {
|
||||||
return new BAttribute(attribute).save();
|
return new BAttribute(attribute).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAttributeNames(type, nameLike) {
|
function getAttributeNames(type: string, nameLike: string) {
|
||||||
nameLike = nameLike.toLowerCase();
|
nameLike = nameLike.toLowerCase();
|
||||||
|
|
||||||
let names = sql.getColumn(
|
let names = sql.getColumn<string>(
|
||||||
`SELECT DISTINCT name
|
`SELECT DISTINCT name
|
||||||
FROM attributes
|
FROM attributes
|
||||||
WHERE isDeleted = 0
|
WHERE isDeleted = 0
|
||||||
@ -98,11 +98,11 @@ function getAttributeNames(type, nameLike) {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAttributeType(type) {
|
function isAttributeType(type: string): boolean {
|
||||||
return ATTRIBUTE_TYPES.includes(type);
|
return ATTRIBUTE_TYPES.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAttributeDangerous(type, name) {
|
function isAttributeDangerous(type: string, name: string): boolean {
|
||||||
return BUILTIN_ATTRIBUTES.some(attr =>
|
return BUILTIN_ATTRIBUTES.some(attr =>
|
||||||
attr.type === type &&
|
attr.type === type &&
|
||||||
attr.name.toLowerCase() === name.trim().toLowerCase() &&
|
attr.name.toLowerCase() === name.trim().toLowerCase() &&
|
||||||
@ -110,7 +110,7 @@ function isAttributeDangerous(type, name) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getNotesWithLabel,
|
getNotesWithLabel,
|
||||||
getNoteWithLabel,
|
getNoteWithLabel,
|
||||||
createLabel,
|
createLabel,
|
@ -2,22 +2,22 @@ const log = require('./log');
|
|||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const attributeService = require('./attributes.js');
|
const attributeService = require('./attributes');
|
||||||
const dateNoteService = require('./date_notes.js');
|
const dateNoteService = require('./date_notes');
|
||||||
const treeService = require('./tree.js');
|
const treeService = require('./tree');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
const xml2js = require('xml2js');
|
const xml2js = require('xml2js');
|
||||||
const cloningService = require('./cloning.js');
|
const cloningService = require('./cloning');
|
||||||
const appInfo = require('./app_info');
|
const appInfo = require('./app_info');
|
||||||
const searchService = require('./search/services/search.js');
|
const searchService = require('./search/services/search');
|
||||||
const SearchContext = require('./search/search_context.js');
|
const SearchContext = require('./search/search_context');
|
||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const ws = require('./ws');
|
const ws = require('./ws');
|
||||||
const SpacedUpdate = require('./spaced_update.js');
|
const SpacedUpdate = require('./spaced_update.js');
|
||||||
const specialNotesService = require('./special_notes.js');
|
const specialNotesService = require('./special_notes.js');
|
||||||
const branchService = require('./branches.js');
|
const branchService = require('./branches');
|
||||||
const exportService = require('./export/zip.js');
|
const exportService = require('./export/zip.js');
|
||||||
const syncMutex = require('./sync_mutex');
|
const syncMutex = require('./sync_mutex');
|
||||||
const backupService = require('./backup');
|
const backupService = require('./backup');
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
const treeService = require('./tree.js');
|
import treeService = require('./tree');
|
||||||
const sql = require('./sql');
|
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) {
|
if (branchToMove.parentNoteId === targetParentNoteId) {
|
||||||
return {success: true}; // no-op
|
return {success: true}; // no-op
|
||||||
}
|
}
|
||||||
@ -12,8 +13,8 @@ function moveBranchToNote(branchToMove, targetParentNoteId) {
|
|||||||
return [200, validationResult];
|
return [200, validationResult];
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [targetParentNoteId]);
|
const maxNotePos = sql.getValue<number | null>('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [targetParentNoteId]);
|
||||||
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 10;
|
const newNotePos = !maxNotePos ? 0 : maxNotePos + 10;
|
||||||
|
|
||||||
const newBranch = branchToMove.createClone(targetParentNoteId, newNotePos);
|
const newBranch = branchToMove.createClone(targetParentNoteId, newNotePos);
|
||||||
newBranch.save();
|
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);
|
const res = moveBranchToNote(branchToMove, targetParentBranch.noteId);
|
||||||
|
|
||||||
if (!res.success) {
|
if (!("success" in res) || !res.success) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ function moveBranchToBranch(branchToMove, targetParentBranch) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
moveBranchToBranch,
|
moveBranchToBranch,
|
||||||
moveBranchToNote
|
moveBranchToNote
|
||||||
};
|
};
|
@ -1,12 +1,30 @@
|
|||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const revisionService = require('./revisions');
|
import becca = require('../becca/becca');
|
||||||
const becca = require('../becca/becca');
|
import cloningService = require('./cloning');
|
||||||
const cloningService = require('./cloning.js');
|
import branchService = require('./branches');
|
||||||
const branchService = require('./branches.js');
|
import utils = require('./utils');
|
||||||
const utils = require('./utils');
|
import eraseService = require("./erase");
|
||||||
const 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) => {
|
addLabel: (action, note) => {
|
||||||
note.addLabel(action.labelName, action.labelValue);
|
note.addLabel(action.labelName, action.labelValue);
|
||||||
},
|
},
|
||||||
@ -19,7 +37,10 @@ const ACTION_HANDLERS = {
|
|||||||
note.deleteNote(deleteId);
|
note.deleteNote(deleteId);
|
||||||
},
|
},
|
||||||
deleteRevisions: (action, note) => {
|
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) => {
|
deleteLabel: (action, note) => {
|
||||||
for (const label of note.getOwnedLabels(action.labelName)) {
|
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')
|
return note.getLabels('action')
|
||||||
.map(actionLabel => {
|
.map(actionLabel => {
|
||||||
let action;
|
let action;
|
||||||
@ -129,7 +150,7 @@ function getActions(note) {
|
|||||||
.filter(a => !!a);
|
.filter(a => !!a);
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeActions(note, searchResultNoteIds) {
|
function executeActions(note: BNote, searchResultNoteIds: string[]) {
|
||||||
const actions = getActions(note);
|
const actions = getActions(note);
|
||||||
|
|
||||||
for (const resultNoteId of searchResultNoteIds) {
|
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)}`);
|
log.info(`Applying action handler to note ${resultNote.noteId}: ${JSON.stringify(action)}`);
|
||||||
|
|
||||||
ACTION_HANDLERS[action.name](action, resultNote);
|
ACTION_HANDLERS[action.name](action, resultNote);
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log.error(`ExecuteScript search action failed with ${e.message}`);
|
log.error(`ExecuteScript search action failed with ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
executeActions
|
executeActions
|
||||||
};
|
};
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const eventChangesService = require('./entity_changes');
|
const eventChangesService = require('./entity_changes');
|
||||||
const treeService = require('./tree.js');
|
const treeService = require('./tree');
|
||||||
const BBranch = require('../becca/entities/bbranch');
|
const BBranch = require('../becca/entities/bbranch');
|
||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const log = require('./log');
|
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)) {
|
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.' };
|
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);
|
const parentBranch = becca.getBranch(parentBranchId);
|
||||||
|
|
||||||
if (!parentBranch) {
|
if (!parentBranch) {
|
||||||
@ -58,7 +58,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
|
function ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string) {
|
||||||
if (!(noteId in becca.notes)) {
|
if (!(noteId in becca.notes)) {
|
||||||
return { branch: null, success: false, message: `Note '${noteId}' is deleted.` };
|
return { branch: null, success: false, message: `Note '${noteId}' is deleted.` };
|
||||||
} else if (!(parentNoteId in becca.notes)) {
|
} else if (!(parentNoteId in becca.notes)) {
|
||||||
@ -89,7 +89,7 @@ function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
|
|||||||
return { branch: branch, success: true };
|
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 branchId = sql.getValue(`SELECT branchId FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]);
|
||||||
const branch = becca.getBranch(branchId);
|
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) {
|
if (present) {
|
||||||
return ensureNoteIsPresentInParent(noteId, parentNoteId, prefix);
|
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)) {
|
if (['_hidden', 'root'].includes(noteId)) {
|
||||||
return { success: false, message: `Cloning the note '${noteId}' is forbidden.` };
|
return { success: false, message: `Cloning the note '${noteId}' is forbidden.` };
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ function cloneNoteAfter(noteId, afterBranchId) {
|
|||||||
return { success: true, branchId: branch.branchId };
|
return { success: true, branchId: branch.branchId };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
cloneNoteToBranch,
|
cloneNoteToBranch,
|
||||||
cloneNoteToParentNote,
|
cloneNoteToParentNote,
|
||||||
ensureNoteIsPresentInParent,
|
ensureNoteIsPresentInParent,
|
@ -1,9 +1,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const utils = require('./utils');
|
import utils = require('./utils');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const eraseService = require('./erase');
|
import eraseService = require('./erase');
|
||||||
|
|
||||||
|
type SectorHash = Record<string, string>;
|
||||||
|
|
||||||
function getEntityHashes() {
|
function getEntityHashes() {
|
||||||
// blob erasure is not synced, we should check before each sync if there's some blob to erase
|
// 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();
|
const startTime = new Date();
|
||||||
|
|
||||||
// we know this is slow and the total content hash calculation time is logged
|
// we know this is slow and the total content hash calculation time is logged
|
||||||
|
type HashRow = [ string, string, string, boolean ];
|
||||||
const hashRows = sql.disableSlowQueryLogging(
|
const hashRows = sql.disableSlowQueryLogging(
|
||||||
() => sql.getRawRows(`
|
() => sql.getRawRows<HashRow>(`
|
||||||
SELECT entityName,
|
SELECT entityName,
|
||||||
entityId,
|
entityId,
|
||||||
hash,
|
hash,
|
||||||
@ -27,7 +30,7 @@ function getEntityHashes() {
|
|||||||
// sorting by entityId is enough, hashes will be segmented by entityName later on anyway
|
// sorting by entityId is enough, hashes will be segmented by entityName later on anyway
|
||||||
hashRows.sort((a, b) => a[1] < b[1] ? -1 : 1);
|
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) {
|
for (const [entityName, entityId, hash, isErased] of hashRows) {
|
||||||
const entityHashMap = hashMap[entityName] = hashMap[entityName] || {};
|
const entityHashMap = hashMap[entityName] = hashMap[entityName] || {};
|
||||||
@ -51,13 +54,13 @@ function getEntityHashes() {
|
|||||||
return hashMap;
|
return hashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkContentHashes(otherHashes) {
|
function checkContentHashes(otherHashes: Record<string, SectorHash>) {
|
||||||
const entityHashes = getEntityHashes();
|
const entityHashes = getEntityHashes();
|
||||||
const failedChecks = [];
|
const failedChecks = [];
|
||||||
|
|
||||||
for (const entityName in entityHashes) {
|
for (const entityName in entityHashes) {
|
||||||
const thisSectorHashes = entityHashes[entityName] || {};
|
const thisSectorHashes: SectorHash = entityHashes[entityName] || {};
|
||||||
const otherSectorHashes = otherHashes[entityName] || {};
|
const otherSectorHashes: SectorHash = otherHashes[entityName] || {};
|
||||||
|
|
||||||
const sectors = new Set(Object.keys(thisSectorHashes).concat(Object.keys(otherSectorHashes)));
|
const sectors = new Set(Object.keys(thisSectorHashes).concat(Object.keys(otherSectorHashes)));
|
||||||
|
|
||||||
@ -77,7 +80,7 @@ function checkContentHashes(otherHashes) {
|
|||||||
return failedChecks;
|
return failedChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getEntityHashes,
|
getEntityHashes,
|
||||||
checkContentHashes
|
checkContentHashes
|
||||||
};
|
};
|
@ -1,13 +1,14 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('./notes');
|
import noteService = require('./notes');
|
||||||
const attributeService = require('./attributes.js');
|
import attributeService = require('./attributes');
|
||||||
const dateUtils = require('./date_utils');
|
import dateUtils = require('./date_utils');
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const protectedSessionService = require('./protected_session');
|
import protectedSessionService = require('./protected_session');
|
||||||
const searchService = require('../services/search/services/search.js');
|
import searchService = require('../services/search/services/search');
|
||||||
const SearchContext = require('../services/search/search_context.js');
|
import SearchContext = require('../services/search/search_context');
|
||||||
const hoistedNoteService = require('./hoisted_note.js');
|
import hoistedNoteService = require('./hoisted_note');
|
||||||
|
import BNote = require('../becca/entities/bnote');
|
||||||
|
|
||||||
const CALENDAR_ROOT_LABEL = 'calendarRoot';
|
const CALENDAR_ROOT_LABEL = 'calendarRoot';
|
||||||
const YEAR_LABEL = 'yearNote';
|
const YEAR_LABEL = 'yearNote';
|
||||||
@ -17,7 +18,9 @@ const DATE_LABEL = 'dateNote';
|
|||||||
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
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({
|
return noteService.createNewNote({
|
||||||
parentNoteId: parentNote.noteId,
|
parentNoteId: parentNote.noteId,
|
||||||
title: noteTitle,
|
title: noteTitle,
|
||||||
@ -27,13 +30,12 @@ function createNote(parentNote, noteTitle) {
|
|||||||
}).note;
|
}).note;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {BNote} */
|
function getRootCalendarNote(): BNote {
|
||||||
function getRootCalendarNote() {
|
|
||||||
let rootNote;
|
let rootNote;
|
||||||
|
|
||||||
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
||||||
|
|
||||||
if (!workspaceNote.isRoot()) {
|
if (!workspaceNote || !workspaceNote.isRoot()) {
|
||||||
rootNote = searchService.findFirstNoteWithQuery('#workspaceCalendarRoot', new SearchContext({ignoreHoistedNote: false}));
|
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: string, _rootNote: BNote | null = null): BNote {
|
||||||
function getYearNote(dateStr, rootNote = null) {
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
if (!rootNote) {
|
|
||||||
rootNote = getRootCalendarNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearStr = dateStr.trim().substr(0, 4);
|
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 pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
|
||||||
const monthName = MONTHS[dateObj.getMonth()];
|
const monthName = MONTHS[dateObj.getMonth()];
|
||||||
|
|
||||||
@ -102,11 +101,8 @@ function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
|
|||||||
.replace(/{month}/g, monthName);
|
.replace(/{month}/g, monthName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {BNote} */
|
function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
||||||
function getMonthNote(dateStr, rootNote = null) {
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
if (!rootNote) {
|
|
||||||
rootNote = getRootCalendarNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
const monthStr = dateStr.substr(0, 7);
|
const monthStr = dateStr.substr(0, 7);
|
||||||
const monthNumber = dateStr.substr(5, 2);
|
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 pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
||||||
const weekDay = DAYS[dateObj.getDay()];
|
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 */
|
/** 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 suffixes = ["th", "st", "nd", "rd"];
|
||||||
const suffix = suffixes[(dayNumber - 20) % 10] || suffixes[dayNumber] || suffixes[0];
|
const suffix = suffixes[(dayNumber - 20) % 10] || suffixes[dayNumber] || suffixes[0];
|
||||||
|
|
||||||
return `${dayNumber}${suffix}`;
|
return `${dayNumber}${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {BNote} */
|
function getDayNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
||||||
function getDayNote(dateStr, rootNote = null) {
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
if (!rootNote) {
|
|
||||||
rootNote = getRootCalendarNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
dateStr = dateStr.trim().substr(0, 10);
|
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) {
|
function getTodayNote(rootNote = null) {
|
||||||
return getDayNote(dateUtils.localNowDate(), rootNote);
|
return getDayNote(dateUtils.localNowDate(), rootNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartOfTheWeek(date, startOfTheWeek) {
|
function getStartOfTheWeek(date: Date, startOfTheWeek: StartOfWeek) {
|
||||||
const day = date.getDay();
|
const day = date.getDay();
|
||||||
let diff;
|
let diff;
|
||||||
|
|
||||||
@ -219,7 +212,11 @@ function getStartOfTheWeek(date, startOfTheWeek) {
|
|||||||
return new Date(date.setDate(diff));
|
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 startOfTheWeek = options.startOfTheWeek || "monday";
|
||||||
|
|
||||||
const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek);
|
const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek);
|
||||||
@ -229,7 +226,7 @@ function getWeekNote(dateStr, options = {}, rootNote = null) {
|
|||||||
return getDayNote(dateStr, rootNote);
|
return getDayNote(dateStr, rootNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getRootCalendarNote,
|
getRootCalendarNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
@ -13,3 +13,13 @@ export interface EntityChange {
|
|||||||
changeId?: string | null;
|
changeId?: string | null;
|
||||||
instanceId?: 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 eventService = require('./events');
|
||||||
const scriptService = require('./script.js');
|
const scriptService = require('./script.js');
|
||||||
const treeService = require('./tree.js');
|
const treeService = require('./tree');
|
||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const BAttribute = require('../becca/entities/battribute');
|
const BAttribute = require('../becca/entities/battribute');
|
||||||
const hiddenSubtreeService = require('./hidden_subtree');
|
const hiddenSubtreeService = require('./hidden_subtree');
|
||||||
const oneTimeTimer = require('./one_time_timer.js');
|
const oneTimeTimer = require('./one_time_timer');
|
||||||
|
|
||||||
function runAttachedRelations(note, relationName, originEntity) {
|
function runAttachedRelations(note, relationName, originEntity) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const cls = require('./cls');
|
import cls = require('./cls');
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
|
|
||||||
function getHoistedNoteId() {
|
function getHoistedNoteId() {
|
||||||
return cls.getHoistedNoteId();
|
return cls.getHoistedNoteId();
|
||||||
@ -26,14 +26,14 @@ function isHoistedInHiddenSubtree() {
|
|||||||
function getWorkspaceNote() {
|
function getWorkspaceNote() {
|
||||||
const hoistedNote = becca.getNote(cls.getHoistedNoteId());
|
const hoistedNote = becca.getNote(cls.getHoistedNoteId());
|
||||||
|
|
||||||
if (hoistedNote.isRoot() || hoistedNote.hasLabel('workspace')) {
|
if (hoistedNote && (hoistedNote.isRoot() || hoistedNote.hasLabel('workspace'))) {
|
||||||
return hoistedNote;
|
return hoistedNote;
|
||||||
} else {
|
} else {
|
||||||
return becca.getRoot();
|
return becca.getRoot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getHoistedNoteId,
|
getHoistedNoteId,
|
||||||
getWorkspaceNote,
|
getWorkspaceNote,
|
||||||
isHoistedInHiddenSubtree
|
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 utils = require('../../services/utils');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const attributeService = require('../../services/attributes.js');
|
const attributeService = require('../../services/attributes');
|
||||||
const BBranch = require('../../becca/entities/bbranch');
|
const BBranch = require('../../becca/entities/bbranch');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const protectedSessionService = require('../protected_session');
|
const protectedSessionService = require('../protected_session');
|
||||||
const mimeService = require('./mime.js');
|
const mimeService = require('./mime.js');
|
||||||
const treeService = require('../tree.js');
|
const treeService = require('../tree');
|
||||||
const yauzl = require("yauzl");
|
const yauzl = require("yauzl");
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
const htmlSanitizer = require('../html_sanitizer');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
|
@ -3,19 +3,11 @@
|
|||||||
import optionService = require('./options');
|
import optionService = require('./options');
|
||||||
import log = require('./log');
|
import log = require('./log');
|
||||||
import utils = require('./utils');
|
import utils = require('./utils');
|
||||||
|
import { KeyboardShortcut } from './keyboard_actions_interface';
|
||||||
|
|
||||||
const isMac = process.platform === "darwin";
|
const isMac = process.platform === "darwin";
|
||||||
const isElectron = utils.isElectron();
|
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,
|
* 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.
|
* 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;
|
dateCreated?: string;
|
||||||
utcDateCreated?: string;
|
utcDateCreated?: string;
|
||||||
ignoreForbiddenParents?: boolean;
|
ignoreForbiddenParents?: boolean;
|
||||||
|
target?: "into";
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewNote(params: NoteParams): {
|
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.
|
* 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
|
* The good thing about synchronous better-sqlite3 is that this cannot interrupt transaction. The execution will be called
|
||||||
* only outside of a transaction.
|
* only outside of a transaction.
|
||||||
*/
|
*/
|
||||||
function scheduleExecution(name, milliseconds, cb) {
|
function scheduleExecution(name: string, milliseconds: number, cb: () => void) {
|
||||||
if (name in scheduledExecutions) {
|
if (name in scheduledExecutions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -20,6 +20,6 @@ function scheduleExecution(name, milliseconds, cb) {
|
|||||||
}, milliseconds);
|
}, milliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
scheduleExecution
|
scheduleExecution
|
||||||
};
|
};
|
@ -1,16 +1,22 @@
|
|||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
const appInfo = require('./app_info');
|
import appInfo = require('./app_info');
|
||||||
const utils = require('./utils');
|
import utils = require('./utils');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const dateUtils = require('./date_utils');
|
import dateUtils = require('./date_utils');
|
||||||
const keyboardActions = require('./keyboard_actions');
|
import keyboardActions = require('./keyboard_actions');
|
||||||
|
import { KeyboardShortcutWithRequiredActionName } from './keyboard_actions_interface';
|
||||||
|
|
||||||
function initDocumentOptions() {
|
function initDocumentOptions() {
|
||||||
optionService.createOption('documentId', utils.randomSecureToken(16), false);
|
optionService.createOption('documentId', utils.randomSecureToken(16), false);
|
||||||
optionService.createOption('documentSecret', 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([
|
optionService.createOption('openNoteContexts', JSON.stringify([
|
||||||
{
|
{
|
||||||
notePath: 'root',
|
notePath: 'root',
|
||||||
@ -21,7 +27,7 @@ function initNotSyncedOptions(initialized, opts = {}) {
|
|||||||
optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
|
optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
|
||||||
optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
|
optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
|
||||||
optionService.createOption('lastMonthlyBackupDate', 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);
|
optionService.createOption('initialized', initialized ? 'true' : 'false', false);
|
||||||
|
|
||||||
@ -117,8 +123,8 @@ function initStartupOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getKeyboardDefaultOptions() {
|
function getKeyboardDefaultOptions() {
|
||||||
return keyboardActions.DEFAULT_KEYBOARD_ACTIONS
|
return (keyboardActions.DEFAULT_KEYBOARD_ACTIONS
|
||||||
.filter(ka => !!ka.actionName)
|
.filter(ka => !!ka.actionName) as KeyboardShortcutWithRequiredActionName[])
|
||||||
.map(ka => ({
|
.map(ka => ({
|
||||||
name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`,
|
name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`,
|
||||||
value: JSON.stringify(ka.defaultShortcuts),
|
value: JSON.stringify(ka.defaultShortcuts),
|
||||||
@ -126,7 +132,7 @@ function getKeyboardDefaultOptions() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
initDocumentOptions,
|
initDocumentOptions,
|
||||||
initNotSyncedOptions,
|
initNotSyncedOptions,
|
||||||
initStartupOptions
|
initStartupOptions
|
@ -4,29 +4,11 @@ import utils = require('./utils');
|
|||||||
import log = require('./log');
|
import log = require('./log');
|
||||||
import url = require('url');
|
import url = require('url');
|
||||||
import syncOptions = require('./sync_options');
|
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 service provides abstraction over node's HTTP/HTTPS and electron net.client APIs
|
||||||
// this allows supporting system proxy
|
// 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 {
|
interface ClientOpts {
|
||||||
method: string;
|
method: string;
|
||||||
url: 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),
|
// it's not clear how to explicitly configure proxy (as opposed to system proxy),
|
||||||
// so in that case, we always use node's modules
|
// so in that case, we always use node's modules
|
||||||
if (utils.isElectron() && !opts.proxy) {
|
if (utils.isElectron() && !opts.proxy) {
|
||||||
return require('electron').net;
|
return require('electron').net as Client;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const {protocol} = url.parse(opts.url);
|
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 sqlInit = require('./sql_init');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const attributeService = require('../services/attributes.js');
|
const attributeService = require('../services/attributes');
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const hiddenSubtreeService = require('./hidden_subtree');
|
const hiddenSubtreeService = require('./hidden_subtree');
|
||||||
|
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const log = require('../../log');
|
import log = require('../../log');
|
||||||
const becca = require('../../../becca/becca');
|
import becca = require('../../../becca/becca');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class AncestorExp extends Expression {
|
class AncestorExp extends Expression {
|
||||||
constructor(ancestorNoteId, ancestorDepth) {
|
|
||||||
|
private ancestorNoteId: string;
|
||||||
|
private ancestorDepthComparator;
|
||||||
|
|
||||||
|
ancestorDepth?: string;
|
||||||
|
|
||||||
|
constructor(ancestorNoteId: string, ancestorDepth?: string) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.ancestorNoteId = ancestorNoteId;
|
this.ancestorNoteId = ancestorNoteId;
|
||||||
@ -14,7 +21,7 @@ class AncestorExp extends Expression {
|
|||||||
this.ancestorDepthComparator = this.getComparator(ancestorDepth);
|
this.ancestorDepthComparator = this.getComparator(ancestorDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const ancestorNote = becca.notes[this.ancestorNoteId];
|
const ancestorNote = becca.notes[this.ancestorNoteId];
|
||||||
|
|
||||||
if (!ancestorNote) {
|
if (!ancestorNote) {
|
||||||
@ -44,7 +51,7 @@ class AncestorExp extends Expression {
|
|||||||
return depthConformingNoteSet;
|
return depthConformingNoteSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
getComparator(depthCondition) {
|
getComparator(depthCondition?: string): ((depth: number) => boolean) | null {
|
||||||
if (!depthCondition) {
|
if (!depthCondition) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -67,4 +74,4 @@ class AncestorExp extends Expression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AncestorExp;
|
export = AncestorExp;
|
@ -1,11 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import NoteSet = require('../note_set');
|
||||||
const TrueExp = require('./true.js');
|
import SearchContext = require('../search_context');
|
||||||
|
import Expression = require('./expression');
|
||||||
|
import TrueExp = require('./true');
|
||||||
|
|
||||||
class AndExp extends Expression {
|
class AndExp extends Expression {
|
||||||
static of(subExpressions) {
|
private subExpressions: Expression[];
|
||||||
subExpressions = subExpressions.filter(exp => !!exp);
|
|
||||||
|
static of(_subExpressions: (Expression | null | undefined)[]) {
|
||||||
|
const subExpressions = _subExpressions.filter((exp) => !!exp) as Expression[];
|
||||||
|
|
||||||
if (subExpressions.length === 1) {
|
if (subExpressions.length === 1) {
|
||||||
return subExpressions[0];
|
return subExpressions[0];
|
||||||
@ -16,12 +20,12 @@ class AndExp extends Expression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(subExpressions) {
|
constructor(subExpressions: Expression[]) {
|
||||||
super();
|
super();
|
||||||
this.subExpressions = subExpressions;
|
this.subExpressions = subExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
for (const subExpression of this.subExpressions) {
|
for (const subExpression of this.subExpressions) {
|
||||||
inputNoteSet = subExpression.execute(inputNoteSet, executionContext, searchContext);
|
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";
|
"use strict";
|
||||||
|
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require("../note_set");
|
||||||
const becca = require('../../../becca/becca');
|
import SearchContext = require("../search_context");
|
||||||
const Expression = require('./expression.js');
|
|
||||||
|
import becca = require('../../../becca/becca');
|
||||||
|
import Expression = require('./expression');
|
||||||
|
|
||||||
class AttributeExistsExp extends 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();
|
super();
|
||||||
|
|
||||||
this.attributeType = attributeType;
|
this.attributeType = attributeType;
|
||||||
@ -15,7 +23,7 @@ class AttributeExistsExp extends Expression {
|
|||||||
this.prefixMatch = prefixMatch;
|
this.prefixMatch = prefixMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const attrs = this.prefixMatch
|
const attrs = this.prefixMatch
|
||||||
? becca.findAttributesWithPrefix(this.attributeType, this.attributeName)
|
? becca.findAttributesWithPrefix(this.attributeType, this.attributeName)
|
||||||
: becca.findAttributes(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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class ChildOfExp extends Expression {
|
class ChildOfExp extends Expression {
|
||||||
constructor(subExpression) {
|
|
||||||
|
private subExpression: Expression;
|
||||||
|
|
||||||
|
constructor(subExpression: Expression) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.subExpression = subExpression;
|
this.subExpression = subExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const subInputNoteSet = new NoteSet();
|
const subInputNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const note of inputNoteSet.notes) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const becca = require('../../../becca/becca');
|
import becca = require('../../../becca/becca');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class DescendantOfExp extends Expression {
|
class DescendantOfExp extends Expression {
|
||||||
constructor(subExpression) {
|
private subExpression: Expression;
|
||||||
|
|
||||||
|
constructor(subExpression: Expression) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.subExpression = subExpression;
|
this.subExpression = subExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const subInputNoteSet = new NoteSet(Object.values(becca.notes));
|
const subInputNoteSet = new NoteSet(Object.values(becca.notes));
|
||||||
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext);
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
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)
|
* 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 {
|
class IsHiddenExp extends Expression {
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const resultNoteSet = new NoteSet();
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const note of inputNoteSet.notes) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const becca = require('../../../becca/becca');
|
import becca = require('../../../becca/becca');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
|
type Comparator = (value: string) => boolean;
|
||||||
|
|
||||||
class LabelComparisonExp extends Expression {
|
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();
|
super();
|
||||||
|
|
||||||
this.attributeType = attributeType;
|
this.attributeType = attributeType;
|
||||||
@ -13,7 +21,7 @@ class LabelComparisonExp extends Expression {
|
|||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const attrs = becca.findAttributes(this.attributeType, this.attributeName);
|
const attrs = becca.findAttributes(this.attributeType, this.attributeName);
|
||||||
const resultNoteSet = new NoteSet();
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import { NoteRow } from "../../../becca/entities/rows";
|
||||||
const NoteSet = require('../note_set');
|
import SearchContext = require("../search_context");
|
||||||
const log = require('../../log');
|
|
||||||
const becca = require('../../../becca/becca');
|
import Expression = require('./expression');
|
||||||
const protectedSessionService = require('../../protected_session');
|
import NoteSet = require('../note_set');
|
||||||
const striptags = require('striptags');
|
import log = require('../../log');
|
||||||
const utils = require('../../utils');
|
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 ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%='];
|
||||||
|
|
||||||
const cachedRegexes = {};
|
const cachedRegexes: Record<string, RegExp> = {};
|
||||||
|
|
||||||
function getRegex(str) {
|
function getRegex(str: string): RegExp {
|
||||||
if (!(str in cachedRegexes)) {
|
if (!(str in cachedRegexes)) {
|
||||||
cachedRegexes[str] = new RegExp(str, 'ms'); // multiline, dot-all
|
cachedRegexes[str] = new RegExp(str, 'ms'); // multiline, dot-all
|
||||||
}
|
}
|
||||||
@ -20,8 +24,22 @@ function getRegex(str) {
|
|||||||
return cachedRegexes[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 {
|
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();
|
super();
|
||||||
|
|
||||||
this.operator = operator;
|
this.operator = operator;
|
||||||
@ -30,7 +48,7 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
this.flatText = !!flatText;
|
this.flatText = !!flatText;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
if (!ALLOWED_OPERATORS.includes(this.operator)) {
|
if (!ALLOWED_OPERATORS.includes(this.operator)) {
|
||||||
searchContext.addError(`Note content can be searched only with operators: ${ALLOWED_OPERATORS.join(", ")}, operator ${this.operator} given.`);
|
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 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
|
SELECT noteId, type, mime, content, isProtected
|
||||||
FROM notes JOIN blobs USING (blobId)
|
FROM notes JOIN blobs USING (blobId)
|
||||||
WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) {
|
WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) {
|
||||||
@ -51,18 +68,18 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
return resultNoteSet;
|
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)) {
|
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProtected) {
|
if (isProtected) {
|
||||||
if (!protectedSessionService.isProtectedSessionAvailable()) {
|
if (!protectedSessionService.isProtectedSessionAvailable() || !content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
content = protectedSessionService.decryptString(content);
|
content = protectedSessionService.decryptString(content) || undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.info(`Cannot decrypt content of note ${noteId}`);
|
log.info(`Cannot decrypt content of note ${noteId}`);
|
||||||
return;
|
return;
|
||||||
@ -89,7 +106,7 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const nonMatchingToken = this.tokens.find(token =>
|
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
|
// 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
|
// 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;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
preprocessContent(content, type, mime) {
|
preprocessContent(content: string, type: string, mime: string) {
|
||||||
content = utils.normalize(content.toString());
|
content = utils.normalize(content.toString());
|
||||||
|
|
||||||
if (type === 'text' && mime === 'text/html') {
|
if (type === 'text' && mime === 'text/html') {
|
||||||
@ -120,7 +137,7 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
return content.trim();
|
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 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)
|
// 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
|
// 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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import BNote = require("../../../becca/entities/bnote");
|
||||||
const NoteSet = require('../note_set');
|
import SearchContext = require("../search_context");
|
||||||
const becca = require('../../../becca/becca');
|
|
||||||
const utils = require('../../utils');
|
import Expression = require('./expression');
|
||||||
|
import NoteSet = require('../note_set');
|
||||||
|
import becca = require('../../../becca/becca');
|
||||||
|
import utils = require('../../utils');
|
||||||
|
|
||||||
class NoteFlatTextExp extends Expression {
|
class NoteFlatTextExp extends Expression {
|
||||||
constructor(tokens) {
|
private tokens: string[];
|
||||||
|
|
||||||
|
constructor(tokens: string[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.tokens = tokens;
|
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
|
// has deps on SQL which breaks unit test so needs to be dynamically required
|
||||||
const beccaService = require('../../../becca/becca_service');
|
const beccaService = require('../../../becca/becca_service');
|
||||||
const resultNoteSet = new NoteSet();
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BNote} note
|
* @param note
|
||||||
* @param {string[]} remainingTokens - tokens still needed to be found in the path towards root
|
* @param 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.
|
* @param takenPath - path so far taken towards from candidate note towards the root.
|
||||||
* It contains the suffix fragment of the full note path.
|
* 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) {
|
if (remainingTokens.length === 0) {
|
||||||
// we're done, just build the result
|
// we're done, just build the result
|
||||||
const resultPath = this.getNotePath(note, takenPath);
|
const resultPath = this.getNotePath(note, takenPath);
|
||||||
@ -134,12 +139,7 @@ class NoteFlatTextExp extends Expression {
|
|||||||
return resultNoteSet;
|
return resultNoteSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getNotePath(note: BNote, takenPath: string[]): string[] {
|
||||||
* @param {BNote} note
|
|
||||||
* @param {string[]} takenPath
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
getNotePath(note, takenPath) {
|
|
||||||
if (takenPath.length === 0) {
|
if (takenPath.length === 0) {
|
||||||
throw new Error("Path is not expected to be empty.");
|
throw new Error("Path is not expected to be empty.");
|
||||||
} else if (takenPath.length === 1 && takenPath[0] === note.noteId) {
|
} else if (takenPath.length === 1 && takenPath[0] === note.noteId) {
|
||||||
@ -147,7 +147,7 @@ class NoteFlatTextExp extends Expression {
|
|||||||
} else {
|
} else {
|
||||||
// this note is the closest to root containing the last matching token(s), thus completing the requirements
|
// 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
|
// 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)];
|
return [...topMostMatchingTokenNotePath, ...takenPath.slice(1)];
|
||||||
}
|
}
|
||||||
@ -155,11 +155,8 @@ class NoteFlatTextExp extends Expression {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns noteIds which have at least one matching tokens
|
* Returns noteIds which have at least one matching tokens
|
||||||
*
|
|
||||||
* @param {NoteSet} noteSet
|
|
||||||
* @returns {BNote[]}
|
|
||||||
*/
|
*/
|
||||||
getCandidateNotes(noteSet) {
|
getCandidateNotes(noteSet: NoteSet): BNote[] {
|
||||||
const candidateNotes = [];
|
const candidateNotes = [];
|
||||||
|
|
||||||
for (const note of noteSet.notes) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const TrueExp = require('./true.js');
|
import TrueExp = require('./true');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class OrExp extends Expression {
|
class OrExp extends Expression {
|
||||||
static of(subExpressions) {
|
private subExpressions: Expression[];
|
||||||
|
|
||||||
|
static of(subExpressions: Expression[]) {
|
||||||
subExpressions = subExpressions.filter(exp => !!exp);
|
subExpressions = subExpressions.filter(exp => !!exp);
|
||||||
|
|
||||||
if (subExpressions.length === 1) {
|
if (subExpressions.length === 1) {
|
||||||
@ -19,13 +22,13 @@ class OrExp extends Expression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(subExpressions) {
|
constructor(subExpressions: Expression[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.subExpressions = subExpressions;
|
this.subExpressions = subExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const resultNoteSet = new NoteSet();
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const subExpression of this.subExpressions) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import BNote = require("../../../becca/entities/bnote");
|
||||||
const NoteSet = require('../note_set');
|
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 {
|
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();
|
super();
|
||||||
|
|
||||||
this.orderDefinitions = orderDefinitions;
|
this.orderDefinitions = orderDefinitions as OrderDefinition[];
|
||||||
|
|
||||||
for (const od of this.orderDefinitions) {
|
for (const od of this.orderDefinitions) {
|
||||||
od.smaller = od.direction === "asc" ? -1 : 1;
|
od.smaller = od.direction === "asc" ? -1 : 1;
|
||||||
@ -16,11 +34,14 @@ class OrderByAndLimitExp extends Expression {
|
|||||||
|
|
||||||
this.limit = limit || 0;
|
this.limit = limit || 0;
|
||||||
|
|
||||||
/** @type {Expression} */
|
|
||||||
this.subExpression = null; // it's expected to be set after construction
|
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);
|
let {notes} = this.subExpression.execute(inputNoteSet, executionContext, searchContext);
|
||||||
|
|
||||||
notes.sort((a, b) => {
|
notes.sort((a, b) => {
|
||||||
@ -48,7 +69,8 @@ class OrderByAndLimitExp extends Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if both are numbers, then parse them for numerical comparison
|
// 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);
|
valA = parseFloat(valA);
|
||||||
valB = parseFloat(valB);
|
valB = parseFloat(valB);
|
||||||
}
|
}
|
||||||
@ -77,16 +99,16 @@ class OrderByAndLimitExp extends Expression {
|
|||||||
return noteSet;
|
return noteSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
isNumber(x) {
|
isNumber(x: number | string) {
|
||||||
if (typeof x === 'number') {
|
if (typeof x === 'number') {
|
||||||
return true;
|
return true;
|
||||||
} else if (typeof x === 'string') {
|
} else if (typeof x === 'string') {
|
||||||
// isNaN will return false for blank string
|
// isNaN will return false for blank string
|
||||||
return x.trim() !== "" && !isNaN(x);
|
return x.trim() !== "" && !isNaN(parseInt(x, 10));
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OrderByAndLimitExp;
|
export = OrderByAndLimitExp;
|
@ -1,16 +1,19 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class ParentOfExp extends Expression {
|
class ParentOfExp extends Expression {
|
||||||
constructor(subExpression) {
|
private subExpression: Expression;
|
||||||
|
|
||||||
|
constructor(subExpression: Expression) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.subExpression = subExpression;
|
this.subExpression = subExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const subInputNoteSet = new NoteSet();
|
const subInputNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const note of inputNoteSet.notes) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const buildComparator = require('../services/build_comparator.js');
|
import buildComparator = require('../services/build_comparator');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search string is lower cased for case-insensitive comparison. But when retrieving properties,
|
* 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.
|
* we need the case-sensitive form, so we have this translation object.
|
||||||
*/
|
*/
|
||||||
const PROP_MAPPING = {
|
const PROP_MAPPING: Record<string, string> = {
|
||||||
"noteid": "noteId",
|
"noteid": "noteId",
|
||||||
"title": "title",
|
"title": "title",
|
||||||
"type": "type",
|
"type": "type",
|
||||||
@ -36,12 +36,22 @@ const PROP_MAPPING = {
|
|||||||
"revisioncount": "revisionCount"
|
"revisioncount": "revisionCount"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SearchContext {
|
||||||
|
dbLoadNeeded?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
class PropertyComparisonExp extends Expression {
|
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;
|
return name in PROP_MAPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(searchContext, propertyName, operator, comparedValue) {
|
constructor(searchContext: SearchContext, propertyName: string, operator: string, comparedValue: string) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.propertyName = PROP_MAPPING[propertyName];
|
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();
|
const resNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const note of inputNoteSet.notes) {
|
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') {
|
if (value !== undefined && value !== null && typeof value !== 'string') {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
@ -68,7 +78,7 @@ class PropertyComparisonExp extends Expression {
|
|||||||
value = value.toLowerCase();
|
value = value.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.comparator(value)) {
|
if (this.comparator && this.comparator(value)) {
|
||||||
resNoteSet.add(note);
|
resNoteSet.add(note);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,4 +87,4 @@ class PropertyComparisonExp extends Expression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PropertyComparisonExp;
|
export = PropertyComparisonExp;
|
@ -1,18 +1,22 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression.js');
|
import Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
import NoteSet = require('../note_set');
|
||||||
const becca = require('../../../becca/becca');
|
import becca = require('../../../becca/becca');
|
||||||
|
import SearchContext = require('../search_context');
|
||||||
|
|
||||||
class RelationWhereExp extends Expression {
|
class RelationWhereExp extends Expression {
|
||||||
constructor(relationName, subExpression) {
|
private relationName: string;
|
||||||
|
private subExpression: Expression;
|
||||||
|
|
||||||
|
constructor(relationName: string, subExpression: Expression) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.relationName = relationName;
|
this.relationName = relationName;
|
||||||
this.subExpression = subExpression;
|
this.subExpression = subExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(inputNoteSet, executionContext, searchContext) {
|
execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
|
||||||
const candidateNoteSet = new NoteSet();
|
const candidateNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const attr of becca.findAttributes('relation', this.relationName)) {
|
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";
|
"use strict";
|
||||||
|
|
||||||
const hoistedNoteService = require('../hoisted_note.js');
|
import hoistedNoteService = require('../hoisted_note');
|
||||||
|
import { SearchParams } from './services/types';
|
||||||
|
|
||||||
class SearchContext {
|
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.fastSearch = !!params.fastSearch;
|
||||||
this.includeArchivedNotes = !!params.includeArchivedNotes;
|
this.includeArchivedNotes = !!params.includeArchivedNotes;
|
||||||
this.includeHiddenNotes = !!params.includeHiddenNotes;
|
this.includeHiddenNotes = !!params.includeHiddenNotes;
|
||||||
@ -32,7 +52,7 @@ class SearchContext {
|
|||||||
this.error = null;
|
this.error = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addError(error) {
|
addError(error: string) {
|
||||||
// we record only the first error, subsequent ones are usually a consequence of the first
|
// we record only the first error, subsequent ones are usually a consequence of the first
|
||||||
if (!this.error) {
|
if (!this.error) {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
@ -48,4 +68,4 @@ class SearchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SearchContext;
|
export = SearchContext;
|
@ -1,12 +1,18 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const beccaService = require('../../becca/becca_service');
|
import beccaService = require('../../becca/becca_service');
|
||||||
const becca = require('../../becca/becca');
|
import becca = require('../../becca/becca');
|
||||||
|
|
||||||
class SearchResult {
|
class SearchResult {
|
||||||
constructor(notePathArray) {
|
notePathArray: string[];
|
||||||
|
score: number;
|
||||||
|
notePathTitle: string;
|
||||||
|
highlightedNotePathTitle?: string;
|
||||||
|
|
||||||
|
constructor(notePathArray: string[]) {
|
||||||
this.notePathArray = notePathArray;
|
this.notePathArray = notePathArray;
|
||||||
this.notePathTitle = beccaService.getNoteTitleForPath(notePathArray);
|
this.notePathTitle = beccaService.getNoteTitleForPath(notePathArray);
|
||||||
|
this.score = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get notePath() {
|
get notePath() {
|
||||||
@ -17,7 +23,7 @@ class SearchResult {
|
|||||||
return this.notePathArray[this.notePathArray.length - 1];
|
return this.notePathArray[this.notePathArray.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
computeScore(fulltextQuery, tokens) {
|
computeScore(fulltextQuery: string, tokens: string[]) {
|
||||||
this.score = 0;
|
this.score = 0;
|
||||||
|
|
||||||
const note = becca.notes[this.noteId];
|
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(" ");
|
const chunks = str.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
this.score = 0;
|
||||||
|
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (chunk === token) {
|
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)) {
|
if (!(str in cachedRegexes)) {
|
||||||
cachedRegexes[str] = new RegExp(str);
|
cachedRegexes[str] = new RegExp(str);
|
||||||
}
|
}
|
||||||
@ -8,31 +8,36 @@ function getRegex(str) {
|
|||||||
return cachedRegexes[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 >= 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.endsWith(comparedValue)),
|
||||||
"=*": comparedValue => (val => val && val.startsWith(comparedValue)),
|
"=*": comparedValue => (val => !!val && val.startsWith(comparedValue)),
|
||||||
"*=*": comparedValue => (val => val && val.includes(comparedValue)),
|
"*=*": comparedValue => (val => !!val && val.includes(comparedValue)),
|
||||||
"%=": comparedValue => (val => val && !!getRegex(comparedValue).test(val)),
|
"%=": 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),
|
||||||
"<": 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();
|
comparedValue = comparedValue.toLowerCase();
|
||||||
|
|
||||||
if (operator in numericComparators && !isNaN(comparedValue)) {
|
if (operator in numericComparators) {
|
||||||
return numericComparators[operator](parseFloat(comparedValue));
|
const floatValue = parseFloat(comparedValue);
|
||||||
|
if (!isNaN(floatValue)) {
|
||||||
|
return numericComparators[operator](floatValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operator in stringComparators) {
|
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
|
* 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) {
|
if (tokens.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const leftIdx = tokens.findIndex(token => token.token === '(');
|
const leftIdx = tokens.findIndex(token => "token" in token && token.token === '(');
|
||||||
|
|
||||||
if (leftIdx === -1) {
|
if (leftIdx === -1) {
|
||||||
return tokens;
|
return tokens;
|
||||||
@ -17,13 +19,18 @@ function handleParens(tokens) {
|
|||||||
let parensLevel = 0
|
let parensLevel = 0
|
||||||
|
|
||||||
for (rightIdx = leftIdx; rightIdx < tokens.length; rightIdx++) {
|
for (rightIdx = leftIdx; rightIdx < tokens.length; rightIdx++) {
|
||||||
if (tokens[rightIdx].token === ')') {
|
const token = tokens[rightIdx];
|
||||||
|
if (!("token" in token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.token === ')') {
|
||||||
parensLevel--;
|
parensLevel--;
|
||||||
|
|
||||||
if (parensLevel === 0) {
|
if (parensLevel === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (tokens[rightIdx].token === '(') {
|
} else if (token.token === '(') {
|
||||||
parensLevel++;
|
parensLevel++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,8 +43,8 @@ function handleParens(tokens) {
|
|||||||
...tokens.slice(0, leftIdx),
|
...tokens.slice(0, leftIdx),
|
||||||
handleParens(tokens.slice(leftIdx + 1, rightIdx)),
|
handleParens(tokens.slice(leftIdx + 1, rightIdx)),
|
||||||
...tokens.slice(rightIdx + 1)
|
...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();
|
str = str.toLowerCase();
|
||||||
|
|
||||||
let fulltextQuery = "";
|
let fulltextQuery = "";
|
||||||
const fulltextTokens = [];
|
const fulltextTokens: TokenData[] = [];
|
||||||
const expressionTokens = [];
|
const expressionTokens: TokenData[] = [];
|
||||||
|
|
||||||
/** @type {boolean|string} */
|
let quotes: boolean | string = false; // otherwise contains used quote - ', " or `
|
||||||
let quotes = false; // otherwise contains used quote - ', " or `
|
|
||||||
let fulltextEnded = false;
|
let fulltextEnded = false;
|
||||||
let currentWord = '';
|
let currentWord = '';
|
||||||
|
|
||||||
function isSymbolAnOperator(chr) {
|
function isSymbolAnOperator(chr: string) {
|
||||||
return ['=', '*', '>', '<', '!', "-", "+", '%', ','].includes(chr);
|
return ['=', '*', '>', '<', '!', "-", "+", '%', ','].includes(chr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ function lex(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishWord(endIndex, createAlsoForEmptyWords = false) {
|
function finishWord(endIndex: number, createAlsoForEmptyWords = false) {
|
||||||
if (currentWord === '' && !createAlsoForEmptyWords) {
|
if (currentWord === '' && !createAlsoForEmptyWords) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rec = {
|
const rec: TokenData = {
|
||||||
token: currentWord,
|
token: currentWord,
|
||||||
inQuotes: !!quotes,
|
inQuotes: !!quotes,
|
||||||
startIndex: endIndex - currentWord.length + 1,
|
startIndex: endIndex - currentWord.length + 1,
|
||||||
@ -146,4 +147,4 @@ function lex(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = lex;
|
export = lex;
|
@ -1,28 +1,31 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const dayjs = require("dayjs");
|
import dayjs = require("dayjs");
|
||||||
const AndExp = require('../expressions/and.js');
|
import AndExp = require('../expressions/and');
|
||||||
const OrExp = require('../expressions/or.js');
|
import OrExp = require('../expressions/or');
|
||||||
const NotExp = require('../expressions/not.js');
|
import NotExp = require('../expressions/not');
|
||||||
const ChildOfExp = require('../expressions/child_of.js');
|
import ChildOfExp = require('../expressions/child_of');
|
||||||
const DescendantOfExp = require('../expressions/descendant_of.js');
|
import DescendantOfExp = require('../expressions/descendant_of');
|
||||||
const ParentOfExp = require('../expressions/parent_of.js');
|
import ParentOfExp = require('../expressions/parent_of');
|
||||||
const RelationWhereExp = require('../expressions/relation_where.js');
|
import RelationWhereExp = require('../expressions/relation_where');
|
||||||
const PropertyComparisonExp = require('../expressions/property_comparison.js');
|
import PropertyComparisonExp = require('../expressions/property_comparison');
|
||||||
const AttributeExistsExp = require('../expressions/attribute_exists.js');
|
import AttributeExistsExp = require('../expressions/attribute_exists');
|
||||||
const LabelComparisonExp = require('../expressions/label_comparison.js');
|
import LabelComparisonExp = require('../expressions/label_comparison');
|
||||||
const NoteFlatTextExp = require('../expressions/note_flat_text.js');
|
import NoteFlatTextExp = require('../expressions/note_flat_text');
|
||||||
const NoteContentFulltextExp = require('../expressions/note_content_fulltext.js');
|
import NoteContentFulltextExp = require('../expressions/note_content_fulltext');
|
||||||
const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js');
|
import OrderByAndLimitExp = require('../expressions/order_by_and_limit');
|
||||||
const AncestorExp = require('../expressions/ancestor.js');
|
import AncestorExp = require('../expressions/ancestor');
|
||||||
const buildComparator = require('./build_comparator.js');
|
import buildComparator = require('./build_comparator');
|
||||||
const ValueExtractor = require('../value_extractor.js');
|
import ValueExtractor = require('../value_extractor');
|
||||||
const utils = require('../../utils');
|
import utils = require('../../utils');
|
||||||
const TrueExp = require('../expressions/true.js');
|
import TrueExp = require('../expressions/true');
|
||||||
const IsHiddenExp = require('../expressions/is_hidden.js');
|
import IsHiddenExp = require('../expressions/is_hidden');
|
||||||
|
import SearchContext = require("../search_context");
|
||||||
|
import { TokenData } from "./types";
|
||||||
|
import Expression = require("../expressions/expression");
|
||||||
|
|
||||||
function getFulltext(tokens, searchContext) {
|
function getFulltext(_tokens: TokenData[], searchContext: SearchContext) {
|
||||||
tokens = tokens.map(t => utils.removeDiacritic(t.token));
|
const tokens: string[] = _tokens.map(t => utils.removeDiacritic(t.token));
|
||||||
|
|
||||||
searchContext.highlightedTokens.push(...tokens);
|
searchContext.highlightedTokens.push(...tokens);
|
||||||
|
|
||||||
@ -54,7 +57,7 @@ const OPERATORS = [
|
|||||||
"%="
|
"%="
|
||||||
];
|
];
|
||||||
|
|
||||||
function isOperator(token) {
|
function isOperator(token: TokenData) {
|
||||||
if (Array.isArray(token)) {
|
if (Array.isArray(token)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -62,20 +65,20 @@ function isOperator(token) {
|
|||||||
return OPERATORS.includes(token.token);
|
return OPERATORS.includes(token.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExpression(tokens, searchContext, level = 0) {
|
function getExpression(tokens: TokenData[], searchContext: SearchContext, level = 0) {
|
||||||
if (tokens.length === 0) {
|
if (tokens.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expressions = [];
|
const expressions: Expression[] = [];
|
||||||
let op = null;
|
let op: string | null = null;
|
||||||
|
|
||||||
let i;
|
let i: number;
|
||||||
|
|
||||||
function context(i) {
|
function context(i: number) {
|
||||||
let {startIndex, endIndex} = tokens[i];
|
let {startIndex, endIndex} = tokens[i];
|
||||||
startIndex = Math.max(0, startIndex - 20);
|
startIndex = Math.max(0, (startIndex || 0) - 20);
|
||||||
endIndex = Math.min(searchContext.originalQuery.length, endIndex + 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 ? "..." : ""}"`;
|
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);
|
return date.format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNoteProperty() {
|
function parseNoteProperty(): Expression | undefined | null {
|
||||||
if (tokens[i].token !== '.') {
|
if (tokens[i].token !== '.') {
|
||||||
searchContext.addError('Expected "." to separate field path');
|
searchContext.addError('Expected "." to separate field path');
|
||||||
return;
|
return;
|
||||||
@ -161,19 +164,25 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
if (tokens[i].token === 'parents') {
|
if (tokens[i].token === 'parents') {
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
return new ChildOfExp(parseNoteProperty());
|
const expression = parseNoteProperty();
|
||||||
|
if (!expression) { return; }
|
||||||
|
return new ChildOfExp(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[i].token === 'children') {
|
if (tokens[i].token === 'children') {
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
return new ParentOfExp(parseNoteProperty());
|
const expression = parseNoteProperty();
|
||||||
|
if (!expression) { return; }
|
||||||
|
return new ParentOfExp(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[i].token === 'ancestors') {
|
if (tokens[i].token === 'ancestors') {
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
return new DescendantOfExp(parseNoteProperty());
|
const expression = parseNoteProperty();
|
||||||
|
if (!expression) { return; }
|
||||||
|
return new DescendantOfExp(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[i].token === 'labels') {
|
if (tokens[i].token === 'labels') {
|
||||||
@ -219,6 +228,10 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
i += 2;
|
i += 2;
|
||||||
|
|
||||||
const comparedValue = resolveConstantOperand();
|
const comparedValue = resolveConstantOperand();
|
||||||
|
if (!comparedValue) {
|
||||||
|
searchContext.addError(`Unresolved constant operand.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new PropertyComparisonExp(searchContext, propertyName, operator, comparedValue);
|
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)}`);
|
searchContext.addError(`Unrecognized note property "${tokens[i].token}" in ${context(i)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAttribute(name) {
|
function parseAttribute(name: string) {
|
||||||
const isLabel = name.startsWith('#');
|
const isLabel = name.startsWith('#');
|
||||||
|
|
||||||
name = name.substr(1);
|
name = name.substr(1);
|
||||||
@ -239,10 +252,10 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
|
|
||||||
const subExp = isLabel ? parseLabel(name) : parseRelation(name);
|
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);
|
searchContext.highlightedTokens.push(labelName);
|
||||||
|
|
||||||
if (i < tokens.length - 2 && isOperator(tokens[i + 1])) {
|
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);
|
searchContext.highlightedTokens.push(relationName);
|
||||||
|
|
||||||
if (i < tokens.length - 2 && tokens[i + 1].token === '.') {
|
if (i < tokens.length - 2 && tokens[i + 1].token === '.') {
|
||||||
i += 1;
|
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])) {
|
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)}`);
|
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() {
|
function parseOrderByAndLimit() {
|
||||||
const orderDefinitions = [];
|
const orderDefinitions: {
|
||||||
|
valueExtractor: ValueExtractor,
|
||||||
|
direction: string
|
||||||
|
}[] = [];
|
||||||
let limit;
|
let limit;
|
||||||
|
|
||||||
if (tokens[i].token === 'orderby') {
|
if (tokens[i].token === 'orderby') {
|
||||||
@ -316,8 +334,9 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
|
|
||||||
const valueExtractor = new ValueExtractor(searchContext, propertyPath);
|
const valueExtractor = new ValueExtractor(searchContext, propertyPath);
|
||||||
|
|
||||||
if (valueExtractor.validate()) {
|
const validationError = valueExtractor.validate();
|
||||||
searchContext.addError(valueExtractor.validate());
|
if (validationError) {
|
||||||
|
searchContext.addError(validationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
orderDefinitions.push({
|
orderDefinitions.push({
|
||||||
@ -348,7 +367,10 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
|
|
||||||
for (i = 0; i < tokens.length; i++) {
|
for (i = 0; i < tokens.length; i++) {
|
||||||
if (Array.isArray(tokens[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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +381,10 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.startsWith('#') || token.startsWith('~')) {
|
if (token.startsWith('#') || token.startsWith('~')) {
|
||||||
expressions.push(parseAttribute(token));
|
const attribute = parseAttribute(token);
|
||||||
|
if (attribute) {
|
||||||
|
expressions.push(attribute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (['orderby', 'limit'].includes(token)) {
|
else if (['orderby', 'limit'].includes(token)) {
|
||||||
if (level !== 0) {
|
if (level !== 0) {
|
||||||
@ -384,12 +409,17 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
continue;
|
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') {
|
else if (token === 'note') {
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
expressions.push(parseNoteProperty());
|
const expression = parseNoteProperty();
|
||||||
|
if (!expression) { return; }
|
||||||
|
expressions.push(expression);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -416,13 +446,18 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
return getAggregateExpression();
|
return getAggregateExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse({fulltextTokens, expressionTokens, searchContext}) {
|
function parse({fulltextTokens, expressionTokens, searchContext}: {
|
||||||
let expression;
|
fulltextTokens: TokenData[],
|
||||||
|
expressionTokens: (TokenData | TokenData[])[],
|
||||||
|
searchContext: SearchContext,
|
||||||
|
originalQuery: string
|
||||||
|
}) {
|
||||||
|
let expression: Expression | undefined | null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expression = getExpression(expressionTokens, searchContext);
|
expression = getExpression(expressionTokens as TokenData[], searchContext);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
searchContext.addError(e.message);
|
searchContext.addError(e.message);
|
||||||
|
|
||||||
expression = new TrueExp();
|
expression = new TrueExp();
|
||||||
@ -441,15 +476,15 @@ function parse({fulltextTokens, expressionTokens, searchContext}) {
|
|||||||
exp = new OrderByAndLimitExp([{
|
exp = new OrderByAndLimitExp([{
|
||||||
valueExtractor: new ValueExtractor(searchContext, ['note', searchContext.orderBy]),
|
valueExtractor: new ValueExtractor(searchContext, ['note', searchContext.orderBy]),
|
||||||
direction: searchContext.orderDirection
|
direction: searchContext.orderDirection
|
||||||
}], searchContext.limit);
|
}], searchContext.limit || undefined);
|
||||||
|
|
||||||
exp.subExpression = filterExp;
|
(exp as any).subExpression = filterExp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) {
|
function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}: SearchContext) {
|
||||||
if (ancestorNoteId && ancestorNoteId !== 'root') {
|
if (ancestorNoteId && ancestorNoteId !== 'root') {
|
||||||
return new AncestorExp(ancestorNoteId, ancestorDepth);
|
return new AncestorExp(ancestorNoteId, ancestorDepth);
|
||||||
} else if (!includeHiddenNotes) {
|
} else if (!includeHiddenNotes) {
|
||||||
@ -459,4 +494,4 @@ function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = parse;
|
export = parse;
|
@ -1,22 +1,28 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const normalizeString = require("normalize-strings");
|
import normalizeString = require("normalize-strings");
|
||||||
const lex = require('./lex.js');
|
import lex = require('./lex');
|
||||||
const handleParens = require('./handle_parens.js');
|
import handleParens = require('./handle_parens');
|
||||||
const parse = require('./parse.js');
|
import parse = require('./parse');
|
||||||
const SearchResult = require('../search_result.js');
|
import SearchResult = require('../search_result');
|
||||||
const SearchContext = require('../search_context.js');
|
import SearchContext = require('../search_context');
|
||||||
const becca = require('../../../becca/becca');
|
import becca = require('../../../becca/becca');
|
||||||
const beccaService = require('../../../becca/becca_service');
|
import beccaService = require('../../../becca/becca_service');
|
||||||
const utils = require('../../utils');
|
import utils = require('../../utils');
|
||||||
const log = require('../../log');
|
import log = require('../../log');
|
||||||
const hoistedNoteService = require('../../hoisted_note.js');
|
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) {
|
function searchFromNote(note: BNote) {
|
||||||
let searchResultNoteIds, highlightedTokens;
|
let searchResultNoteIds;
|
||||||
|
let highlightedTokens: string[];
|
||||||
|
|
||||||
const searchScript = note.getRelationValue('searchScript');
|
const searchScript = note.getRelationValue('searchScript');
|
||||||
const searchString = note.getLabelValue('searchString');
|
const searchString = note.getLabelValue('searchString') || "";
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
if (searchScript) {
|
if (searchScript) {
|
||||||
@ -25,12 +31,12 @@ function searchFromNote(note) {
|
|||||||
} else {
|
} else {
|
||||||
const searchContext = new SearchContext({
|
const searchContext = new SearchContext({
|
||||||
fastSearch: note.hasLabel('fastSearch'),
|
fastSearch: note.hasLabel('fastSearch'),
|
||||||
ancestorNoteId: note.getRelationValue('ancestor'),
|
ancestorNoteId: note.getRelationValue('ancestor') || undefined,
|
||||||
ancestorDepth: note.getLabelValue('ancestorDepth'),
|
ancestorDepth: note.getLabelValue('ancestorDepth') || undefined,
|
||||||
includeArchivedNotes: note.hasLabel('includeArchivedNotes'),
|
includeArchivedNotes: note.hasLabel('includeArchivedNotes'),
|
||||||
orderBy: note.getLabelValue('orderBy'),
|
orderBy: note.getLabelValue('orderBy') || undefined,
|
||||||
orderDirection: note.getLabelValue('orderDirection'),
|
orderDirection: note.getLabelValue('orderDirection') || undefined,
|
||||||
limit: note.getLabelValue('limit'),
|
limit: parseInt(note.getLabelValue('limit') || "0", 10),
|
||||||
debug: note.hasLabel('debug'),
|
debug: note.hasLabel('debug'),
|
||||||
fuzzyAttributeSearch: false
|
fuzzyAttributeSearch: false
|
||||||
});
|
});
|
||||||
@ -51,7 +57,7 @@ function searchFromNote(note) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchFromRelation(note, relationName) {
|
function searchFromRelation(note: BNote, relationName: string) {
|
||||||
const scriptNote = note.getRelationTarget(relationName);
|
const scriptNote = note.getRelationTarget(relationName);
|
||||||
|
|
||||||
if (!scriptNote) {
|
if (!scriptNote) {
|
||||||
@ -90,18 +96,21 @@ function searchFromRelation(note, relationName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadNeededInfoFromDatabase() {
|
function loadNeededInfoFromDatabase() {
|
||||||
const sql = require('../../sql');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This complex structure is needed to calculate total occupied space by a note. Several object instances
|
* 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
|
* (note, revisions, attachments) can point to a single blobId, and thus the blob size should count towards the total
|
||||||
* only once.
|
* 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
|
SELECT
|
||||||
noteId,
|
noteId,
|
||||||
blobId,
|
blobId,
|
||||||
@ -122,7 +131,12 @@ function loadNeededInfoFromDatabase() {
|
|||||||
noteBlobs[noteId] = { [blobId]: length };
|
noteBlobs[noteId] = { [blobId]: length };
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentContentLengths = sql.getRows(`
|
type AttachmentContentLengthsRow = {
|
||||||
|
noteId: string;
|
||||||
|
blobId: string;
|
||||||
|
length: number;
|
||||||
|
};
|
||||||
|
const attachmentContentLengths = sql.getRows<AttachmentContentLengthsRow>(`
|
||||||
SELECT
|
SELECT
|
||||||
ownerId AS noteId,
|
ownerId AS noteId,
|
||||||
attachments.blobId,
|
attachments.blobId,
|
||||||
@ -151,7 +165,13 @@ function loadNeededInfoFromDatabase() {
|
|||||||
becca.notes[noteId].contentAndAttachmentsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
|
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
|
SELECT
|
||||||
noteId,
|
noteId,
|
||||||
revisions.blobId,
|
revisions.blobId,
|
||||||
@ -187,7 +207,10 @@ function loadNeededInfoFromDatabase() {
|
|||||||
noteBlobs[noteId][blobId] = length;
|
noteBlobs[noteId][blobId] = length;
|
||||||
|
|
||||||
if (isNoteRevision) {
|
if (isNoteRevision) {
|
||||||
becca.notes[noteId].revisionCount++;
|
const noteRevision = becca.notes[noteId];
|
||||||
|
if (noteRevision && noteRevision.revisionCount) {
|
||||||
|
noteRevision.revisionCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,20 +219,16 @@ function loadNeededInfoFromDatabase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function findResultsWithExpression(expression: Expression, searchContext: SearchContext): SearchResult[] {
|
||||||
* @param {Expression} expression
|
|
||||||
* @param {SearchContext} searchContext
|
|
||||||
* @returns {SearchResult[]}
|
|
||||||
*/
|
|
||||||
function findResultsWithExpression(expression, searchContext) {
|
|
||||||
if (searchContext.dbLoadNeeded) {
|
if (searchContext.dbLoadNeeded) {
|
||||||
loadNeededInfoFromDatabase();
|
loadNeededInfoFromDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNoteSet = becca.getAllNoteSet();
|
const allNoteSet = becca.getAllNoteSet();
|
||||||
|
|
||||||
|
const noteIdToNotePath: Record<string, string[]> = {};
|
||||||
const executionContext = {
|
const executionContext = {
|
||||||
noteIdToNotePath: {}
|
noteIdToNotePath
|
||||||
};
|
};
|
||||||
|
|
||||||
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
|
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
|
||||||
@ -250,16 +269,16 @@ function findResultsWithExpression(expression, searchContext) {
|
|||||||
return searchResults;
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseQueryToExpression(query, searchContext) {
|
function parseQueryToExpression(query: string, searchContext: SearchContext) {
|
||||||
const {fulltextQuery, fulltextTokens, expressionTokens} = lex(query);
|
const {fulltextQuery, fulltextTokens, expressionTokens} = lex(query);
|
||||||
searchContext.fulltextQuery = fulltextQuery;
|
searchContext.fulltextQuery = fulltextQuery;
|
||||||
|
|
||||||
let structuredExpressionTokens;
|
let structuredExpressionTokens: (TokenData | TokenData[])[];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
structuredExpressionTokens = handleParens(expressionTokens);
|
structuredExpressionTokens = handleParens(expressionTokens);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
structuredExpressionTokens = [];
|
structuredExpressionTokens = [];
|
||||||
searchContext.addError(e.message);
|
searchContext.addError(e.message);
|
||||||
}
|
}
|
||||||
@ -284,23 +303,13 @@ function parseQueryToExpression(query, searchContext) {
|
|||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function searchNotes(query: string, params: SearchParams = {}): BNote[] {
|
||||||
* @param {string} query
|
|
||||||
* @param {object} params - see SearchContext
|
|
||||||
* @returns {BNote[]}
|
|
||||||
*/
|
|
||||||
function searchNotes(query, params = {}) {
|
|
||||||
const searchResults = findResultsWithQuery(query, new SearchContext(params));
|
const searchResults = findResultsWithQuery(query, new SearchContext(params));
|
||||||
|
|
||||||
return searchResults.map(sr => becca.notes[sr.noteId]);
|
return searchResults.map(sr => becca.notes[sr.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function findResultsWithQuery(query: string, searchContext: SearchContext): SearchResult[] {
|
||||||
* @param {string} query
|
|
||||||
* @param {SearchContext} searchContext
|
|
||||||
* @returns {SearchResult[]}
|
|
||||||
*/
|
|
||||||
function findResultsWithQuery(query, searchContext) {
|
|
||||||
query = query || "";
|
query = query || "";
|
||||||
searchContext.originalQuery = query;
|
searchContext.originalQuery = query;
|
||||||
|
|
||||||
@ -313,18 +322,13 @@ function findResultsWithQuery(query, searchContext) {
|
|||||||
return findResultsWithExpression(expression, searchContext);
|
return findResultsWithExpression(expression, searchContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function findFirstNoteWithQuery(query: string, searchContext: SearchContext): BNote | null {
|
||||||
* @param {string} query
|
|
||||||
* @param {SearchContext} searchContext
|
|
||||||
* @returns {BNote|null}
|
|
||||||
*/
|
|
||||||
function findFirstNoteWithQuery(query, searchContext) {
|
|
||||||
const searchResults = findResultsWithQuery(query, searchContext);
|
const searchResults = findResultsWithQuery(query, searchContext);
|
||||||
|
|
||||||
return searchResults.length > 0 ? becca.notes[searchResults[0].noteId] : null;
|
return searchResults.length > 0 ? becca.notes[searchResults[0].noteId] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchNotesForAutocomplete(query) {
|
function searchNotesForAutocomplete(query: string) {
|
||||||
const searchContext = new SearchContext({
|
const searchContext = new SearchContext({
|
||||||
fastSearch: true,
|
fastSearch: true,
|
||||||
includeArchivedNotes: false,
|
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));
|
highlightedTokens = Array.from(new Set(highlightedTokens));
|
||||||
|
|
||||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
// 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);
|
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;
|
let match;
|
||||||
|
|
||||||
// Find all matches
|
// Find all matches
|
||||||
|
if (!result.highlightedNotePathTitle) { continue; }
|
||||||
while ((match = tokenRegex.exec(normalizeString(result.highlightedNotePathTitle))) !== null) {
|
while ((match = tokenRegex.exec(normalizeString(result.highlightedNotePathTitle))) !== null) {
|
||||||
result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}");
|
result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}");
|
||||||
|
|
||||||
@ -413,6 +418,7 @@ function highlightSearchResults(searchResults, highlightedTokens) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const result of searchResults) {
|
for (const result of searchResults) {
|
||||||
|
if (!result.highlightedNotePathTitle) { continue; }
|
||||||
result.highlightedNotePathTitle = result.highlightedNotePathTitle
|
result.highlightedNotePathTitle = result.highlightedNotePathTitle
|
||||||
.replace(/"/g, "<small>")
|
.replace(/"/g, "<small>")
|
||||||
.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') {
|
if (attr.type === 'relation') {
|
||||||
return `~${utils.escapeHtml(attr.name)}=…`;
|
return `~${utils.escapeHtml(attr.name)}=…`;
|
||||||
}
|
}
|
||||||
@ -438,7 +444,7 @@ function formatAttribute(attr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
searchFromNote,
|
searchFromNote,
|
||||||
searchNotesForAutocomplete,
|
searchNotesForAutocomplete,
|
||||||
findResultsWithQuery,
|
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";
|
"use strict";
|
||||||
|
|
||||||
|
import BNote = require("../../becca/entities/bnote");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search string is lower cased for case-insensitive comparison. But when retrieving properties,
|
* 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.
|
* we need a case-sensitive form, so we have this translation object.
|
||||||
*/
|
*/
|
||||||
const PROP_MAPPING = {
|
const PROP_MAPPING: Record<string, string> = {
|
||||||
"noteid": "noteId",
|
"noteid": "noteId",
|
||||||
"title": "title",
|
"title": "title",
|
||||||
"type": "type",
|
"type": "type",
|
||||||
@ -32,8 +34,14 @@ const PROP_MAPPING = {
|
|||||||
"revisioncount": "revisionCount"
|
"revisioncount": "revisionCount"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SearchContext {
|
||||||
|
dbLoadNeeded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
class ValueExtractor {
|
class ValueExtractor {
|
||||||
constructor(searchContext, propertyPath) {
|
private propertyPath: string[];
|
||||||
|
|
||||||
|
constructor(searchContext: SearchContext, propertyPath: string[]) {
|
||||||
this.propertyPath = propertyPath.map(pathEl => pathEl.toLowerCase());
|
this.propertyPath = propertyPath.map(pathEl => pathEl.toLowerCase());
|
||||||
|
|
||||||
if (this.propertyPath[0].startsWith('#')) {
|
if (this.propertyPath[0].startsWith('#')) {
|
||||||
@ -81,10 +89,10 @@ class ValueExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extract(note) {
|
extract(note: BNote) {
|
||||||
let cursor = note;
|
let cursor: BNote | null = note;
|
||||||
|
|
||||||
let i;
|
let i: number = 0;
|
||||||
|
|
||||||
const cur = () => this.propertyPath[i];
|
const cur = () => this.propertyPath[i];
|
||||||
|
|
||||||
@ -105,8 +113,7 @@ class ValueExtractor {
|
|||||||
i++;
|
i++;
|
||||||
|
|
||||||
const attr = cursor.getAttributeCaseInsensitive('relation', cur());
|
const attr = cursor.getAttributeCaseInsensitive('relation', cur());
|
||||||
|
cursor = attr?.targetNote || null;
|
||||||
cursor = attr ? attr.targetNote : null;
|
|
||||||
}
|
}
|
||||||
else if (cur() === 'parents') {
|
else if (cur() === 'parents') {
|
||||||
cursor = cursor.parents[0];
|
cursor = cursor.parents[0];
|
||||||
@ -118,7 +125,7 @@ class ValueExtractor {
|
|||||||
return Math.random().toString(); // string is expected for comparison
|
return Math.random().toString(); // string is expected for comparison
|
||||||
}
|
}
|
||||||
else if (cur() in PROP_MAPPING) {
|
else if (cur() in PROP_MAPPING) {
|
||||||
return cursor[PROP_MAPPING[cur()]];
|
return (cursor as any)[PROP_MAPPING[cur()]];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// FIXME
|
// FIXME
|
||||||
@ -127,4 +134,4 @@ class ValueExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ValueExtractor;
|
export = ValueExtractor;
|
@ -1,15 +1,17 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const fs = require('fs');
|
import fs = require('fs');
|
||||||
const crypto = require('crypto');
|
import crypto = require('crypto');
|
||||||
const dataDir = require('./data_dir');
|
import dataDir = require('./data_dir');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
|
|
||||||
const sessionSecretPath = `${dataDir.TRILIUM_DATA_DIR}/session_secret.txt`;
|
const sessionSecretPath = `${dataDir.TRILIUM_DATA_DIR}/session_secret.txt`;
|
||||||
|
|
||||||
let sessionSecret;
|
let sessionSecret;
|
||||||
|
|
||||||
function randomValueHex(len) {
|
const ENCODING = "ascii";
|
||||||
|
|
||||||
|
function randomValueHex(len: number) {
|
||||||
return crypto.randomBytes(Math.ceil(len / 2))
|
return crypto.randomBytes(Math.ceil(len / 2))
|
||||||
.toString('hex') // convert to hexadecimal format
|
.toString('hex') // convert to hexadecimal format
|
||||||
.slice(0, len).toUpperCase(); // return required number of characters
|
.slice(0, len).toUpperCase(); // return required number of characters
|
||||||
@ -20,10 +22,10 @@ if (!fs.existsSync(sessionSecretPath)) {
|
|||||||
|
|
||||||
log.info("Generated session secret");
|
log.info("Generated session secret");
|
||||||
|
|
||||||
fs.writeFileSync(sessionSecretPath, sessionSecret, 'ASCII');
|
fs.writeFileSync(sessionSecretPath, sessionSecret, ENCODING);
|
||||||
}
|
}
|
||||||
else {
|
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 log = require('./log');
|
||||||
const sqlInit = require('./sql_init');
|
const sqlInit = require('./sql_init');
|
||||||
const optionService = require('./options');
|
const optionService = require('./options');
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
const attributeService = require('./attributes.js');
|
const attributeService = require('./attributes');
|
||||||
const dateNoteService = require('./date_notes.js');
|
const dateNoteService = require('./date_notes');
|
||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const hoistedNoteService = require('./hoisted_note.js');
|
const hoistedNoteService = require('./hoisted_note');
|
||||||
const searchService = require('./search/services/search.js');
|
const searchService = require('./search/services/search');
|
||||||
const SearchContext = require('./search/search_context.js');
|
const SearchContext = require('./search/search_context');
|
||||||
const {LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT} = require('./hidden_subtree');
|
const {LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT} = require('./hidden_subtree');
|
||||||
|
|
||||||
function getInboxNote(date) {
|
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[]) || [];
|
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) {
|
if (LOG_ALL_QUERIES) {
|
||||||
console.log(query);
|
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 = []) {
|
function getMap<K extends string | number | symbol, V>(query: string, params: Params = []) {
|
||||||
|
@ -84,7 +84,7 @@ async function createInitialDatabase() {
|
|||||||
notePosition: 10
|
notePosition: 10
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const optionsInitService = require('./options_init.js');
|
const optionsInitService = require('./options_init');
|
||||||
|
|
||||||
optionsInitService.initDocumentOptions();
|
optionsInitService.initDocumentOptions();
|
||||||
optionsInitService.initNotSyncedOptions(true, {});
|
optionsInitService.initNotSyncedOptions(true, {});
|
||||||
@ -132,7 +132,7 @@ function createDatabaseForSync(options: OptionRow[], syncServerHost = '', syncPr
|
|||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
sql.executeScript(schema);
|
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
|
// document options required for sync to kick off
|
||||||
for (const opt of options) {
|
for (const opt of options) {
|
||||||
|
@ -1,27 +1,50 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
const utils = require('./utils');
|
import utils = require('./utils');
|
||||||
const instanceId = require('./instance_id');
|
import instanceId = require('./instance_id');
|
||||||
const dateUtils = require('./date_utils');
|
import dateUtils = require('./date_utils');
|
||||||
const syncUpdateService = require('./sync_update.js');
|
import syncUpdateService = require('./sync_update');
|
||||||
const contentHashService = require('./content_hash.js');
|
import contentHashService = require('./content_hash');
|
||||||
const appInfo = require('./app_info');
|
import appInfo = require('./app_info');
|
||||||
const syncOptions = require('./sync_options');
|
import syncOptions = require('./sync_options');
|
||||||
const syncMutexService = require('./sync_mutex');
|
import syncMutexService = require('./sync_mutex');
|
||||||
const cls = require('./cls');
|
import cls = require('./cls');
|
||||||
const request = require('./request');
|
import request = require('./request');
|
||||||
const ws = require('./ws');
|
import ws = require('./ws');
|
||||||
const entityChangesService = require('./entity_changes');
|
import entityChangesService = require('./entity_changes');
|
||||||
const entityConstructor = require('../becca/entity_constructor');
|
import entityConstructor = require('../becca/entity_constructor');
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
|
import { EntityChange, EntityChangeRecord, EntityRow } from './entity_changes_interface';
|
||||||
|
import { CookieJar, ExecOpts } from './request_interface';
|
||||||
|
|
||||||
let proxyToggle = true;
|
let proxyToggle = true;
|
||||||
|
|
||||||
let outstandingPullCount = 0;
|
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() {
|
async function sync() {
|
||||||
try {
|
try {
|
||||||
return await syncMutexService.doExclusively(async () => {
|
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
|
// we're dynamically switching whether we're using proxy or not based on whether we encountered error with the current method
|
||||||
proxyToggle = !proxyToggle;
|
proxyToggle = !proxyToggle;
|
||||||
|
|
||||||
@ -93,19 +116,23 @@ async function login() {
|
|||||||
return await doLogin();
|
return await doLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doLogin() {
|
async function doLogin(): Promise<SyncContext> {
|
||||||
const timestamp = dateUtils.utcNowDateTime();
|
const timestamp = dateUtils.utcNowDateTime();
|
||||||
|
|
||||||
const documentSecret = optionService.getOption('documentSecret');
|
const documentSecret = optionService.getOption('documentSecret');
|
||||||
const hash = utils.hmac(documentSecret, timestamp);
|
const hash = utils.hmac(documentSecret, timestamp);
|
||||||
|
|
||||||
const syncContext = { cookieJar: {} };
|
const syncContext: SyncContext = { cookieJar: {} };
|
||||||
const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', {
|
const resp = await syncRequest<SyncResponse>(syncContext, 'POST', '/api/login/sync', {
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
syncVersion: appInfo.syncVersion,
|
syncVersion: appInfo.syncVersion,
|
||||||
hash: hash
|
hash: hash
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
throw new Error("Got no response.");
|
||||||
|
}
|
||||||
|
|
||||||
if (resp.instanceId === instanceId) {
|
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.`);
|
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;
|
return syncContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullChanges(syncContext) {
|
async function pullChanges(syncContext: SyncContext) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const lastSyncedPull = getLastSyncedPull();
|
const lastSyncedPull = getLastSyncedPull();
|
||||||
const logMarkerId = utils.randomString(10); // to easily pair sync events between client and server logs
|
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 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;
|
const {entityChanges, lastEntityChangeId} = resp;
|
||||||
|
|
||||||
outstandingPullCount = resp.outstandingPullCount;
|
outstandingPullCount = resp.outstandingPullCount;
|
||||||
@ -141,7 +171,9 @@ async function pullChanges(syncContext) {
|
|||||||
const pulledDate = Date.now();
|
const pulledDate = Date.now();
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
syncUpdateService.updateEntities(entityChanges, syncContext.instanceId);
|
if (syncContext.instanceId) {
|
||||||
|
syncUpdateService.updateEntities(entityChanges, syncContext.instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
if (lastSyncedPull !== lastEntityChangeId) {
|
if (lastSyncedPull !== lastEntityChangeId) {
|
||||||
setLastSyncedPull(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`);
|
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}`);
|
log.error(`Error occurred ${e.message} ${e.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,11 +197,11 @@ async function pullChanges(syncContext) {
|
|||||||
log.info("Finished pull");
|
log.info("Finished pull");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pushChanges(syncContext) {
|
async function pushChanges(syncContext: SyncContext) {
|
||||||
let lastSyncedPush = getLastSyncedPush();
|
let lastSyncedPush: number | null | undefined = getLastSyncedPush();
|
||||||
|
|
||||||
while (true) {
|
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) {
|
if (entityChanges.length === 0) {
|
||||||
log.info("Nothing to push");
|
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
|
// there still might be more sync changes (because of batch limit), just all the current batch
|
||||||
// has been filtered out
|
// has been filtered out
|
||||||
setLastSyncedPush(lastSyncedPush);
|
setLastSyncedPush(lastSyncedPush);
|
||||||
@ -214,16 +246,22 @@ async function pushChanges(syncContext) {
|
|||||||
|
|
||||||
lastSyncedPush = entityChangesRecords[entityChangesRecords.length - 1].entityChange.id;
|
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');
|
await syncRequest(syncContext, 'POST', '/api/sync/finished');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkContentHash(syncContext) {
|
async function checkContentHash(syncContext: SyncContext) {
|
||||||
const resp = await syncRequest(syncContext, 'GET', '/api/sync/check');
|
const resp = await syncRequest<CheckResponse>(syncContext, 'GET', '/api/sync/check');
|
||||||
|
if (!resp) {
|
||||||
|
throw new Error("Got no response.");
|
||||||
|
}
|
||||||
|
|
||||||
const lastSyncedPullId = getLastSyncedPull();
|
const lastSyncedPullId = getLastSyncedPull();
|
||||||
|
|
||||||
if (lastSyncedPullId < resp.maxEntityChangeId) {
|
if (lastSyncedPullId < resp.maxEntityChangeId) {
|
||||||
@ -261,8 +299,12 @@ async function checkContentHash(syncContext) {
|
|||||||
|
|
||||||
const PAGE_SIZE = 1000000;
|
const PAGE_SIZE = 1000000;
|
||||||
|
|
||||||
async function syncRequest(syncContext, method, requestPath, body) {
|
interface SyncContext {
|
||||||
body = body ? JSON.stringify(body) : '';
|
cookieJar: CookieJar
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncRequest<T extends {}>(syncContext: SyncContext, method: string, requestPath: string, _body?: {}) {
|
||||||
|
const body = _body ? JSON.stringify(_body) : '';
|
||||||
|
|
||||||
const timeout = syncOptions.getSyncTimeout();
|
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));
|
const pageCount = Math.max(1, Math.ceil(body.length / PAGE_SIZE));
|
||||||
|
|
||||||
for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||||
const opts = {
|
const opts: ExecOpts = {
|
||||||
method,
|
method,
|
||||||
url: syncOptions.getSyncServerHost() + requestPath,
|
url: syncOptions.getSyncServerHost() + requestPath,
|
||||||
cookieJar: syncContext.cookieJar,
|
cookieJar: syncContext.cookieJar,
|
||||||
@ -286,13 +328,13 @@ async function syncRequest(syncContext, method, requestPath, body) {
|
|||||||
proxy: proxyToggle ? syncOptions.getSyncProxy() : null
|
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;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntityChangeRow(entityChange) {
|
function getEntityChangeRow(entityChange: EntityChange) {
|
||||||
const {entityName, entityId} = entityChange;
|
const {entityName, entityId} = entityChange;
|
||||||
|
|
||||||
if (entityName === 'note_reordering') {
|
if (entityName === 'note_reordering') {
|
||||||
@ -305,7 +347,7 @@ function getEntityChangeRow(entityChange) {
|
|||||||
throw new Error(`Unknown entity for entity change ${JSON.stringify(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) {
|
if (!entityRow) {
|
||||||
log.error(`Cannot find entity for entity change ${JSON.stringify(entityChange)}`);
|
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 = Buffer.from(entityRow.content, 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
entityRow.content = entityRow.content.toString("base64");
|
if (entityRow.content) {
|
||||||
|
entityRow.content = entityRow.content.toString("base64");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entityRow;
|
return entityRow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntityChangeRecords(entityChanges) {
|
function getEntityChangeRecords(entityChanges: EntityChange[]) {
|
||||||
const records = [];
|
const records: EntityChangeRecord[] = [];
|
||||||
let length = 0;
|
let length = 0;
|
||||||
|
|
||||||
for (const entityChange of entityChanges) {
|
for (const entityChange of entityChanges) {
|
||||||
@ -340,7 +384,7 @@ function getEntityChangeRecords(entityChanges) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = { entityChange, entity };
|
const record: EntityChangeRecord = { entityChange, entity };
|
||||||
|
|
||||||
records.push(record);
|
records.push(record);
|
||||||
|
|
||||||
@ -359,7 +403,7 @@ function getLastSyncedPull() {
|
|||||||
return parseInt(optionService.getOption('lastSyncedPull'));
|
return parseInt(optionService.getOption('lastSyncedPull'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSyncedPull(entityChangeId) {
|
function setLastSyncedPull(entityChangeId: number) {
|
||||||
const lastSyncedPullOption = becca.getOption('lastSyncedPull');
|
const lastSyncedPullOption = becca.getOption('lastSyncedPull');
|
||||||
|
|
||||||
if (lastSyncedPullOption) { // might be null in initial sync when becca is not loaded
|
if (lastSyncedPullOption) { // might be null in initial sync when becca is not loaded
|
||||||
@ -378,7 +422,7 @@ function getLastSyncedPush() {
|
|||||||
return lastSyncedPush;
|
return lastSyncedPush;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSyncedPush(entityChangeId) {
|
function setLastSyncedPush(entityChangeId: number) {
|
||||||
ws.setLastSyncedPush(entityChangeId);
|
ws.setLastSyncedPush(entityChangeId);
|
||||||
|
|
||||||
const lastSyncedPushOption = becca.getOption('lastSyncedPush');
|
const lastSyncedPushOption = becca.getOption('lastSyncedPush');
|
||||||
@ -409,7 +453,7 @@ require('../becca/becca_loader').beccaLoaded.then(() => {
|
|||||||
getLastSyncedPush();
|
getLastSyncedPush();
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
sync,
|
sync,
|
||||||
login,
|
login,
|
||||||
getEntityChangeRecords,
|
getEntityChangeRecords,
|
@ -1,11 +1,18 @@
|
|||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const entityChangesService = require('./entity_changes');
|
import entityChangesService = require('./entity_changes');
|
||||||
const eventService = require('./events');
|
import eventService = require('./events');
|
||||||
const entityConstructor = require('../becca/entity_constructor');
|
import entityConstructor = require('../becca/entity_constructor');
|
||||||
const ws = require('./ws');
|
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) {
|
if (entityChanges.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -34,13 +41,15 @@ function updateEntities(entityChanges, instanceId) {
|
|||||||
atLeastOnePullApplied = true;
|
atLeastOnePullApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEntity(entityChange, entity, instanceId, updateContext);
|
if (entity) {
|
||||||
|
updateEntity(entityChange, entity, instanceId, updateContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logUpdateContext(updateContext);
|
logUpdateContext(updateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEntity(remoteEC, remoteEntityRow, instanceId, updateContext) {
|
function updateEntity(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string, updateContext: UpdateContext) {
|
||||||
if (!remoteEntityRow && remoteEC.entityName === 'options') {
|
if (!remoteEntityRow && remoteEC.entityName === 'options') {
|
||||||
return; // can be undefined for options with isSynced=false
|
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) {
|
function updateNormalEntity(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string, updateContext: UpdateContext) {
|
||||||
const localEC = sql.getRow(`SELECT * FROM entity_changes WHERE entityName = ? AND entityId = ?`, [remoteEC.entityName, remoteEC.entityId]);
|
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 (!localEC || localEC.utcDateChanged <= remoteEC.utcDateChanged) {
|
||||||
if (remoteEC.isErased) {
|
if (remoteEC.isErased) {
|
||||||
@ -110,28 +123,30 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function preProcessContent(remoteEC, remoteEntityRow) {
|
function preProcessContent(remoteEC: EntityChange, remoteEntityRow: EntityRow) {
|
||||||
if (remoteEC.entityName === 'blobs' && remoteEntityRow.content !== null) {
|
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
|
// 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
|
// "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)
|
// 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) {
|
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
|
// 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)
|
// (possibly not a problem anymore with the newer better-sqlite3)
|
||||||
remoteEntityRow.content = "";
|
remoteEntityRow.content = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNoteReordering(remoteEC, remoteEntityRow, instanceId) {
|
function updateNoteReordering(remoteEC: EntityChange, remoteEntityRow: EntityRow, instanceId: string) {
|
||||||
if (!remoteEntityRow) {
|
if (!remoteEntityRow) {
|
||||||
throw new Error(`Empty note_reordering body for: ${JSON.stringify(remoteEC)}`);
|
throw new Error(`Empty note_reordering body for: ${JSON.stringify(remoteEC)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in remoteEntityRow) {
|
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);
|
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
|
||||||
@ -139,7 +154,7 @@ function updateNoteReordering(remoteEC, remoteEntityRow, instanceId) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraseEntity(entityChange) {
|
function eraseEntity(entityChange: EntityChange) {
|
||||||
const {entityName, entityId} = entityChange;
|
const {entityName, entityId} = entityChange;
|
||||||
|
|
||||||
const entityNames = [
|
const entityNames = [
|
||||||
@ -161,7 +176,7 @@ function eraseEntity(entityChange) {
|
|||||||
sql.execute(`DELETE FROM ${entityName} WHERE ${primaryKeyName} = ?`, [entityId]);
|
sql.execute(`DELETE FROM ${entityName} WHERE ${primaryKeyName} = ?`, [entityId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logUpdateContext(updateContext) {
|
function logUpdateContext(updateContext: UpdateContext) {
|
||||||
const message = JSON.stringify(updateContext)
|
const message = JSON.stringify(updateContext)
|
||||||
.replaceAll('"', '')
|
.replaceAll('"', '')
|
||||||
.replaceAll(":", ": ")
|
.replaceAll(":", ": ")
|
||||||
@ -170,6 +185,6 @@ function logUpdateContext(updateContext) {
|
|||||||
log.info(message.substr(1, message.length - 2));
|
log.info(message.substr(1, message.length - 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
updateEntities
|
updateEntities
|
||||||
};
|
};
|
@ -1,13 +1,9 @@
|
|||||||
const { Menu, Tray } = require('electron');
|
import { Menu, Tray } from 'electron';
|
||||||
const path = require('path');
|
import path = require('path');
|
||||||
const windowService = require('./window.js');
|
import windowService = require('./window');
|
||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
|
|
||||||
const UPDATE_TRAY_EVENTS = [
|
let tray: Tray;
|
||||||
'minimize', 'maximize', 'show', 'hide'
|
|
||||||
]
|
|
||||||
|
|
||||||
let tray = null;
|
|
||||||
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
||||||
// is minimized
|
// is minimized
|
||||||
let isVisible = true;
|
let isVisible = true;
|
||||||
@ -37,22 +33,25 @@ const getIconPath = () => {
|
|||||||
}
|
}
|
||||||
const registerVisibilityListener = () => {
|
const registerVisibilityListener = () => {
|
||||||
const mainWindow = windowService.getMainWindow();
|
const mainWindow = windowService.getMainWindow();
|
||||||
|
if (!mainWindow) { return; }
|
||||||
|
|
||||||
// They need to be registered before the tray updater is registered
|
// They need to be registered before the tray updater is registered
|
||||||
mainWindow.on('show', () => {
|
mainWindow.on('show', () => {
|
||||||
isVisible = true;
|
isVisible = true;
|
||||||
|
updateTrayMenu();
|
||||||
});
|
});
|
||||||
mainWindow.on('hide', () => {
|
mainWindow.on('hide', () => {
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
|
updateTrayMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
UPDATE_TRAY_EVENTS.forEach(eventName => {
|
mainWindow.on("minimize", updateTrayMenu);
|
||||||
mainWindow.on(eventName, updateTrayMenu)
|
mainWindow.on("maximize", updateTrayMenu);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTrayMenu = () => {
|
const updateTrayMenu = () => {
|
||||||
const mainWindow = windowService.getMainWindow();
|
const mainWindow = windowService.getMainWindow();
|
||||||
|
if (!mainWindow) { return; }
|
||||||
|
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
@ -83,6 +82,7 @@ const updateTrayMenu = () => {
|
|||||||
}
|
}
|
||||||
const changeVisibility = () => {
|
const changeVisibility = () => {
|
||||||
const window = windowService.getMainWindow();
|
const window = windowService.getMainWindow();
|
||||||
|
if (!window) { return; }
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
window.hide();
|
window.hide();
|
||||||
@ -106,6 +106,6 @@ function createTray() {
|
|||||||
registerVisibilityListener();
|
registerVisibilityListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
createTray
|
createTray
|
||||||
}
|
}
|
@ -1,12 +1,13 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const BBranch = require('../becca/entities/bbranch');
|
import BBranch = require('../becca/entities/bbranch');
|
||||||
const entityChangesService = require('./entity_changes');
|
import entityChangesService = require('./entity_changes');
|
||||||
const becca = require('../becca/becca');
|
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)) {
|
if (['root', '_hidden', '_share', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(childNoteId)) {
|
||||||
return { branch: null, success: false, message: `Cannot change this note's location.` };
|
return { branch: null, success: false, message: `Cannot change this note's location.` };
|
||||||
}
|
}
|
||||||
@ -25,7 +26,7 @@ function validateParentChild(parentNoteId, childNoteId, branchId = null) {
|
|||||||
return {
|
return {
|
||||||
branch: existingBranch,
|
branch: existingBranch,
|
||||||
success: false,
|
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 {
|
return {
|
||||||
branch: null,
|
branch: null,
|
||||||
success: false,
|
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.
|
* 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) {
|
if (parentNoteId === childNoteId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -70,20 +71,22 @@ function wouldAddingBranchCreateCycle(parentNoteId, childNoteId) {
|
|||||||
return parentAncestorNoteIds.some(parentAncestorNoteId => childSubtreeNoteIds.has(parentAncestorNoteId));
|
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) {
|
if (!customSortBy) {
|
||||||
customSortBy = 'title';
|
customSortBy = 'title';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sortLocale) {
|
// sortLocale can not be empty string or null value, default value must be set to undefined.
|
||||||
// sortLocale can not be empty string or null value, default value must be set to undefined.
|
const sortLocale = (_sortLocale || undefined);
|
||||||
sortLocale = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.transactional(() => {
|
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) => {
|
notes.sort((a, b) => {
|
||||||
if (foldersFirst) {
|
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;
|
let rawValue;
|
||||||
|
|
||||||
if (key === 'title') {
|
if (key === 'title') {
|
||||||
@ -105,14 +108,14 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder
|
|||||||
rawValue = prefix ? `${prefix} - ${note.title}` : note.title;
|
rawValue = prefix ? `${prefix} - ${note.title}` : note.title;
|
||||||
} else {
|
} else {
|
||||||
rawValue = ['dateCreated', 'dateModified'].includes(key)
|
rawValue = ['dateCreated', 'dateModified'].includes(key)
|
||||||
? note[key]
|
? (note as any)[key]
|
||||||
: note.getLabelValue(key);
|
: note.getLabelValue(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalize(rawValue);
|
return normalize(rawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare(a, b) {
|
function compare(a: string, b: string) {
|
||||||
if (!sortNatural) {
|
if (!sortNatural) {
|
||||||
// alphabetical sort
|
// alphabetical sort
|
||||||
return b === null || b === undefined || a < b ? -1 : 1;
|
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) {
|
for (const note of notes) {
|
||||||
const branch = note.getParentBranches().find(b => b.parentNoteId === parentNoteId);
|
const branch = note.getParentBranches().find(b => b.parentNoteId === parentNoteId);
|
||||||
|
if (!branch) { continue; }
|
||||||
|
|
||||||
if (branch.noteId === '_hidden') {
|
if (branch.noteId === '_hidden') {
|
||||||
position = 999_999_999;
|
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);
|
const parentNote = becca.getNote(parentNoteId);
|
||||||
|
|
||||||
if (!parentNote) {
|
if (!parentNote) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -206,7 +209,7 @@ function sortNotesIfNeeded(parentNoteId) {
|
|||||||
/**
|
/**
|
||||||
* @deprecated this will be removed in the future
|
* @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);
|
const parentNote = becca.getNote(parentNoteId);
|
||||||
|
|
||||||
if (parentNoteId && !parentNote) {
|
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
|
// 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);
|
const branch = becca.getBranch(branchId);
|
||||||
|
|
||||||
if (branch) {
|
if (branch) {
|
||||||
@ -233,12 +236,15 @@ function setNoteToParent(noteId, prefix, parentNoteId) {
|
|||||||
}
|
}
|
||||||
else if (parentNoteId) {
|
else if (parentNoteId) {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
|
if (!note) {
|
||||||
|
throw new Error(`Cannot find note '${noteId}.`);
|
||||||
|
}
|
||||||
|
|
||||||
if (note.isDeleted) {
|
if (note.isDeleted) {
|
||||||
throw new Error(`Cannot create a branch for '${noteId}' which is deleted.`);
|
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);
|
const branch = becca.getBranch(branchId);
|
||||||
|
|
||||||
if (branch) {
|
if (branch) {
|
||||||
@ -255,7 +261,7 @@ function setNoteToParent(noteId, prefix, parentNoteId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
validateParentChild,
|
validateParentChild,
|
||||||
sortNotes,
|
sortNotes,
|
||||||
sortNotesIfNeeded,
|
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
|
if (!promise || !promise.then) { // it's not actually a promise
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
const path = require('path');
|
import path = require('path');
|
||||||
const url = require("url");
|
import url = require("url");
|
||||||
const port = require('./port');
|
import port = require('./port');
|
||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
const env = require('./env');
|
import env = require('./env');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const sqlInit = require('./sql_init');
|
import sqlInit = require('./sql_init');
|
||||||
const cls = require('./cls');
|
import cls = require('./cls');
|
||||||
const keyboardActionsService = require('./keyboard_actions');
|
import keyboardActionsService = require('./keyboard_actions');
|
||||||
const { ipcMain } = require('electron');
|
import remoteMain = require("@electron/remote/main")
|
||||||
|
import { App, BrowserWindow, WebContents, ipcMain } from 'electron';
|
||||||
|
|
||||||
// Prevent the window being garbage collected
|
// Prevent the window being garbage collected
|
||||||
/** @type {Electron.BrowserWindow} */
|
let mainWindow: BrowserWindow | null;
|
||||||
let mainWindow;
|
let setupWindow: BrowserWindow | null;
|
||||||
/** @type {Electron.BrowserWindow} */
|
|
||||||
let setupWindow;
|
|
||||||
|
|
||||||
async function createExtraWindow(extraWindowHash) {
|
async function createExtraWindow(extraWindowHash: string) {
|
||||||
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
|
||||||
|
|
||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
@ -25,7 +24,6 @@ async function createExtraWindow(extraWindowHash) {
|
|||||||
height: 800,
|
height: 800,
|
||||||
title: 'Trilium Notes',
|
title: 'Trilium Notes',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
enableRemoteModule: true,
|
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
spellcheck: spellcheckEnabled
|
spellcheck: spellcheckEnabled
|
||||||
@ -44,7 +42,7 @@ ipcMain.on('create-extra-window', (event, arg) => {
|
|||||||
createExtraWindow(arg.extraWindowHash);
|
createExtraWindow(arg.extraWindowHash);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createMainWindow(app) {
|
async function createMainWindow(app: App) {
|
||||||
const windowStateKeeper = require('electron-window-state'); // should not be statically imported
|
const windowStateKeeper = require('electron-window-state'); // should not be statically imported
|
||||||
|
|
||||||
const mainWindowState = windowStateKeeper({
|
const mainWindowState = windowStateKeeper({
|
||||||
@ -64,7 +62,6 @@ async function createMainWindow(app) {
|
|||||||
height: mainWindowState.height,
|
height: mainWindowState.height,
|
||||||
title: 'Trilium Notes',
|
title: 'Trilium Notes',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
enableRemoteModule: true,
|
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
spellcheck: spellcheckEnabled,
|
spellcheck: spellcheckEnabled,
|
||||||
@ -95,8 +92,12 @@ async function createMainWindow(app) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureWebContents(webContents, spellcheckEnabled) {
|
function configureWebContents(webContents: WebContents, spellcheckEnabled: boolean) {
|
||||||
require("@electron/remote/main").enable(webContents);
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteMain.enable(webContents);
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
require("electron").shell.openExternal(details.url);
|
require("electron").shell.openExternal(details.url);
|
||||||
@ -108,8 +109,7 @@ function configureWebContents(webContents, spellcheckEnabled) {
|
|||||||
const parsedUrl = url.parse(targetUrl);
|
const parsedUrl = url.parse(targetUrl);
|
||||||
|
|
||||||
// we still need to allow internal redirects from setup and migration pages
|
// 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();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -168,6 +168,10 @@ async function registerGlobalShortcuts() {
|
|||||||
const translatedShortcut = shortcut.substr(7);
|
const translatedShortcut = shortcut.substr(7);
|
||||||
|
|
||||||
const result = globalShortcut.register(translatedShortcut, cls.wrap(() => {
|
const result = globalShortcut.register(translatedShortcut, cls.wrap(() => {
|
||||||
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// window may be hidden / not in focus
|
// window may be hidden / not in focus
|
||||||
mainWindow.focus();
|
mainWindow.focus();
|
||||||
|
|
||||||
@ -189,8 +193,7 @@ function getMainWindow() {
|
|||||||
return mainWindow;
|
return mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
module.exports = {
|
|
||||||
createMainWindow,
|
createMainWindow,
|
||||||
createSetupWindow,
|
createSetupWindow,
|
||||||
closeSetupWindow,
|
closeSetupWindow,
|
@ -9,8 +9,8 @@ const shareRoot = require('./share_root.js');
|
|||||||
const contentRenderer = require('./content_renderer.js');
|
const contentRenderer = require('./content_renderer.js');
|
||||||
const assetPath = require('../services/asset_path');
|
const assetPath = require('../services/asset_path');
|
||||||
const appPath = require('../services/app_path');
|
const appPath = require('../services/app_path');
|
||||||
const searchService = require('../services/search/services/search.js');
|
const searchService = require('../services/search/services/search');
|
||||||
const SearchContext = require('../services/search/search_context.js');
|
const SearchContext = require('../services/search/search_context');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
require('../becca/entity_constructor');
|
require('../becca/entity_constructor');
|
||||||
const sqlInit = require('../services/sql_init');
|
const sqlInit = require('../services/sql_init');
|
||||||
const noteService = require('../services/notes');
|
const noteService = require('../services/notes');
|
||||||
const attributeService = require('../services/attributes.js');
|
const attributeService = require('../services/attributes');
|
||||||
const cls = require('../services/cls');
|
const cls = require('../services/cls');
|
||||||
const cloningService = require('../services/cloning.js');
|
const cloningService = require('../services/cloning');
|
||||||
const loremIpsum = require('lorem-ipsum').loremIpsum;
|
const loremIpsum = require('lorem-ipsum').loremIpsum;
|
||||||
|
|
||||||
const noteCount = parseInt(process.argv[2]);
|
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;
|
function html2plaintext(htmlText: string): string;
|
||||||
export = html2plaintext;
|
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
|
* 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.
|
* 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.
|
* 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
|
* 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 => {
|
httpServer.on('error', error => {
|
||||||
if (!listenOnTcp || error.syscall !== 'listen') {
|
if (!listenOnTcp || error.syscall !== 'listen') {
|
||||||
throw error;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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', () => {
|
httpServer.on('listening', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user