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",
"@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9",
"@types/bootstrap": "^5.2.10",
"@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7",
@ -2060,6 +2061,16 @@
"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": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@ -2153,6 +2164,15 @@
"@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": {
"version": "6.0.2",
"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",
"@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9",
"@types/bootstrap": "^5.2.10",
"@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5",
"@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) {
logInfo(`Frontend app reload: ${reason}`);
}
@ -6,33 +8,33 @@ function reloadFrontendApp(reason) {
window.location.reload();
}
function parseDate(str) {
function parseDate(str: string) {
try {
return new Date(Date.parse(str));
}
catch (e) {
catch (e: any) {
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}`;
}
function formatTime(date) {
function formatTime(date: Date) {
return `${padNum(date.getHours())}:${padNum(date.getMinutes())}`;
}
function formatTimeWithSeconds(date) {
function formatTimeWithSeconds(date: Date) {
return `${padNum(date.getHours())}:${padNum(date.getMinutes())}:${padNum(date.getSeconds())}`;
}
function formatTimeInterval(ms) {
function formatTimeInterval(ms: number) {
const seconds = Math.round(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
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 = [];
if (days > 0) {
@ -60,20 +62,20 @@ function formatTimeInterval(ms) {
return segments.join(", ");
}
// this is producing local time!
function formatDate(date) {
/** this is producing local time! **/
function formatDate(date: Date) {
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
// instead of european format we'll just use ISO as that's pretty unambiguous
return formatDateISO(date);
}
// this is producing local time!
function formatDateISO(date) {
/** this is producing local time! **/
function formatDateISO(date: Date) {
return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`;
}
function formatDateTime(date) {
function formatDateTime(date: Date) {
return `${formatDate(date)} ${formatTime(date)}`;
}
@ -93,7 +95,7 @@ function isMac() {
return navigator.platform.indexOf('Mac') > -1;
}
function isCtrlKey(evt) {
function isCtrlKey(evt: KeyboardEvent) {
return (!isMac() && evt.ctrlKey)
|| (isMac() && evt.metaKey);
}
@ -106,7 +108,7 @@ function assertArguments() {
}
}
const entityMap = {
const entityMap: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
@ -117,11 +119,11 @@ const entityMap = {
'=': '&#x3D;'
};
function escapeHtml(str) {
function escapeHtml(str: string) {
return str.replace(/[&<>"'`=\/]/g, s => entityMap[s]);
}
function formatSize(size) {
function formatSize(size: number) {
size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) {
@ -132,8 +134,8 @@ function formatSize(size) {
}
}
function toObject(array, fn) {
const obj = {};
function toObject<T>(array: T[], fn: (arg0: T) => [key: string, value: T]) {
const obj: Record<string, T> = {};
for (const item of array) {
const [key, value] = fn(item);
@ -144,7 +146,7 @@ function toObject(array, fn) {
return obj;
}
function randomString(len) {
function randomString(len: number) {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@ -161,27 +163,28 @@ function isMobile() {
|| (!window.glob?.device && /Mobi/.test(navigator.userAgent));
}
function isDesktop() {
function isDesktop() {
return window.glob?.device === "desktop"
// window.glob.device is not available in setup
|| (!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
function setCookie(name, value) {
/**
* 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: string, value: string) {
const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000);
const expires = `; expires=${date.toUTCString()}`;
document.cookie = `${name}=${value || ""}${expires};`;
}
function getNoteTypeClass(type) {
function getNoteTypeClass(type: string) {
return `type-${type}`;
}
function getMimeTypeClass(mime) {
function getMimeTypeClass(mime: string) {
if (!mime) {
return "";
}
@ -203,7 +206,7 @@ function closeActiveDialog() {
}
}
let $lastFocusedElement = null;
let $lastFocusedElement: JQuery<HTMLElement> | null;
// perhaps there should be saved focused element per tab?
function saveFocusedElement() {
@ -235,7 +238,7 @@ function focusSavedElement() {
$lastFocusedElement = null;
}
async function openDialog($dialog, closeActDialog = true) {
async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true) {
if (closeActDialog) {
closeActiveDialog();
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;
keyboardActionsService.updateDisplayedShortcuts($dialog);
}
function isHtmlEmpty(html) {
function isHtmlEmpty(html: string) {
if (!html) {
return true;
} else if (typeof html !== 'string') {
@ -281,13 +286,13 @@ async function clearBrowserCache() {
}
function copySelectionToClipboard() {
const text = window.getSelection().toString();
if (navigator.clipboard) {
const text = window?.getSelection()?.toString();
if (text && navigator.clipboard) {
navigator.clipboard.writeText(text);
}
}
function dynamicRequire(moduleName) {
function dynamicRequire(moduleName: string) {
if (typeof __non_webpack_require__ !== 'undefined') {
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
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
const $dropdownMenu = $el.find('.help-dropdown .dropdown-menu');
$dropdownMenu.on('click', e => e.stopPropagation());
@ -332,7 +337,7 @@ function initHelpDropdown($el) {
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
function openHelp($button) {
function openHelp($button: JQuery<HTMLElement>) {
const helpPage = $button.attr("data-help-page");
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)
// so we do it manually
$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, "");
}
const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u");
function isValidAttributeName(name) {
function isValidAttributeName(name: string) {
return ATTR_NAME_MATCHER.test(name);
}
function sleep(time_ms) {
function sleep(time_ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, time_ms);
});
}
function escapeRegExp(str) {
function escapeRegExp(str: string) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function areObjectsEqual() {
let i, l, leftChain, rightChain;
function areObjectsEqual () {
let i;
let l;
let leftChain: Object[];
let rightChain: Object[];
function compare2Objects(x, y) {
function compare2Objects (x: unknown, y: unknown) {
let p;
// remember that NaN === NaN returns false
// 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;
}
@ -414,7 +422,7 @@ function areObjectsEqual() {
return false;
}
if (x.prototype !== y.prototype) {
if ((x as any).prototype !== (y as any).prototype) {
return false;
}
@ -429,7 +437,7 @@ function areObjectsEqual() {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
else if (typeof (y as any)[p] !== typeof (x as any)[p]) {
return false;
}
}
@ -438,18 +446,18 @@ function areObjectsEqual() {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
else if (typeof (y as any)[p] !== typeof (x as any)[p]) {
return false;
}
switch (typeof (x[p])) {
switch (typeof ((x as any)[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!compare2Objects(x[p], y[p])) {
if (!compare2Objects((x as any)[p], (y as any)[p])) {
return false;
}
@ -458,7 +466,7 @@ function areObjectsEqual() {
break;
default:
if (x[p] !== y[p]) {
if ((x as any)[p] !== (y as any)[p]) {
return false;
}
break;
@ -486,10 +494,12 @@ function areObjectsEqual() {
return true;
}
function copyHtmlToClipboard(content) {
function listener(e) {
e.clipboardData.setData("text/html", content);
e.clipboardData.setData("text/plain", content);
function copyHtmlToClipboard(content: string) {
function listener(e: ClipboardEvent) {
if (e.clipboardData) {
e.clipboardData.setData("text/html", content);
e.clipboardData.setData("text/plain", content);
}
e.preventDefault();
}
document.addEventListener("copy", listener);
@ -497,11 +507,8 @@ function copyHtmlToClipboard(content) {
document.removeEventListener("copy", listener);
}
/**
* @param {FNote} note
* @return {string}
*/
function createImageSrcUrl(note) {
// TODO: Set to FNote once the file is ported.
function createImageSrcUrl(note: { noteId: string; title: string }) {
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/**/*"],
"files": [
"src/types.d.ts"
"src/types.d.ts",
"src/public/app/types.d.ts"
]
}