| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | import dayjs from "dayjs"; | 
					
						
							| 
									
										
										
										
											2025-02-12 08:28:29 +01:00
										 |  |  | import { Modal } from "bootstrap"; | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 20:47:33 +03:00
										 |  |  | function reloadFrontendApp(reason?: string) { | 
					
						
							| 
									
										
										
										
											2021-09-17 22:34:23 +02:00
										 |  |  |     if (reason) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |         logInfo(`Frontend app reload: ${reason}`); | 
					
						
							| 
									
										
										
										
											2021-09-17 22:34:23 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-15 21:04:17 +01:00
										 |  |  |     window.location.reload(); | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-01 11:07:47 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Triggers the system tray to update its menu items, i.e. after a change in dynamic content such as bookmarks or recent notes. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * On any other platform than Electron, nothing happens. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-02-01 11:04:49 +02:00
										 |  |  | function reloadTray() { | 
					
						
							|  |  |  |     if (!isElectron()) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { ipcRenderer } = dynamicRequire("electron"); | 
					
						
							|  |  |  |     ipcRenderer.send("reload-tray"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function parseDate(str: string) { | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     try { | 
					
						
							|  |  |  |         return new Date(Date.parse(str)); | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     } catch (e: any) { | 
					
						
							| 
									
										
										
										
											2023-04-20 00:11:09 +02:00
										 |  |  |         throw new Error(`Can't parse date from '${str}': ${e.message} ${e.stack}`); | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-22 10:56:10 +02:00
										 |  |  | // Source: https://stackoverflow.com/a/30465299/4898894
 | 
					
						
							|  |  |  | function getMonthsInDateRange(startDate: string, endDate: string) { | 
					
						
							|  |  |  |     const start = startDate.split('-'); | 
					
						
							|  |  |  |     const end = endDate.split('-'); | 
					
						
							|  |  |  |     const startYear = parseInt(start[0]); | 
					
						
							|  |  |  |     const endYear = parseInt(end[0]); | 
					
						
							|  |  |  |     const dates = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (let i = startYear; i <= endYear; i++) { | 
					
						
							|  |  |  |         const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1; | 
					
						
							|  |  |  |         const startMon = i === startYear ? parseInt(start[1])-1 : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for(let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j+1) { | 
					
						
							|  |  |  |             const month = j+1; | 
					
						
							|  |  |  |             const displayMonth = month < 10 ? '0'+month : month; | 
					
						
							|  |  |  |             dates.push([i, displayMonth].join('-')); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return dates; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function padNum(num: number) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `${num <= 9 ? "0" : ""}${num}`; | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function formatTime(date: Date) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `${padNum(date.getHours())}:${padNum(date.getMinutes())}`; | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function formatTimeWithSeconds(date: Date) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `${padNum(date.getHours())}:${padNum(date.getMinutes())}:${padNum(date.getSeconds())}`; | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-23 11:02:38 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function formatTimeInterval(ms: number) { | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |     const seconds = Math.round(ms / 1000); | 
					
						
							|  |  |  |     const minutes = Math.floor(seconds / 60); | 
					
						
							|  |  |  |     const hours = Math.floor(minutes / 60); | 
					
						
							|  |  |  |     const days = Math.floor(hours / 24); | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? "s" : ""}`; | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |     const segments = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (days > 0) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         segments.push(plural(days, "day")); | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (days < 2) { | 
					
						
							|  |  |  |         if (hours % 24 > 0) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |             segments.push(plural(hours % 24, "hour")); | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (hours < 4) { | 
					
						
							|  |  |  |             if (minutes % 60 > 0) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |                 segments.push(plural(minutes % 60, "minute")); | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (minutes < 5) { | 
					
						
							|  |  |  |                 if (seconds % 60 > 0) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |                     segments.push(plural(seconds % 60, "second")); | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return segments.join(", "); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | /** this is producing local time! **/ | 
					
						
							|  |  |  | function formatDate(date: Date) { | 
					
						
							| 
									
										
										
										
											2023-04-08 21:07:48 +08:00
										 |  |  |     //    return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
 | 
					
						
							| 
									
										
										
										
											2018-08-15 08:48:16 +02:00
										 |  |  |     // instead of european format we'll just use ISO as that's pretty unambiguous
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return formatDateISO(date); | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-23 12:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | /** this is producing local time! **/ | 
					
						
							|  |  |  | function formatDateISO(date: Date) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`; | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-23 12:19:15 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function formatDateTime(date: Date) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `${formatDate(date)} ${formatTime(date)}`; | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-28 19:00:31 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-26 14:26:57 +02:00
										 |  |  | function localNowDateTime() { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return dayjs().format("YYYY-MM-DD HH:mm:ss.SSSZZ"); | 
					
						
							| 
									
										
										
										
											2020-04-26 14:26:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | function now() { | 
					
						
							|  |  |  |     return formatTimeWithSeconds(new Date()); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-01-22 23:18:08 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-16 21:41:15 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Returns `true` if the client is currently running under Electron, or `false` if running in a web browser. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | function isElectron() { | 
					
						
							| 
									
										
										
										
											2019-02-09 19:25:55 +01:00
										 |  |  |     return !!(window && window.process && window.process.type); | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-01-25 23:49:03 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 14:04:53 +01:00
										 |  |  | function isMac() { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return navigator.platform.indexOf("Mac") > -1; | 
					
						
							| 
									
										
										
										
											2018-12-02 14:04:53 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-18 11:09:57 +02:00
										 |  |  | function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); | 
					
						
							| 
									
										
										
										
											2022-12-09 16:48:00 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:44:15 +02:00
										 |  |  | function assertArguments<T>(...args: T[]) { | 
					
						
							| 
									
										
										
										
											2024-07-25 20:55:04 +03:00
										 |  |  |     for (const i in args) { | 
					
						
							|  |  |  |         if (!args[i]) { | 
					
						
							|  |  |  |             console.trace(`Argument idx#${i} should not be falsy: ${args[i]}`); | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-06 23:04:35 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | const entityMap: Record<string, string> = { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     "&": "&", | 
					
						
							|  |  |  |     "<": "<", | 
					
						
							|  |  |  |     ">": ">", | 
					
						
							|  |  |  |     '"': """, | 
					
						
							|  |  |  |     "'": "'", | 
					
						
							|  |  |  |     "/": "/", | 
					
						
							|  |  |  |     "`": "`", | 
					
						
							|  |  |  |     "=": "=" | 
					
						
							| 
									
										
										
										
											2020-06-24 21:07:55 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function escapeHtml(str: string) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return str.replace(/[&<>"'`=\/]/g, (s) => entityMap[s]); | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 21:03:39 +02:00
										 |  |  | export function escapeQuotes(value: string) { | 
					
						
							|  |  |  |     return value.replaceAll("\"", """); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function formatSize(size: number) { | 
					
						
							| 
									
										
										
										
											2023-03-24 10:57:32 +01:00
										 |  |  |     size = Math.max(Math.round(size / 1024), 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (size < 1024) { | 
					
						
							|  |  |  |         return `${size} KiB`; | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2023-03-24 10:57:32 +01:00
										 |  |  |         return `${Math.round(size / 102.4) / 10} MiB`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-19 22:16:03 +02:00
										 |  |  | function toObject<T, R>(array: T[], fn: (arg0: T) => [key: string, value: R]) { | 
					
						
							|  |  |  |     const obj: Record<string, R> = {}; | 
					
						
							| 
									
										
										
										
											2018-03-04 23:28:26 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     for (const item of array) { | 
					
						
							| 
									
										
										
										
											2018-03-25 23:25:17 -04:00
										 |  |  |         const [key, value] = fn(item); | 
					
						
							| 
									
										
										
										
											2018-03-24 23:37:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 23:25:17 -04:00
										 |  |  |         obj[key] = value; | 
					
						
							| 
									
										
										
										
											2018-03-04 23:28:26 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     return obj; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-12 23:27:21 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function randomString(len: number) { | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     let text = ""; | 
					
						
							|  |  |  |     const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | 
					
						
							| 
									
										
										
										
											2018-03-12 23:27:21 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     for (let i = 0; i < len; i++) { | 
					
						
							|  |  |  |         text += possible.charAt(Math.floor(Math.random() * possible.length)); | 
					
						
							| 
									
										
										
										
											2018-03-12 23:27:21 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     return text; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-24 10:10:36 +01:00
										 |  |  | function isMobile() { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         window.glob?.device === "mobile" || | 
					
						
							| 
									
										
										
										
											2023-05-03 22:49:24 +02:00
										 |  |  |         // window.glob.device is not available in setup
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         (!window.glob?.device && /Mobi/.test(navigator.userAgent)) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2018-12-24 10:10:36 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | function isDesktop() { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         window.glob?.device === "desktop" || | 
					
						
							| 
									
										
										
										
											2023-05-03 22:49:24 +02:00
										 |  |  |         // window.glob.device is not available in setup
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         (!window.glob?.device && !/Mobi/.test(navigator.userAgent)) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2018-12-24 10:10:36 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * 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) { | 
					
						
							| 
									
										
										
										
											2018-12-29 00:09:16 +01:00
										 |  |  |     const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000); | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     const expires = `; expires=${date.toUTCString()}`; | 
					
						
							| 
									
										
										
										
											2018-12-29 00:09:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     document.cookie = `${name}=${value || ""}${expires};`; | 
					
						
							| 
									
										
										
										
											2019-03-13 21:53:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function getNoteTypeClass(type: string) { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `type-${type}`; | 
					
						
							| 
									
										
										
										
											2019-01-28 21:42:37 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function getMimeTypeClass(mime: string) { | 
					
						
							| 
									
										
										
										
											2021-02-22 21:59:37 +01:00
										 |  |  |     if (!mime) { | 
					
						
							|  |  |  |         return ""; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     const semicolonIdx = mime.indexOf(";"); | 
					
						
							| 
									
										
										
										
											2019-01-28 21:42:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (semicolonIdx !== -1) { | 
					
						
							|  |  |  |         // stripping everything following the semicolon
 | 
					
						
							|  |  |  |         mime = mime.substr(0, semicolonIdx); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |     return `mime-${mime.toLowerCase().replace(/[\W_]+/g, "-")}`; | 
					
						
							| 
									
										
										
										
											2019-01-28 21:42:37 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 22:45:03 +02:00
										 |  |  | function closeActiveDialog() { | 
					
						
							|  |  |  |     if (glob.activeDialog) { | 
					
						
							| 
									
										
										
										
											2025-02-12 08:28:29 +01:00
										 |  |  |         Modal.getOrCreateInstance(glob.activeDialog[0]).hide(); | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  |         glob.activeDialog = null; | 
					
						
							| 
									
										
										
										
											2019-06-10 22:45:03 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | let $lastFocusedElement: JQuery<HTMLElement> | null; | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-04 23:35:10 +02:00
										 |  |  | // perhaps there should be saved focused element per tab?
 | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  | function saveFocusedElement() { | 
					
						
							|  |  |  |     $lastFocusedElement = $(":focus"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function focusSavedElement() { | 
					
						
							|  |  |  |     if (!$lastFocusedElement) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-23 22:47:20 +01:00
										 |  |  |     if ($lastFocusedElement.hasClass("ck")) { | 
					
						
							|  |  |  |         // must handle CKEditor separately because of this bug: https://github.com/ckeditor/ckeditor5/issues/607
 | 
					
						
							|  |  |  |         // the bug manifests itself in resetting the cursor position to the first character - jumping above
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         const editor = $lastFocusedElement.closest(".ck-editor__editable").prop("ckeditorInstance"); | 
					
						
							| 
									
										
										
										
											2022-11-23 22:47:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-03 13:26:33 +01:00
										 |  |  |         if (editor) { | 
					
						
							|  |  |  |             editor.editing.view.focus(); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             console.log("Could not find CKEditor instance to focus last element"); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-11-23 22:47:20 +01:00
										 |  |  |     } else { | 
					
						
							|  |  |  |         $lastFocusedElement.focus(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  |     $lastFocusedElement = null; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true) { | 
					
						
							| 
									
										
										
										
											2022-01-10 17:09:20 +01:00
										 |  |  |     if (closeActDialog) { | 
					
						
							|  |  |  |         closeActiveDialog(); | 
					
						
							|  |  |  |         glob.activeDialog = $dialog; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     saveFocusedElement(); | 
					
						
							| 
									
										
										
										
											2025-02-12 08:28:29 +01:00
										 |  |  |     Modal.getOrCreateInstance($dialog[0]).show(); | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     $dialog.on("hidden.bs.modal", () => { | 
					
						
							| 
									
										
										
										
											2025-01-04 19:15:24 +02:00
										 |  |  |         const $autocompleteEl = $(".aa-input"); | 
					
						
							|  |  |  |         if ("autocomplete" in $autocompleteEl) { | 
					
						
							|  |  |  |             $autocompleteEl.autocomplete("close"); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-26 16:52:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  |         if (!glob.activeDialog || glob.activeDialog === $dialog) { | 
					
						
							|  |  |  |             focusSavedElement(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-18 11:14:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |     // TODO: Fix once keyboard_actions is ported.
 | 
					
						
							|  |  |  |     // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-04-18 11:14:09 +02:00
										 |  |  |     const keyboardActionsService = (await import("./keyboard_actions.js")).default; | 
					
						
							|  |  |  |     keyboardActionsService.updateDisplayedShortcuts($dialog); | 
					
						
							| 
									
										
										
										
											2025-01-05 00:24:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return $dialog; | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function isHtmlEmpty(html: string) { | 
					
						
							| 
									
										
										
										
											2020-03-26 16:59:40 +01:00
										 |  |  |     if (!html) { | 
					
						
							|  |  |  |         return true; | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     } else if (typeof html !== "string") { | 
					
						
							| 
									
										
										
										
											2023-06-14 22:21:22 +02:00
										 |  |  |         logError(`Got object of type '${typeof html}' where string was expected.`); | 
					
						
							|  |  |  |         return false; | 
					
						
							| 
									
										
										
										
											2020-03-26 16:59:40 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-04 13:22:07 +01:00
										 |  |  |     html = html.toLowerCase(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return ( | 
					
						
							|  |  |  |         !html.includes("<img") && | 
					
						
							|  |  |  |         !html.includes("<section") && | 
					
						
							| 
									
										
										
										
											2023-06-14 22:21:22 +02:00
										 |  |  |         // the line below will actually attempt to load images so better to check for images first
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         $("<div>").html(html).text().trim().length === 0 | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2019-10-05 20:27:30 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-08 23:09:57 +01:00
										 |  |  | async function clearBrowserCache() { | 
					
						
							|  |  |  |     if (isElectron()) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         const win = dynamicRequire("@electron/remote").getCurrentWindow(); | 
					
						
							| 
									
										
										
										
											2019-11-08 23:09:57 +01:00
										 |  |  |         await win.webContents.session.clearCache(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 11:16:36 +03:00
										 |  |  | function copySelectionToClipboard() { | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |     const text = window?.getSelection()?.toString(); | 
					
						
							|  |  |  |     if (text && navigator.clipboard) { | 
					
						
							| 
									
										
										
										
											2020-01-19 09:25:35 +01:00
										 |  |  |         navigator.clipboard.writeText(text); | 
					
						
							| 
									
										
										
										
											2020-01-19 11:16:36 +03:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function dynamicRequire(moduleName: string) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     if (typeof __non_webpack_require__ !== "undefined") { | 
					
						
							| 
									
										
										
										
											2020-04-12 14:22:51 +02:00
										 |  |  |         return __non_webpack_require__(moduleName); | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2025-01-19 12:27:43 +01:00
										 |  |  |         // explicitly pass as string and not as expression to suppress webpack warning
 | 
					
						
							|  |  |  |         // 'Critical dependency: the request of a dependency is an expression'
 | 
					
						
							|  |  |  |         return require(`${moduleName}`); | 
					
						
							| 
									
										
										
										
											2020-04-12 14:22:51 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 20:47:33 +03:00
										 |  |  | function timeLimit<T>(promise: Promise<T>, limitMs: number, errorMessage?: string) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     if (!promise || !promise.then) { | 
					
						
							|  |  |  |         // it's not actually a promise
 | 
					
						
							| 
									
										
										
										
											2021-02-20 23:17:29 +01:00
										 |  |  |         return promise; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-29 23:34:49 +02:00
										 |  |  |     // better stack trace if created outside of promise
 | 
					
						
							| 
									
										
										
										
											2021-02-20 23:17:29 +01:00
										 |  |  |     const error = new Error(errorMessage || `Process exceeded time limit ${limitMs}`); | 
					
						
							| 
									
										
										
										
											2020-07-29 23:34:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 19:21:24 +03:00
										 |  |  |     return new Promise<T>((res, rej) => { | 
					
						
							| 
									
										
										
										
											2020-06-11 00:13:56 +02:00
										 |  |  |         let resolved = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         promise.then((result) => { | 
					
						
							| 
									
										
										
										
											2020-06-11 00:13:56 +02:00
										 |  |  |             resolved = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 22:34:15 +02:00
										 |  |  |             res(result); | 
					
						
							| 
									
										
										
										
											2020-06-11 00:13:56 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         setTimeout(() => { | 
					
						
							|  |  |  |             if (!resolved) { | 
					
						
							| 
									
										
										
										
											2020-07-29 23:34:49 +02:00
										 |  |  |                 rej(error); | 
					
						
							| 
									
										
										
										
											2020-06-11 00:13:56 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         }, limitMs); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function initHelpDropdown($el: JQuery<HTMLElement>) { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  |     // stop inside clicks from closing the menu
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     const $dropdownMenu = $el.find(".help-dropdown .dropdown-menu"); | 
					
						
							|  |  |  |     $dropdownMenu.on("click", (e) => e.stopPropagation()); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-15 21:04:17 +01:00
										 |  |  |     // previous propagation stop will also block help buttons from being opened, so we need to re-init for this element
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  |     initHelpButtons($dropdownMenu); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 00:26:39 +03:00
										 |  |  | const wikiBaseUrl = "https://triliumnext.github.io/Docs/Wiki/"; | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function openHelp($button: JQuery<HTMLElement>) { | 
					
						
							| 
									
										
										
										
											2023-07-14 21:59:43 +02:00
										 |  |  |     const helpPage = $button.attr("data-help-page"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (helpPage) { | 
					
						
							|  |  |  |         const url = wikiBaseUrl + helpPage; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         window.open(url, "_blank"); | 
					
						
							| 
									
										
										
										
											2023-07-14 21:59:43 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-12-20 17:30:47 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-21 17:47:09 +02:00
										 |  |  | function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) { | 
					
						
							| 
									
										
										
										
											2023-06-29 23:32:19 +02:00
										 |  |  |     // for some reason, the .on(event, listener, handler) does not work here (e.g. Options -> Sync -> Help button)
 | 
					
						
							| 
									
										
										
										
											2022-12-08 15:18:41 +01:00
										 |  |  |     // so we do it manually
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     $el.on("click", (e) => { | 
					
						
							| 
									
										
										
										
											2023-07-14 21:59:43 +02:00
										 |  |  |         const $helpButton = $(e.target).closest("[data-help-page]"); | 
					
						
							|  |  |  |         openHelp($helpButton); | 
					
						
							| 
									
										
										
										
											2022-12-08 15:18:41 +01:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function filterAttributeName(name: string) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return name.replace(/[^\p{L}\p{N}_:]/gu, ""); | 
					
						
							| 
									
										
										
										
											2021-02-17 23:22:14 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function isValidAttributeName(name: string) { | 
					
						
							| 
									
										
										
										
											2021-02-17 23:22:14 +01:00
										 |  |  |     return ATTR_NAME_MATCHER.test(name); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function sleep(time_ms: number) { | 
					
						
							| 
									
										
										
										
											2022-05-09 16:50:06 +02:00
										 |  |  |     return new Promise((resolve) => { | 
					
						
							|  |  |  |         setTimeout(resolve, time_ms); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-05-13 23:20:56 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-09 16:50:06 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function escapeRegExp(str: string) { | 
					
						
							| 
									
										
										
										
											2022-05-25 23:38:06 +02:00
										 |  |  |     return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-23 15:16:41 +02:00
										 |  |  | function areObjectsEqual(...args: unknown[]) { | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |     let i; | 
					
						
							|  |  |  |     let l; | 
					
						
							|  |  |  |     let leftChain: Object[]; | 
					
						
							|  |  |  |     let rightChain: Object[]; | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     function compare2Objects(x: unknown, y: unknown) { | 
					
						
							| 
									
										
										
										
											2023-05-05 23:17:23 +02:00
										 |  |  |         let p; | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // remember that NaN === NaN returns false
 | 
					
						
							|  |  |  |         // and isNaN(undefined) returns true
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         if (typeof x === "number" && typeof y === "number" && isNaN(x) && isNaN(y)) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Compare primitives and functions.
 | 
					
						
							|  |  |  |         // Check if both arguments link to the same object.
 | 
					
						
							|  |  |  |         // Especially useful on the step where we compare prototypes
 | 
					
						
							|  |  |  |         if (x === y) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Works in case when functions are created in constructor.
 | 
					
						
							|  |  |  |         // Comparing dates is a common scenario. Another built-ins?
 | 
					
						
							|  |  |  |         // We can even handle functions passed across iframes
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |         if ( | 
					
						
							|  |  |  |             (typeof x === "function" && typeof y === "function") || | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |             (x instanceof Date && y instanceof Date) || | 
					
						
							|  |  |  |             (x instanceof RegExp && y instanceof RegExp) || | 
					
						
							|  |  |  |             (x instanceof String && y instanceof String) || | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |             (x instanceof Number && y instanceof Number) | 
					
						
							|  |  |  |         ) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |             return x.toString() === y.toString(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-30 11:18:34 +02:00
										 |  |  |         // At last, checking prototypes as good as we can
 | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |         if (!(x instanceof Object && y instanceof Object)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (x.constructor !== y.constructor) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |         if ((x as any).prototype !== (y as any).prototype) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check for infinitive linking loops
 | 
					
						
							|  |  |  |         if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Quick checking of one object being a subset of another.
 | 
					
						
							|  |  |  |         // todo: cache the structure of arguments[0] for performance
 | 
					
						
							|  |  |  |         for (p in y) { | 
					
						
							|  |  |  |             if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |             } else if (typeof (y as any)[p] !== typeof (x as any)[p]) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (p in x) { | 
					
						
							|  |  |  |             if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |             } else if (typeof (y as any)[p] !== typeof (x as any)[p]) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |             switch (typeof (x as any)[p]) { | 
					
						
							|  |  |  |                 case "object": | 
					
						
							|  |  |  |                 case "function": | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |                     leftChain.push(x); | 
					
						
							|  |  |  |                     rightChain.push(y); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |                     if (!compare2Objects((x as any)[p], (y as any)[p])) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |                         return false; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     leftChain.pop(); | 
					
						
							|  |  |  |                     rightChain.pop(); | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 default: | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  |                     if ((x as any)[p] !== (y as any)[p]) { | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |                         return false; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (arguments.length < 1) { | 
					
						
							|  |  |  |         return true; //Die silently? Don't know how to handle such case, please help...
 | 
					
						
							|  |  |  |         // throw "Need two or more arguments to compare";
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (i = 1, l = arguments.length; i < l; i++) { | 
					
						
							|  |  |  |         leftChain = []; //Todo: this can be cached
 | 
					
						
							|  |  |  |         rightChain = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!compare2Objects(arguments[0], arguments[i])) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | function copyHtmlToClipboard(content: string) { | 
					
						
							|  |  |  |     function listener(e: ClipboardEvent) { | 
					
						
							|  |  |  |         if (e.clipboardData) { | 
					
						
							|  |  |  |             e.clipboardData.setData("text/html", content); | 
					
						
							|  |  |  |             e.clipboardData.setData("text/plain", content); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-27 20:42:36 +01:00
										 |  |  |         e.preventDefault(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     document.addEventListener("copy", listener); | 
					
						
							|  |  |  |     document.execCommand("copy"); | 
					
						
							|  |  |  |     document.removeEventListener("copy", listener); | 
					
						
							| 
									
										
										
										
											2023-05-29 00:19:54 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 00:18:57 +03:00
										 |  |  | // TODO: Set to FNote once the file is ported.
 | 
					
						
							|  |  |  | function createImageSrcUrl(note: { noteId: string; title: string }) { | 
					
						
							| 
									
										
										
										
											2023-10-19 09:20:23 +02:00
										 |  |  |     return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Given a string representation of an SVG, triggers a download of the file on the client device. | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2024-10-26 10:29:15 +03:00
										 |  |  |  * @param nameWithoutExtension the name of the file. The .svg suffix is automatically added to it. | 
					
						
							|  |  |  |  * @param svgContent the content of the SVG file download. | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2024-10-26 10:29:15 +03:00
										 |  |  | function downloadSvg(nameWithoutExtension: string, svgContent: string) { | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  |     const filename = `${nameWithoutExtension}.svg`; | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     const element = document.createElement("a"); | 
					
						
							|  |  |  |     element.setAttribute("href", `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`); | 
					
						
							|  |  |  |     element.setAttribute("download", filename); | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     element.style.display = "none"; | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  |     document.body.appendChild(element); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     element.click(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     document.body.removeChild(element); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Compares two semantic version strings. | 
					
						
							|  |  |  |  * Returns: | 
					
						
							|  |  |  |  *   1  if v1 is greater than v2 | 
					
						
							|  |  |  |  *   0  if v1 is equal to v2 | 
					
						
							|  |  |  |  *   -1 if v1 is less than v2 | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2024-12-14 09:43:01 +02:00
										 |  |  |  * @param v1 First version string | 
					
						
							|  |  |  |  * @param v2 Second version string | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  |  * @returns | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2024-12-14 09:43:01 +02:00
										 |  |  | function compareVersions(v1: string, v2: string): number { | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     // Remove 'v' prefix and everything after dash if present
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     v1 = v1.replace(/^v/, "").split("-")[0]; | 
					
						
							|  |  |  |     v2 = v2.replace(/^v/, "").split("-")[0]; | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     const v1parts = v1.split(".").map(Number); | 
					
						
							|  |  |  |     const v2parts = v2.split(".").map(Number); | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     // Pad shorter version with zeros
 | 
					
						
							|  |  |  |     while (v1parts.length < 3) v1parts.push(0); | 
					
						
							|  |  |  |     while (v2parts.length < 3) v2parts.push(0); | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     // Compare major version
 | 
					
						
							|  |  |  |     if (v1parts[0] !== v2parts[0]) { | 
					
						
							|  |  |  |         return v1parts[0] > v2parts[0] ? 1 : -1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     // Compare minor version
 | 
					
						
							|  |  |  |     if (v1parts[1] !== v2parts[1]) { | 
					
						
							|  |  |  |         return v1parts[1] > v2parts[1] ? 1 : -1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     // Compare patch version
 | 
					
						
							|  |  |  |     if (v1parts[2] !== v2parts[2]) { | 
					
						
							|  |  |  |         return v1parts[2] > v2parts[2] ? 1 : -1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-12-23 11:00:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-09 22:23:02 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Compares two semantic version strings and returns `true` if the latest version is greater than the current version. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-02-08 10:50:55 +02:00
										 |  |  | function isUpdateAvailable(latestVersion: string | null | undefined, currentVersion: string): boolean { | 
					
						
							|  |  |  |     if (!latestVersion) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     return compareVersions(latestVersion, currentVersion) > 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 21:52:41 +02:00
										 |  |  | function isLaunchBarConfig(noteId: string) { | 
					
						
							| 
									
										
										
										
											2025-01-09 18:07:02 +02:00
										 |  |  |     return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId); | 
					
						
							| 
									
										
										
										
											2025-01-04 21:52:41 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 18:33:39 +03:00
										 |  |  | export default { | 
					
						
							| 
									
										
										
										
											2021-08-24 22:59:51 +02:00
										 |  |  |     reloadFrontendApp, | 
					
						
							| 
									
										
										
										
											2025-02-01 11:04:49 +02:00
										 |  |  |     reloadTray, | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     parseDate, | 
					
						
							| 
									
										
										
										
											2025-02-22 10:56:10 +02:00
										 |  |  |     getMonthsInDateRange, | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     formatDateISO, | 
					
						
							|  |  |  |     formatDateTime, | 
					
						
							| 
									
										
										
										
											2023-04-21 00:19:17 +02:00
										 |  |  |     formatTimeInterval, | 
					
						
							| 
									
										
										
										
											2023-03-24 10:57:32 +01:00
										 |  |  |     formatSize, | 
					
						
							| 
									
										
										
										
											2020-04-26 14:26:57 +02:00
										 |  |  |     localNowDateTime, | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     now, | 
					
						
							|  |  |  |     isElectron, | 
					
						
							| 
									
										
										
										
											2018-12-02 14:04:53 +01:00
										 |  |  |     isMac, | 
					
						
							| 
									
										
										
										
											2022-12-09 16:48:00 +01:00
										 |  |  |     isCtrlKey, | 
					
						
							| 
									
										
										
										
											2018-03-25 11:09:17 -04:00
										 |  |  |     assertArguments, | 
					
						
							|  |  |  |     escapeHtml, | 
					
						
							|  |  |  |     toObject, | 
					
						
							| 
									
										
										
										
											2018-03-25 19:49:33 -04:00
										 |  |  |     randomString, | 
					
						
							| 
									
										
										
										
											2018-12-24 10:10:36 +01:00
										 |  |  |     isMobile, | 
					
						
							| 
									
										
										
										
											2018-12-29 00:09:16 +01:00
										 |  |  |     isDesktop, | 
					
						
							| 
									
										
										
										
											2019-01-28 21:42:37 +01:00
										 |  |  |     setCookie, | 
					
						
							|  |  |  |     getNoteTypeClass, | 
					
						
							| 
									
										
										
										
											2019-06-10 22:45:03 +02:00
										 |  |  |     getMimeTypeClass, | 
					
						
							| 
									
										
										
										
											2019-10-05 20:27:30 +02:00
										 |  |  |     closeActiveDialog, | 
					
						
							| 
									
										
										
										
											2020-02-09 10:00:13 +01:00
										 |  |  |     openDialog, | 
					
						
							|  |  |  |     saveFocusedElement, | 
					
						
							|  |  |  |     focusSavedElement, | 
					
						
							| 
									
										
										
										
											2019-11-08 23:09:57 +01:00
										 |  |  |     isHtmlEmpty, | 
					
						
							| 
									
										
										
										
											2019-11-22 20:35:17 +01:00
										 |  |  |     clearBrowserCache, | 
					
						
							| 
									
										
										
										
											2020-02-02 16:28:19 +08:00
										 |  |  |     copySelectionToClipboard, | 
					
						
							| 
									
										
										
										
											2020-06-11 00:13:56 +02:00
										 |  |  |     dynamicRequire, | 
					
						
							| 
									
										
										
										
											2021-01-26 14:44:53 +01:00
										 |  |  |     timeLimit, | 
					
						
							|  |  |  |     initHelpDropdown, | 
					
						
							| 
									
										
										
										
											2021-02-17 23:22:14 +01:00
										 |  |  |     initHelpButtons, | 
					
						
							| 
									
										
										
										
											2021-12-20 17:30:47 +01:00
										 |  |  |     openHelp, | 
					
						
							| 
									
										
										
										
											2021-02-17 23:22:14 +01:00
										 |  |  |     filterAttributeName, | 
					
						
							| 
									
										
										
										
											2022-05-09 16:50:06 +02:00
										 |  |  |     isValidAttributeName, | 
					
						
							|  |  |  |     sleep, | 
					
						
							| 
									
										
										
										
											2023-04-11 17:45:51 +02:00
										 |  |  |     escapeRegExp, | 
					
						
							| 
									
										
										
										
											2023-05-29 00:19:54 +02:00
										 |  |  |     areObjectsEqual, | 
					
						
							| 
									
										
										
										
											2023-10-19 09:20:23 +02:00
										 |  |  |     copyHtmlToClipboard, | 
					
						
							| 
									
										
										
										
											2024-09-01 23:20:41 +03:00
										 |  |  |     createImageSrcUrl, | 
					
						
							| 
									
										
										
										
											2024-11-09 22:16:00 +00:00
										 |  |  |     downloadSvg, | 
					
						
							|  |  |  |     compareVersions, | 
					
						
							| 
									
										
										
										
											2025-01-04 21:52:41 +02:00
										 |  |  |     isUpdateAvailable, | 
					
						
							|  |  |  |     isLaunchBarConfig | 
					
						
							| 
									
										
										
										
											2020-05-20 00:03:33 +02:00
										 |  |  | }; |