client-ts: Port services/utils

This commit is contained in:
Elian Doran 2024-07-25 00:18:57 +03:00
parent 0c8092b8f4
commit ba7035a346
No known key found for this signature in database
5 changed files with 136 additions and 61 deletions

20
package-lock.json generated
View File

@ -94,6 +94,7 @@
"@electron-forge/plugin-auto-unpack-natives": "^6.4.2", "@electron-forge/plugin-auto-unpack-natives": "^6.4.2",
"@types/archiver": "^6.0.2", "@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9", "@types/better-sqlite3": "^7.6.9",
"@types/bootstrap": "^5.2.10",
"@types/cls-hooked": "^4.3.8", "@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5", "@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7", "@types/cookie-parser": "^1.4.7",
@ -2060,6 +2061,16 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sindresorhus/is": { "node_modules/@sindresorhus/is": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@ -2153,6 +2164,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/bootstrap": {
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/cacheable-request": { "node_modules/@types/cacheable-request": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",

View File

@ -128,6 +128,7 @@
"@electron-forge/plugin-auto-unpack-natives": "^6.4.2", "@electron-forge/plugin-auto-unpack-natives": "^6.4.2",
"@types/archiver": "^6.0.2", "@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9", "@types/better-sqlite3": "^7.6.9",
"@types/bootstrap": "^5.2.10",
"@types/cls-hooked": "^4.3.8", "@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5", "@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7", "@types/cookie-parser": "^1.4.7",

View File

@ -1,4 +1,6 @@
function reloadFrontendApp(reason) { import dayjs from "dayjs";
function reloadFrontendApp(reason: string) {
if (reason) { if (reason) {
logInfo(`Frontend app reload: ${reason}`); logInfo(`Frontend app reload: ${reason}`);
} }
@ -6,33 +8,33 @@ function reloadFrontendApp(reason) {
window.location.reload(); window.location.reload();
} }
function parseDate(str) { function parseDate(str: string) {
try { try {
return new Date(Date.parse(str)); return new Date(Date.parse(str));
} }
catch (e) { catch (e: any) {
throw new Error(`Can't parse date from '${str}': ${e.message} ${e.stack}`); throw new Error(`Can't parse date from '${str}': ${e.message} ${e.stack}`);
} }
} }
function padNum(num) { function padNum(num: number) {
return `${num <= 9 ? "0" : ""}${num}`; return `${num <= 9 ? "0" : ""}${num}`;
} }
function formatTime(date) { function formatTime(date: Date) {
return `${padNum(date.getHours())}:${padNum(date.getMinutes())}`; return `${padNum(date.getHours())}:${padNum(date.getMinutes())}`;
} }
function formatTimeWithSeconds(date) { function formatTimeWithSeconds(date: Date) {
return `${padNum(date.getHours())}:${padNum(date.getMinutes())}:${padNum(date.getSeconds())}`; return `${padNum(date.getHours())}:${padNum(date.getMinutes())}:${padNum(date.getSeconds())}`;
} }
function formatTimeInterval(ms) { function formatTimeInterval(ms: number) {
const seconds = Math.round(ms / 1000); const seconds = Math.round(ms / 1000);
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
const plural = (count, name) => `${count} ${name}${count > 1 ? 's' : ''}`; const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? 's' : ''}`;
const segments = []; const segments = [];
if (days > 0) { if (days > 0) {
@ -60,20 +62,20 @@ function formatTimeInterval(ms) {
return segments.join(", "); return segments.join(", ");
} }
// this is producing local time! /** this is producing local time! **/
function formatDate(date) { function formatDate(date: Date) {
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear(); // return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
// instead of european format we'll just use ISO as that's pretty unambiguous // instead of european format we'll just use ISO as that's pretty unambiguous
return formatDateISO(date); return formatDateISO(date);
} }
// this is producing local time! /** this is producing local time! **/
function formatDateISO(date) { function formatDateISO(date: Date) {
return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`; return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`;
} }
function formatDateTime(date) { function formatDateTime(date: Date) {
return `${formatDate(date)} ${formatTime(date)}`; return `${formatDate(date)} ${formatTime(date)}`;
} }
@ -93,7 +95,7 @@ function isMac() {
return navigator.platform.indexOf('Mac') > -1; return navigator.platform.indexOf('Mac') > -1;
} }
function isCtrlKey(evt) { function isCtrlKey(evt: KeyboardEvent) {
return (!isMac() && evt.ctrlKey) return (!isMac() && evt.ctrlKey)
|| (isMac() && evt.metaKey); || (isMac() && evt.metaKey);
} }
@ -106,7 +108,7 @@ function assertArguments() {
} }
} }
const entityMap = { const entityMap: Record<string, string> = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
@ -117,11 +119,11 @@ const entityMap = {
'=': '&#x3D;' '=': '&#x3D;'
}; };
function escapeHtml(str) { function escapeHtml(str: string) {
return str.replace(/[&<>"'`=\/]/g, s => entityMap[s]); return str.replace(/[&<>"'`=\/]/g, s => entityMap[s]);
} }
function formatSize(size) { function formatSize(size: number) {
size = Math.max(Math.round(size / 1024), 1); size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) { if (size < 1024) {
@ -132,8 +134,8 @@ function formatSize(size) {
} }
} }
function toObject(array, fn) { function toObject<T>(array: T[], fn: (arg0: T) => [key: string, value: T]) {
const obj = {}; const obj: Record<string, T> = {};
for (const item of array) { for (const item of array) {
const [key, value] = fn(item); const [key, value] = fn(item);
@ -144,7 +146,7 @@ function toObject(array, fn) {
return obj; return obj;
} }
function randomString(len) { function randomString(len: number) {
let text = ""; let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@ -161,27 +163,28 @@ function isMobile() {
|| (!window.glob?.device && /Mobi/.test(navigator.userAgent)); || (!window.glob?.device && /Mobi/.test(navigator.userAgent));
} }
function isDesktop() { function isDesktop() {
return window.glob?.device === "desktop" return window.glob?.device === "desktop"
// window.glob.device is not available in setup // window.glob.device is not available in setup
|| (!window.glob?.device && !/Mobi/.test(navigator.userAgent)); || (!window.glob?.device && !/Mobi/.test(navigator.userAgent));
} }
// the cookie code below works for simple use cases only - ASCII only /**
// not setting a path so that cookies do not leak into other websites if multiplexed with reverse proxy * the cookie code below works for simple use cases only - ASCII only
* not setting a path so that cookies do not leak into other websites if multiplexed with reverse proxy
function setCookie(name, value) { */
function setCookie(name: string, value: string) {
const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000); const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000);
const expires = `; expires=${date.toUTCString()}`; const expires = `; expires=${date.toUTCString()}`;
document.cookie = `${name}=${value || ""}${expires};`; document.cookie = `${name}=${value || ""}${expires};`;
} }
function getNoteTypeClass(type) { function getNoteTypeClass(type: string) {
return `type-${type}`; return `type-${type}`;
} }
function getMimeTypeClass(mime) { function getMimeTypeClass(mime: string) {
if (!mime) { if (!mime) {
return ""; return "";
} }
@ -203,7 +206,7 @@ function closeActiveDialog() {
} }
} }
let $lastFocusedElement = null; let $lastFocusedElement: JQuery<HTMLElement> | null;
// perhaps there should be saved focused element per tab? // perhaps there should be saved focused element per tab?
function saveFocusedElement() { function saveFocusedElement() {
@ -235,7 +238,7 @@ function focusSavedElement() {
$lastFocusedElement = null; $lastFocusedElement = null;
} }
async function openDialog($dialog, closeActDialog = true) { async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true) {
if (closeActDialog) { if (closeActDialog) {
closeActiveDialog(); closeActiveDialog();
glob.activeDialog = $dialog; glob.activeDialog = $dialog;
@ -253,11 +256,13 @@ async function openDialog($dialog, closeActDialog = true) {
} }
}); });
// TODO: Fix once keyboard_actions is ported.
// @ts-ignore
const keyboardActionsService = (await import("./keyboard_actions.js")).default; const keyboardActionsService = (await import("./keyboard_actions.js")).default;
keyboardActionsService.updateDisplayedShortcuts($dialog); keyboardActionsService.updateDisplayedShortcuts($dialog);
} }
function isHtmlEmpty(html) { function isHtmlEmpty(html: string) {
if (!html) { if (!html) {
return true; return true;
} else if (typeof html !== 'string') { } else if (typeof html !== 'string') {
@ -281,13 +286,13 @@ async function clearBrowserCache() {
} }
function copySelectionToClipboard() { function copySelectionToClipboard() {
const text = window.getSelection().toString(); const text = window?.getSelection()?.toString();
if (navigator.clipboard) { if (text && navigator.clipboard) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
} }
} }
function dynamicRequire(moduleName) { function dynamicRequire(moduleName: string) {
if (typeof __non_webpack_require__ !== 'undefined') { if (typeof __non_webpack_require__ !== 'undefined') {
return __non_webpack_require__(moduleName); return __non_webpack_require__(moduleName);
} }
@ -296,7 +301,7 @@ function dynamicRequire(moduleName) {
} }
} }
function timeLimit(promise, limitMs, errorMessage) { function timeLimit(promise: Promise<void>, limitMs: number, errorMessage: string) {
if (!promise || !promise.then) { // it's not actually a promise if (!promise || !promise.then) { // it's not actually a promise
return promise; return promise;
} }
@ -321,7 +326,7 @@ function timeLimit(promise, limitMs, errorMessage) {
}); });
} }
function initHelpDropdown($el) { function initHelpDropdown($el: JQuery<HTMLElement>) {
// stop inside clicks from closing the menu // stop inside clicks from closing the menu
const $dropdownMenu = $el.find('.help-dropdown .dropdown-menu'); const $dropdownMenu = $el.find('.help-dropdown .dropdown-menu');
$dropdownMenu.on('click', e => e.stopPropagation()); $dropdownMenu.on('click', e => e.stopPropagation());
@ -332,7 +337,7 @@ function initHelpDropdown($el) {
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/"; const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
function openHelp($button) { function openHelp($button: JQuery<HTMLElement>) {
const helpPage = $button.attr("data-help-page"); const helpPage = $button.attr("data-help-page");
if (helpPage) { if (helpPage) {
@ -342,7 +347,7 @@ function openHelp($button) {
} }
} }
function initHelpButtons($el) { function initHelpButtons($el: JQuery<HTMLElement>) {
// for some reason, the .on(event, listener, handler) does not work here (e.g. Options -> Sync -> Help button) // for some reason, the .on(event, listener, handler) does not work here (e.g. Options -> Sync -> Help button)
// so we do it manually // so we do it manually
$el.on("click", e => { $el.on("click", e => {
@ -351,35 +356,38 @@ function initHelpButtons($el) {
}); });
} }
function filterAttributeName(name) { function filterAttributeName(name: string) {
return name.replace(/[^\p{L}\p{N}_:]/ug, ""); return name.replace(/[^\p{L}\p{N}_:]/ug, "");
} }
const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u");
function isValidAttributeName(name) { function isValidAttributeName(name: string) {
return ATTR_NAME_MATCHER.test(name); return ATTR_NAME_MATCHER.test(name);
} }
function sleep(time_ms) { function sleep(time_ms: number) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(resolve, time_ms); setTimeout(resolve, time_ms);
}); });
} }
function escapeRegExp(str) { function escapeRegExp(str: string) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
} }
function areObjectsEqual() { function areObjectsEqual () {
let i, l, leftChain, rightChain; let i;
let l;
let leftChain: Object[];
let rightChain: Object[];
function compare2Objects(x, y) { function compare2Objects (x: unknown, y: unknown) {
let p; let p;
// remember that NaN === NaN returns false // remember that NaN === NaN returns false
// and isNaN(undefined) returns true // and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') { if (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)) {
return true; return true;
} }
@ -414,7 +422,7 @@ function areObjectsEqual() {
return false; return false;
} }
if (x.prototype !== y.prototype) { if ((x as any).prototype !== (y as any).prototype) {
return false; return false;
} }
@ -429,7 +437,7 @@ function areObjectsEqual() {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false; return false;
} }
else if (typeof y[p] !== typeof x[p]) { else if (typeof (y as any)[p] !== typeof (x as any)[p]) {
return false; return false;
} }
} }
@ -438,18 +446,18 @@ function areObjectsEqual() {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false; return false;
} }
else if (typeof y[p] !== typeof x[p]) { else if (typeof (y as any)[p] !== typeof (x as any)[p]) {
return false; return false;
} }
switch (typeof (x[p])) { switch (typeof ((x as any)[p])) {
case 'object': case 'object':
case 'function': case 'function':
leftChain.push(x); leftChain.push(x);
rightChain.push(y); rightChain.push(y);
if (!compare2Objects(x[p], y[p])) { if (!compare2Objects((x as any)[p], (y as any)[p])) {
return false; return false;
} }
@ -458,7 +466,7 @@ function areObjectsEqual() {
break; break;
default: default:
if (x[p] !== y[p]) { if ((x as any)[p] !== (y as any)[p]) {
return false; return false;
} }
break; break;
@ -486,10 +494,12 @@ function areObjectsEqual() {
return true; return true;
} }
function copyHtmlToClipboard(content) { function copyHtmlToClipboard(content: string) {
function listener(e) { function listener(e: ClipboardEvent) {
e.clipboardData.setData("text/html", content); if (e.clipboardData) {
e.clipboardData.setData("text/plain", content); e.clipboardData.setData("text/html", content);
e.clipboardData.setData("text/plain", content);
}
e.preventDefault(); e.preventDefault();
} }
document.addEventListener("copy", listener); document.addEventListener("copy", listener);
@ -497,11 +507,8 @@ function copyHtmlToClipboard(content) {
document.removeEventListener("copy", listener); document.removeEventListener("copy", listener);
} }
/** // TODO: Set to FNote once the file is ported.
* @param {FNote} note function createImageSrcUrl(note: { noteId: string; title: string }) {
* @return {string}
*/
function createImageSrcUrl(note) {
return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`; return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
} }

46
src/public/app/types.d.ts vendored Normal file
View File

@ -0,0 +1,46 @@
import FNote from "./entities/fnote";
interface ElectronProcess {
type: string;
}
interface CustomGlobals {
isDesktop: boolean;
isMobile: boolean;
device: "mobile" | "desktop";
getComponentsByEl: (el: unknown) => unknown;
getHeaders: Promise<Record<string, string>>;
getReferenceLinkTitle: (href: string) => Promise<string>;
getReferenceLinkTitleSync: (href: string) => string;
getActiveContextNote: FNote;
requireLibrary: (library: string) => Promise<void>;
ESLINT: { js: string[]; };
appContext: AppContext;
froca: Froca;
treeCache: Froca;
importMarkdownInline: () => Promise<unknown>;
SEARCH_HELP_TEXT: string;
activeDialog: JQuery<HTMLElement> | null;
}
type RequireMethod = (moduleName: string) => any;
declare global {
interface Window {
logError(message: string);
logInfo(message: string);
process?: ElectronProcess;
glob?: CustomGlobals;
}
interface JQuery {
autocomplete: (action: "close") => void;
}
declare var logError: (message: string) => void;
declare var logInfo: (message: string) => void;
declare var glob: CustomGlobals;
declare var require: RequireMethod;
declare var __non_webpack_require__: RequireMethod | undefined;
}

View File

@ -21,6 +21,7 @@
], ],
"exclude": ["./node_modules/**/*"], "exclude": ["./node_modules/**/*"],
"files": [ "files": [
"src/types.d.ts" "src/types.d.ts",
"src/public/app/types.d.ts"
] ]
} }