chore(code): fix editorconfig for src/public

This commit is contained in:
Elian Doran 2024-12-22 15:42:15 +02:00
parent ae90ff2df4
commit b321d99076
No known key found for this signature in database
70 changed files with 589 additions and 590 deletions

View File

@ -16,7 +16,7 @@ bundleService.getWidgetBundlesByParent().then(async widgetBundles => {
// A dynamic import is required for layouts since they initialize components which require translations. // A dynamic import is required for layouts since they initialize components which require translations.
const DesktopLayout = (await import("./layouts/desktop_layout.js")).default; const DesktopLayout = (await import("./layouts/desktop_layout.js")).default;
appContext.setLayout(new DesktopLayout(widgetBundles)); appContext.setLayout(new DesktopLayout(widgetBundles));
appContext.start() appContext.start()
.catch((e) => { .catch((e) => {
toastService.showPersistent({ toastService.showPersistent({
@ -47,16 +47,16 @@ if (utils.isElectron()) {
function initOnElectron() { function initOnElectron() {
const electron = utils.dynamicRequire('electron'); const electron = utils.dynamicRequire('electron');
electron.ipcRenderer.on('globalShortcut', async (event, actionName) => appContext.triggerCommand(actionName)); electron.ipcRenderer.on('globalShortcut', async (event, actionName) => appContext.triggerCommand(actionName));
const electronRemote = utils.dynamicRequire("@electron/remote"); const electronRemote = utils.dynamicRequire("@electron/remote");
const currentWindow = electronRemote.getCurrentWindow(); const currentWindow = electronRemote.getCurrentWindow();
const style = window.getComputedStyle(document.body); const style = window.getComputedStyle(document.body);
initTransparencyEffects(style, currentWindow); initTransparencyEffects(style, currentWindow);
if (options.get("nativeTitleBarVisible") !== "true") { if (options.get("nativeTitleBarVisible") !== "true") {
initTitleBarButtons(style, currentWindow); initTitleBarButtons(style, currentWindow);
} }
} }
function initTitleBarButtons(style, currentWindow) { function initTitleBarButtons(style, currentWindow) {
@ -68,7 +68,7 @@ function initTitleBarButtons(style, currentWindow) {
currentWindow.setTitleBarOverlay({ color, symbolColor }); currentWindow.setTitleBarOverlay({ color, symbolColor });
} }
}; };
applyWindowsOverlay(); applyWindowsOverlay();
// Register for changes to the native title bar colors. // Register for changes to the native title bar colors.
@ -80,7 +80,7 @@ function initTitleBarButtons(style, currentWindow) {
const xOffset = parseInt(style.getPropertyValue("--native-titlebar-darwin-x-offset"), 10); const xOffset = parseInt(style.getPropertyValue("--native-titlebar-darwin-x-offset"), 10);
const yOffset = parseInt(style.getPropertyValue("--native-titlebar-darwin-y-offset"), 10); const yOffset = parseInt(style.getPropertyValue("--native-titlebar-darwin-y-offset"), 10);
currentWindow.setWindowButtonPosition({ x: xOffset, y: yOffset }); currentWindow.setWindowButtonPosition({ x: xOffset, y: yOffset });
} }
} }
function initTransparencyEffects(style, currentWindow) { function initTransparencyEffects(style, currentWindow) {
@ -88,4 +88,4 @@ function initTransparencyEffects(style, currentWindow) {
const material = style.getPropertyValue("--background-material"); const material = style.getPropertyValue("--background-material");
currentWindow.setBackgroundMaterial(material); currentWindow.setBackgroundMaterial(material);
} }
} }

View File

@ -1,18 +1,18 @@
// TODO: Deduplicate with src/services/entity_changes_interface.ts // TODO: Deduplicate with src/services/entity_changes_interface.ts
export interface EntityChange { export interface EntityChange {
id?: number | null; id?: number | null;
noteId?: string; noteId?: string;
entityName: string; entityName: string;
entityId: string; entityId: string;
entity?: any; entity?: any;
positions?: Record<string, number>; positions?: Record<string, number>;
hash: string; hash: string;
utcDateChanged?: string; utcDateChanged?: string;
utcDateModified?: string; utcDateModified?: string;
utcDateCreated?: string; utcDateCreated?: string;
isSynced: boolean | 1 | 0; isSynced: boolean | 1 | 0;
isErased: boolean | 1 | 0; isErased: boolean | 1 | 0;
componentId?: string | null; componentId?: string | null;
changeId?: string | null; changeId?: string | null;
instanceId?: string | null; instanceId?: string | null;
} }

View File

@ -48,8 +48,8 @@ type RequireMethod = (moduleName: string) => any;
declare global { declare global {
interface Window { interface Window {
logError(message: string); logError(message: string);
logInfo(message: string); logInfo(message: string);
process?: ElectronProcess; process?: ElectronProcess;
glob?: CustomGlobals; glob?: CustomGlobals;
} }
@ -75,10 +75,10 @@ declare global {
suggestion: (suggestion: Suggestion) => string | undefined suggestion: (suggestion: Suggestion) => string | undefined
} }
}; };
interface JQuery { interface JQuery {
autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: AutoCompleteArg[] | string) => JQuery<?>; autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: AutoCompleteArg[] | string) => JQuery<?>;
getSelectedNotePath(): string | undefined; getSelectedNotePath(): string | undefined;
getSelectedNoteId(): string | null; getSelectedNoteId(): string | null;
setSelectedNotePath(notePath: string | null | undefined); setSelectedNotePath(notePath: string | null | undefined);
@ -88,7 +88,7 @@ declare global {
} }
var logError: (message: string, e?: Error) => void; var logError: (message: string, e?: Error) => void;
var logInfo: (message: string) => void; var logInfo: (message: string) => void;
var glob: CustomGlobals; var glob: CustomGlobals;
var require: RequireMethod; var require: RequireMethod;
var __non_webpack_require__: RequireMethod | undefined; var __non_webpack_require__: RequireMethod | undefined;
@ -132,7 +132,7 @@ declare global {
interface MermaidLoader { interface MermaidLoader {
} }
var mermaid: { var mermaid: {
mermaidAPI: MermaidApi; mermaidAPI: MermaidApi;
registerLayoutLoaders(loader: MermaidLoader); registerLayoutLoaders(loader: MermaidLoader);
parse(content: string, opts: { parse(content: string, opts: {
@ -140,7 +140,7 @@ declare global {
}): { }): {
config: { config: {
layout: string; layout: string;
} }
} }
}; };
var MERMAID_ELK: MermaidLoader; var MERMAID_ELK: MermaidLoader;

View File

@ -111,4 +111,3 @@
.calendar-dropdown-widget .calendar-date:not(.calendar-date-active) { .calendar-dropdown-widget .calendar-date:not(.calendar-date-active) {
cursor: pointer; cursor: pointer;
} }

View File

@ -20,7 +20,7 @@
} }
:root { :root {
--submenu-opening-delay: 300ms; --submenu-opening-delay: 300ms;
} }
html { html {
@ -30,7 +30,7 @@ html {
body { body {
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter /* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
on the last line of the editor. */ on the last line of the editor. */
position: fixed; position: fixed;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -38,7 +38,7 @@ body {
color: var(--main-text-color); color: var(--main-text-color);
font-family: var(--main-font-family); font-family: var(--main-font-family);
font-size: var(--main-font-size); font-size: var(--main-font-size);
--native-titlebar-foreground: var(--main-text-color); --native-titlebar-foreground: var(--main-text-color);
--native-titlebar-darwin-x-offset: 10; --native-titlebar-darwin-x-offset: 10;
--native-titlebar-darwin-y-offset: 12; --native-titlebar-darwin-y-offset: 12;
@ -1097,7 +1097,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
cursor: pointer; cursor: pointer;
border: none; border: none;
color: var(--launcher-pane-text-color); color: var(--launcher-pane-text-color);
background-color: var(--launcher-pane-background-color); background-color: var(--launcher-pane-background-color);
} }
#launcher-pane.vertical .launcher-button { #launcher-pane.vertical .launcher-button {
@ -1297,7 +1297,7 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
width: 24px; width: 24px;
height: 24px; height: 24px;
color: var(--launcher-pane-text-color); color: var(--launcher-pane-text-color);
margin: 8px 10px; margin: 8px 10px;
font-size: 18px; font-size: 18px;
} }
@ -1313,4 +1313,4 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
border-color: var(--hover-item-border-color); border-color: var(--hover-item-border-color);
background: var(--hover-item-background-color); background: var(--hover-item-background-color);
color: var(--hover-item-text-color); color: var(--hover-item-text-color);
} }

View File

@ -100,4 +100,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.ck-content pre { .ck-content pre {
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important;
} }

View File

@ -1,6 +1,6 @@
/* Light theme is special since it's also baseline/default so when a theme does not define some variable then /* Light theme is special since it's also baseline/default so when a theme does not define some variable then
value from this theme will be used. For this reason this theme uses "html" instead of ":root" value from this theme will be used. For this reason this theme uses "html" instead of ":root"
since it's less "specific" and thus serves as default */ since it's less "specific" and thus serves as default */
html { html {
/* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */ /* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */
--theme-style: light; --theme-style: light;

View File

@ -9,7 +9,7 @@
--native-titlebar-background: #00000000; --native-titlebar-background: #00000000;
--main-background-color: #333; --main-background-color: #333;
--main-text-color: #ccc; --main-text-color: #ccc;
--main-border-color: #454545; --main-border-color: #454545;
--subtle-border-color: #313131; --subtle-border-color: #313131;
--dropdown-border-color: #292929; --dropdown-border-color: #292929;
@ -81,7 +81,7 @@
--left-pane-item-action-button-hover-background: #ffffffad; --left-pane-item-action-button-hover-background: #ffffffad;
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, .15); --left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, .15);
--left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, .25); --left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, .25);
--launcher-pane-background-color: #1a1a1a; --launcher-pane-background-color: #1a1a1a;
--launcher-pane-horizontal-background-color: #282828; --launcher-pane-horizontal-background-color: #282828;
--launcher-pane-horizontal-border-color: rgb(22, 22, 22); --launcher-pane-horizontal-border-color: rgb(22, 22, 22);
@ -145,8 +145,8 @@
--tooltip-shadow-color: rgba(0, 0, 0, 0.4); --tooltip-shadow-color: rgba(0, 0, 0, 0.4);
} }
/* /*
* Dark color scheme tweaks * Dark color scheme tweaks
*/ */
body ::-webkit-calendar-picker-indicator { body ::-webkit-calendar-picker-indicator {
@ -167,4 +167,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.btn-close { .btn-close {
filter: invert(1); filter: invert(1);
} }

View File

@ -1,5 +1,5 @@
/* Import the Next theme base style */ /* Import the Next theme base style */
@import url(./theme-next/base.css); @import url(./theme-next/base.css);
/* /*
* Color scheme * Color scheme
@ -7,7 +7,7 @@
:root { :root {
--theme-style: light; --theme-style: light;
--native-titlebar-background: #ffffff00; --native-titlebar-background: #ffffff00;
--main-background-color: white; --main-background-color: white;
--main-text-color: black; --main-text-color: black;
--main-border-color: #dbdbdb; --main-border-color: #dbdbdb;
@ -16,33 +16,33 @@
--dropdown-shadow-opacity: .2; --dropdown-shadow-opacity: .2;
--dropdown-item-icon-destructive-color: #ec5138; --dropdown-item-icon-destructive-color: #ec5138;
--disabled-tooltip-icon-color: #004382; --disabled-tooltip-icon-color: #004382;
--accented-background-color: #f5f5f5; --accented-background-color: #f5f5f5;
--button-background-color: transparent; --button-background-color: transparent;
--button-border-color: #ddd; --button-border-color: #ddd;
--button-text-color: black; --button-text-color: black;
--button-border-radius: 5px; --button-border-radius: 5px;
--button-disabled-background-color: #ddd; --button-disabled-background-color: #ddd;
--button-disabled-text-color: black; --button-disabled-text-color: black;
--primary-button-background-color: #6c757d; --primary-button-background-color: #6c757d;
--primary-button-text-color: white; --primary-button-text-color: white;
--primary-button-border-color: #6c757d; --primary-button-border-color: #6c757d;
--muted-text-color: #666; --muted-text-color: #666;
--input-text-color: black; --input-text-color: black;
--input-background-color: transparent; --input-background-color: transparent;
--hover-item-text-color: black; --hover-item-text-color: black;
--hover-item-background-color: #0000001a; --hover-item-background-color: #0000001a;
--hover-item-border-color: transparent; --hover-item-border-color: transparent;
--active-item-text-color: var(--left-pane-text-color); --active-item-text-color: var(--left-pane-text-color);
--active-item-background-color: #ddd; --active-item-background-color: #ddd;
--active-item-border-color: transparent; --active-item-border-color: transparent;
--menu-text-color: #272727; --menu-text-color: #272727;
--menu-background-color: #ffffffd9; --menu-background-color: #ffffffd9;
--menu-item-icon-color: #727272; --menu-item-icon-color: #727272;
@ -50,17 +50,17 @@
--menu-item-keyboard-shortcut-color: #666666a8; --menu-item-keyboard-shortcut-color: #666666a8;
--menu-item-arrow-color: #00000080; --menu-item-arrow-color: #00000080;
--menu-item-delimiter-color: #00000030; --menu-item-delimiter-color: #00000030;
--modal-background-color: white; --modal-background-color: white;
--modal-backdrop-color: black; --modal-backdrop-color: black;
--quick-search-background: #00000012; --quick-search-background: #00000012;
--quick-search-color: #06060682; --quick-search-color: #06060682;
--quick-search-hover-background: #00000020; --quick-search-hover-background: #00000020;
--quick-search-focus-border: #00000029; --quick-search-focus-border: #00000029;
--quick-search-focus-background: #ffffff80; --quick-search-focus-background: #ffffff80;
--quick-search-focus-color: #000; --quick-search-focus-color: #000;
--left-pane-collapsed-border-color: #0000000d; --left-pane-collapsed-border-color: #0000000d;
--left-pane-background-color: #f2f2f2; --left-pane-background-color: #f2f2f2;
--left-pane-text-color: #383838; --left-pane-text-color: #383838;
@ -73,7 +73,7 @@
--left-pane-item-action-button-hover-background: white; --left-pane-item-action-button-hover-background: white;
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, .15); --left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, .15);
--left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, .25); --left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, .25);
--launcher-pane-background-color: #e8e8e8; --launcher-pane-background-color: #e8e8e8;
--launcher-pane-horizontal-background-color: #fafafa; --launcher-pane-horizontal-background-color: #fafafa;
--launcher-pane-horizontal-border-color: rgba(0, 0, 0, 0.1); --launcher-pane-horizontal-border-color: rgba(0, 0, 0, 0.1);
@ -81,65 +81,65 @@
--launcher-pane-button-hover-color: black; --launcher-pane-button-hover-color: black;
--launcher-pane-button-hover-background: white; --launcher-pane-button-hover-background: white;
--launcher-pane-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, .075); --launcher-pane-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, .075);
--protected-session-active-icon-color: #16b516; --protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528; --sync-status-error-pulse-color: #ff5528;
--root-background: var(--left-pane-background-color); --root-background: var(--left-pane-background-color);
--gutter-color: transparent; --gutter-color: transparent;
--gutter-hover-color: #bfbfbf; --gutter-hover-color: #bfbfbf;
--tab-close-button-hover-background: #c95a5a; --tab-close-button-hover-background: #c95a5a;
--tab-close-button-hover-color: white; --tab-close-button-hover-color: white;
--active-tab-background-color: white; --active-tab-background-color: white;
--active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-text-color: black; --active-tab-text-color: black;
--active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, .1), -1px -1px 3px rgba(0, 0, 0, .05); --active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, .1), -1px -1px 3px rgba(0, 0, 0, .05);
--active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, .1); --active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, .1);
--inactive-tab-background-color: transparent; --inactive-tab-background-color: transparent;
--inactive-tab-hover-background-color: #00000016; --inactive-tab-hover-background-color: #00000016;
--inactive-tab-text-color: #4e4e4e; --inactive-tab-text-color: #4e4e4e;
--new-tab-button-background: #d8d8d8; --new-tab-button-background: #d8d8d8;
--new-tab-button-color: #3a3a3a; --new-tab-button-color: #3a3a3a;
--new-tab-button-shadow: 2px 2px 4px rgba(0, 0, 0, .2); --new-tab-button-shadow: 2px 2px 4px rgba(0, 0, 0, .2);
--new-tab-button-hover-background: white; --new-tab-button-hover-background: white;
--new-tab-button-hover-color: black; --new-tab-button-hover-color: black;
--right-pane-item-hover-background: #ececec; --right-pane-item-hover-background: #ececec;
--right-pane-item-hover-color: inherit; --right-pane-item-hover-color: inherit;
--scrollbar-border-color: #ddd; --scrollbar-border-color: #ddd;
--scrollbar-background-color: #ddd; --scrollbar-background-color: #ddd;
--link-color: blue; --link-color: blue;
--mermaid-theme: default; --mermaid-theme: default;
--code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2); --code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
--card-background-color: var(--accented-background-color); --card-background-color: var(--accented-background-color);
--card-background-hover-color: #f9f9f9; --card-background-hover-color: #f9f9f9;
--card-background-press-color: #efefef; --card-background-press-color: #efefef;
--card-border-color: #eaeaea; --card-border-color: #eaeaea;
--card-shadow-color: rgba(0, 0, 0, 0.1); --card-shadow-color: rgba(0, 0, 0, 0.1);
--card-box-shadow: 0 0 12px var(--card-shadow-color); --card-box-shadow: 0 0 12px var(--card-shadow-color);
--calendar-color: var(--menu-text-color); --calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color); --calendar-weekday-labels-color: var(--muted-text-color);
--calendar-day-hover-color: var(--hover-item-text-color); --calendar-day-hover-color: var(--hover-item-text-color);
--calendar-day-hover-background: var(--active-item-background-color); --calendar-day-hover-background: var(--active-item-background-color);
--calendar-day-highlight-background: #80808024; --calendar-day-highlight-background: #80808024;
--timeline-bullet-color: #a5a5a5; --timeline-bullet-color: #a5a5a5;
--timeline-bullet-hover-color: black; --timeline-bullet-hover-color: black;
--timeline-connector-color: #f1f1f1; --timeline-connector-color: #f1f1f1;
--timeline-connector-active-color: #ddd; --timeline-connector-active-color: #ddd;
--timeline-connector-hover-blend-mode: multiply; --timeline-connector-hover-blend-mode: multiply;
--tooltip-background-color: rgba(255, 255, 255, 0.85); --tooltip-background-color: rgba(255, 255, 255, 0.85);
--tooltip-foreground-color: #000000ba; --tooltip-foreground-color: #000000ba;
--tooltip-shadow-color: rgba(0, 0, 0, .15); --tooltip-shadow-color: rgba(0, 0, 0, .15);
} }

View File

@ -1,7 +1,7 @@
/* Import the light color scheme. /* Import the light color scheme.
* This is the base color scheme, always active and overridden by the dark * This is the base color scheme, always active and overridden by the dark
* color scheme stylesheet when necessary. */ * color scheme stylesheet when necessary. */
@import url(./theme-next-light.css); @import url(./theme-next-light.css);
/* Import the dark color scheme when the system preference is set to dark mode */ /* Import the dark color scheme when the system preference is set to dark mode */
@import url(./theme-next-dark.css) (prefers-color-scheme: dark); @import url(./theme-next-dark.css) (prefers-color-scheme: dark);

View File

@ -1,7 +1,7 @@
/* Import the light color scheme. /* Import the light color scheme.
* This is the base color scheme, always active and overridden by the dark * This is the base color scheme, always active and overridden by the dark
* color scheme stylesheet when necessary. */ * color scheme stylesheet when necessary. */
@import url(./theme-light.css); @import url(./theme-light.css);
/* Import the dark color scheme when the system preference is set to dark mode */ /* Import the dark color scheme when the system preference is set to dark mode */
@import url(./theme-dark.css) (prefers-color-scheme: dark); @import url(./theme-dark.css) (prefers-color-scheme: dark);

View File

@ -15,7 +15,7 @@ function getAutocomplete(req: Request) {
} }
const query = (req.query.query || "").trim(); const query = (req.query.query || "").trim();
const fastSearch = String(req.query.fastSearch).toLowerCase() === "false" ? false : true; const fastSearch = String(req.query.fastSearch).toLowerCase() === "false" ? false : true;
const activeNoteId = req.query.activeNoteId || 'none'; const activeNoteId = req.query.activeNoteId || 'none';
let results; let results;
@ -49,18 +49,18 @@ function getRecentNotes(activeNoteId: string) {
} }
const recentNotes = becca.getRecentNotesFromQuery(` const recentNotes = becca.getRecentNotesFromQuery(`
SELECT SELECT
recent_notes.* recent_notes.*
FROM FROM
recent_notes recent_notes
JOIN notes USING(noteId) JOIN notes USING(noteId)
WHERE WHERE
notes.isDeleted = 0 notes.isDeleted = 0
AND notes.noteId != ? AND notes.noteId != ?
${extraCondition} ${extraCondition}
ORDER BY ORDER BY
utcDateCreated DESC utcDateCreated DESC
LIMIT 200`, params); LIMIT 200`, params);
return recentNotes.map(rn => { return recentNotes.map(rn => {
const notePathArray = rn.notePath.split('/'); const notePathArray = rn.notePath.split('/');

View File

@ -176,7 +176,7 @@ function saveToTmpDir(fileName: string, content: string | Buffer, entityType: st
if (typeof content === "string") { if (typeof content === "string") {
fs.writeSync(tmpObj.fd, content); fs.writeSync(tmpObj.fd, content);
} else { } else {
fs.writeSync(tmpObj.fd, content); fs.writeSync(tmpObj.fd, content);
} }
fs.closeSync(tmpObj.fd); fs.closeSync(tmpObj.fd);

View File

@ -107,7 +107,7 @@ function updateImage(req: Request) {
message: "Invalid image content." message: "Invalid image content."
}; };
} }
imageService.updateImage(noteId, file.buffer, file.originalname); imageService.updateImage(noteId, file.buffer, file.originalname);
return { uploaded: true }; return { uploaded: true };

View File

@ -219,9 +219,9 @@ function getDeleteNotesPreview(req: Request) {
brokenRelations = sql.getRows<AttributeRow>(` brokenRelations = sql.getRows<AttributeRow>(`
SELECT attr.noteId, attr.name, attr.value SELECT attr.noteId, attr.name, attr.value
FROM attributes attr FROM attributes attr
JOIN param_list ON param_list.paramId = attr.value JOIN param_list ON param_list.paramId = attr.value
WHERE attr.isDeleted = 0 WHERE attr.isDeleted = 0
AND attr.type = 'relation'`).filter(attr => attr.noteId && !noteIdsToBeDeleted.has(attr.noteId)); AND attr.type = 'relation'`).filter(attr => attr.noteId && !noteIdsToBeDeleted.has(attr.noteId));
} }
return { return {

View File

@ -25,7 +25,7 @@ function getRecentChanges(req: Request) {
let recentChanges = []; let recentChanges = [];
const revisionRows = sql.getRows<RecentChangeRow>(` const revisionRows = sql.getRows<RecentChangeRow>(`
SELECT SELECT
notes.noteId, notes.noteId,
notes.isDeleted AS current_isDeleted, notes.isDeleted AS current_isDeleted,
notes.deleteId AS current_deleteId, notes.deleteId AS current_deleteId,
@ -34,7 +34,7 @@ function getRecentChanges(req: Request) {
revisions.title, revisions.title,
revisions.utcDateCreated AS utcDate, revisions.utcDateCreated AS utcDate,
revisions.dateCreated AS date revisions.dateCreated AS date
FROM FROM
revisions revisions
JOIN notes USING(noteId)`); JOIN notes USING(noteId)`);

View File

@ -35,9 +35,9 @@ function getRevisionBlob(req: Request) {
function getRevisions(req: Request) { function getRevisions(req: Request) {
return becca.getRevisionsFromQuery(` return becca.getRevisionsFromQuery(`
SELECT revisions.*, SELECT revisions.*,
LENGTH(blobs.content) AS contentLength LENGTH(blobs.content) AS contentLength
FROM revisions FROM revisions
JOIN blobs ON revisions.blobId = blobs.blobId JOIN blobs ON revisions.blobId = blobs.blobId
WHERE revisions.noteId = ? WHERE revisions.noteId = ?
ORDER BY revisions.utcDateCreated DESC`, [req.params.noteId]); ORDER BY revisions.utcDateCreated DESC`, [req.params.noteId]);
} }
@ -158,9 +158,9 @@ function getEditedNotesOnDate(req: Request) {
SELECT notes.* SELECT notes.*
FROM notes FROM notes
WHERE noteId IN ( WHERE noteId IN (
SELECT noteId FROM notes SELECT noteId FROM notes
WHERE notes.dateCreated LIKE :date WHERE notes.dateCreated LIKE :date
OR notes.dateModified LIKE :date OR notes.dateModified LIKE :date
UNION ALL UNION ALL
SELECT noteId FROM revisions SELECT noteId FROM revisions
WHERE revisions.dateLastEdited LIKE :date WHERE revisions.dateLastEdited LIKE :date

View File

@ -40,7 +40,7 @@ function execute(req: Request) {
const pivot = query.indexOf('\n'); const pivot = query.indexOf('\n');
query = pivot > 0 ? query.substr(pivot + 1).trim() : ""; query = pivot > 0 ? query.substr(pivot + 1).trim() : "";
} }
if (!query) { if (!query) {
continue; continue;
} }

View File

@ -11,9 +11,9 @@ function getNoteSize(req: Request) {
LEFT JOIN notes ON notes.blobId = blobs.blobId AND notes.noteId = ? AND notes.isDeleted = 0 LEFT JOIN notes ON notes.blobId = blobs.blobId AND notes.noteId = ? AND notes.isDeleted = 0
LEFT JOIN attachments ON attachments.blobId = blobs.blobId AND attachments.ownerId = ? AND attachments.isDeleted = 0 LEFT JOIN attachments ON attachments.blobId = blobs.blobId AND attachments.ownerId = ? AND attachments.isDeleted = 0
LEFT JOIN revisions ON revisions.blobId = blobs.blobId AND revisions.noteId = ? LEFT JOIN revisions ON revisions.blobId = blobs.blobId AND revisions.noteId = ?
WHERE notes.noteId IS NOT NULL WHERE notes.noteId IS NOT NULL
OR attachments.attachmentId IS NOT NULL OR attachments.attachmentId IS NOT NULL
OR revisions.revisionId IS NOT NULL`, [noteId, noteId, noteId]); OR revisions.revisionId IS NOT NULL`, [noteId, noteId, noteId]);
const noteSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0); const noteSize = Object.values(blobSizes).reduce((acc, blobSize) => acc + blobSize, 0);

View File

@ -103,7 +103,7 @@ function getChanged(req: Request) {
SELECT * SELECT *
FROM entity_changes FROM entity_changes
WHERE isSynced = 1 WHERE isSynced = 1
AND id > ? AND id > ?
ORDER BY id ORDER BY id
LIMIT 1000`, [lastEntityChangeId]); LIMIT 1000`, [lastEntityChangeId]);
@ -130,11 +130,11 @@ function getChanged(req: Request) {
entityChanges: entityChangeRecords, entityChanges: entityChangeRecords,
lastEntityChangeId, lastEntityChangeId,
outstandingPullCount: sql.getValue(` outstandingPullCount: sql.getValue(`
SELECT COUNT(id) SELECT COUNT(id)
FROM entity_changes FROM entity_changes
WHERE isSynced = 1 WHERE isSynced = 1
AND instanceId != ? AND instanceId != ?
AND id > ?`, [clientInstanceId, lastEntityChangeId]) AND id > ?`, [clientInstanceId, lastEntityChangeId])
}; };
} }

View File

@ -18,22 +18,22 @@ const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOp
async function register(app: express.Application) { async function register(app: express.Application) {
const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
if (env.isDev()) { if (env.isDev()) {
const webpack = (await import("webpack")).default; const webpack = (await import("webpack")).default;
const webpackMiddleware = (await import("webpack-dev-middleware")).default; const webpackMiddleware = (await import("webpack-dev-middleware")).default;
const productionConfig = (await import("../../webpack.config.js")).default; const productionConfig = (await import("../../webpack.config.js")).default;
const frontendCompiler = webpack({ const frontendCompiler = webpack({
mode: "development", mode: "development",
entry: productionConfig.entry, entry: productionConfig.entry,
module: productionConfig.module, module: productionConfig.module,
resolve: productionConfig.resolve, resolve: productionConfig.resolve,
devtool: productionConfig.devtool, devtool: productionConfig.devtool,
target: productionConfig.target target: productionConfig.target
}); });
app.use(`/${assetPath}/app`, webpackMiddleware(frontendCompiler)); app.use(`/${assetPath}/app`, webpackMiddleware(frontendCompiler));
} else { } else {
app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(srcRoot, 'public/app'))); app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(srcRoot, 'public/app')));
} }
app.use(`/${assetPath}/app-dist`, persistentCacheStatic(path.join(srcRoot, 'public/app-dist'))); app.use(`/${assetPath}/app-dist`, persistentCacheStatic(path.join(srcRoot, 'public/app-dist')));
app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(srcRoot, 'public/fonts'))); app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(srcRoot, 'public/fonts')));
@ -56,19 +56,19 @@ async function register(app: express.Application) {
// KaTeX // KaTeX
app.use( app.use(
`/${assetPath}/node_modules/katex/dist/katex.min.js`, `/${assetPath}/node_modules/katex/dist/katex.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/katex.min.js'))); persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/katex.min.js')));
app.use( app.use(
`/${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js`, `/${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/mhchem.min.js'))); persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/katex/dist/contrib/mhchem.min.js')));
app.use( app.use(
`/${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js`, `/${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js`,
persistentCacheStatic(path.join(srcRoot, '..', '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 // expose the whole dist folder
app.use(`/node_modules/katex/dist/`, app.use(`/node_modules/katex/dist/`,
express.static(path.join(srcRoot, '..', 'node_modules/katex/dist/'))); express.static(path.join(srcRoot, '..', 'node_modules/katex/dist/')));
app.use(`/${assetPath}/node_modules/katex/dist/`, app.use(`/${assetPath}/node_modules/katex/dist/`,
persistentCacheStatic(path.join(srcRoot, '..', '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/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/'))); app.use(`/${assetPath}/node_modules/force-graph/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/force-graph/dist/')));
@ -98,9 +98,9 @@ async function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/jsplumb/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/jsplumb/dist/'))); app.use(`/${assetPath}/node_modules/jsplumb/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/jsplumb/dist/')));
app.use(`/${assetPath}/node_modules/vanilla-js-wheel-zoom/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/vanilla-js-wheel-zoom/dist/'))); app.use(`/${assetPath}/node_modules/vanilla-js-wheel-zoom/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/vanilla-js-wheel-zoom/dist/')));
app.use(`/${assetPath}/node_modules/mark.js/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/mark.js/dist/'))); app.use(`/${assetPath}/node_modules/mark.js/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/mark.js/dist/')));
// Deprecated, https://www.npmjs.com/package/autocomplete.js?activeTab=readme // Deprecated, https://www.npmjs.com/package/autocomplete.js?activeTab=readme
app.use(`/${assetPath}/node_modules/autocomplete.js/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/autocomplete.js/dist/'))); app.use(`/${assetPath}/node_modules/autocomplete.js/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/autocomplete.js/dist/')));

View File

@ -10,7 +10,7 @@ import { Request, Response } from 'express';
function setupPage(req: Request, res: Response) { function setupPage(req: Request, res: Response) {
if (sqlInit.isDbInitialized()) { if (sqlInit.isDbInitialized()) {
if (utils.isElectron()) { if (utils.isElectron()) {
handleElectronRedirect(); handleElectronRedirect();
} else { } else {
res.redirect('.'); res.redirect('.');
} }

View File

@ -23,10 +23,10 @@ UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND na
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames}); UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered'; UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
UPDATE options SET value = 'anonymized' WHERE name IN UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey', ('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt', 'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy') 'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != ''; AND value != '';
VACUUM; VACUUM;
`; `;
@ -37,15 +37,15 @@ VACUUM;
function getLightAnonymizationScript() { function getLightAnonymizationScript() {
return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN ( return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN (
SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend') SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
UNION ALL UNION ALL
SELECT blobId FROM revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend') SELECT blobId FROM revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
); );
UPDATE options SET value = 'anonymized' WHERE name IN UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey', ('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt', 'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy') 'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != '';`; AND value != '';`;
} }
async function createAnonymizedCopy(type: "full" | "light") { async function createAnonymizedCopy(type: "full" | "light") {

View File

@ -14,4 +14,4 @@ export interface SetupStatusResponse {
export interface SetupSyncSeedResponse { export interface SetupSyncSeedResponse {
syncVersion: number; syncVersion: number;
options: OptionRow[] options: OptionRow[]
} }

View File

@ -48,7 +48,7 @@ function installLocalAppIcon() {
fs.writeFile(desktopFilePath, getDesktopFileContent(), function (err) { fs.writeFile(desktopFilePath, getDesktopFileContent(), function (err) {
if (err) { if (err) {
log.error("Desktop icon installation to ~/.local/share/applications failed."); log.error("Desktop icon installation to ~/.local/share/applications failed.");
} }
}); });
} }
@ -66,7 +66,7 @@ function escapePath(path: string) {
} }
function getExePath() { function getExePath() {
return path.resolve(resourceDir.ELECTRON_APP_ROOT_DIR, 'trilium'); return path.resolve(resourceDir.ELECTRON_APP_ROOT_DIR, 'trilium');
} }
export default { export default {

View File

@ -65,11 +65,11 @@ function getAttributeNames(type: string, nameLike: string) {
nameLike = nameLike.toLowerCase(); nameLike = nameLike.toLowerCase();
let names = sql.getColumn<string>( let names = sql.getColumn<string>(
`SELECT DISTINCT name `SELECT DISTINCT name
FROM attributes FROM attributes
WHERE isDeleted = 0 WHERE isDeleted = 0
AND type = ? AND type = ?
AND name LIKE ?`, [type, `%${nameLike}%`]); AND name LIKE ?`, [type, `%${nameLike}%`]);
for (const attr of BUILTIN_ATTRIBUTES) { for (const attr of BUILTIN_ATTRIBUTES) {
if (attr.type === type && attr.name.toLowerCase().includes(nameLike) && !names.includes(attr.name)) { if (attr.type === type && attr.name.toLowerCase().includes(nameLike) && !names.includes(attr.name)) {

View File

@ -61,53 +61,53 @@ interface NoteAndBranch {
interface Api { interface Api {
/** /**
* Note where the script started executing (entrypoint). * Note where the script started executing (entrypoint).
* As an analogy, in C this would be the file which contains the main() function of the current process. * As an analogy, in C this would be the file which contains the main() function of the current process.
*/ */
startNote?: BNote | null; startNote?: BNote | null;
/** /**
* Note where the script is currently executing. This comes into play when your script is spread in multiple code * Note where the script is currently executing. This comes into play when your script is spread in multiple code
* notes, the script starts in "startNote", but then through function calls may jump into another note (currentNote). * notes, the script starts in "startNote", but then through function calls may jump into another note (currentNote).
* A similar concept in C would be __FILE__ * A similar concept in C would be __FILE__
* Don't mix this up with the concept of active note. * Don't mix this up with the concept of active note.
*/ */
currentNote: BNote; currentNote: BNote;
/** /**
* Entity whose event triggered this execution * Entity whose event triggered this execution
*/ */
originEntity?: AbstractBeccaEntity<any> | null; originEntity?: AbstractBeccaEntity<any> | null;
/** /**
* Axios library for HTTP requests. See {@link https://axios-http.com} for documentation * Axios library for HTTP requests. See {@link https://axios-http.com} for documentation
* @deprecated use native (browser compatible) fetch() instead * @deprecated use native (browser compatible) fetch() instead
*/ */
axios: typeof axios; axios: typeof axios;
/** /**
* day.js library for date manipulation. See {@link https://day.js.org} for documentation * day.js library for date manipulation. See {@link https://day.js.org} for documentation
*/ */
dayjs: typeof dayjs; dayjs: typeof dayjs;
/** /**
* xml2js library for XML parsing. See {@link https://github.com/Leonidas-from-XIV/node-xml2js} for documentation * xml2js library for XML parsing. See {@link https://github.com/Leonidas-from-XIV/node-xml2js} for documentation
*/ */
xml2js: typeof xml2js; xml2js: typeof xml2js;
/** /**
* cheerio library for HTML parsing and manipulation. See {@link https://cheerio.js.org} for documentation * cheerio library for HTML parsing and manipulation. See {@link https://cheerio.js.org} for documentation
*/ */
cheerio: typeof cheerio; cheerio: typeof cheerio;
/** /**
* Instance name identifies particular Trilium instance. It can be useful for scripts * Instance name identifies particular Trilium instance. It can be useful for scripts
* if some action needs to happen on only one specific instance. * if some action needs to happen on only one specific instance.
*/ */
getInstanceName(): string | null; getInstanceName(): string | null;
getNote(noteId: string): BNote | null; getNote(noteId: string): BNote | null;
getBranch(branchId: string): BBranch | null; getBranch(branchId: string): BBranch | null;
getAttribute(attachmentId: string): BAttribute | null; getAttribute(attachmentId: string): BAttribute | null;
@ -118,78 +118,78 @@ interface Api {
getOption(optionName: string): BOption | null; getOption(optionName: string): BOption | null;
getOptions(): BOption[]; getOptions(): BOption[];
getAttribute(attributeId: string): BAttribute | null; getAttribute(attributeId: string): BAttribute | null;
/** /**
* This is a powerful search method - you can search by attributes and their values, e.g.: * This is a powerful search method - you can search by attributes and their values, e.g.:
* "#dateModified =* MONTH AND #log". See {@link https://triliumnext.github.io/Docs/Wiki/search.html} for full documentation for all options * "#dateModified =* MONTH AND #log". See {@link https://triliumnext.github.io/Docs/Wiki/search.html} for full documentation for all options
*/ */
searchForNotes(query: string, searchParams: SearchParams): BNote[]; searchForNotes(query: string, searchParams: SearchParams): BNote[];
/** /**
* This is a powerful search method - you can search by attributes and their values, e.g.: * This is a powerful search method - you can search by attributes and their values, e.g.:
* "#dateModified =* MONTH AND #log". See {@link https://triliumnext.github.io/Docs/Wiki/search.html} for full documentation for all options * "#dateModified =* MONTH AND #log". See {@link https://triliumnext.github.io/Docs/Wiki/search.html} for full documentation for all options
*/ */
searchForNote(query: string, searchParams: SearchParams): BNote | null; searchForNote(query: string, searchParams: SearchParams): BNote | null;
/** /**
* Retrieves notes with given label name & value * Retrieves notes with given label name & value
* *
* @param name - attribute name * @param name - attribute name
* @param value - attribute value * @param value - attribute value
*/ */
getNotesWithLabel(name: string, value?: string): BNote[]; getNotesWithLabel(name: string, value?: string): BNote[];
/** /**
* Retrieves first note with given label name & value * Retrieves first note with given label name & value
* *
* @param name - attribute name * @param name - attribute name
* @param value - attribute value * @param value - attribute value
*/ */
getNoteWithLabel(name: string, value?: string): BNote | null; getNoteWithLabel(name: string, value?: string): BNote | null;
/** /**
* If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch. * If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch.
* *
* @param prefix - if branch is created between note and parent note, set this prefix * @param prefix - if branch is created between note and parent note, set this prefix
*/ */
ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string): { ensureNoteIsPresentInParent(noteId: string, parentNoteId: string, prefix: string): {
branch: BBranch | null branch: BBranch | null
}; };
/** /**
* If there's a branch between note and parent note, remove it. Otherwise, do nothing. * If there's a branch between note and parent note, remove it. Otherwise, do nothing.
*/ */
ensureNoteIsAbsentFromParent(noteId: string, parentNoteId: string): void; ensureNoteIsAbsentFromParent(noteId: string, parentNoteId: string): void;
/** /**
* Based on the value, either create or remove branch between note and parent note. * Based on the value, either create or remove branch between note and parent note.
* *
* @param present - true if we want the branch to exist, false if we want it gone * @param present - true if we want the branch to exist, false if we want it gone
* @param prefix - if branch is created between note and parent note, set this prefix * @param prefix - if branch is created between note and parent note, set this prefix
*/ */
toggleNoteInParent(present: true, noteId: string, parentNoteId: string, prefix: string): void; toggleNoteInParent(present: true, noteId: string, parentNoteId: string, prefix: string): void;
/** /**
* Create text note. See also createNewNote() for more options. * Create text note. See also createNewNote() for more options.
*/ */
createTextNote(parentNoteId: string, title: string, content: string): NoteAndBranch; createTextNote(parentNoteId: string, title: string, content: string): NoteAndBranch;
/** /**
* Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and * Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
* JSON MIME type. See also createNewNote() for more options. * JSON MIME type. See also createNewNote() for more options.
*/ */
createDataNote(parentNoteId: string, title: string, content: {}): NoteAndBranch; createDataNote(parentNoteId: string, title: string, content: {}): NoteAndBranch;
/** /**
* @returns object contains newly created entities note and branch * @returns object contains newly created entities note and branch
*/ */
createNewNote(params: NoteParams): NoteAndBranch; createNewNote(params: NoteParams): NoteAndBranch;
/** /**
* @deprecated please use createTextNote() with similar API for simpler use cases or createNewNote() for more complex needs * @deprecated please use createTextNote() with similar API for simpler use cases or createNewNote() for more complex needs
* @param parentNoteId - create new note under this parent * @param parentNoteId - create new note under this parent
* @returns object contains newly created entities note and branch * @returns object contains newly created entities note and branch
*/ */
createNote(parentNoteId: string, title: string, content: string, extraOptions: Omit<NoteParams, "title" | "content" | "type" | "parentNoteId"> & { createNote(parentNoteId: string, title: string, content: string, extraOptions: Omit<NoteParams, "title" | "content" | "type" | "parentNoteId"> & {
/** should the note be JSON */ /** should the note be JSON */
json?: boolean; json?: boolean;
@ -200,37 +200,37 @@ interface Api {
logSpacedUpdates: Record<string, SpacedUpdate>; logSpacedUpdates: Record<string, SpacedUpdate>;
/** /**
* Log given message to trilium logs and log pane in UI * Log given message to trilium logs and log pane in UI
*/ */
log(message: string): void; log(message: string): void;
/** /**
* Returns root note of the calendar. * Returns root note of the calendar.
*/ */
getRootCalendarNote(): BNote | null; getRootCalendarNote(): BNote | null;
/** /**
* Returns day note for given date. If such note doesn't exist, it is created. * Returns day note for given date. If such note doesn't exist, it is created.
* *
* @method * @method
* @param date in YYYY-MM-DD format * @param date in YYYY-MM-DD format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar * @param rootNote - specify calendar root note, normally leave empty to use the default calendar
*/ */
getDayNote(date: string, rootNote?: BNote): BNote | null; getDayNote(date: string, rootNote?: BNote): BNote | null;
/** /**
* Returns today's day note. If such note doesn't exist, it is created. * 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 * @param rootNote specify calendar root note, normally leave empty to use the default calendar
*/ */
getTodayNote(rootNote?: BNote): BNote | null; getTodayNote(rootNote?: BNote): BNote | null;
/** /**
* Returns note for the first date of the week of the given date. * Returns note for the first date of the week of the given date.
* *
* @param date in YYYY-MM-DD format * @param date in YYYY-MM-DD format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar * @param rootNote - specify calendar root note, normally leave empty to use the default calendar
*/ */
getWeekNote(date: string, options: { getWeekNote(date: string, options: {
// TODO: Deduplicate type with date_notes.ts once ES modules are added. // TODO: Deduplicate type with date_notes.ts once ES modules are added.
/** either "monday" (default) or "sunday" */ /** either "monday" (default) or "sunday" */
@ -238,90 +238,90 @@ interface Api {
}, rootNote: BNote): BNote | null; }, rootNote: BNote): BNote | null;
/** /**
* Returns month note for given date. If such a note doesn't exist, it is created. * Returns month note for given date. If such a note doesn't exist, it is created.
* *
* @param date in YYYY-MM format * @param date in YYYY-MM format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar * @param rootNote - specify calendar root note, normally leave empty to use the default calendar
*/ */
getMonthNote(date: string, rootNote: BNote): BNote | null; getMonthNote(date: string, rootNote: BNote): BNote | null;
/** /**
* Returns year note for given year. If such a note doesn't exist, it is created. * Returns year note for given year. If such a note doesn't exist, it is created.
* *
* @param year in YYYY format * @param year in YYYY format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar * @param rootNote - specify calendar root note, normally leave empty to use the default calendar
*/ */
getYearNote(year: string, rootNote?: BNote): BNote | null; getYearNote(year: string, rootNote?: BNote): BNote | null;
/** /**
* Sort child notes of a given note. * Sort child notes of a given note.
*/ */
sortNotes(parentNoteId: string, sortConfig: { sortNotes(parentNoteId: string, sortConfig: {
/** 'title', 'dateCreated', 'dateModified' or a label name /** 'title', 'dateCreated', 'dateModified' or a label name
* See {@link https://triliumnext.github.io/Docs/Wiki/sorting.html} for details. */ * See {@link https://triliumnext.github.io/Docs/Wiki/sorting.html} for details. */
sortBy?: string; sortBy?: string;
reverse?: boolean; reverse?: boolean;
foldersFirst?: boolean; foldersFirst?: boolean;
}): void; }): void;
/** /**
* This method finds note by its noteId and prefix and either sets it to the given parentNoteId * This method finds note by its noteId and prefix and either sets it to the given parentNoteId
* or removes the branch (if parentNoteId is not given). * or removes the branch (if parentNoteId is not given).
* *
* This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix. * This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix.
* *
* @deprecated this method is pretty confusing and serves specialized purpose only * @deprecated this method is pretty confusing and serves specialized purpose only
*/ */
setNoteToParent(noteId: string, prefix: string, parentNoteId: string | null): void; setNoteToParent(noteId: string, prefix: string, parentNoteId: string | null): void;
/** /**
* This functions wraps code which is supposed to be running in transaction. If transaction already * This functions wraps code which is supposed to be running in transaction. If transaction already
* exists, then we'll use that transaction. * exists, then we'll use that transaction.
* *
* @param func * @param func
* @returns result of func callback * @returns result of func callback
*/ */
transactional(func: () => void): any; transactional(func: () => void): any;
/** /**
* Return randomly generated string of given length. This random string generation is NOT cryptographically secure. * Return randomly generated string of given length. This random string generation is NOT cryptographically secure.
* *
* @param length of the string * @param length of the string
* @returns random string * @returns random string
*/ */
randomString(length: number): string; randomString(length: number): string;
/** /**
* @param to escape * @param to escape
* @returns escaped string * @returns escaped string
*/ */
escapeHtml(string: string): string; escapeHtml(string: string): string;
/** /**
* @param string to unescape * @param string to unescape
* @returns unescaped string * @returns unescaped string
*/ */
unescapeHtml(string: string): string; unescapeHtml(string: string): string;
/** /**
* sql * sql
* @type {module:sql} * @type {module:sql}
*/ */
sql: any; sql: any;
getAppInfo(): typeof appInfo; getAppInfo(): typeof appInfo;
/** /**
* Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated. * Creates a new launcher to the launchbar. If the launcher (id) already exists, it will be updated.
*/ */
createOrUpdateLauncher(opts: { createOrUpdateLauncher(opts: {
/** id of the launcher, only alphanumeric at least 6 characters long */ /** id of the launcher, only alphanumeric at least 6 characters long */
id: string; id: string;
/** one of /** one of
* - "note" - activating the launcher will navigate to the target note (specified in targetNoteId param) * - "note" - activating the launcher will navigate to the target note (specified in targetNoteId param)
* - "script" - activating the launcher will execute the script (specified in scriptNoteId param) * - "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) * - "customWidget" - the launcher will be rendered with a custom widget (specified in widgetNoteId param)
*/ */
type: "note" | "script" | "customWidget"; type: "note" | "script" | "customWidget";
title: string; title: string;
/** if true, will be created in the "Visible launchers", otherwise in "Available launchers" */ /** if true, will be created in the "Visible launchers", otherwise in "Available launchers" */
@ -339,44 +339,44 @@ interface Api {
}): { note: BNote }; }): { note: BNote };
/** /**
* @param format - either 'html' or 'markdown' * @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). * Executes given anonymous function on the frontend(s).
* Internally, this serializes the anonymous function into string and sends it to frontend(s) via WebSocket. * Internally, this serializes the anonymous function into string and sends it to frontend(s) via WebSocket.
* Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all * Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all
* instances execute the given function. * instances execute the given function.
* *
* @param script - script to be executed on the frontend * @param script - script to be executed on the frontend
* @param params - list of parameters to the anonymous function to be sent to frontend * @param params - list of parameters to the anonymous function to be sent to frontend
* @returns no return value is provided. * @returns no return value is provided.
*/ */
runOnFrontend(script: () => void | string, params: []): void; runOnFrontend(script: () => void | string, params: []): void;
/** /**
* Sync process can make data intermittently inconsistent. Scripts which require strong data consistency * Sync process can make data intermittently inconsistent. Scripts which require strong data consistency
* can use this function to wait for a possible sync process to finish and prevent new sync process from starting * can use this function to wait for a possible sync process to finish and prevent new sync process from starting
* while it is running. * while it is running.
* *
* Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case * Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case
* you need to make some DB changes, you need to surround your call with api.transactional(...) * you need to make some DB changes, you need to surround your call with api.transactional(...)
* *
* @param callback - function to be executed while sync process is not running * @param callback - function to be executed while sync process is not running
* @returns resolves once the callback is finished (callback is awaited) * @returns resolves once the callback is finished (callback is awaited)
*/ */
runOutsideOfSync(callback: () => void): Promise<void>; runOutsideOfSync(callback: () => void): Promise<void>;
/** /**
* @param backupName - If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file * @param backupName - If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file
* @returns resolves once the backup is finished * @returns resolves once the backup is finished
*/ */
backupNow(backupName: string): Promise<string>; backupNow(backupName: string): Promise<string>;
/** /**
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases. * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
*/ */
__private: { __private: {
/** provides access to the backend in-memory object graph, see {@link Becca} */ /** provides access to the backend in-memory object graph, see {@link Becca} */
becca: Becca; becca: Becca;
@ -392,9 +392,9 @@ interface Api {
*/ */
function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) { function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.startNote = apiParams.startNote; this.startNote = apiParams.startNote;
this.currentNote = currentNote; this.currentNote = currentNote;
this.originEntity = apiParams.originEntity; this.originEntity = apiParams.originEntity;
for (const key in apiParams) { for (const key in apiParams) {
@ -416,7 +416,7 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.getOption = optionName => becca.getOption(optionName); this.getOption = optionName => becca.getOption(optionName);
this.getOptions = () => optionsService.getOptions(); this.getOptions = () => optionsService.getOptions();
this.getAttribute = attributeId => becca.getAttribute(attributeId); this.getAttribute = attributeId => becca.getAttribute(attributeId);
this.searchForNotes = (query, searchParams = {}) => { this.searchForNotes = (query, searchParams = {}) => {
if (searchParams.includeArchivedNotes === undefined) { if (searchParams.includeArchivedNotes === undefined) {
searchParams.includeArchivedNotes = true; searchParams.includeArchivedNotes = true;
@ -432,14 +432,14 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
return becca.getNotes(noteIds); return becca.getNotes(noteIds);
}; };
this.searchForNote = (query, searchParams = {}) => { this.searchForNote = (query, searchParams = {}) => {
const notes = this.searchForNotes(query, searchParams); const notes = this.searchForNotes(query, searchParams);
return notes.length > 0 ? notes[0] : null; return notes.length > 0 ? notes[0] : null;
}; };
this.getNotesWithLabel = attributeService.getNotesWithLabel; this.getNotesWithLabel = attributeService.getNotesWithLabel;
this.getNoteWithLabel = attributeService.getNoteWithLabel; this.getNoteWithLabel = attributeService.getNoteWithLabel;
this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent;
this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent; this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent;
@ -458,9 +458,9 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
type: 'code', type: 'code',
mime: 'application/json' mime: 'application/json'
}); });
this.createNewNote = noteService.createNewNote; this.createNewNote = noteService.createNewNote;
this.createNote = (parentNoteId, title, content = "", _extraOptions = {}) => { this.createNote = (parentNoteId, title, content = "", _extraOptions = {}) => {
const parentNote = becca.getNote(parentNoteId); const parentNote = becca.getNote(parentNoteId);
if (!parentNote) { if (!parentNote) {
@ -507,7 +507,7 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.logMessages = {}; this.logMessages = {};
this.logSpacedUpdates = {}; this.logSpacedUpdates = {};
this.log = message => { this.log = message => {
log.info(message); log.info(message);
@ -555,7 +555,7 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
this.sql = sql; this.sql = sql;
this.getAppInfo = () => appInfo; this.getAppInfo = () => appInfo;
this.createOrUpdateLauncher = opts => { this.createOrUpdateLauncher = opts => {
if (!opts.id) { throw new Error("ID is a mandatory parameter for api.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.id.match(/[a-z0-9]{6,1000}/i)) { throw new Error(`ID must be an alphanumeric string at least 6 characters long.`); }
@ -650,10 +650,10 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
}); });
} }
}; };
this.runOutsideOfSync = syncMutex.doExclusively; this.runOutsideOfSync = syncMutex.doExclusively;
this.backupNow = backupService.backupNow; this.backupNow = backupService.backupNow;
this.__private = { this.__private = {
becca becca
} }

View File

@ -8,4 +8,4 @@ export interface ApiParams {
pathParams?: string[], pathParams?: string[],
req?: Request, req?: Request,
res?: Response res?: Response
} }

View File

@ -2,4 +2,4 @@ export interface Blob {
blobId: string; blobId: string;
content: string | Buffer; content: string | Buffer;
utcDateModified: string; utcDateModified: string;
} }

View File

@ -1,6 +1,6 @@
/** /**
* @module * @module
* *
* Manages the server-side functionality of the code blocks feature, mostly for obtaining the available themes for syntax highlighting. * Manages the server-side functionality of the code blocks feature, mostly for obtaining the available themes for syntax highlighting.
*/ */
@ -23,11 +23,11 @@ interface ColorTheme {
/** /**
* Returns all the supported syntax highlighting themes for code blocks, in groups. * Returns all the supported syntax highlighting themes for code blocks, in groups.
* *
* The return value is an object where the keys represent groups in their human-readable name (e.g. "Light theme") * The return value is an object where the keys represent groups in their human-readable name (e.g. "Light theme")
* and the values are an array containing the information about every theme. There is also a special group with no * and the values are an array containing the information about every theme. There is also a special group with no
* title (empty string) which should be displayed at the top of the listing pages, without a group. * title (empty string) which should be displayed at the top of the listing pages, without a group.
* *
* @returns the supported themes, grouped. * @returns the supported themes, grouped.
*/ */
export function listSyntaxHighlightingThemes() { export function listSyntaxHighlightingThemes() {
@ -55,9 +55,9 @@ function getStylesDirectory() {
/** /**
* Reads all the predefined themes by listing all minified CSSes from a given directory. * Reads all the predefined themes by listing all minified CSSes from a given directory.
* *
* The theme names are mapped against a known list in order to provide more descriptive names such as "Visual Studio 2015 (Dark)" instead of "vs2015". * The theme names are mapped against a known list in order to provide more descriptive names such as "Visual Studio 2015 (Dark)" instead of "vs2015".
* *
* @param path the path to read from. Usually this is the highlight.js `styles` directory. * @param path the path to read from. Usually this is the highlight.js `styles` directory.
* @returns the list of themes. * @returns the list of themes.
*/ */
@ -65,13 +65,13 @@ function readThemesFromFileSystem(path: string): ColorTheme[] {
return fs.readdirSync(path) return fs.readdirSync(path)
.filter((el) => el.endsWith(".min.css")) .filter((el) => el.endsWith(".min.css"))
.map((name) => { .map((name) => {
const nameWithoutExtension = name.replace(".min.css", ""); const nameWithoutExtension = name.replace(".min.css", "");
let title = nameWithoutExtension.replace(/-/g, " "); let title = nameWithoutExtension.replace(/-/g, " ");
if (title in themeNames) { if (title in themeNames) {
title = (themeNames as Record<string, string>)[title]; title = (themeNames as Record<string, string>)[title];
} }
return { return {
val: `default:${nameWithoutExtension}`, val: `default:${nameWithoutExtension}`,
title: title title: title
@ -82,7 +82,7 @@ function readThemesFromFileSystem(path: string): ColorTheme[] {
/** /**
* Groups a list of themes by dark or light themes. This is done simply by checking whether "Dark" is present in the given theme, otherwise it's considered a light theme. * Groups a list of themes by dark or light themes. This is done simply by checking whether "Dark" is present in the given theme, otherwise it's considered a light theme.
* This generally only works if the theme has a known human-readable name (see {@link #readThemesFromFileSystem()}) * This generally only works if the theme has a known human-readable name (see {@link #readThemesFromFileSystem()})
* *
* @param listOfThemes the list of themes to be grouped. * @param listOfThemes the list of themes to be grouped.
* @returns the grouped themes by light or dark. * @returns the grouped themes by light or dark.
*/ */
@ -102,4 +102,4 @@ function groupThemesByLightOrDark(listOfThemes: ColorTheme[]) {
output[t("code_block.theme_group_light")] = lightThemes; output[t("code_block.theme_group_light")] = lightThemes;
output[t("code_block.theme_group_dark")] = darkThemes; output[t("code_block.theme_group_dark")] = darkThemes;
return output; return output;
} }

View File

@ -72,4 +72,4 @@
"vs2015": "Visual Studio 2015 (Dark)", "vs2015": "Visual Studio 2015 (Dark)",
"xcode": "Xcode (Light)", "xcode": "Xcode (Light)",
"xt256": "xt256 (Dark)" "xt256": "xt256 (Dark)"
} }

View File

@ -23,12 +23,12 @@ class ConsistencyChecks {
private autoFix: boolean; private autoFix: boolean;
private unrecoveredConsistencyErrors: boolean; private unrecoveredConsistencyErrors: boolean;
private fixedIssues: boolean; private fixedIssues: boolean;
private reloadNeeded: boolean; private reloadNeeded: boolean;
/** /**
* @param autoFix - automatically fix all encountered problems. False is only for debugging during development (fail fast) * @param autoFix - automatically fix all encountered problems. False is only for debugging during development (fail fast)
*/ */
constructor(autoFix: boolean) { constructor(autoFix: boolean) {
this.autoFix = autoFix; this.autoFix = autoFix;
this.unrecoveredConsistencyErrors = false; this.unrecoveredConsistencyErrors = false;
@ -138,9 +138,9 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT branchId, branches.noteId SELECT branchId, branches.noteId
FROM branches FROM branches
LEFT JOIN notes USING (noteId) LEFT JOIN notes USING (noteId)
WHERE branches.isDeleted = 0 WHERE branches.isDeleted = 0
AND notes.noteId IS NULL`, AND notes.noteId IS NULL`,
({branchId, noteId}) => { ({branchId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branch = becca.getBranch(branchId); const branch = becca.getBranch(branchId);
@ -160,10 +160,10 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT branchId, branches.parentNoteId AS parentNoteId SELECT branchId, branches.parentNoteId AS parentNoteId
FROM branches FROM branches
LEFT JOIN notes ON notes.noteId = branches.parentNoteId LEFT JOIN notes ON notes.noteId = branches.parentNoteId
WHERE branches.isDeleted = 0 WHERE branches.isDeleted = 0
AND branches.noteId != 'root' AND branches.noteId != 'root'
AND notes.noteId IS NULL`, AND notes.noteId IS NULL`,
({branchId, parentNoteId}) => { ({branchId, parentNoteId}) => {
if (this.autoFix) { if (this.autoFix) {
// Delete the old branch and recreate it with root as parent. // Delete the old branch and recreate it with root as parent.
@ -205,9 +205,9 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attributeId, attributes.noteId SELECT attributeId, attributes.noteId
FROM attributes FROM attributes
LEFT JOIN notes USING (noteId) LEFT JOIN notes USING (noteId)
WHERE attributes.isDeleted = 0 WHERE attributes.isDeleted = 0
AND notes.noteId IS NULL`, AND notes.noteId IS NULL`,
({attributeId, noteId}) => { ({attributeId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const attribute = becca.getAttribute(attributeId); const attribute = becca.getAttribute(attributeId);
@ -227,10 +227,10 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attributeId, attributes.value AS noteId SELECT attributeId, attributes.value AS noteId
FROM attributes FROM attributes
LEFT JOIN notes ON notes.noteId = attributes.value LEFT JOIN notes ON notes.noteId = attributes.value
WHERE attributes.isDeleted = 0 WHERE attributes.isDeleted = 0
AND attributes.type = 'relation' AND attributes.type = 'relation'
AND notes.noteId IS NULL`, AND notes.noteId IS NULL`,
({attributeId, noteId}) => { ({attributeId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const attribute = becca.getAttribute(attributeId); const attribute = becca.getAttribute(attributeId);
@ -255,7 +255,7 @@ class ConsistencyChecks {
UNION ALL UNION ALL
SELECT revisionId FROM revisions SELECT revisionId FROM revisions
) )
AND attachments.isDeleted = 0`, AND attachments.isDeleted = 0`,
({attachmentId, ownerId}) => { ({attachmentId, ownerId}) => {
if (this.autoFix) { if (this.autoFix) {
const attachment = becca.getAttachment(attachmentId); const attachment = becca.getAttachment(attachmentId);
@ -282,11 +282,11 @@ class ConsistencyChecks {
// another check might create missing branch // another check might create missing branch
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT branchId, SELECT branchId,
noteId noteId
FROM branches FROM branches
JOIN notes USING (noteId) JOIN notes USING (noteId)
WHERE notes.isDeleted = 1 WHERE notes.isDeleted = 1
AND branches.isDeleted = 0`, AND branches.isDeleted = 0`,
({branchId, noteId}) => { ({branchId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branch = becca.getBranch(branchId); const branch = becca.getBranch(branchId);
@ -303,11 +303,11 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT branchId, SELECT branchId,
parentNoteId parentNoteId
FROM branches FROM branches
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
WHERE parentNote.isDeleted = 1 WHERE parentNote.isDeleted = 1
AND branches.isDeleted = 0 AND branches.isDeleted = 0
`, ({branchId, parentNoteId}) => { `, ({branchId, parentNoteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branch = becca.getBranch(branchId); const branch = becca.getBranch(branchId);
@ -327,9 +327,9 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT DISTINCT notes.noteId SELECT DISTINCT notes.noteId
FROM notes FROM notes
LEFT JOIN branches ON notes.noteId = branches.noteId AND branches.isDeleted = 0 LEFT JOIN branches ON notes.noteId = branches.noteId AND branches.isDeleted = 0
WHERE notes.isDeleted = 0 WHERE notes.isDeleted = 0
AND branches.branchId IS NULL AND branches.branchId IS NULL
`, ({noteId}) => { `, ({noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branch = new BBranch({ const branch = new BBranch({
@ -349,21 +349,21 @@ class ConsistencyChecks {
// there should be a unique relationship between note and its parent // there should be a unique relationship between note and its parent
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT noteId, SELECT noteId,
parentNoteId parentNoteId
FROM branches FROM branches
WHERE branches.isDeleted = 0 WHERE branches.isDeleted = 0
GROUP BY branches.parentNoteId, GROUP BY branches.parentNoteId,
branches.noteId branches.noteId
HAVING COUNT(1) > 1`, HAVING COUNT(1) > 1`,
({noteId, parentNoteId}) => { ({noteId, parentNoteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branchIds = sql.getColumn<string>( const branchIds = sql.getColumn<string>(
`SELECT branchId `SELECT branchId
FROM branches FROM branches
WHERE noteId = ? WHERE noteId = ?
and parentNoteId = ? and parentNoteId = ?
and isDeleted = 0 and isDeleted = 0
ORDER BY utcDateModified`, [noteId, parentNoteId]); ORDER BY utcDateModified`, [noteId, parentNoteId]);
const branches = branchIds.map(branchId => becca.getBranch(branchId)); const branches = branchIds.map(branchId => becca.getBranch(branchId));
@ -393,11 +393,11 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attachmentId, SELECT attachmentId,
attachments.ownerId AS noteId attachments.ownerId AS noteId
FROM attachments FROM attachments
JOIN notes ON notes.noteId = attachments.ownerId JOIN notes ON notes.noteId = attachments.ownerId
WHERE notes.isDeleted = 1 WHERE notes.isDeleted = 1
AND attachments.isDeleted = 0`, AND attachments.isDeleted = 0`,
({attachmentId, noteId}) => { ({attachmentId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const attachment = becca.getAttachment(attachmentId); const attachment = becca.getAttachment(attachmentId);
@ -420,7 +420,7 @@ class ConsistencyChecks {
SELECT noteId, type SELECT noteId, type
FROM notes FROM notes
WHERE isDeleted = 0 WHERE isDeleted = 0
AND type NOT IN (${noteTypesStr})`, AND type NOT IN (${noteTypesStr})`,
({noteId, type}) => { ({noteId, type}) => {
if (this.autoFix) { if (this.autoFix) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
@ -439,7 +439,7 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT notes.noteId, notes.isProtected, notes.type, notes.mime SELECT notes.noteId, notes.isProtected, notes.type, notes.mime
FROM notes FROM notes
LEFT JOIN blobs USING (blobId) LEFT JOIN blobs USING (blobId)
WHERE blobs.blobId IS NULL WHERE blobs.blobId IS NULL
AND notes.isDeleted = 0`, AND notes.isDeleted = 0`,
({noteId, isProtected, type, mime}) => { ({noteId, isProtected, type, mime}) => {
@ -494,10 +494,10 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT notes.noteId, notes.type, notes.mime SELECT notes.noteId, notes.type, notes.mime
FROM notes FROM notes
JOIN blobs USING (blobId) JOIN blobs USING (blobId)
WHERE isDeleted = 0 WHERE isDeleted = 0
AND isProtected = 0 AND isProtected = 0
AND content IS NULL`, AND content IS NULL`,
({noteId, type, mime}) => { ({noteId, type, mime}) => {
if (this.autoFix) { if (this.autoFix) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
@ -520,7 +520,7 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT revisions.revisionId, blobs.blobId SELECT revisions.revisionId, blobs.blobId
FROM revisions FROM revisions
LEFT JOIN blobs USING (blobId) LEFT JOIN blobs USING (blobId)
WHERE blobs.blobId IS NULL`, WHERE blobs.blobId IS NULL`,
({revisionId, blobId}) => { ({revisionId, blobId}) => {
if (this.autoFix) { if (this.autoFix) {
@ -537,7 +537,7 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attachments.attachmentId, blobs.blobId SELECT attachments.attachmentId, blobs.blobId
FROM attachments FROM attachments
LEFT JOIN blobs USING (blobId) LEFT JOIN blobs USING (blobId)
WHERE blobs.blobId IS NULL`, WHERE blobs.blobId IS NULL`,
({attachmentId, blobId}) => { ({attachmentId, blobId}) => {
if (this.autoFix) { if (this.autoFix) {
@ -554,17 +554,17 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT parentNoteId SELECT parentNoteId
FROM branches FROM branches
JOIN notes ON notes.noteId = branches.parentNoteId JOIN notes ON notes.noteId = branches.parentNoteId
WHERE notes.isDeleted = 0 WHERE notes.isDeleted = 0
AND notes.type == 'search' AND notes.type == 'search'
AND branches.isDeleted = 0`, AND branches.isDeleted = 0`,
({parentNoteId}) => { ({parentNoteId}) => {
if (this.autoFix) { if (this.autoFix) {
const branchIds = sql.getColumn<string>(` const branchIds = sql.getColumn<string>(`
SELECT branchId SELECT branchId
FROM branches FROM branches
WHERE isDeleted = 0 WHERE isDeleted = 0
AND parentNoteId = ?`, [parentNoteId]); AND parentNoteId = ?`, [parentNoteId]);
const branches = branchIds.map(branchId => becca.getBranch(branchId)); const branches = branchIds.map(branchId => becca.getBranch(branchId));
@ -594,8 +594,8 @@ class ConsistencyChecks {
SELECT attributeId SELECT attributeId
FROM attributes FROM attributes
WHERE isDeleted = 0 WHERE isDeleted = 0
AND type = 'relation' AND type = 'relation'
AND value = ''`, AND value = ''`,
({attributeId}) => { ({attributeId}) => {
if (this.autoFix) { if (this.autoFix) {
const relation = becca.getAttribute(attributeId); const relation = becca.getAttribute(attributeId);
@ -612,11 +612,11 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attributeId, SELECT attributeId,
type type
FROM attributes FROM attributes
WHERE isDeleted = 0 WHERE isDeleted = 0
AND type != 'label' AND type != 'label'
AND type != 'relation'`, AND type != 'relation'`,
({attributeId, type}) => { ({attributeId, type}) => {
if (this.autoFix) { if (this.autoFix) {
const attribute = becca.getAttribute(attributeId); const attribute = becca.getAttribute(attributeId);
@ -634,11 +634,11 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attributeId, SELECT attributeId,
attributes.noteId attributes.noteId
FROM attributes FROM attributes
JOIN notes ON attributes.noteId = notes.noteId JOIN notes ON attributes.noteId = notes.noteId
WHERE attributes.isDeleted = 0 WHERE attributes.isDeleted = 0
AND notes.isDeleted = 1`, AND notes.isDeleted = 1`,
({attributeId, noteId}) => { ({attributeId, noteId}) => {
if (this.autoFix) { if (this.autoFix) {
const attribute = becca.getAttribute(attributeId); const attribute = becca.getAttribute(attributeId);
@ -655,12 +655,12 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT attributeId, SELECT attributeId,
attributes.value AS targetNoteId attributes.value AS targetNoteId
FROM attributes FROM attributes
JOIN notes ON attributes.value = notes.noteId JOIN notes ON attributes.value = notes.noteId
WHERE attributes.type = 'relation' WHERE attributes.type = 'relation'
AND attributes.isDeleted = 0 AND attributes.isDeleted = 0
AND notes.isDeleted = 1`, AND notes.isDeleted = 1`,
({attributeId, targetNoteId}) => { ({attributeId, targetNoteId}) => {
if (this.autoFix) { if (this.autoFix) {
const attribute = becca.getAttribute(attributeId); const attribute = becca.getAttribute(attributeId);
@ -680,7 +680,7 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT ${key} as entityId SELECT ${key} as entityId
FROM ${entityName} FROM ${entityName}
LEFT JOIN entity_changes ec ON ec.entityName = '${entityName}' AND ec.entityId = ${entityName}.${key} LEFT JOIN entity_changes ec ON ec.entityName = '${entityName}' AND ec.entityId = ${entityName}.${key}
WHERE ec.id IS NULL`, WHERE ec.id IS NULL`,
({entityId}) => { ({entityId}) => {
const entityRow = sql.getRow<EntityChange>(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]); const entityRow = sql.getRow<EntityChange>(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
@ -703,12 +703,12 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT id, entityId SELECT id, entityId
FROM entity_changes FROM entity_changes
LEFT JOIN ${entityName} ON entityId = ${entityName}.${key} LEFT JOIN ${entityName} ON entityId = ${entityName}.${key}
WHERE WHERE
entity_changes.isErased = 0 entity_changes.isErased = 0
AND entity_changes.entityName = '${entityName}' AND entity_changes.entityName = '${entityName}'
AND ${entityName}.${key} IS NULL`, AND ${entityName}.${key} IS NULL`,
({id, entityId}) => { ({id, entityId}) => {
if (this.autoFix) { if (this.autoFix) {
sql.execute("DELETE FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]); sql.execute("DELETE FROM entity_changes WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
@ -721,11 +721,11 @@ class ConsistencyChecks {
this.findAndFixIssues(` this.findAndFixIssues(`
SELECT id, entityId SELECT id, entityId
FROM entity_changes FROM entity_changes
JOIN ${entityName} ON entityId = ${entityName}.${key} JOIN ${entityName} ON entityId = ${entityName}.${key}
WHERE WHERE
entity_changes.isErased = 1 entity_changes.isErased = 1
AND entity_changes.entityName = '${entityName}'`, AND entity_changes.entityName = '${entityName}'`,
({id, entityId}) => { ({id, entityId}) => {
if (this.autoFix) { if (this.autoFix) {
sql.execute(`DELETE FROM ${entityName} WHERE ${key} = ?`, [entityId]); sql.execute(`DELETE FROM ${entityName} WHERE ${key} = ?`, [entityId]);

View File

@ -18,12 +18,12 @@ function getEntityHashes() {
const hashRows = sql.disableSlowQueryLogging( const hashRows = sql.disableSlowQueryLogging(
() => sql.getRawRows<HashRow>(` () => sql.getRawRows<HashRow>(`
SELECT entityName, SELECT entityName,
entityId, entityId,
hash, hash,
isErased isErased
FROM entity_changes FROM entity_changes
WHERE isSynced = 1 WHERE isSynced = 1
AND entityName != 'note_reordering'`) AND entityName != 'note_reordering'`)
); );
// sorting is faster in memory // sorting is faster in memory

View File

@ -29,7 +29,7 @@ function changePassword(currentPassword: string, newPassword: string) {
optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32)); optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword)); const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword));
if (decryptedDataKey) { if (decryptedDataKey) {
// TODO: what should happen if the decrypted data key is null? // TODO: what should happen if the decrypted data key is null?
passwordEncryptionService.setDataKey(newPassword, decryptedDataKey); passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);

View File

@ -39,9 +39,9 @@ function putEntityChange(origEntityChange: EntityChange) {
ec.isErased = ec.isErased ? 1 : 0; ec.isErased = ec.isErased ? 1 : 0;
ec.id = sql.replace("entity_changes", ec); ec.id = sql.replace("entity_changes", ec);
if (ec.id) { if (ec.id) {
maxEntityChangeId = Math.max(maxEntityChangeId, ec.id); maxEntityChangeId = Math.max(maxEntityChangeId, ec.id);
} }
cls.putEntityChange(ec); cls.putEntityChange(ec);
} }
@ -95,7 +95,7 @@ function addEntityChangesForSector(entityName: string, sector: string) {
function addEntityChangesForDependingEntity(sector: string, tableName: string, primaryKeyColumn: string) { function addEntityChangesForDependingEntity(sector: string, tableName: string, primaryKeyColumn: string) {
// problem in blobs might be caused by problem in entity referencing the blob // problem in blobs might be caused by problem in entity referencing the blob
const dependingEntityChanges = sql.getRows<EntityChange>(` const dependingEntityChanges = sql.getRows<EntityChange>(`
SELECT dep_change.* SELECT dep_change.*
FROM entity_changes orig_sector FROM entity_changes orig_sector
JOIN ${tableName} ON ${tableName}.blobId = orig_sector.entityId JOIN ${tableName} ON ${tableName}.blobId = orig_sector.entityId
JOIN entity_changes dep_change ON dep_change.entityName = '${tableName}' AND dep_change.entityId = ${tableName}.${primaryKeyColumn} JOIN entity_changes dep_change ON dep_change.entityName = '${tableName}' AND dep_change.entityId = ${tableName}.${primaryKeyColumn}
@ -110,11 +110,11 @@ function addEntityChangesForDependingEntity(sector: string, tableName: string, p
function cleanupEntityChangesForMissingEntities(entityName: string, entityPrimaryKey: string) { function cleanupEntityChangesForMissingEntities(entityName: string, entityPrimaryKey: string) {
sql.execute(` sql.execute(`
DELETE DELETE
FROM entity_changes FROM entity_changes
WHERE WHERE
isErased = 0 isErased = 0
AND entityName = '${entityName}' AND entityName = '${entityName}'
AND entityId NOT IN (SELECT ${entityPrimaryKey} FROM ${entityName})`); AND entityId NOT IN (SELECT ${entityPrimaryKey} FROM ${entityName})`);
} }

View File

@ -1,27 +1,27 @@
export interface EntityChange { export interface EntityChange {
id?: number | null; id?: number | null;
noteId?: string; noteId?: string;
entityName: string; entityName: string;
entityId: string; entityId: string;
entity?: any; entity?: any;
positions?: Record<string, number>; positions?: Record<string, number>;
hash: string; hash: string;
utcDateChanged?: string; utcDateChanged?: string;
utcDateModified?: string; utcDateModified?: string;
utcDateCreated?: string; utcDateCreated?: string;
isSynced: boolean | 1 | 0; isSynced: boolean | 1 | 0;
isErased: boolean | 1 | 0; isErased: boolean | 1 | 0;
componentId?: string | null; componentId?: string | null;
changeId?: string | null; changeId?: string | null;
instanceId?: string | null; instanceId?: string | null;
} }
export interface EntityRow { export interface EntityRow {
isDeleted?: boolean; isDeleted?: boolean;
content?: Buffer | string; content?: Buffer | string;
} }
export interface EntityChangeRecord { export interface EntityChangeRecord {
entityChange: EntityChange; entityChange: EntityChange;
entity?: EntityRow; entity?: EntityRow;
} }

View File

@ -4,4 +4,4 @@ function isDev() {
export default { export default {
isDev isDev
}; };

View File

@ -100,9 +100,9 @@ function eraseUnusedBlobs() {
LEFT JOIN notes ON notes.blobId = blobs.blobId LEFT JOIN notes ON notes.blobId = blobs.blobId
LEFT JOIN attachments ON attachments.blobId = blobs.blobId LEFT JOIN attachments ON attachments.blobId = blobs.blobId
LEFT JOIN revisions ON revisions.blobId = blobs.blobId LEFT JOIN revisions ON revisions.blobId = blobs.blobId
WHERE notes.noteId IS NULL WHERE notes.noteId IS NULL
AND attachments.attachmentId IS NULL AND attachments.attachmentId IS NULL
AND revisions.revisionId IS NULL`); AND revisions.revisionId IS NULL`);
if (unusedBlobIds.length === 0) { if (unusedBlobIds.length === 0) {
return; return;
@ -181,7 +181,7 @@ export function startScheduledCleanup() {
// first cleanup kickoff 5 minutes after startup // first cleanup kickoff 5 minutes after startup
setTimeout(cls.wrap(() => eraseDeletedEntities()), 5 * 60 * 1000); setTimeout(cls.wrap(() => eraseDeletedEntities()), 5 * 60 * 1000);
setTimeout(cls.wrap(() => eraseScheduledAttachments()), 6 * 60 * 1000); setTimeout(cls.wrap(() => eraseScheduledAttachments()), 6 * 60 * 1000);
setInterval(cls.wrap(() => eraseDeletedEntities()), 4 * 3600 * 1000); setInterval(cls.wrap(() => eraseDeletedEntities()), 4 * 3600 * 1000);
setInterval(cls.wrap(() => eraseScheduledAttachments()), 3600 * 1000); setInterval(cls.wrap(() => eraseScheduledAttachments()), 3600 * 1000);
}); });

View File

@ -8,25 +8,25 @@ let instance: TurndownService | null = null;
const fencedCodeBlockFilter: TurndownService.Rule = { const fencedCodeBlockFilter: TurndownService.Rule = {
filter: function (node, options) { filter: function (node, options) {
return ( return (
options.codeBlockStyle === 'fenced' && options.codeBlockStyle === 'fenced' &&
node.nodeName === 'PRE' && node.nodeName === 'PRE' &&
node.firstChild !== null && node.firstChild !== null &&
node.firstChild.nodeName === 'CODE' node.firstChild.nodeName === 'CODE'
) )
}, },
replacement: function (content, node, options) { replacement: function (content, node, options) {
if (!node.firstChild || !("getAttribute" in node.firstChild) || typeof node.firstChild.getAttribute !== "function") { if (!node.firstChild || !("getAttribute" in node.firstChild) || typeof node.firstChild.getAttribute !== "function") {
return content; return content;
} }
const className = node.firstChild.getAttribute('class') || '' const className = node.firstChild.getAttribute('class') || ''
const language = rewriteLanguageTag((className.match(/language-(\S+)/) || [null, ''])[1]); const language = rewriteLanguageTag((className.match(/language-(\S+)/) || [null, ''])[1]);
return ( return (
'\n\n' + options.fence + language + '\n' + '\n\n' + options.fence + language + '\n' +
node.firstChild.textContent + node.firstChild.textContent +
'\n' + options.fence + '\n\n' '\n' + options.fence + '\n\n'
) )
} }
}; };
@ -34,7 +34,7 @@ const fencedCodeBlockFilter: TurndownService.Rule = {
function toMarkdown(content: string) { function toMarkdown(content: string) {
if (instance === null) { if (instance === null) {
instance = new TurndownService({ codeBlockStyle: 'fenced' }); instance = new TurndownService({ codeBlockStyle: 'fenced' });
// Filter is heavily based on: https://github.com/mixmark-io/turndown/issues/274#issuecomment-458730974 // Filter is heavily based on: https://github.com/mixmark-io/turndown/issues/274#issuecomment-458730974
instance.addRule('fencedCodeBlock', fencedCodeBlockFilter); instance.addRule('fencedCodeBlock', fencedCodeBlockFilter);
instance.use(turndownPluginGfm.gfm); instance.use(turndownPluginGfm.gfm);
} }
@ -44,7 +44,7 @@ function toMarkdown(content: string) {
function rewriteLanguageTag(source: string) { function rewriteLanguageTag(source: string) {
if (!source) { if (!source) {
return source; return source;
} }
if (source === "text-x-trilium-auto") { if (source === "text-x-trilium-auto") {

View File

@ -63,7 +63,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
if (fileName.length > 30) { if (fileName.length > 30) {
// We use regex to match the extension to preserve multiple dots in extensions (e.g. .tar.gz). // We use regex to match the extension to preserve multiple dots in extensions (e.g. .tar.gz).
let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/); let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/);
let ext = match ? match[0] : ''; let ext = match ? match[0] : '';
// Crop the extension if extension length exceeds 30 // Crop the extension if extension length exceeds 30
const croppedExt = ext.slice(-30); const croppedExt = ext.slice(-30);
// Crop the file name section and append the cropped extension // Crop the file name section and append the cropped extension
@ -324,9 +324,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<h1 data-trilium-h1>${htmlTitle}</h1> <h1 data-trilium-h1>${htmlTitle}</h1>
<div class="ck-content">${content}</div> <div class="ck-content">${content}</div>
</div> </div>
</body> </body>
</html>`; </html>`;

View File

@ -281,7 +281,7 @@ interface CheckHiddenExtraOpts {
restoreNames?: boolean; restoreNames?: boolean;
} }
function checkHiddenSubtree(force = false, extraOpts: CheckHiddenExtraOpts = {}) { function checkHiddenSubtree(force = false, extraOpts: CheckHiddenExtraOpts = {}) {
if (!force && !migrationService.isDbUpToDate()) { if (!force && !migrationService.isDbUpToDate()) {
// on-delete hook might get triggered during some future migration and cause havoc // on-delete hook might get triggered during some future migration and cause havoc
log.info("Will not check hidden subtree until migration is finished."); log.info("Will not check hidden subtree until migration is finished.");

View File

@ -6,8 +6,8 @@ function getHost() {
if (envHost && !utils.isElectron) { if (envHost && !utils.isElectron) {
return envHost; return envHost;
} }
return config['Network']['host'] || '0.0.0.0'; return config['Network']['host'] || '0.0.0.0';
} }
export default getHost(); export default getHost();

View File

@ -11,19 +11,19 @@ export async function initializeTranslations() {
// Initialize translations // Initialize translations
await i18next.use(Backend).init({ await i18next.use(Backend).init({
lng: getCurrentLanguage(), lng: getCurrentLanguage(),
fallbackLng: "en", fallbackLng: "en",
ns: "server", ns: "server",
backend: { backend: {
loadPath: join(resourceDir, "translations/{{lng}}/{{ns}}.json") loadPath: join(resourceDir, "translations/{{lng}}/{{ns}}.json")
} }
}); });
} }
function getCurrentLanguage() { function getCurrentLanguage() {
let language; let language;
if (sql_init.isDbInitialized()) { if (sql_init.isDbInitialized()) {
language = options.getOptionOrNull("locale"); language = options.getOptionOrNull("locale");
} }
if (!language) { if (!language) {

View File

@ -208,7 +208,7 @@ async function resize(buffer: Buffer, quality: number) {
const start = Date.now(); const start = Date.now();
const image = await Jimp.read(buffer); const image = await Jimp.read(buffer);
if (image.bitmap.width > image.bitmap.height && image.bitmap.width > imageMaxWidthHeight) { if (image.bitmap.width > image.bitmap.height && image.bitmap.width > imageMaxWidthHeight) {
image.resize({ w: imageMaxWidthHeight }); image.resize({ w: imageMaxWidthHeight });

View File

@ -2,4 +2,4 @@ export interface File {
originalname: string; originalname: string;
mimetype: string; mimetype: string;
buffer: string | Buffer; buffer: string | Buffer;
} }

View File

@ -240,8 +240,8 @@ function importEnex(taskContext: TaskContext, file: File, parentNote: BNote): Pr
function updateDates(note: BNote, utcDateCreated?: string, utcDateModified?: string) { function updateDates(note: BNote, utcDateCreated?: string, utcDateModified?: string) {
// it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL // it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL
sql.execute(` sql.execute(`
UPDATE notes UPDATE notes
SET dateCreated = ?, SET dateCreated = ?,
utcDateCreated = ?, utcDateCreated = ?,
dateModified = ?, dateModified = ?,
utcDateModified = ? utcDateModified = ?
@ -314,7 +314,7 @@ function importEnex(taskContext: TaskContext, file: File, parentNote: BNote): Pr
if (typeof resource.content !== "string") { if (typeof resource.content !== "string") {
throw new Error("Missing or wrong content type for resource."); throw new Error("Missing or wrong content type for resource.");
} }
const resourceNote = noteService.createNewNote({ const resourceNote = noteService.createNewNote({
parentNoteId: noteEntity.noteId, parentNoteId: noteEntity.noteId,
title: resource.title, title: resource.title,

View File

@ -114,4 +114,4 @@ export default {
getMime, getMime,
getType, getType,
normalizeMimeType normalizeMimeType
}; };

View File

@ -160,9 +160,9 @@ function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) {
if (taskContext?.data?.safeImport) { if (taskContext?.data?.safeImport) {
content = htmlSanitizer.sanitize(content); content = htmlSanitizer.sanitize(content);
} }
const {note} = noteService.createNewNote({ const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId, parentNoteId: parentNote.noteId,
title, title,
@ -171,9 +171,9 @@ function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) {
mime: 'text/html', mime: 'text/html',
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
}); });
taskContext.increaseProgressCount(); taskContext.increaseProgressCount();
return note; return note;
} }

View File

@ -13,7 +13,7 @@ function getDefaultKeyboardActions() {
if (!t("keyboard_actions.note-navigation")) { if (!t("keyboard_actions.note-navigation")) {
throw new Error("Keyboard actions loaded before translations."); throw new Error("Keyboard actions loaded before translations.");
} }
const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [
{ {
separator: t("keyboard_actions.note-navigation") separator: t("keyboard_actions.note-navigation")
@ -76,8 +76,8 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.sort-child-notes"), description: t("keyboard_actions.sort-child-notes"),
scope: "note-tree" scope: "note-tree"
}, },
{ {
separator: t("keyboard_actions.creating-and-moving-notes") separator: t("keyboard_actions.creating-and-moving-notes")
}, },
@ -149,11 +149,11 @@ function getDefaultKeyboardActions() {
defaultShortcuts: ["CommandOrControl+Shift+X"], defaultShortcuts: ["CommandOrControl+Shift+X"],
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.note-clipboard") separator: t("keyboard_actions.note-clipboard")
}, },
{ {
actionName: "copyNotesToClipboard", actionName: "copyNotesToClipboard",
defaultShortcuts: ["CommandOrControl+C"], defaultShortcuts: ["CommandOrControl+C"],
@ -196,8 +196,8 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.duplicate-subtree"), description: t("keyboard_actions.duplicate-subtree"),
scope: "note-tree" scope: "note-tree"
}, },
{ {
separator: t("keyboard_actions.tabs-and-windows") separator: t("keyboard_actions.tabs-and-windows")
}, },
@ -303,8 +303,8 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.last-tab"), description: t("keyboard_actions.last-tab"),
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.dialogs") separator: t("keyboard_actions.dialogs")
}, },
@ -350,12 +350,12 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.show-help"), description: t("keyboard_actions.show-help"),
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.text-note-operations") separator: t("keyboard_actions.text-note-operations")
}, },
{ {
actionName: "addLinkToText", actionName: "addLinkToText",
defaultShortcuts: ["CommandOrControl+L"], defaultShortcuts: ["CommandOrControl+L"],
@ -398,11 +398,11 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.edit-readonly-note"), description: t("keyboard_actions.edit-readonly-note"),
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.attributes-labels-and-relations") separator: t("keyboard_actions.attributes-labels-and-relations")
}, },
{ {
actionName: "addNewLabel", actionName: "addNewLabel",
defaultShortcuts: ["Alt+L"], defaultShortcuts: ["Alt+L"],
@ -415,11 +415,11 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.create-new-relation"), description: t("keyboard_actions.create-new-relation"),
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.ribbon-tabs") separator: t("keyboard_actions.ribbon-tabs")
}, },
{ {
actionName: "toggleRibbonTabClassicEditor", actionName: "toggleRibbonTabClassicEditor",
defaultShortcuts: [], defaultShortcuts: [],
@ -492,11 +492,11 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.toggle-similar-notes"), description: t("keyboard_actions.toggle-similar-notes"),
scope: "window" scope: "window"
}, },
{ {
separator: t("keyboard_actions.other") separator: t("keyboard_actions.other")
}, },
{ {
actionName: "toggleRightPane", actionName: "toggleRightPane",
defaultShortcuts: [], defaultShortcuts: [],
@ -601,10 +601,10 @@ function getDefaultKeyboardActions() {
]; ];
/* /*
* Apply macOS-specific tweaks. * Apply macOS-specific tweaks.
*/ */
const platformModifier = isMac ? 'Meta' : 'Ctrl'; const platformModifier = isMac ? 'Meta' : 'Ctrl';
for (const action of DEFAULT_KEYBOARD_ACTIONS) { for (const action of DEFAULT_KEYBOARD_ACTIONS) {
if (action.defaultShortcuts) { if (action.defaultShortcuts) {
action.defaultShortcuts = action.defaultShortcuts.map(shortcut => shortcut.replace("CommandOrControl", platformModifier)); action.defaultShortcuts = action.defaultShortcuts.map(shortcut => shortcut.replace("CommandOrControl", platformModifier));

View File

@ -5,16 +5,16 @@ export interface KeyboardShortcut {
defaultShortcuts?: string[]; defaultShortcuts?: string[];
effectiveShortcuts?: string[]; effectiveShortcuts?: 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.
* *
* So e.g. shortcuts with "note-tree" scope work only when the focus is in note tree. * So e.g. shortcuts with "note-tree" scope work only when the focus is in note tree.
* This allows to have the same shortcut have different actions attached based on the context * This allows to have the same shortcut have different actions attached based on the context
* e.g. CTRL-C in note tree does something a bit different from CTRL-C in the text editor. * e.g. CTRL-C in note tree does something a bit different from CTRL-C in the text editor.
*/ */
scope?: "window" | "note-tree" | "text-detail" | "code-detail"; scope?: "window" | "note-tree" | "text-detail" | "code-detail";
} }
export interface KeyboardShortcutWithRequiredActionName extends KeyboardShortcut { export interface KeyboardShortcutWithRequiredActionName extends KeyboardShortcut {
actionName: string; actionName: string;
} }

View File

@ -75,8 +75,8 @@ async function migrate() {
await executeMigration(mig); await executeMigration(mig);
sql.execute(`UPDATE options sql.execute(`UPDATE options
SET value = ? SET value = ?
WHERE name = ?`, [mig.dbVersion.toString(), "dbVersion"]); WHERE name = ?`, [mig.dbVersion.toString(), "dbVersion"]);
log.info(`Migration to version ${mig.dbVersion} has been successful.`); log.info(`Migration to version ${mig.dbVersion} has been successful.`);
} catch (e: any) { } catch (e: any) {

View File

@ -24,4 +24,4 @@ export interface NoteParams {
utcDateCreated?: string; utcDateCreated?: string;
ignoreForbiddenParents?: boolean; ignoreForbiddenParents?: boolean;
target?: "into"; target?: "into";
} }

View File

@ -490,7 +490,7 @@ async function downloadImage(noteId: string, imageUrl: string) {
const title = path.basename(parsedUrl.pathname || ""); const title = path.basename(parsedUrl.pathname || "");
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true); const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
if (attachment.attachmentId) { if (attachment.attachmentId) {
imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId; imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId;
} else { } else {
@ -810,11 +810,11 @@ function undeleteBranch(branchId: string, deleteId: string, taskContext: TaskCon
noteEntity.save(); noteEntity.save();
const attributeRows = sql.getRows<AttributeRow>(` const attributeRows = sql.getRows<AttributeRow>(`
SELECT * FROM attributes SELECT * FROM attributes
WHERE isDeleted = 1 WHERE isDeleted = 1
AND deleteId = ? AND deleteId = ?
AND (noteId = ? AND (noteId = ?
OR (type = 'relation' AND value = ?))`, [deleteId, noteRow.noteId, noteRow.noteId]); OR (type = 'relation' AND value = ?))`, [deleteId, noteRow.noteId, noteRow.noteId]);
for (const attributeRow of attributeRows) { for (const attributeRow of attributeRows) {
// relation might point to a note which hasn't been undeleted yet and would thus throw up // relation might point to a note which hasn't been undeleted yet and would thus throw up
@ -824,8 +824,8 @@ function undeleteBranch(branchId: string, deleteId: string, taskContext: TaskCon
const attachmentRows = sql.getRows<AttachmentRow>(` const attachmentRows = sql.getRows<AttachmentRow>(`
SELECT * FROM attachments SELECT * FROM attachments
WHERE isDeleted = 1 WHERE isDeleted = 1
AND deleteId = ? AND deleteId = ?
AND ownerId = ?`, [deleteId, noteRow.noteId]); AND ownerId = ?`, [deleteId, noteRow.noteId]);
for (const attachmentRow of attachmentRows) { for (const attachmentRow of attachmentRows) {
new BAttachment(attachmentRow).save(); new BAttachment(attachmentRow).save();
@ -835,8 +835,8 @@ function undeleteBranch(branchId: string, deleteId: string, taskContext: TaskCon
SELECT branches.branchId SELECT branches.branchId
FROM branches FROM branches
WHERE branches.isDeleted = 1 WHERE branches.isDeleted = 1
AND branches.deleteId = ? AND branches.deleteId = ?
AND branches.parentNoteId = ?`, [deleteId, noteRow.noteId]); AND branches.parentNoteId = ?`, [deleteId, noteRow.noteId]);
for (const childBranchId of childBranchIds) { for (const childBranchId of childBranchIds) {
undeleteBranch(childBranchId, deleteId, taskContext); undeleteBranch(childBranchId, deleteId, taskContext);
@ -853,9 +853,9 @@ function getUndeletedParentBranchIds(noteId: string, deleteId: string) {
FROM branches FROM branches
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
WHERE branches.noteId = ? WHERE branches.noteId = ?
AND branches.isDeleted = 1 AND branches.isDeleted = 1
AND branches.deleteId = ? AND branches.deleteId = ?
AND parentNote.isDeleted = 0`, [noteId, deleteId]); AND parentNote.isDeleted = 0`, [noteId, deleteId]);
} }
function scanForLinks(note: BNote, content: string | Buffer) { function scanForLinks(note: BNote, content: string | Buffer) {
@ -941,7 +941,7 @@ function duplicateSubtreeWithoutRoot(origNoteId: string, newNoteId: string) {
if (origNote == null) { if (origNote == null) {
throw new Error("Unable to find note to duplicate."); throw new Error("Unable to find note to duplicate.");
} }
const noteIdMapping = getNoteIdMapping(origNote); const noteIdMapping = getNoteIdMapping(origNote);
for (const childBranch of origNote.getChildBranches()) { for (const childBranch of origNote.getChildBranches()) {
if (childBranch) { if (childBranch) {

View File

@ -1,12 +1,12 @@
/** /**
* @module * @module
* *
* Options are key-value pairs that are used to store information such as user preferences (for example * Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application. * the current theme, sync server information), but also information about the state of the application.
* *
* Although options internally are represented as strings, their value can be interpreted as a number or * Although options internally are represented as strings, their value can be interpreted as a number or
* boolean by calling the appropriate methods from this service (e.g. {@link #getOptionInt}).\ * boolean by calling the appropriate methods from this service (e.g. {@link #getOptionInt}).\
* *
* Generally options are shared across multiple instances of the application via the sync mechanism, * Generally options are shared across multiple instances of the application via the sync mechanism,
* however it is possible to have options that are local to an instance. For example, the user can select * however it is possible to have options that are local to an instance. For example, the user can select
* a theme on a device and it will not affect other devices. * a theme on a device and it will not affect other devices.
@ -90,7 +90,7 @@ function setOption(name: string, value: string | number | boolean) {
/** /**
* Creates a new option in the database, with the given name, value and whether it should be synced. * Creates a new option in the database, with the given name, value and whether it should be synced.
* *
* @param name the name of the option to be created. * @param name the name of the option to be created.
* @param value the value of the option, as a string. It can then be interpreted as other types such as a number of boolean. * @param value the value of the option, as a string. It can then be interpreted as other types such as a number of boolean.
* @param isSynced `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme). * @param isSynced `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme).

View File

@ -26,17 +26,17 @@ interface NotSyncedOpts {
interface DefaultOption { interface DefaultOption {
name: string; name: string;
/** /**
* The value to initialize the option with, if the option is not already present in the database. * The value to initialize the option with, if the option is not already present in the database.
* *
* If a function is passed in instead, the function is called if the option does not exist (with access to the current options) and the return value is used instead. Useful to migrate a new option with a value depending on some other option that might be initialized. * If a function is passed in instead, the function is called if the option does not exist (with access to the current options) and the return value is used instead. Useful to migrate a new option with a value depending on some other option that might be initialized.
*/ */
value: string | ((options: OptionMap) => string); value: string | ((options: OptionMap) => string);
isSynced: boolean; isSynced: boolean;
} }
/** /**
* Initializes the default options for new databases only. * Initializes the default options for new databases only.
* *
* @param initialized `true` if the database has been fully initialized (i.e. a new database was created), or `false` if the database is created for sync. * @param initialized `true` if the database has been fully initialized (i.e. a new database was created), or `false` if the database is created for sync.
* @param opts additional options to be initialized, for example the sync configuration. * @param opts additional options to be initialized, for example the sync configuration.
*/ */
@ -56,10 +56,10 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts =
optionService.createOption('initialized', initialized ? 'true' : 'false', false); optionService.createOption('initialized', initialized ? 'true' : 'false', false);
optionService.createOption('lastSyncedPull', '0', false); optionService.createOption('lastSyncedPull', '0', false);
optionService.createOption('lastSyncedPush', '0', false); optionService.createOption('lastSyncedPush', '0', false);
optionService.createOption('theme', 'next', false); optionService.createOption('theme', 'next', false);
optionService.createOption('syncServerHost', opts.syncServerHost || '', false); optionService.createOption('syncServerHost', opts.syncServerHost || '', false);
optionService.createOption('syncServerTimeout', '120000', false); optionService.createOption('syncServerTimeout', '120000', false);
optionService.createOption('syncProxy', opts.syncProxy || '', false); optionService.createOption('syncProxy', opts.syncProxy || '', false);
@ -155,7 +155,7 @@ const defaultOptions: DefaultOption[] = [
/** /**
* Initializes the options, by checking which options from {@link #allDefaultOptions()} are missing and registering them. It will also check some environment variables such as safe mode, to make any necessary adjustments. * Initializes the options, by checking which options from {@link #allDefaultOptions()} are missing and registering them. It will also check some environment variables such as safe mode, to make any necessary adjustments.
* *
* This method is called regardless of whether a new database is created, or an existing database is used. * This method is called regardless of whether a new database is created, or an existing database is used.
*/ */
function initStartupOptions() { function initStartupOptions() {

View File

@ -29,13 +29,13 @@ interface Request {
end(payload?: string): void; end(payload?: string): void;
} }
interface Client { interface Client {
request(opts: ClientOpts): Request; request(opts: ClientOpts): Request;
} }
async function exec<T>(opts: ExecOpts): Promise<T> { async function exec<T>(opts: ExecOpts): Promise<T> {
const client = getClient(opts); const client = getClient(opts);
// hack for cases where electron.net does not work, but we don't want to set proxy // hack for cases where electron.net does not work, but we don't want to set proxy
if (opts.proxy === 'noproxy') { if (opts.proxy === 'noproxy') {
opts.proxy = null; opts.proxy = null;

View File

@ -17,4 +17,4 @@ export interface ExecOpts {
}, },
timeout: number; timeout: number;
body?: string | {}; body?: string | {};
} }

View File

@ -27,7 +27,7 @@ function executeNote(note: BNote, apiParams: ApiParams) {
if (!bundle) { if (!bundle) {
throw new Error("Unable to determine bundle."); throw new Error("Unable to determine bundle.");
} }
return executeBundle(bundle, apiParams); return executeBundle(bundle, apiParams);
} }

View File

@ -12,7 +12,7 @@ class ScriptContext {
notes: {}; notes: {};
apis: {}; apis: {};
allNotes: BNote[]; allNotes: BNote[];
constructor(allNotes: BNote[], apiParams: ApiParams) { constructor(allNotes: BNote[], apiParams: ApiParams) {
this.allNotes = allNotes; this.allNotes = allNotes;
this.modules = {}; this.modules = {};

View File

@ -3,9 +3,9 @@
import BNote from "../../becca/entities/bnote.js"; import BNote from "../../becca/entities/bnote.js";
class NoteSet { class NoteSet {
private noteIdSet: Set<string>; private noteIdSet: Set<string>;
notes: BNote[]; notes: BNote[];
sorted: boolean; sorted: boolean;

View File

@ -4,7 +4,7 @@ import hoistedNoteService from "../hoisted_note.js";
import { SearchParams } from './services/types.js'; import { SearchParams } from './services/types.js';
class SearchContext { class SearchContext {
fastSearch: boolean; fastSearch: boolean;
includeArchivedNotes: boolean; includeArchivedNotes: boolean;
includeHiddenNotes: boolean; includeHiddenNotes: boolean;

View File

@ -42,8 +42,8 @@ class SearchResult {
else if (normalizedTitle.startsWith(normalizedQuery)) { else if (normalizedTitle.startsWith(normalizedQuery)) {
this.score += 500; // Increased to give more weight to prefix matches this.score += 500; // Increased to give more weight to prefix matches
} }
else if (normalizedTitle.includes(` ${normalizedQuery} `) || else if (normalizedTitle.includes(` ${normalizedQuery} `) ||
normalizedTitle.startsWith(`${normalizedQuery} `) || normalizedTitle.startsWith(`${normalizedQuery} `) ||
normalizedTitle.endsWith(` ${normalizedQuery}`)) { normalizedTitle.endsWith(` ${normalizedQuery}`)) {
this.score += 300; // Increased to better distinguish word matches this.score += 300; // Increased to better distinguish word matches
} }

View File

@ -88,7 +88,7 @@ async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string
} }
await sqlInit.createDatabaseForSync(resp.options, syncServerHost, syncProxy); await sqlInit.createDatabaseForSync(resp.options, syncServerHost, syncProxy);
triggerSync(); triggerSync();
return { result: 'success' }; return { result: 'success' };

View File

@ -19,7 +19,7 @@ let statementCache: Record<string, Statement> = {};
function buildDatabase() { function buildDatabase() {
if (process.env.TRILIUM_INTEGRATION_TEST === "memory") { if (process.env.TRILIUM_INTEGRATION_TEST === "memory") {
return buildIntegrationTestDatabase(); return buildIntegrationTestDatabase();
} }
return new Database(dataDir.DOCUMENT_PATH); return new Database(dataDir.DOCUMENT_PATH);
@ -95,8 +95,8 @@ function upsert<T extends {}>(tableName: string, primaryKey: string, rec: T) {
const updateMarks = keys.map(colName => `${colName} = @${colName}`).join(", "); const updateMarks = keys.map(colName => `${colName} = @${colName}`).join(", ");
const query = `INSERT INTO ${tableName} (${columns}) VALUES (${questionMarks}) const query = `INSERT INTO ${tableName} (${columns}) VALUES (${questionMarks})
ON CONFLICT (${primaryKey}) DO UPDATE SET ${updateMarks}`; ON CONFLICT (${primaryKey}) DO UPDATE SET ${updateMarks}`;
for (const idx in rec) { for (const idx in rec) {
if (rec[idx] === true || rec[idx] === false) { if (rec[idx] === true || rec[idx] === false) {
@ -345,60 +345,60 @@ export default {
replace, replace,
/** /**
* Get single value from the given query - first column from first returned row. * Get single value from the given query - first column from first returned row.
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
* @returns single value * @returns single value
*/ */
getValue, getValue,
/** /**
* Get first returned row. * Get first returned row.
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
* @returns - map of column name to column value * @returns - map of column name to column value
*/ */
getRow, getRow,
getRowOrNull, getRowOrNull,
/** /**
* Get all returned rows. * Get all returned rows.
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
* @returns - array of all rows, each row is a map of column name to column value * @returns - array of all rows, each row is a map of column name to column value
*/ */
getRows, getRows,
getRawRows, getRawRows,
iterateRows, iterateRows,
getManyRows, getManyRows,
/** /**
* Get a map of first column mapping to second column. * Get a map of first column mapping to second column.
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
* @returns - map of first column to second column * @returns - map of first column to second column
*/ */
getMap, getMap,
/** /**
* Get a first column in an array. * Get a first column in an array.
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
* @returns array of first column of all returned rows * @returns array of first column of all returned rows
*/ */
getColumn, getColumn,
/** /**
* Execute SQL * Execute SQL
* *
* @param query - SQL query with ? used as parameter placeholder * @param query - SQL query with ? used as parameter placeholder
* @param params - array of params if needed * @param params - array of params if needed
*/ */
execute, execute,
executeMany, executeMany,
executeScript, executeScript,

View File

@ -22,7 +22,7 @@ const dbReady = utils.deferred<void>();
function schemaExists() { function schemaExists() {
return !!sql.getValue(`SELECT name FROM sqlite_master return !!sql.getValue(`SELECT name FROM sqlite_master
WHERE type = 'table' AND name = 'options'`); WHERE type = 'table' AND name = 'options'`);
} }
function isDbInitialized() { function isDbInitialized() {
@ -87,7 +87,7 @@ async function createInitialDatabase() {
isExpanded: true, isExpanded: true,
notePosition: 10 notePosition: 10
}).save(); }).save();
optionsInitService.initDocumentOptions(); optionsInitService.initDocumentOptions();
optionsInitService.initNotSyncedOptions(true, {}); optionsInitService.initNotSyncedOptions(true, {});
optionsInitService.initStartupOptions(); optionsInitService.initStartupOptions();
@ -131,7 +131,7 @@ async function createDatabaseForSync(options: OptionRow[], syncServerHost = '',
// We have to import async since options init requires keyboard actions which require translations. // We have to import async since options init requires keyboard actions which require translations.
const optionsInitService = (await import("./options_init.js")).default; const optionsInitService = (await import("./options_init.js")).default;
sql.transactional(() => { sql.transactional(() => {
sql.executeScript(schema); sql.executeScript(schema);
@ -171,22 +171,22 @@ function initializeDb() {
cls.init(initDbConnection); cls.init(initDbConnection);
log.info(`DB size: ${getDbSize()} KB`); log.info(`DB size: ${getDbSize()} KB`);
dbReady.then(() => { dbReady.then(() => {
if (config.General && config.General.noBackup === true) { if (config.General && config.General.noBackup === true) {
log.info("Disabling scheduled backups."); log.info("Disabling scheduled backups.");
return; return;
} }
setInterval(() => backup.regularBackup(), 4 * 60 * 60 * 1000); setInterval(() => backup.regularBackup(), 4 * 60 * 60 * 1000);
// kickoff first backup soon after start up // kickoff first backup soon after start up
setTimeout(() => backup.regularBackup(), 5 * 60 * 1000); setTimeout(() => backup.regularBackup(), 5 * 60 * 1000);
// optimize is usually inexpensive no-op, so running it semi-frequently is not a big deal // optimize is usually inexpensive no-op, so running it semi-frequently is not a big deal
setTimeout(() => optimize(), 60 * 60 * 1000); setTimeout(() => optimize(), 60 * 60 * 1000);
setInterval(() => optimize(), 10 * 60 * 60 * 1000); setInterval(() => optimize(), 10 * 60 * 60 * 1000);
}); });
} }

View File

@ -153,7 +153,7 @@ async function doLogin(): Promise<SyncContext> {
return syncContext; return syncContext;
} }
async function pullChanges(syncContext: 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

View File

@ -14,7 +14,7 @@ class TaskContext {
private lastSentCountTs: number; private lastSentCountTs: number;
data: TaskData | null; data: TaskData | null;
noteDeletionHandlerTriggered: boolean; noteDeletionHandlerTriggered: boolean;
constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) { constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) {
this.taskId = taskId; this.taskId = taskId;
this.taskType = taskType; this.taskType = taskType;

View File

@ -272,14 +272,14 @@ function timeLimit<T>(promise: Promise<T>, limitMs: number, errorMessage?: strin
} }
interface DeferredPromise<T> extends Promise<T> { interface DeferredPromise<T> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void, resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void reject: (reason?: any) => void
} }
function deferred<T>(): DeferredPromise<T> { function deferred<T>(): DeferredPromise<T> {
return (() => { return (() => {
let resolve!: (value: T | PromiseLike<T>) => void; let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void; let reject!: (reason?: any) => void;
let promise = new Promise<T>((res, rej) => { let promise = new Promise<T>((res, rej) => {
resolve = res; resolve = res;
@ -321,7 +321,7 @@ function isString(x: any) {
/** /**
* Returns the directory for resources. On Electron builds this corresponds to the `resources` subdirectory inside the distributable package. * Returns the directory for resources. On Electron builds this corresponds to the `resources` subdirectory inside the distributable package.
* On development builds, this simply refers to the root directory of the application. * On development builds, this simply refers to the root directory of the application.
* *
* @returns the resource dir. * @returns the resource dir.
*/ */
export function getResourceDir() { export function getResourceDir() {
@ -329,7 +329,7 @@ export function getResourceDir() {
return process.resourcesPath; return process.resourcesPath;
} else { } else {
return join(dirname(fileURLToPath(import.meta.url)), "..", ".."); return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
} }
} }
export default { export default {

View File

@ -58,7 +58,7 @@ async function createMainWindow(app: App) {
} }
]); ]);
} }
const windowStateKeeper = (await import('electron-window-state')).default; // should not be statically imported const windowStateKeeper = (await import('electron-window-state')).default; // should not be statically imported
const mainWindowState = windowStateKeeper({ const mainWindowState = windowStateKeeper({
@ -71,7 +71,7 @@ async function createMainWindow(app: App) {
const { BrowserWindow } = (await import('electron')); // should not be statically imported const { BrowserWindow } = (await import('electron')); // should not be statically imported
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
x: mainWindowState.x, x: mainWindowState.x,
@ -84,7 +84,7 @@ async function createMainWindow(app: App) {
contextIsolation: false, contextIsolation: false,
spellcheck: spellcheckEnabled, spellcheck: spellcheckEnabled,
webviewTag: true webviewTag: true
}, },
icon: getIcon(), icon: getIcon(),
...getWindowExtraOpts() ...getWindowExtraOpts()
}); });
@ -99,7 +99,7 @@ async function createMainWindow(app: App) {
app.on('second-instance', (event, commandLine) => { app.on('second-instance', (event, commandLine) => {
if (commandLine.includes('--new-window')) { if (commandLine.includes('--new-window')) {
createExtraWindow(""); createExtraWindow("");
} else if (mainWindow) { } else if (mainWindow) {
// Someone tried to run a second instance, we should focus our window. // Someone tried to run a second instance, we should focus our window.
// see www.ts "requestSingleInstanceLock" for the rest of this logic with explanation // see www.ts "requestSingleInstanceLock" for the rest of this logic with explanation
@ -146,7 +146,7 @@ function configureWebContents(webContents: WebContents, spellcheckEnabled: boole
async function openExternal() { async function openExternal() {
(await import('electron')).shell.openExternal(details.url); (await import('electron')).shell.openExternal(details.url);
} }
openExternal(); openExternal();
return { action: 'deny' } return { action: 'deny' }
}); });

View File

@ -35,12 +35,12 @@ interface Message {
shrinkImages?: boolean shrinkImages?: boolean
} | null, } | null,
lastSyncedPush?: number | null, lastSyncedPush?: number | null,
progressCount?: number; progressCount?: number;
taskId?: string; taskId?: string;
taskType?: string | null; taskType?: string | null;
message?: string; message?: string;
reason?: string; reason?: string;
result?: string | Record<string, string | undefined>; result?: string | Record<string, string | undefined>;
script?: string; script?: string;