format some files

This commit is contained in:
alexpietsch
2024-05-30 17:25:16 +02:00
parent 434e1a9a2f
commit 2c5681ba88
7 changed files with 257 additions and 245 deletions

View File

@@ -8,7 +8,7 @@ import BAttribute = require('./entities/battribute');
import BBranch = require('./entities/bbranch');
import BRevision = require('./entities/brevision');
import BAttachment = require('./entities/battachment');
import { AttachmentRow, RevisionRow } from './entities/rows';
import {AttachmentRow, RevisionRow} from './entities/rows';
import BBlob = require('./entities/bblob');
import BRecentNote = require('./entities/brecent_note');
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
@@ -44,7 +44,7 @@ export default class Becca {
this.notes = {};
this.branches = {};
this.childParentToBranch = {};
this.attributes = {};
this.attributes = {};
this.attributeIndex = {};
this.options = {};
this.etapiTokens = {};
@@ -155,7 +155,7 @@ export default class Becca {
}
getRevision(revisionId: string): BRevision | null {
const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
const row = sql.getRow('SELECT * FROM revisions WHERE revisionId = ?', [revisionId]);
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
return row ? new BRevision(row) : null;
@@ -181,8 +181,7 @@ export default class Becca {
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
return sql.getRows(query, [attachmentId])
.map(row => new BAttachment(row))[0];
return sql.getRows(query, [attachmentId]).map((row) => new BAttachment(row))[0];
}
getAttachmentOrThrow(attachmentId: string, opts: AttachmentOpts = {}): BAttachment {
@@ -195,16 +194,15 @@ export default class Becca {
getAttachments(attachmentIds: string[]): BAttachment[] {
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
return sql.getManyRows<AttachmentRow>("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
.map(row => new BAttachment(row));
return sql.getManyRows<AttachmentRow>('SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0', attachmentIds).map((row) => new BAttachment(row));
}
getBlob(entity: { blobId?: string }): BBlob | null {
getBlob(entity: {blobId?: string}): BBlob | null {
if (!entity.blobId) {
return null;
}
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
const row = sql.getRow('SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?', [entity.blobId]);
const BBlob = require('./entities/bblob'); // avoiding circular dependency problems
return row ? new BBlob(row) : null;
@@ -233,12 +231,7 @@ export default class Becca {
return this.getAttachment(entityId);
}
const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g,
group =>
group
.toUpperCase()
.replace('_', '')
);
const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g, (group) => group.toUpperCase().replace('_', ''));
if (!(camelCaseEntityName in this)) {
throw new Error(`Unknown entity name '${camelCaseEntityName}' (original argument '${entityName}')`);
@@ -251,14 +244,14 @@ export default class Becca {
const rows = sql.getRows(query, params);
const BRecentNote = require('./entities/brecent_note'); // avoiding circular dependency problems
return rows.map(row => new BRecentNote(row));
return rows.map((row) => new BRecentNote(row));
}
getRevisionsFromQuery(query: string, params: string[] = []): BRevision[] {
const rows = sql.getRows<RevisionRow>(query, params);
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
return rows.map(row => new BRevision(row));
return rows.map((row) => new BRevision(row));
}
/** Should be called when the set of all non-skeleton notes changes (added/removed) */
@@ -290,7 +283,7 @@ export default class Becca {
/**
* This interface contains the data that is shared across all the objects of a given derived class of {@link AbstractBeccaEntity}.
* For example, all BAttributes will share their content, but all BBranches will have another set of this data.
* For example, all BAttributes will share their content, but all BBranches will have another set of this data.
*/
export interface ConstructorData<T extends AbstractBeccaEntity<T>> {
primaryKeyName: string;
@@ -310,4 +303,4 @@ export interface NotePojo {
dateModified?: string;
utcDateCreated: string;
utcDateModified?: string;
}
}

View File

@@ -1,6 +1,6 @@
import assetPath = require('../services/asset_path');
import path = require("path");
import express = require("express");
import path = require('path');
import express = require('express');
import env = require('../services/env');
import serveStatic = require('serve-static');
@@ -37,20 +37,12 @@ function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
// KaTeX
app.use(
`/${assetPath}/node_modules/katex/dist/katex.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/katex.min.js')));
app.use(
`/${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/mhchem.min.js')));
app.use(
`/${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/auto-render.min.js')));
app.use(`/${assetPath}/node_modules/katex/dist/katex.min.js`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/katex.min.js')));
app.use(`/${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/mhchem.min.js')));
app.use(`/${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/auto-render.min.js')));
// expose the whole dist folder
app.use(`/node_modules/katex/dist/`,
express.static(path.join(srcRoot, '..', 'node_modules/katex/dist/')));
app.use(`/${assetPath}/node_modules/katex/dist/`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/')));
app.use(`/node_modules/katex/dist/`, express.static(path.join(srcRoot, '..', 'node_modules/katex/dist/')));
app.use(`/${assetPath}/node_modules/katex/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/')));
app.use(`/${assetPath}/node_modules/dayjs/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/dayjs/')));
app.use(`/${assetPath}/node_modules/force-graph/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/force-graph/dist/')));

View File

@@ -1,4 +1,4 @@
"use strict";
'use strict';
import sql = require('../services/sql');
import attributeService = require('../services/attributes');
@@ -11,14 +11,12 @@ import protectedSessionService = require('../services/protected_session');
import packageJson = require('../../package.json');
import assetPath = require('../services/asset_path');
import appPath = require('../services/app_path');
import { Request, Response } from 'express';
import {Request, Response} from 'express';
function index(req: Request, res: Response) {
const options = optionService.getOptionMap();
const view = (!utils.isElectron() && req.cookies['trilium-device'] === 'mobile')
? 'mobile'
: 'desktop';
const view = !utils.isElectron() && req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
const csrfToken = req.csrfToken();
log.info(`Generated CSRF token ${csrfToken} with secret ${res.getHeader('set-cookie')}`);
@@ -30,8 +28,8 @@ function index(req: Request, res: Response) {
mainFontSize: parseInt(options.mainFontSize),
treeFontSize: parseInt(options.treeFontSize),
detailFontSize: parseInt(options.detailFontSize),
maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"),
maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"),
maxEntityChangeIdAtLoad: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes'),
maxEntityChangeSyncIdAtLoad: sql.getValue('SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1'),
instanceName: config.General ? config.General.instanceName : null,
appCssNoteIds: getAppCssNoteIds(),
isDev: env.isDev(),
@@ -61,7 +59,7 @@ function getThemeCssUrl(theme: string) {
}
function getAppCssNoteIds() {
return attributeService.getNotesWithLabel('appCss').map(note => note.noteId);
return attributeService.getNotesWithLabel('appCss').map((note) => note.noteId);
}
export = {

View File

@@ -1,21 +1,18 @@
import { NextFunction, Request, Response } from "express";
import { Session, SessionData } from "express-session";
import {NextFunction, Request, Response} from 'express';
import {Session, SessionData} from 'express-session';
export interface AppRequest extends Request {
headers: {
authorization?: string;
"trilium-cred"?: string;
"x-local-date"?: string;
"x-labels"?: string;
"trilium-local-now-datetime"?: string;
}
session: Session & Partial<SessionData> & {
loggedIn: boolean;
}
'trilium-cred'?: string;
'x-local-date'?: string;
'x-labels'?: string;
'trilium-local-now-datetime'?: string;
};
session: Session &
Partial<SessionData> & {
loggedIn: boolean;
};
}
export type AppRequestHandler = (
req: AppRequest,
res: Response,
next: NextFunction
) => void;
export type AppRequestHandler = (req: AppRequest, res: Response, next: NextFunction) => void;

View File

@@ -1,4 +1,4 @@
"use strict";
'use strict';
import utils = require('../services/utils');
import multer = require('multer');
@@ -10,8 +10,8 @@ import cls = require('../services/cls');
import sql = require('../services/sql');
import entityChangesService = require('../services/entity_changes');
import csurf = require('csurf');
import { createPartialContentHandler } from "express-partial-content";
import rateLimit = require("express-rate-limit");
import {createPartialContentHandler} from 'express-partial-content';
import rateLimit = require('express-rate-limit');
import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity');
import NotFoundError = require('../errors/not_found_error');
import ValidationError = require('../errors/validation_error');
@@ -70,32 +70,33 @@ import etapiNoteRoutes = require('../etapi/notes');
import etapiSpecialNoteRoutes = require('../etapi/special_notes');
import etapiSpecRoute = require('../etapi/spec');
import etapiBackupRoute = require('../etapi/backup');
import { AppRequest, AppRequestHandler } from './route-interface';
import {AppRequest, AppRequestHandler} from './route-interface';
const csrfMiddleware = csurf({
cookie: {
path: "" // empty, so cookie is valid only for the current path
path: '' // empty, so cookie is valid only for the current path
}
});
const MAX_ALLOWED_FILE_SIZE_MB = 250;
const GET = 'get', PST = 'post', PUT = 'put', PATCH = 'patch', DEL = 'delete';
const GET = 'get',
PST = 'post',
PUT = 'put',
PATCH = 'patch',
DEL = 'delete';
type ApiResultHandler = (req: express.Request, res: express.Response, result: unknown) => number;
// TODO: Deduplicate with etapi_utils.ts afterwards.
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
type HttpMethod = 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head';
const uploadMiddleware = createUploadMiddleware();
const uploadMiddlewareWithErrorHandling = function (req: express.Request, res: express.Response, next: express.NextFunction) {
uploadMiddleware(req, res, function (err) {
if (err?.code === 'LIMIT_FILE_SIZE') {
res.setHeader("Content-Type", "text/plain")
.status(400)
.send(`Cannot upload file because it excceeded max allowed file size of ${MAX_ALLOWED_FILE_SIZE_MB} MiB`);
}
else {
res.setHeader('Content-Type', 'text/plain').status(400).send(`Cannot upload file because it excceeded max allowed file size of ${MAX_ALLOWED_FILE_SIZE_MB} MiB`);
} else {
next();
}
});
@@ -137,13 +138,18 @@ function register(app: express.Application) {
apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent);
apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToParentNote);
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware],
filesRoute.updateFile, apiResultHandler);
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], filesRoute.updateFile, apiResultHandler);
route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron], filesRoute.openFile);
route(GET, '/api/notes/:noteId/open-partial', [auth.checkApiAuthOrElectron],
route(
GET,
'/api/notes/:noteId/open-partial',
[auth.checkApiAuthOrElectron],
createPartialContentHandler(filesRoute.fileContentProvider, {
debug: (string, extra) => { console.log(string, extra); }
}));
debug: (string, extra) => {
console.log(string, extra);
}
})
);
route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
// this "hacky" path is used for easier referencing of CSS resources
route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
@@ -170,17 +176,22 @@ function register(app: express.Application) {
apiRoute(GET, '/api/attachments/:attachmentId/blob', attachmentsApiRoute.getAttachmentBlob);
route(GET, '/api/attachments/:attachmentId/image/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnAttachedImage);
route(GET, '/api/attachments/:attachmentId/open', [auth.checkApiAuthOrElectron], filesRoute.openAttachment);
route(GET, '/api/attachments/:attachmentId/open-partial', [auth.checkApiAuthOrElectron],
route(
GET,
'/api/attachments/:attachmentId/open-partial',
[auth.checkApiAuthOrElectron],
createPartialContentHandler(filesRoute.attachmentContentProvider, {
debug: (string, extra) => { console.log(string, extra); }
}));
debug: (string, extra) => {
console.log(string, extra);
}
})
);
route(GET, '/api/attachments/:attachmentId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadAttachment);
// this "hacky" path is used for easier referencing of CSS resources
route(GET, '/api/attachments/download/:attachmentId', [auth.checkApiAuthOrElectron], filesRoute.downloadAttachment);
apiRoute(PST, '/api/attachments/:attachmentId/save-to-tmp-dir', filesRoute.saveAttachmentToTmpDir);
apiRoute(PST, '/api/attachments/:attachmentId/upload-modified-file', filesRoute.uploadModifiedFileToAttachment);
route(PUT, '/api/attachments/:attachmentId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware],
filesRoute.updateAttachment, apiResultHandler);
route(PUT, '/api/attachments/:attachmentId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], filesRoute.updateAttachment, apiResultHandler);
apiRoute(GET, '/api/notes/:noteId/revisions', revisionsApiRoute.getRevisions);
apiRoute(DEL, '/api/notes/:noteId/revisions', revisionsApiRoute.eraseAllRevisions);
@@ -192,7 +203,6 @@ function register(app: express.Application) {
route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
route(GET, '/api/branches/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
route(PST, '/api/notes/:parentNoteId/notes-import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importNotesToBranch, apiResultHandler);
route(PST, '/api/notes/:parentNoteId/attachments-import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importAttachmentsToNote, apiResultHandler);
@@ -237,7 +247,7 @@ function register(app: express.Application) {
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
// docker health check
route(GET, '/api/health-check', [], () => ({ "status": "ok" }), apiResultHandler);
route(GET, '/api/health-check', [], () => ({status: 'ok'}), apiResultHandler);
// group of the services below are meant to be executed from the outside
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
@@ -363,25 +373,24 @@ function register(app: express.Application) {
function convertEntitiesToPojo(result: unknown) {
if (result instanceof AbstractBeccaEntity) {
result = result.getPojo();
}
else if (Array.isArray(result)) {
} else if (Array.isArray(result)) {
for (const idx in result) {
if (result[idx] instanceof AbstractBeccaEntity) {
result[idx] = result[idx].getPojo();
}
}
}
else if (result && typeof result === "object") {
if ("note" in result && result.note instanceof AbstractBeccaEntity) {
} else if (result && typeof result === 'object') {
if ('note' in result && result.note instanceof AbstractBeccaEntity) {
result.note = result.note.getPojo();
}
if ("branch" in result && result.branch instanceof AbstractBeccaEntity) {
if ('branch' in result && result.branch instanceof AbstractBeccaEntity) {
result.branch = result.branch.getPojo();
}
}
if (result && typeof result === "object" && "executionResult" in result) { // from runOnBackend()
if (result && typeof result === 'object' && 'executionResult' in result) {
// from runOnBackend()
result.executionResult = convertEntitiesToPojo(result.executionResult);
}
@@ -402,11 +411,9 @@ function apiResultHandler(req: express.Request, res: express.Response, result: u
}
return send(res, statusCode, response);
}
else if (result === undefined) {
return send(res, 204, "");
}
else {
} else if (result === undefined) {
return send(res, 204, '');
} else {
return send(res, 200, result);
}
}
@@ -414,17 +421,16 @@ function apiResultHandler(req: express.Request, res: express.Response, result: u
function send(res: express.Response, statusCode: number, response: unknown) {
if (typeof response === 'string') {
if (statusCode >= 400) {
res.setHeader("Content-Type", "text/plain");
res.setHeader('Content-Type', 'text/plain');
}
res.status(statusCode).send(response);
return response.length;
}
else {
} else {
const json = JSON.stringify(response);
res.setHeader("Content-Type", "application/json");
res.setHeader('Content-Type', 'application/json');
res.status(statusCode).send(json);
return json.length;
@@ -435,7 +441,14 @@ function apiRoute(method: HttpMethod, path: string, routeHandler: express.Handle
route(method, path, [auth.checkApiAuth, csrfMiddleware], routeHandler, apiResultHandler);
}
function route(method: HttpMethod, path: string, middleware: (express.Handler | AppRequestHandler)[], routeHandler: AppRequestHandler, resultHandler: ApiResultHandler | null = null, transactional = true) {
function route(
method: HttpMethod,
path: string,
middleware: (express.Handler | AppRequestHandler)[],
routeHandler: AppRequestHandler,
resultHandler: ApiResultHandler | null = null,
transactional = true
) {
router[method](path, ...(middleware as express.Handler[]), (req: express.Request, res: express.Response, next: express.NextFunction) => {
const start = Date.now();
@@ -457,15 +470,13 @@ function route(method: HttpMethod, path: string, middleware: (express.Handler |
return;
}
if (result?.then) { // promise
result
.then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start))
.catch((e: any) => handleException(e, method, path, res));
if (result?.then) {
// promise
result.then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start)).catch((e: any) => handleException(e, method, path, res));
} else {
handleResponse(resultHandler, req, res, result, start)
handleResponse(resultHandler, req, res, result, start);
}
}
catch (e) {
} catch (e) {
handleException(e, method, path, res);
}
});
@@ -481,20 +492,17 @@ function handleException(e: any, method: HttpMethod, path: string, res: express.
log.error(`${method} ${path} threw exception: '${e.message}', stack: ${e.stack}`);
if (e instanceof ValidationError) {
res.status(400)
.json({
message: e.message
});
res.status(400).json({
message: e.message
});
} else if (e instanceof NotFoundError) {
res.status(404)
.json({
message: e.message
});
res.status(404).json({
message: e.message
});
} else {
res.status(500)
.json({
message: e.message
});
res.status(500).json({
message: e.message
});
}
}
@@ -503,7 +511,7 @@ function createUploadMiddleware() {
fileFilter: (req: express.Request, file, cb) => {
// UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side.
// See https://github.com/expressjs/multer/pull/1102.
file.originalname = Buffer.from(file.originalname, "latin1").toString("utf-8");
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf-8');
cb(null, true);
}
};

View File

@@ -1,17 +1,17 @@
import BUILTIN_ATTRIBUTES = require('./builtin_attributes');
import fs = require("fs-extra");
import fs = require('fs-extra');
import dataDir = require('./data_dir');
import dateUtils = require('./date_utils');
import Database = require("better-sqlite3");
import Database = require('better-sqlite3');
import sql = require('./sql');
import path = require("path");
import path = require('path');
function getFullAnonymizationScript() {
// we want to delete all non-builtin attributes because they can contain sensitive names and values
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrNames = BUILTIN_ATTRIBUTES
.filter(attr => !["shareCredentials", "shareAlias"].includes(attr.name))
.map(attr => `'${attr.name}'`).join(', ');
const builtinAttrNames = BUILTIN_ATTRIBUTES.filter((attr) => !['shareCredentials', 'shareAlias'].includes(attr.name))
.map((attr) => `'${attr.name}'`)
.join(', ');
const anonymizeScript = `
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
@@ -48,7 +48,7 @@ function getLightAnonymizationScript() {
AND value != '';`;
}
async function createAnonymizedCopy(type: "full" | "light") {
async function createAnonymizedCopy(type: 'full' | 'light') {
if (!['full', 'light'].includes(type)) {
throw new Error(`Unrecognized anonymization type '${type}'`);
}
@@ -63,9 +63,7 @@ async function createAnonymizedCopy(type: "full" | "light") {
const db = new Database(anonymizedFile);
const anonymizationScript = type === 'light'
? getLightAnonymizationScript()
: getFullAnonymizationScript();
const anonymizationScript = type === 'light' ? getLightAnonymizationScript() : getFullAnonymizationScript();
db.exec(anonymizationScript);
@@ -82,9 +80,10 @@ function getExistingAnonymizedDatabases() {
return [];
}
return fs.readdirSync(dataDir.ANONYMIZED_DB_DIR)
.filter(fileName => fileName.includes("anonymized"))
.map(fileName => ({
return fs
.readdirSync(dataDir.ANONYMIZED_DB_DIR)
.filter((fileName) => fileName.includes('anonymized'))
.map((fileName) => ({
fileName: fileName,
filePath: path.resolve(dataDir.ANONYMIZED_DB_DIR, fileName)
}));
@@ -94,4 +93,4 @@ export = {
getFullAnonymizationScript,
createAnonymizedCopy,
getExistingAnonymizedDatabases
}
};

View File

@@ -30,11 +30,10 @@ import BAttachment = require('../becca/entities/battachment');
import BRevision = require('../becca/entities/brevision');
import BEtapiToken = require('../becca/entities/betapi_token');
import BOption = require('../becca/entities/boption');
import { AttributeRow, AttributeType, NoteType } from '../becca/entities/rows';
import {AttributeRow, AttributeType, NoteType} from '../becca/entities/rows';
import Becca from '../becca/becca-interface';
import { NoteParams } from './note-interface';
import { ApiParams } from './backend_script_api_interface';
import {NoteParams} from './note-interface';
import {ApiParams} from './backend_script_api_interface';
/**
* A whole number
@@ -77,7 +76,7 @@ interface Api {
* Entity whose event triggered this execution
*/
originEntity?: AbstractBeccaEntity<any>;
/**
* Axios library for HTTP requests. See {@link https://axios-http.com} for documentation
* @deprecated use native (browser compatible) fetch() instead
@@ -94,13 +93,13 @@ interface Api {
*/
xml2js: typeof xml2js;
/**
* Instance name identifies particular Trilium instance. It can be useful for scripts
* if some action needs to happen on only one specific instance.
*/
getInstanceName(): string | null;
getNote(noteId: string): BNote | null;
getBranch(branchId: string): BBranch | null;
getAttribute(attachmentId: string): BAttribute | null;
@@ -111,19 +110,19 @@ interface Api {
getOption(optionName: string): BOption | null;
getOptions(): BOption[];
getAttribute(attributeId: string): BAttribute | null;
/**
* This is a powerful search method - you can search by attributes and their values, e.g.:
* "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
*/
searchForNotes(query: string, searchParams: SearchParams): BNote[];
/**
* This is a powerful search method - you can search by attributes and their values, e.g.:
* "#dateModified =* MONTH AND #log". See {@link https://github.com/zadam/trilium/wiki/Search} for full documentation for all options
*/
searchForNote(query: string, searchParams: SearchParams): BNote | null;
/**
* Retrieves notes with given label name & value
*
@@ -145,8 +144,12 @@ interface Api {
*
* @param prefix - if branch is created between note and parent note, set this prefix
*/
ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string): {
branch: BBranch | null
ensureNoteIsPresentInParent(
noteId: string,
parentNoteId: string,
prefix: string
): {
branch: BBranch | null;
};
/**
@@ -183,11 +186,16 @@ interface Api {
* @param parentNoteId - create new note under this parent
* @returns object contains newly created entities note and branch
*/
createNote(parentNoteId: string, title: string, content: string, extraOptions: Omit<NoteParams, "title" | "content" | "type" | "parentNoteId"> & {
/** should the note be JSON */
json?: boolean;
attributes?: AttributeRow[]
}): NoteAndBranch;
createNote(
parentNoteId: string,
title: string,
content: string,
extraOptions: Omit<NoteParams, 'title' | 'content' | 'type' | 'parentNoteId'> & {
/** should the note be JSON */
json?: boolean;
attributes?: AttributeRow[];
}
): NoteAndBranch;
logMessages: Record<string, string[]>;
logSpacedUpdates: Record<string, SpacedUpdate>;
@@ -213,7 +221,7 @@ interface Api {
/**
* Returns today's day note. If such note doesn't exist, it is created.
*
*
* @param rootNote specify calendar root note, normally leave empty to use the default calendar
*/
getTodayNote(rootNote?: BNote): BNote | null;
@@ -224,11 +232,15 @@ interface Api {
* @param date in YYYY-MM-DD format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
*/
getWeekNote(date: string, options: {
// TODO: Deduplicate type with date_notes.ts once ES modules are added.
/** either "monday" (default) or "sunday" */
startOfTheWeek: "monday" | "sunday";
}, rootNote: BNote): BNote | null;
getWeekNote(
date: string,
options: {
// TODO: Deduplicate type with date_notes.ts once ES modules are added.
/** either "monday" (default) or "sunday" */
startOfTheWeek: 'monday' | 'sunday';
},
rootNote: BNote
): BNote | null;
/**
* Returns month note for given date. If such a note doesn't exist, it is created.
@@ -249,13 +261,16 @@ interface Api {
/**
* Sort child notes of a given note.
*/
sortNotes(parentNoteId: string, sortConfig: {
/** 'title', 'dateCreated', 'dateModified' or a label name
* See {@link https://github.com/zadam/trilium/wiki/Sorting} for details. */
sortBy?: string;
reverse?: boolean;
foldersFirst?: boolean;
}): void;
sortNotes(
parentNoteId: string,
sortConfig: {
/** 'title', 'dateCreated', 'dateModified' or a label name
* See {@link https://github.com/zadam/trilium/wiki/Sorting} for details. */
sortBy?: string;
reverse?: boolean;
foldersFirst?: boolean;
}
): void;
/**
* This method finds note by its noteId and prefix and either sets it to the given parentNoteId
@@ -315,7 +330,7 @@ interface Api {
* - "script" - activating the launcher will execute the script (specified in scriptNoteId param)
* - "customWidget" - the launcher will be rendered with a custom widget (specified in widgetNoteId param)
*/
type: "note" | "script" | "customWidget";
type: 'note' | 'script' | 'customWidget';
title: string;
/** if true, will be created in the "Visible launchers", otherwise in "Available launchers" */
isVisible: boolean;
@@ -329,12 +344,12 @@ interface Api {
scriptNoteId: string;
/** for type "customWidget" */
widgetNoteId?: string;
}): { note: BNote };
}): {note: BNote};
/**
* @param format - either 'html' or 'markdown'
*/
exportSubtreeToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string): Promise<void>;
exportSubtreeToZipFile(noteId: string, format: 'markdown' | 'html', zipFilePath: string): Promise<void>;
/**
* Executes given anonymous function on the frontend(s).
@@ -367,7 +382,7 @@ interface Api {
*/
backupNow(backupName: string): Promise<string>;
/**
/**
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
*/
__private: {
@@ -385,9 +400,9 @@ interface Api {
*/
function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.startNote = apiParams.startNote;
this.currentNote = currentNote;
this.originEntity = apiParams.originEntity;
for (const key in apiParams) {
@@ -397,18 +412,18 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.axios = axios;
this.dayjs = dayjs;
this.xml2js = xml2js;
this.getInstanceName = () => config.General ? config.General.instanceName : null;
this.getNote = noteId => becca.getNote(noteId);
this.getBranch = branchId => becca.getBranch(branchId);
this.getAttribute = attributeId => becca.getAttribute(attributeId);
this.getAttachment = attachmentId => becca.getAttachment(attachmentId);
this.getRevision = revisionId => becca.getRevision(revisionId);
this.getEtapiToken = etapiTokenId => becca.getEtapiToken(etapiTokenId);
this.getInstanceName = () => (config.General ? config.General.instanceName : null);
this.getNote = (noteId) => becca.getNote(noteId);
this.getBranch = (branchId) => becca.getBranch(branchId);
this.getAttribute = (attributeId) => becca.getAttribute(attributeId);
this.getAttachment = (attachmentId) => becca.getAttachment(attachmentId);
this.getRevision = (revisionId) => becca.getRevision(revisionId);
this.getEtapiToken = (etapiTokenId) => becca.getEtapiToken(etapiTokenId);
this.getEtapiTokens = () => becca.getEtapiTokens();
this.getOption = optionName => becca.getOption(optionName);
this.getOption = (optionName) => becca.getOption(optionName);
this.getOptions = () => optionsService.getOptions();
this.getAttribute = attributeId => becca.getAttribute(attributeId);
this.getAttribute = (attributeId) => becca.getAttribute(attributeId);
this.searchForNotes = (query, searchParams = {}) => {
if (searchParams.includeArchivedNotes === undefined) {
searchParams.includeArchivedNotes = true;
@@ -418,42 +433,42 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
searchParams.ignoreHoistedNote = true;
}
const noteIds = searchService.findResultsWithQuery(query, new SearchContext(searchParams))
.map(sr => sr.noteId);
const noteIds = searchService.findResultsWithQuery(query, new SearchContext(searchParams)).map((sr) => sr.noteId);
return becca.getNotes(noteIds);
};
this.searchForNote = (query, searchParams = {}) => {
const notes = this.searchForNotes(query, searchParams);
return notes.length > 0 ? notes[0] : null;
};
this.getNotesWithLabel = attributeService.getNotesWithLabel;
this.getNotesWithLabel = attributeService.getNotesWithLabel;
this.getNoteWithLabel = attributeService.getNoteWithLabel;
this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent;
this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent;
this.toggleNoteInParent = cloningService.toggleNoteInParent;
this.createTextNote = (parentNoteId, title, content = '') => noteService.createNewNote({
parentNoteId,
title,
content,
type: 'text'
});
this.createTextNote = (parentNoteId, title, content = '') =>
noteService.createNewNote({
parentNoteId,
title,
content,
type: 'text'
});
this.createDataNote = (parentNoteId, title, content = {}) =>
noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
this.createDataNote = (parentNoteId, title, content = {}) => noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
this.createNewNote = noteService.createNewNote;
this.createNote = (parentNoteId, title, content = "", _extraOptions = {}) => {
this.createNote = (parentNoteId, title, content = '', _extraOptions = {}) => {
const parentNote = becca.getNote(parentNoteId);
if (!parentNote) {
throw new Error(`Unable to find parent note with ID ${parentNote}.`);
@@ -461,8 +476,8 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
let extraOptions: NoteParams = {
..._extraOptions,
content: "",
type: "text",
content: '',
type: 'text',
parentNoteId,
title
};
@@ -475,13 +490,12 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
extraOptions.content = JSON.stringify(content || {}, null, '\t');
extraOptions.type = 'code';
extraOptions.mime = 'application/json';
}
else {
} else {
extraOptions.content = content;
}
return sql.transactional(() => {
const { note, branch } = noteService.createNewNote(extraOptions);
const {note, branch} = noteService.createNewNote(extraOptions);
for (const attr of _extraOptions.attributes || []) {
attributeService.createAttribute({
@@ -493,33 +507,35 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
});
}
return { note, branch };
return {note, branch};
});
};
this.logMessages = {};
this.logSpacedUpdates = {};
this.log = message => {
this.log = (message) => {
log.info(message);
if (!this.startNote) {
return;
}
const { noteId } = this.startNote;
const {noteId} = this.startNote;
this.logMessages[noteId] = this.logMessages[noteId] || [];
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
const messages = this.logMessages[noteId];
this.logMessages[noteId] = [];
this.logSpacedUpdates[noteId] =
this.logSpacedUpdates[noteId] ||
new SpacedUpdate(() => {
const messages = this.logMessages[noteId];
this.logMessages[noteId] = [];
ws.sendMessageToAllClients({
type: 'api-log-messages',
noteId,
messages
});
}, 100);
ws.sendMessageToAllClients({
type: 'api-log-messages',
noteId,
messages
});
}, 100);
this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate();
@@ -532,12 +548,7 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.getMonthNote = dateNoteService.getMonthNote;
this.getYearNote = dateNoteService.getYearNote;
this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes(
parentNoteId,
sortConfig.sortBy || "title",
!!sortConfig.reverse,
!!sortConfig.foldersFirst
);
this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes(parentNoteId, sortConfig.sortBy || 'title', !!sortConfig.reverse, !!sortConfig.foldersFirst);
this.setNoteToParent = treeService.setNoteToParent;
this.transactional = sql.transactional;
@@ -547,16 +558,31 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.sql = sql;
this.getAppInfo = () => appInfo;
this.createOrUpdateLauncher = opts => {
if (!opts.id) { throw new Error("ID is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
if (!opts.id.match(/[a-z0-9]{6,1000}/i)) { throw new Error(`ID must be an alphanumeric string at least 6 characters long.`); }
if (!opts.type) { throw new Error("Launcher Type is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
if (!["note", "script", "customWidget"].includes(opts.type)) { throw new Error(`Given launcher type '${opts.type}'`); }
if (!opts.title?.trim()) { throw new Error("Title is a mandatory parameter for api.createOrUpdateLauncher(opts)"); }
if (opts.type === 'note' && !opts.targetNoteId) { throw new Error("targetNoteId is mandatory for launchers of type 'note'"); }
if (opts.type === 'script' && !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); }
if (opts.type === 'customWidget' && !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); }
this.createOrUpdateLauncher = (opts) => {
if (!opts.id) {
throw new Error('ID is a mandatory parameter for api.createOrUpdateLauncher(opts)');
}
if (!opts.id.match(/[a-z0-9]{6,1000}/i)) {
throw new Error(`ID must be an alphanumeric string at least 6 characters long.`);
}
if (!opts.type) {
throw new Error('Launcher Type is a mandatory parameter for api.createOrUpdateLauncher(opts)');
}
if (!['note', 'script', 'customWidget'].includes(opts.type)) {
throw new Error(`Given launcher type '${opts.type}'`);
}
if (!opts.title?.trim()) {
throw new Error('Title is a mandatory parameter for api.createOrUpdateLauncher(opts)');
}
if (opts.type === 'note' && !opts.targetNoteId) {
throw new Error("targetNoteId is mandatory for launchers of type 'note'");
}
if (opts.type === 'script' && !opts.scriptNoteId) {
throw new Error("scriptNoteId is mandatory for launchers of type 'script'");
}
if (opts.type === 'customWidget' && !opts.widgetNoteId) {
throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'");
}
const parentNoteId = opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers';
const noteId = 'al_' + opts.id;
@@ -566,7 +592,7 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
specialNotesService.createLauncher({
noteId: noteId,
parentNoteId: parentNoteId,
launcherType: opts.type,
launcherType: opts.type
}).note;
if (launcherNote.title !== opts.title) {
@@ -604,14 +630,14 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
launcherNote.removeLabel('iconClass');
}
return { note: launcherNote };
return {note: launcherNote};
};
this.exportSubtreeToZipFile = async (noteId, format, zipFilePath) => await exportService.exportToZipFile(noteId, format, zipFilePath);
this.runOnFrontend = async (_script, params = []) => {
let script: string;
if (typeof _script === "string") {
if (typeof _script === 'string') {
script = _script;
} else {
script = _script.toString();
@@ -623,8 +649,8 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
params: prepareParams(params),
startNoteId: this.startNote?.noteId,
currentNoteId: this.currentNote.noteId,
originEntityName: "notes", // currently there's no other entity on the frontend which can trigger event
originEntityId: (this.originEntity && "noteId" in this.originEntity && (this.originEntity as BNote)?.noteId) || null
originEntityName: 'notes', // currently there's no other entity on the frontend which can trigger event
originEntityId: (this.originEntity && 'noteId' in this.originEntity && (this.originEntity as BNote)?.noteId) || null
});
function prepareParams(params: any[]) {
@@ -632,25 +658,24 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
return params;
}
return params.map(p => {
if (typeof p === "function") {
return params.map((p) => {
if (typeof p === 'function') {
return `!@#Function: ${p.toString()}`;
}
else {
} else {
return p;
}
});
}
};
this.runOutsideOfSync = syncMutex.doExclusively;
this.backupNow = backupService.backupNow;
this.__private = {
becca
}
};
}
export = BackendScriptApi as any as {
new (currentNote: BNote, apiParams: ApiParams): Api
new (currentNote: BNote, apiParams: ApiParams): Api;
};