mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 18:05:55 +01:00 
			
		
		
		
	chore(prettier): fix code style
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| import { fileURLToPath } from "url"; | import { fileURLToPath } from "url"; | ||||||
| import { dirname, join } from "path"; | import { dirname, join } from "path"; | ||||||
| import swaggerJsdoc from 'swagger-jsdoc'; | import swaggerJsdoc from "swagger-jsdoc"; | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -12,27 +12,29 @@ import fs from "fs"; | |||||||
|  |  | ||||||
| const options = { | const options = { | ||||||
|     definition: { |     definition: { | ||||||
|     openapi: '3.1.1', |         openapi: "3.1.1", | ||||||
|         info: { |         info: { | ||||||
|       title: 'Trilium Notes - Sync server API', |             title: "Trilium Notes - Sync server API", | ||||||
|       version: '0.96.6', |             version: "0.96.6", | ||||||
|       description: "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).", |             description: | ||||||
|  |                 "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).", | ||||||
|             contact: { |             contact: { | ||||||
|                 name: "TriliumNext issue tracker", |                 name: "TriliumNext issue tracker", | ||||||
|         url: "https://github.com/TriliumNext/Notes/issues", |                 url: "https://github.com/TriliumNext/Notes/issues" | ||||||
|             }, |             }, | ||||||
|             license: { |             license: { | ||||||
|                 name: "GNU Free Documentation License 1.3 (or later)", |                 name: "GNU Free Documentation License 1.3 (or later)", | ||||||
|         url: "https://www.gnu.org/licenses/fdl-1.3", |                 url: "https://www.gnu.org/licenses/fdl-1.3" | ||||||
|       }, |             } | ||||||
|     }, |         } | ||||||
|     }, |     }, | ||||||
|     apis: [ |     apis: [ | ||||||
|         // Put individual files here to have them ordered first. |         // Put individual files here to have them ordered first. | ||||||
|     './src/routes/api/setup.ts', |         "./src/routes/api/setup.ts", | ||||||
|         // all other files |         // all other files | ||||||
|     './src/routes/api/*.ts', './bin/generate-openapi.js' |         "./src/routes/api/*.ts", | ||||||
|   ], |         "./bin/generate-openapi.js" | ||||||
|  |     ] | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const openapiSpecification = swaggerJsdoc(options); | const openapiSpecification = swaggerJsdoc(options); | ||||||
|   | |||||||
| @@ -11,16 +11,14 @@ test("Displays translation on desktop", async ({ page, context }) => { | |||||||
|     const app = new App(page, context); |     const app = new App(page, context); | ||||||
|     await app.goto(); |     await app.goto(); | ||||||
|  |  | ||||||
|     await expect(page.locator("#left-pane .quick-search input")) |     await expect(page.locator("#left-pane .quick-search input")).toHaveAttribute("placeholder", "Quick search"); | ||||||
|         .toHaveAttribute("placeholder", "Quick search"); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test("Displays translation on mobile", async ({ page, context }) => { | test("Displays translation on mobile", async ({ page, context }) => { | ||||||
|     const app = new App(page, context); |     const app = new App(page, context); | ||||||
|     await app.goto({ isMobile: true }); |     await app.goto({ isMobile: true }); | ||||||
|  |  | ||||||
|     await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")) |     await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")).toHaveAttribute("placeholder", "Quick search"); | ||||||
|         .toHaveAttribute("placeholder", "Quick search"); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test("Displays translations in Settings", async ({ page, context }) => { | test("Displays translations in Settings", async ({ page, context }) => { | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ test("Can drag tabs around", async ({ page, context }) => { | |||||||
|     let tab = app.getTab(0); |     let tab = app.getTab(0); | ||||||
|  |  | ||||||
|     // Drag the first tab at the end |     // Drag the first tab at the end | ||||||
|     await tab.dragTo(app.getTab(2), { targetPosition: { x: 50, y: 0 }}); |     await tab.dragTo(app.getTab(2), { targetPosition: { x: 50, y: 0 } }); | ||||||
|  |  | ||||||
|     tab = app.getTab(2); |     tab = app.getTab(2); | ||||||
|     await expect(tab).toContainText(NOTE_TITLE); |     await expect(tab).toContainText(NOTE_TITLE); | ||||||
|  |  | ||||||
|     // Drag the tab to the left |     // Drag the tab to the left | ||||||
|     await tab.dragTo(app.getTab(0), { targetPosition: { x: 50, y: 0 }}); |     await tab.dragTo(app.getTab(0), { targetPosition: { x: 50, y: 0 } }); | ||||||
|     await expect(app.getTab(0)).toContainText(NOTE_TITLE); |     await expect(app.getTab(0)).toContainText(NOTE_TITLE); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,7 +39,9 @@ test("Displays lint errors for backend script", async ({ page, context }) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| async function expectTooltip(page: Page, tooltip: string) { | async function expectTooltip(page: Page, tooltip: string) { | ||||||
|     await expect(page.locator(".CodeMirror-lint-tooltip:visible", { |     await expect( | ||||||
|         "hasText": tooltip |         page.locator(".CodeMirror-lint-tooltip:visible", { | ||||||
|     })).toBeVisible(); |             hasText: tooltip | ||||||
|  |         }) | ||||||
|  |     ).toBeVisible(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,8 @@ import App from "../support/app"; | |||||||
|  |  | ||||||
| test("renders ELK flowchart", async ({ page, context }) => { | test("renders ELK flowchart", async ({ page, context }) => { | ||||||
|     await testAriaSnapshot({ |     await testAriaSnapshot({ | ||||||
|         page, context, |         page, | ||||||
|  |         context, | ||||||
|         noteTitle: "Flowchart ELK on", |         noteTitle: "Flowchart ELK on", | ||||||
|         snapshot: ` |         snapshot: ` | ||||||
|             - document: |             - document: | ||||||
| @@ -22,12 +23,13 @@ test("renders ELK flowchart", async ({ page, context }) => { | |||||||
|                 - paragraph: Guarantee |                 - paragraph: Guarantee | ||||||
|                 - text: Interfaces for B |                 - text: Interfaces for B | ||||||
|         ` |         ` | ||||||
|     }) |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test("renders standard flowchart", async ({ page, context }) => { | test("renders standard flowchart", async ({ page, context }) => { | ||||||
|     await testAriaSnapshot({ |     await testAriaSnapshot({ | ||||||
|         page, context, |         page, | ||||||
|  |         context, | ||||||
|         noteTitle: "Flowchart ELK off", |         noteTitle: "Flowchart ELK off", | ||||||
|         snapshot: ` |         snapshot: ` | ||||||
|             - document: |             - document: | ||||||
| @@ -46,7 +48,7 @@ test("renders standard flowchart", async ({ page, context }) => { | |||||||
|                 - paragraph: C |                 - paragraph: C | ||||||
|                 - text: Interfaces for B |                 - text: Interfaces for B | ||||||
|         ` |         ` | ||||||
|     }) |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| interface AriaTestOpts { | interface AriaTestOpts { | ||||||
|   | |||||||
| @@ -44,8 +44,8 @@ test("Highlights list is displayed", async ({ page, context }) => { | |||||||
|  |  | ||||||
|     await expect(app.sidebar).toContainText("Highlights List"); |     await expect(app.sidebar).toContainText("Highlights List"); | ||||||
|     const rootList = app.sidebar.locator(".highlights-list ol"); |     const rootList = app.sidebar.locator(".highlights-list ol"); | ||||||
|     let index=0; |     let index = 0; | ||||||
|     for (const highlightedEl of [ "Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2" ]) { |     for (const highlightedEl of ["Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2"]) { | ||||||
|         await expect(rootList.locator("li").nth(index++)).toContainText(highlightedEl); |         await expect(rootList.locator("li").nth(index++)).toContainText(highlightedEl); | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| @@ -54,7 +54,7 @@ test("Displays math popup", async ({ page, context }) => { | |||||||
|     const app = new App(page, context); |     const app = new App(page, context); | ||||||
|     await app.goto(); |     await app.goto(); | ||||||
|     await app.goToNoteInNewTab("Empty text"); |     await app.goToNoteInNewTab("Empty text"); | ||||||
|     const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor") |     const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor"); | ||||||
|     await noteContent.fill("Hello world"); |     await noteContent.fill("Hello world"); | ||||||
|     await noteContent.press("ControlOrMeta+M"); |     await noteContent.press("ControlOrMeta+M"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ export default class App { | |||||||
|         this.tabBar = page.locator(".tab-row-widget-container"); |         this.tabBar = page.locator(".tab-row-widget-container"); | ||||||
|         this.noteTree = page.locator(".tree-wrapper"); |         this.noteTree = page.locator(".tree-wrapper"); | ||||||
|         this.launcherBar = page.locator("#launcher-container"); |         this.launcherBar = page.locator("#launcher-container"); | ||||||
|         this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)") |         this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)"); | ||||||
|         this.sidebar = page.locator("#right-pane"); |         this.sidebar = page.locator("#right-pane"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -46,8 +46,7 @@ export default class App { | |||||||
|  |  | ||||||
|         // Wait for the page to load. |         // Wait for the page to load. | ||||||
|         if (url === "/") { |         if (url === "/") { | ||||||
|             await expect(this.page.locator(".tree")) |             await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test"); | ||||||
|                 .toContainText("Trilium Integration Test"); |  | ||||||
|             await this.closeAllTabs(); |             await this.closeAllTabs(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -109,11 +108,12 @@ export default class App { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(csrfToken).toBeTruthy(); |         expect(csrfToken).toBeTruthy(); | ||||||
|         await expect(await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, { |         await expect( | ||||||
|  |             await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, { | ||||||
|                 headers: { |                 headers: { | ||||||
|                     "x-csrf-token": csrfToken |                     "x-csrf-token": csrfToken | ||||||
|                 } |                 } | ||||||
|         })).toBeOK(); |             }) | ||||||
|  |         ).toBeOK(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { defineConfig, devices } from '@playwright/test'; | import { defineConfig, devices } from "@playwright/test"; | ||||||
|  |  | ||||||
| const SERVER_URL = 'http://127.0.0.1:8082'; | const SERVER_URL = "http://127.0.0.1:8082"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Read environment variables from file. |  * Read environment variables from file. | ||||||
| @@ -14,7 +14,7 @@ const SERVER_URL = 'http://127.0.0.1:8082'; | |||||||
|  * See https://playwright.dev/docs/test-configuration. |  * See https://playwright.dev/docs/test-configuration. | ||||||
|  */ |  */ | ||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   testDir: './e2e', |     testDir: "./e2e", | ||||||
|     /* Run tests in files in parallel */ |     /* Run tests in files in parallel */ | ||||||
|     fullyParallel: true, |     fullyParallel: true, | ||||||
|     /* Fail the build on CI if you accidentally left test.only in the source code. */ |     /* Fail the build on CI if you accidentally left test.only in the source code. */ | ||||||
| @@ -24,22 +24,22 @@ export default defineConfig({ | |||||||
|     /* Opt out of parallel tests on CI. */ |     /* Opt out of parallel tests on CI. */ | ||||||
|     workers: process.env.CI ? 1 : undefined, |     workers: process.env.CI ? 1 : undefined, | ||||||
|     /* Reporter to use. See https://playwright.dev/docs/test-reporters */ |     /* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||||||
|   reporter: 'html', |     reporter: "html", | ||||||
|     /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ |     /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||||||
|     use: { |     use: { | ||||||
|         /* Base URL to use in actions like `await page.goto('/')`. */ |         /* Base URL to use in actions like `await page.goto('/')`. */ | ||||||
|         baseURL: SERVER_URL, |         baseURL: SERVER_URL, | ||||||
|  |  | ||||||
|         /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ |         /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||||||
|     trace: 'on-first-retry', |         trace: "on-first-retry" | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     /* Configure projects for major browsers */ |     /* Configure projects for major browsers */ | ||||||
|     projects: [ |     projects: [ | ||||||
|         { |         { | ||||||
|       name: 'chromium', |             name: "chromium", | ||||||
|       use: { ...devices['Desktop Chrome'] }, |             use: { ...devices["Desktop Chrome"] } | ||||||
|     }, |         } | ||||||
|  |  | ||||||
|         // { |         // { | ||||||
|         //   name: 'firefox', |         //   name: 'firefox', | ||||||
| @@ -73,9 +73,11 @@ export default defineConfig({ | |||||||
|     ], |     ], | ||||||
|  |  | ||||||
|     /* Run your local dev server before starting the tests */ |     /* Run your local dev server before starting the tests */ | ||||||
|   webServer: !process.env.TRILIUM_DOCKER ? { |     webServer: !process.env.TRILIUM_DOCKER | ||||||
|     command: 'npm run test:integration-mem-db-dev', |         ? { | ||||||
|  |               command: "npm run test:integration-mem-db-dev", | ||||||
|               url: SERVER_URL, |               url: SERVER_URL, | ||||||
|     reuseExistingServer: !process.env.CI, |               reuseExistingServer: !process.env.CI | ||||||
|   } : undefined, |           } | ||||||
|  |         : undefined | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ export default class BTask extends AbstractBeccaEntity<BOption> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     static get hashedProperties() { |     static get hashedProperties() { | ||||||
|         return [ "taskId", "parentNoteId", "title", "dueDate", "isDone", "isDeleted" ]; |         return ["taskId", "parentNoteId", "title", "dueDate", "isDone", "isDeleted"]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     taskId?: string; |     taskId?: string; | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ export interface NoteCommandData extends CommandData { | |||||||
| } | } | ||||||
|  |  | ||||||
| export interface ExecuteCommandData<T> extends CommandData { | export interface ExecuteCommandData<T> extends CommandData { | ||||||
|     resolve: (data: T) => void |     resolve: (data: T) => void; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -70,7 +70,7 @@ export interface ExecuteCommandData<T> extends CommandData { | |||||||
|  */ |  */ | ||||||
| export type CommandMappings = { | export type CommandMappings = { | ||||||
|     "api-log-messages": CommandData; |     "api-log-messages": CommandData; | ||||||
|     focusTree: CommandData, |     focusTree: CommandData; | ||||||
|     focusOnTitle: CommandData; |     focusOnTitle: CommandData; | ||||||
|     focusOnDetail: CommandData; |     focusOnDetail: CommandData; | ||||||
|     focusOnSearchDefinition: Required<CommandData>; |     focusOnSearchDefinition: Required<CommandData>; | ||||||
| @@ -108,7 +108,7 @@ export type CommandMappings = { | |||||||
|     showInfoDialog: ConfirmWithMessageOptions; |     showInfoDialog: ConfirmWithMessageOptions; | ||||||
|     showConfirmDialog: ConfirmWithMessageOptions; |     showConfirmDialog: ConfirmWithMessageOptions; | ||||||
|     showRecentChanges: CommandData & { ancestorNoteId: string }; |     showRecentChanges: CommandData & { ancestorNoteId: string }; | ||||||
|     showImportDialog: CommandData & { noteId: string; }; |     showImportDialog: CommandData & { noteId: string }; | ||||||
|     openNewNoteSplit: NoteCommandData; |     openNewNoteSplit: NoteCommandData; | ||||||
|     openInWindow: NoteCommandData; |     openInWindow: NoteCommandData; | ||||||
|     openNoteInNewTab: CommandData; |     openNoteInNewTab: CommandData; | ||||||
| @@ -131,8 +131,10 @@ export type CommandMappings = { | |||||||
|     editNoteTitle: ContextMenuCommandData; |     editNoteTitle: ContextMenuCommandData; | ||||||
|     protectSubtree: ContextMenuCommandData; |     protectSubtree: ContextMenuCommandData; | ||||||
|     unprotectSubtree: ContextMenuCommandData; |     unprotectSubtree: ContextMenuCommandData; | ||||||
|     openBulkActionsDialog: ContextMenuCommandData | { |     openBulkActionsDialog: | ||||||
|         selectedOrActiveNoteIds?: string[] |         | ContextMenuCommandData | ||||||
|  |         | { | ||||||
|  |               selectedOrActiveNoteIds?: string[]; | ||||||
|           }; |           }; | ||||||
|     editBranchPrefix: ContextMenuCommandData; |     editBranchPrefix: ContextMenuCommandData; | ||||||
|     convertNoteToAttachment: ContextMenuCommandData; |     convertNoteToAttachment: ContextMenuCommandData; | ||||||
| @@ -221,11 +223,11 @@ export type CommandMappings = { | |||||||
|     moveTabToNewWindow: CommandData; |     moveTabToNewWindow: CommandData; | ||||||
|     copyTabToNewWindow: CommandData; |     copyTabToNewWindow: CommandData; | ||||||
|     closeActiveTab: CommandData & { |     closeActiveTab: CommandData & { | ||||||
|         $el: JQuery<HTMLElement> |         $el: JQuery<HTMLElement>; | ||||||
|     }, |     }; | ||||||
|     setZoomFactorAndSave: { |     setZoomFactorAndSave: { | ||||||
|         zoomFactor: string; |         zoomFactor: string; | ||||||
|     } |     }; | ||||||
|  |  | ||||||
|     reEvaluateRightPaneVisibility: CommandData; |     reEvaluateRightPaneVisibility: CommandData; | ||||||
|     runActiveNote: CommandData; |     runActiveNote: CommandData; | ||||||
| @@ -234,18 +236,18 @@ export type CommandMappings = { | |||||||
|     }; |     }; | ||||||
|     scrollToEnd: CommandData; |     scrollToEnd: CommandData; | ||||||
|     closeThisNoteSplit: CommandData; |     closeThisNoteSplit: CommandData; | ||||||
|     moveThisNoteSplit: CommandData & { isMovingLeft: boolean; }; |     moveThisNoteSplit: CommandData & { isMovingLeft: boolean }; | ||||||
|  |  | ||||||
|     // Geomap |     // Geomap | ||||||
|     deleteFromMap: { noteId: string }, |     deleteFromMap: { noteId: string }; | ||||||
|     openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent } |     openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent }; | ||||||
|  |  | ||||||
|     toggleZenMode: CommandData; |     toggleZenMode: CommandData; | ||||||
|  |  | ||||||
|     updateAttributeList: CommandData & { attributes: Attribute[] }; |     updateAttributeList: CommandData & { attributes: Attribute[] }; | ||||||
|     saveAttributes: CommandData; |     saveAttributes: CommandData; | ||||||
|     reloadAttributes: CommandData; |     reloadAttributes: CommandData; | ||||||
|     refreshNoteList: CommandData & { noteId: string; }; |     refreshNoteList: CommandData & { noteId: string }; | ||||||
|  |  | ||||||
|     refreshResults: {}; |     refreshResults: {}; | ||||||
|     refreshSearchDefinition: {}; |     refreshSearchDefinition: {}; | ||||||
| @@ -348,7 +350,7 @@ type EventMappings = { | |||||||
|         ntxId: string | null | undefined; // TODO: deduplicate ntxId |         ntxId: string | null | undefined; // TODO: deduplicate ntxId | ||||||
|     }; |     }; | ||||||
|     tabReorder: { |     tabReorder: { | ||||||
|         ntxIdsInOrder: string[] |         ntxIdsInOrder: string[]; | ||||||
|     }; |     }; | ||||||
|     refreshNoteList: { |     refreshNoteList: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|   | |||||||
| @@ -37,7 +37,25 @@ const NOTE_TYPE_ICONS = { | |||||||
|  * end user. Those types should be used only for checking against, they are |  * end user. Those types should be used only for checking against, they are | ||||||
|  * not for direct use. |  * not for direct use. | ||||||
|  */ |  */ | ||||||
| export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap" | "taskList"; | export type NoteType = | ||||||
|  |     | "file" | ||||||
|  |     | "image" | ||||||
|  |     | "search" | ||||||
|  |     | "noteMap" | ||||||
|  |     | "launcher" | ||||||
|  |     | "doc" | ||||||
|  |     | "contentWidget" | ||||||
|  |     | "text" | ||||||
|  |     | "relationMap" | ||||||
|  |     | "render" | ||||||
|  |     | "canvas" | ||||||
|  |     | "mermaid" | ||||||
|  |     | "book" | ||||||
|  |     | "webView" | ||||||
|  |     | "code" | ||||||
|  |     | "mindMap" | ||||||
|  |     | "geoMap" | ||||||
|  |     | "taskList"; | ||||||
|  |  | ||||||
| export interface NotePathRecord { | export interface NotePathRecord { | ||||||
|     isArchived: boolean; |     isArchived: boolean; | ||||||
|   | |||||||
| @@ -264,7 +264,7 @@ export default class DesktopLayout { | |||||||
|             .child(new InfoDialog()) |             .child(new InfoDialog()) | ||||||
|             .child(new ConfirmDialog()) |             .child(new ConfirmDialog()) | ||||||
|             .child(new PromptDialog()) |             .child(new PromptDialog()) | ||||||
|             .child(new CloseZenButton()) |             .child(new CloseZenButton()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #buildLauncherPane(isHorizontal) { |     #buildLauncherPane(isHorizontal) { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import "../stylesheets/bootstrap.scss"; | |||||||
| // Required for correct loading of scripts in Electron | // Required for correct loading of scripts in Electron | ||||||
| if (typeof module === 'object') {window.module = module; module = undefined;} | if (typeof module === 'object') {window.module = module; module = undefined;} | ||||||
|  |  | ||||||
| const device = getDeviceType() | const device = getDeviceType(); | ||||||
| console.log("Setting device cookie to:", device); | console.log("Setting device cookie to:", device); | ||||||
| setCookie("trilium-device", device); | setCookie("trilium-device", device); | ||||||
|  |  | ||||||
| @@ -16,17 +16,17 @@ function setCookie(name: string, value?: string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getDeviceType() { | function getDeviceType() { | ||||||
|     if (window.location.search === '?desktop') return "desktop"; |     if (window.location.search === "?desktop") return "desktop"; | ||||||
|     if (window.location.search === '?mobile') return "mobile"; |     if (window.location.search === "?mobile") return "mobile"; | ||||||
|     return isMobile() ? "mobile" : "desktop"; |     return isMobile() ? "mobile" : "desktop"; | ||||||
| } | } | ||||||
|  |  | ||||||
| // https://stackoverflow.com/a/73731646/944162 | // https://stackoverflow.com/a/73731646/944162 | ||||||
| function isMobile() { | function isMobile() { | ||||||
|     const mQ = matchMedia?.('(pointer:coarse)'); |     const mQ = matchMedia?.("(pointer:coarse)"); | ||||||
|     if (mQ?.media === '(pointer:coarse)') return !!mQ.matches; |     if (mQ?.media === "(pointer:coarse)") return !!mQ.matches; | ||||||
|  |  | ||||||
|     if ('orientation' in window) return true; |     if ("orientation" in window) return true; | ||||||
|     const userAgentsRegEx = /\b(Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|webOS|IEMobile)\b/i |     const userAgentsRegEx = /\b(Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|webOS|IEMobile)\b/i; | ||||||
|     return userAgentsRegEx.test(navigator.userAgent) |     return userAgentsRegEx.test(navigator.userAgent); | ||||||
| } | } | ||||||
| @@ -34,8 +34,8 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener< | |||||||
|  |  | ||||||
|         const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers"; |         const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers"; | ||||||
|         const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers"; |         const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers"; | ||||||
|         const isVisibleItem = (parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers"); |         const isVisibleItem = parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers"; | ||||||
|         const isAvailableItem = (parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers"); |         const isAvailableItem = parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers"; | ||||||
|         const isItem = isVisibleItem || isAvailableItem; |         const isItem = isVisibleItem || isAvailableItem; | ||||||
|         const canBeDeleted = !note?.noteId.startsWith("_"); // fixed notes can't be deleted |         const canBeDeleted = !note?.noteId.startsWith("_"); // fixed notes can't be deleted | ||||||
|         const canBeReset = !canBeDeleted && note?.isLaunchBarConfig(); |         const canBeReset = !canBeDeleted && note?.isLaunchBarConfig(); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import { describe, it, expect } from "vitest"; | import { describe, it, expect } from "vitest"; | ||||||
| import attributeParser from "./attribute_parser.js"; | import attributeParser from "./attribute_parser.js"; | ||||||
|  |  | ||||||
|  |  | ||||||
| describe("Lexing", () => { | describe("Lexing", () => { | ||||||
|     it("simple label", () => { |     it("simple label", () => { | ||||||
|         expect(attributeParser.lex("#label").map((t: any) => t.text)).toEqual(["#label"]); |         expect(attributeParser.lex("#label").map((t: any) => t.text)).toEqual(["#label"]); | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ async function renderAttributes(attributes: FAttribute[], renderIsInheritable: b | |||||||
|     return $container; |     return $container; | ||||||
| } | } | ||||||
|  |  | ||||||
| const HIDDEN_ATTRIBUTES = [ "originalFileName", "fileSize", "template", "inherit", "cssClass", "iconClass", "pageSize", "viewType", "geolocation", "docName" ]; | const HIDDEN_ATTRIBUTES = ["originalFileName", "fileSize", "template", "inherit", "cssClass", "iconClass", "pageSize", "viewType", "geolocation", "docName"]; | ||||||
|  |  | ||||||
| async function renderNormalAttributes(note: FNote) { | async function renderNormalAttributes(note: FNote) { | ||||||
|     const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes(); |     const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes(); | ||||||
|   | |||||||
| @@ -99,8 +99,8 @@ const HIGHLIGHT_JS: Library = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const LEAFLET: Library = { | const LEAFLET: Library = { | ||||||
|     css: [ "node_modules/leaflet/dist/leaflet.css" ], |     css: ["node_modules/leaflet/dist/leaflet.css"] | ||||||
| } | }; | ||||||
|  |  | ||||||
| async function requireLibrary(library: Library) { | async function requireLibrary(library: Library) { | ||||||
|     if (library.css) { |     if (library.css) { | ||||||
|   | |||||||
| @@ -274,8 +274,8 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | |||||||
|     const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink); |     const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink); | ||||||
|  |  | ||||||
|     const ctrlKey = utils.isCtrlKey(evt); |     const ctrlKey = utils.isCtrlKey(evt); | ||||||
|     const isLeftClick = ("which" in evt && evt.which === 1); |     const isLeftClick = "which" in evt && evt.which === 1; | ||||||
|     const isMiddleClick = ("which" in evt && evt.which === 2); |     const isMiddleClick = "which" in evt && evt.which === 2; | ||||||
|     const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick; |     const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick; | ||||||
|  |  | ||||||
|     if (notePath) { |     if (notePath) { | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ export default class NoteListRenderer { | |||||||
|             parentNote, |             parentNote, | ||||||
|             noteIds, |             noteIds, | ||||||
|             showNotePath |             showNotePath | ||||||
|         } |         }; | ||||||
|  |  | ||||||
|         if (this.viewType === "list" || this.viewType === "grid") { |         if (this.viewType === "list" || this.viewType === "grid") { | ||||||
|             this.viewMode = new ListOrGridView(this.viewType, args); |             this.viewMode = new ListOrGridView(this.viewType, args); | ||||||
|   | |||||||
| @@ -147,11 +147,11 @@ async function renderTooltip(note: FNote | null) { | |||||||
|         tooltip: true, |         tooltip: true, | ||||||
|         trim: true |         trim: true | ||||||
|     }); |     }); | ||||||
|     const isContentEmpty = ($renderedContent[0].innerHTML.length === 0); |     const isContentEmpty = $renderedContent[0].innerHTML.length === 0; | ||||||
|  |  | ||||||
|     let content = ""; |     let content = ""; | ||||||
|     if (noteTitleWithPathAsSuffix) { |     if (noteTitleWithPathAsSuffix) { | ||||||
|         const classes = [ "note-tooltip-title" ]; |         const classes = ["note-tooltip-title"]; | ||||||
|         if (isContentEmpty) { |         if (isContentEmpty) { | ||||||
|             classes.push("note-no-content"); |             classes.push("note-no-content"); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -33,20 +33,20 @@ function parseDate(str: string) { | |||||||
|  |  | ||||||
| // Source: https://stackoverflow.com/a/30465299/4898894 | // Source: https://stackoverflow.com/a/30465299/4898894 | ||||||
| function getMonthsInDateRange(startDate: string, endDate: string) { | function getMonthsInDateRange(startDate: string, endDate: string) { | ||||||
|     const start = startDate.split('-'); |     const start = startDate.split("-"); | ||||||
|     const end = endDate.split('-'); |     const end = endDate.split("-"); | ||||||
|     const startYear = parseInt(start[0]); |     const startYear = parseInt(start[0]); | ||||||
|     const endYear = parseInt(end[0]); |     const endYear = parseInt(end[0]); | ||||||
|     const dates = []; |     const dates = []; | ||||||
|  |  | ||||||
|     for (let i = startYear; i <= endYear; i++) { |     for (let i = startYear; i <= endYear; i++) { | ||||||
|         const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1; |         const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1; | ||||||
|         const startMon = i === startYear ? parseInt(start[1])-1 : 0; |         const startMon = i === startYear ? parseInt(start[1]) - 1 : 0; | ||||||
|  |  | ||||||
|         for(let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j+1) { |         for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) { | ||||||
|             const month = j+1; |             const month = j + 1; | ||||||
|             const displayMonth = month < 10 ? '0'+month : month; |             const displayMonth = month < 10 ? "0" + month : month; | ||||||
|             dates.push([i, displayMonth].join('-')); |             dates.push([i, displayMonth].join("-")); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return dates; |     return dates; | ||||||
| @@ -161,7 +161,7 @@ function escapeHtml(str: string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function escapeQuotes(value: string) { | export function escapeQuotes(value: string) { | ||||||
|     return value.replaceAll("\"", """); |     return value.replaceAll('"', """); | ||||||
| } | } | ||||||
|  |  | ||||||
| function formatSize(size: number) { | function formatSize(size: number) { | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								src/public/app/types-fancytree.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								src/public/app/types-fancytree.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| interface JQueryStatic { | interface JQueryStatic { | ||||||
|     ui: JQueryUI.UI; |     ui: JQueryUI.UI; | ||||||
| }; | } | ||||||
|  |  | ||||||
| declare namespace JQueryUI { | declare namespace JQueryUI { | ||||||
|     interface UI { |     interface UI { | ||||||
| @@ -679,13 +679,13 @@ declare namespace Fancytree { | |||||||
|         activate = 1, |         activate = 1, | ||||||
|         expand = 2, |         expand = 2, | ||||||
|         activate_and_expand = 3, |         activate_and_expand = 3, | ||||||
|         activate_dblclick_expands = 4, |         activate_dblclick_expands = 4 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     enum FancytreeSelectMode { |     enum FancytreeSelectMode { | ||||||
|         single = 1, |         single = 1, | ||||||
|         multi = 2, |         multi = 2, | ||||||
|         mutlti_hier = 3, |         mutlti_hier = 3 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** Context object passed to events and hook functions. */ |     /** Context object passed to events and hook functions. */ | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								src/public/app/types-lib.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/public/app/types-lib.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,7 @@ declare module "draggabilly" { | |||||||
|             containment: HTMLElement |             containment: HTMLElement | ||||||
|         }); |         }); | ||||||
|         element: HTMLElement; |         element: HTMLElement; | ||||||
|         on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback) |         on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback); | ||||||
|         dragEnd(); |         dragEnd(); | ||||||
|         isDragging: boolean; |         isDragging: boolean; | ||||||
|         positionDrag: () => void; |         positionDrag: () => void; | ||||||
| @@ -21,6 +21,6 @@ declare module "draggabilly" { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| declare module '@mind-elixir/node-menu' { | declare module "@mind-elixir/node-menu" { | ||||||
|     export default mindmap; |     export default mindmap; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ export default class BookmarkButtons extends FlexContainer<Component> { | |||||||
|                 : new OpenNoteButtonWidget(note).class("launcher-button"); |                 : new OpenNoteButtonWidget(note).class("launcher-button"); | ||||||
|  |  | ||||||
|             if (this.settings.titlePlacement) { |             if (this.settings.titlePlacement) { | ||||||
|                 if (!('settings' in buttonWidget)) { |                 if (!("settings" in buttonWidget)) { | ||||||
|                     (buttonWidget as any).settings = {}; |                     (buttonWidget as any).settings = {}; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import appContext from "../../components/app_context.js"; | |||||||
| import openService from "../../services/open.js"; | import openService from "../../services/open.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import { Dropdown } from "bootstrap"; | import { Dropdown } from "bootstrap"; | ||||||
| import type attachmentsApiRoute from "../../../../routes/api/attachments.js" | import type attachmentsApiRoute from "../../../../routes/api/attachments.js"; | ||||||
| import type FAttachment from "../../entities/fattachment.js"; | import type FAttachment from "../../entities/fattachment.js"; | ||||||
| import type AttachmentDetailWidget from "../attachment_detail.js"; | import type AttachmentDetailWidget from "../attachment_detail.js"; | ||||||
|  |  | ||||||
| @@ -105,7 +105,6 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|  |  | ||||||
|         this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input"); |         this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input"); | ||||||
|         this.$uploadNewRevisionInput.on("change", async () => { |         this.$uploadNewRevisionInput.on("change", async () => { | ||||||
|  |  | ||||||
|             const fileToUpload = this.$uploadNewRevisionInput[0].files?.item(0); // copy to allow reset below |             const fileToUpload = this.$uploadNewRevisionInput[0].files?.item(0); // copy to allow reset below | ||||||
|             this.$uploadNewRevisionInput.val(""); |             this.$uploadNewRevisionInput.val(""); | ||||||
|             if (fileToUpload) { |             if (fileToUpload) { | ||||||
| @@ -131,7 +130,6 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|             const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); |             const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); | ||||||
|             $openAttachmentCustomButton.addClass("disabled").append($('<span class="bx bx-info-circle disabled-tooltip" />').attr("title", t("attachments_actions.open_custom_client_only"))); |             $openAttachmentCustomButton.addClass("disabled").append($('<span class="bx bx-info-circle disabled-tooltip" />').attr("title", t("attachments_actions.open_custom_client_only"))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async openAttachmentCommand() { |     async openAttachmentCommand() { | ||||||
| @@ -170,7 +168,6 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         const { note: newNote } = await server.post<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`attachments/${this.attachmentId}/convert-to-note`); |         const { note: newNote } = await server.post<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`attachments/${this.attachmentId}/convert-to-note`); | ||||||
|         toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title })); |         toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title })); | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|   | |||||||
| @@ -139,7 +139,8 @@ export default class RibbonContainer extends NoteContextAwareWidget { | |||||||
|         return super.isEnabled() && this.noteContext?.viewScope?.viewMode === "default"; |         return super.isEnabled() && this.noteContext?.viewScope?.viewMode === "default"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ribbon(widget: NoteContextAwareWidget) { // TODO: Base class |     ribbon(widget: NoteContextAwareWidget) { | ||||||
|  |         // TODO: Base class | ||||||
|         super.child(widget); |         super.child(widget); | ||||||
|  |  | ||||||
|         this.ribbonWidgets.push(widget); |         this.ribbonWidgets.push(widget); | ||||||
| @@ -236,7 +237,12 @@ export default class RibbonContainer extends NoteContextAwareWidget { | |||||||
|             const $ribbonTitle = $('<div class="ribbon-tab-title">') |             const $ribbonTitle = $('<div class="ribbon-tab-title">') | ||||||
|                 .attr("data-ribbon-component-id", ribbonWidget.componentId) |                 .attr("data-ribbon-component-id", ribbonWidget.componentId) | ||||||
|                 .attr("data-ribbon-component-name", (ribbonWidget as any).name as string) // TODO: base class for ribbon widgets |                 .attr("data-ribbon-component-name", (ribbonWidget as any).name as string) // TODO: base class for ribbon widgets | ||||||
|                 .append($('<span class="ribbon-tab-title-icon">').addClass(ret.icon).attr("title", ret.title).attr("data-toggle-command", (ribbonWidget as any).toggleCommand)) // TODO: base class |                 .append( | ||||||
|  |                     $('<span class="ribbon-tab-title-icon">') | ||||||
|  |                         .addClass(ret.icon) | ||||||
|  |                         .attr("title", ret.title) | ||||||
|  |                         .attr("data-toggle-command", (ribbonWidget as any).toggleCommand) | ||||||
|  |                 ) // TODO: base class | ||||||
|                 .append(" ") |                 .append(" ") | ||||||
|                 .append($('<span class="ribbon-tab-title-label">').text(ret.title)); |                 .append($('<span class="ribbon-tab-title-label">').text(ret.title)); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -250,7 +250,7 @@ ws.subscribeToMessages(async (message) => { | |||||||
|             message: message, |             message: message, | ||||||
|             icon: "arrow-square-up-right" |             icon: "arrow-square-up-right" | ||||||
|         }; |         }; | ||||||
|     }; |     } | ||||||
|  |  | ||||||
|     if (message.taskType !== "export") { |     if (message.taskType !== "export") { | ||||||
|         return; |         return; | ||||||
|   | |||||||
| @@ -320,7 +320,8 @@ export default class RevisionsDialog extends BasicWidget { | |||||||
|                         // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note |                         // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note | ||||||
|                         .attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`) |                         .attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`) | ||||||
|                         .css("max-width", "100%") |                         .css("max-width", "100%") | ||||||
|                         .css("max-height", "100%").html() |                         .css("max-height", "100%") | ||||||
|  |                         .html() | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|         } else if (revisionItem.type === "file") { |         } else if (revisionItem.type === "file") { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { t } from "../../services/i18n.js"; | import { t } from "../../services/i18n.js"; | ||||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js" | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
|  |  | ||||||
| const TPL = `\ | const TPL = `\ | ||||||
| <div class="geo-map-buttons"> | <div class="geo-map-buttons"> | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget { | |||||||
|         if (this.note && this.note.type !== "book" && byNoteType[this.note.type]) { |         if (this.note && this.note.type !== "book" && byNoteType[this.note.type]) { | ||||||
|             this.helpNoteIdToOpen = byNoteType[this.note.type]; |             this.helpNoteIdToOpen = byNoteType[this.note.type]; | ||||||
|         } else if (this.note && this.note.type === "book") { |         } else if (this.note && this.note.type === "book") { | ||||||
|             this.helpNoteIdToOpen = byBookType[this.note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""] |             this.helpNoteIdToOpen = byBookType[(this.note.getAttributeValue("label", "viewType") as ViewTypeOptions) ?? ""]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return !!this.helpNoteIdToOpen; |         return !!this.helpNoteIdToOpen; | ||||||
| @@ -64,7 +64,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget { | |||||||
|             const targetNote = `_help_${this.helpNoteIdToOpen}`; |             const targetNote = `_help_${this.helpNoteIdToOpen}`; | ||||||
|             const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); |             const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); | ||||||
|             const viewScope: ViewScope = { |             const viewScope: ViewScope = { | ||||||
|                 viewMode: "contextual-help", |                 viewMode: "contextual-help" | ||||||
|             }; |             }; | ||||||
|             if (!helpSubcontext) { |             if (!helpSubcontext) { | ||||||
|                 // The help is not already open, open a new split with it. |                 // The help is not already open, open a new split with it. | ||||||
| @@ -74,7 +74,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget { | |||||||
|                     notePath: targetNote, |                     notePath: targetNote, | ||||||
|                     hoistedNoteId: "_help", |                     hoistedNoteId: "_help", | ||||||
|                     viewScope |                     viewScope | ||||||
|                 }) |                 }); | ||||||
|             } else { |             } else { | ||||||
|                 // There is already a help window open, make sure it opens on the right note. |                 // There is already a help window open, make sure it opens on the right note. | ||||||
|                 helpSubcontext.setNote(targetNote, { viewScope }); |                 helpSubcontext.setNote(targetNote, { viewScope }); | ||||||
|   | |||||||
| @@ -157,7 +157,7 @@ export default class BacklinksWidget extends NoteContextAwareWidget { | |||||||
|             if (backlink.relationName) { |             if (backlink.relationName) { | ||||||
|                 $item.append($("<p>").text(`${t("zpetne_odkazy.relation")}: ${backlink.relationName}`)); |                 $item.append($("<p>").text(`${t("zpetne_odkazy.relation")}: ${backlink.relationName}`)); | ||||||
|             } else { |             } else { | ||||||
|                 $item.append(...backlink.excerpts ?? []); |                 $item.append(...(backlink.excerpts ?? [])); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             this.$items.append($item); |             this.$items.append($item); | ||||||
|   | |||||||
| @@ -19,10 +19,10 @@ const TPL = `\ | |||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|     <div class="geo-map-container"></div> |     <div class="geo-map-container"></div> | ||||||
| </div>` | </div>`; | ||||||
|  |  | ||||||
| export type Leaflet = typeof import("leaflet"); | export type Leaflet = typeof import("leaflet"); | ||||||
| export type InitCallback = ((L: Leaflet) => void); | export type InitCallback = (L: Leaflet) => void; | ||||||
|  |  | ||||||
| export default class GeoMapWidget extends NoteContextAwareWidget { | export default class GeoMapWidget extends NoteContextAwareWidget { | ||||||
|  |  | ||||||
| @@ -40,8 +40,7 @@ export default class GeoMapWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         this.$container = this.$widget.find(".geo-map-container"); |         this.$container = this.$widget.find(".geo-map-container"); | ||||||
|  |  | ||||||
|         library_loader.requireLibrary(library_loader.LEAFLET) |         library_loader.requireLibrary(library_loader.LEAFLET).then(async () => { | ||||||
|             .then(async () => { |  | ||||||
|             const L = (await import("leaflet")).default; |             const L = (await import("leaflet")).default; | ||||||
|  |  | ||||||
|             const map = L.map(this.$container[0], { |             const map = L.map(this.$container[0], { | ||||||
| @@ -53,7 +52,7 @@ export default class GeoMapWidget extends NoteContextAwareWidget { | |||||||
|                 this.initCallback(L); |                 this.initCallback(L); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|                 L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { |             L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { | ||||||
|                 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', |                 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', | ||||||
|                 detectRetina: true |                 detectRetina: true | ||||||
|             }).addTo(map); |             }).addTo(map); | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         mermaid.mermaidAPI.initialize({ |         mermaid.mermaidAPI.initialize({ | ||||||
|             startOnLoad: false, |             startOnLoad: false, | ||||||
|             ...getMermaidConfig() as any |             ...(getMermaidConfig() as any) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.$display.empty(); |         this.$display.empty(); | ||||||
|   | |||||||
| @@ -81,7 +81,16 @@ const typeWidgetClasses = { | |||||||
|  * A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one, |  * A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one, | ||||||
|  * for protected session or attachment information. |  * for protected session or attachment information. | ||||||
|  */ |  */ | ||||||
| type ExtendedNoteType = Exclude<NoteType, "mermaid" | "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession"; | type ExtendedNoteType = | ||||||
|  |     | Exclude<NoteType, "mermaid" | "launcher" | "text" | "code"> | ||||||
|  |     | "empty" | ||||||
|  |     | "readOnlyCode" | ||||||
|  |     | "readOnlyText" | ||||||
|  |     | "editableText" | ||||||
|  |     | "editableCode" | ||||||
|  |     | "attachmentDetail" | ||||||
|  |     | "attachmentList" | ||||||
|  |     | "protectedSession"; | ||||||
|  |  | ||||||
| export default class NoteDetailWidget extends NoteContextAwareWidget { | export default class NoteDetailWidget extends NoteContextAwareWidget { | ||||||
|  |  | ||||||
| @@ -329,7 +338,9 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             const label = attrs.find( |             const label = attrs.find( | ||||||
|                 (attr) => |                 (attr) => | ||||||
|                     attr.type === "label" && ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note) |                     attr.type === "label" && | ||||||
|  |                     ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") && | ||||||
|  |                     attributeService.isAffecting(attr, this.note) | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|             const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note)); |             const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note)); | ||||||
|   | |||||||
| @@ -139,7 +139,7 @@ interface NotesAndRelationsData { | |||||||
|         source: string; |         source: string; | ||||||
|         target: string; |         target: string; | ||||||
|         name: string; |         name: string; | ||||||
|     }[] |     }[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Replace | // Replace | ||||||
| @@ -152,7 +152,7 @@ interface ResponseLink { | |||||||
|  |  | ||||||
| interface PostNotesMapResponse { | interface PostNotesMapResponse { | ||||||
|     notes: string[]; |     notes: string[]; | ||||||
|     links: ResponseLink[], |     links: ResponseLink[]; | ||||||
|     noteIdToDescendantCountMap: Record<string, number>; |     noteIdToDescendantCountMap: Record<string, number>; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -160,7 +160,7 @@ interface GroupedLink { | |||||||
|     id: string; |     id: string; | ||||||
|     sourceNoteId: string; |     sourceNoteId: string; | ||||||
|     targetNoteId: string; |     targetNoteId: string; | ||||||
|     names: string[] |     names: string[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface CssData { | interface CssData { | ||||||
| @@ -313,9 +313,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|                 ctx.fillStyle = color; |                 ctx.fillStyle = color; | ||||||
|                 ctx.beginPath(); |                 ctx.beginPath(); | ||||||
|                 if (node.x && node.y) { |                 if (node.x && node.y) { | ||||||
|                     ctx.arc(node.x, node.y, |                     ctx.arc(node.x, node.y, this.noteIdToSizeMap[node.id], 0, 2 * Math.PI, false); | ||||||
|                         this.noteIdToSizeMap[node.id], 0, |  | ||||||
|                         2 * Math.PI, false); |  | ||||||
|                 } |                 } | ||||||
|                 ctx.fill(); |                 ctx.fill(); | ||||||
|             }) |             }) | ||||||
| @@ -467,13 +465,13 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (source.x && source.y && target.x && target.y) { |         if (source.x && source.y && target.x && target.y) { | ||||||
|             const x = ((source.x) + (target.x)) / 2; |             const x = (source.x + target.x) / 2; | ||||||
|             const y = ((source.y) + (target.y)) / 2; |             const y = (source.y + target.y) / 2; | ||||||
|             ctx.save(); |             ctx.save(); | ||||||
|             ctx.translate(x, y); |             ctx.translate(x, y); | ||||||
|  |  | ||||||
|             const deltaY = (source.y) - (target.y); |             const deltaY = source.y - target.y; | ||||||
|             const deltaX = (source.x) - (target.x); |             const deltaX = source.x - target.x; | ||||||
|  |  | ||||||
|             let angle = Math.atan2(deltaY, deltaX); |             let angle = Math.atan2(deltaY, deltaX); | ||||||
|             let moveY = 2; |             let moveY = 2; | ||||||
|   | |||||||
| @@ -91,7 +91,7 @@ export default class NoteTitleWidget extends NoteContextAwareWidget { | |||||||
|     async refreshWithNote(note: FNote) { |     async refreshWithNote(note: FNote) { | ||||||
|         const isReadOnly = (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) || utils.isLaunchBarConfig(note.noteId) || this.noteContext?.viewScope?.viewMode !== "default"; |         const isReadOnly = (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) || utils.isLaunchBarConfig(note.noteId) || this.noteContext?.viewScope?.viewMode !== "default"; | ||||||
|  |  | ||||||
|         this.$noteTitle.val(isReadOnly ? await this.noteContext?.getNavigationTitle() || "" : note.title); |         this.$noteTitle.val(isReadOnly ? (await this.noteContext?.getNavigationTitle()) || "" : note.title); | ||||||
|         this.$noteTitle.prop("readonly", isReadOnly); |         this.$noteTitle.prop("readonly", isReadOnly); | ||||||
|  |  | ||||||
|         this.setProtectedStatus(note); |         this.setProtectedStatus(note); | ||||||
|   | |||||||
| @@ -159,11 +159,11 @@ interface CreateLauncherResponse { | |||||||
|     message: string; |     message: string; | ||||||
|     note: { |     note: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface ExpandedSubtreeResponse { | interface ExpandedSubtreeResponse { | ||||||
|     branchIds: string[] |     branchIds: string[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface Node extends Fancytree.NodeData { | interface Node extends Fancytree.NodeData { | ||||||
| @@ -180,7 +180,6 @@ interface RefreshContext { | |||||||
| } | } | ||||||
|  |  | ||||||
| export default class NoteTreeWidget extends NoteContextAwareWidget { | export default class NoteTreeWidget extends NoteContextAwareWidget { | ||||||
|  |  | ||||||
|     private $tree!: JQuery<HTMLElement>; |     private $tree!: JQuery<HTMLElement>; | ||||||
|     private $treeActions!: JQuery<HTMLElement>; |     private $treeActions!: JQuery<HTMLElement>; | ||||||
|     private $treeSettingsButton!: JQuery<HTMLElement>; |     private $treeSettingsButton!: JQuery<HTMLElement>; | ||||||
| @@ -571,10 +570,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             clones: { |             clones: { | ||||||
|                 highlightActiveClones: true |                 highlightActiveClones: true | ||||||
|             }, |             }, | ||||||
|             enhanceTitle: async function (event: Event, data: { |             enhanceTitle: async function ( | ||||||
|  |                 event: Event, | ||||||
|  |                 data: { | ||||||
|                     node: Fancytree.FancytreeNode; |                     node: Fancytree.FancytreeNode; | ||||||
|                     noteId: string; |                     noteId: string; | ||||||
|             }) { |                 } | ||||||
|  |             ) { | ||||||
|                 const node = data.node; |                 const node = data.node; | ||||||
|  |  | ||||||
|                 if (!node.data.noteId) { |                 if (!node.data.noteId) { | ||||||
|   | |||||||
| @@ -126,7 +126,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (![ "list", "grid", "calendar"].includes(type)) { |         if (!["list", "grid", "calendar"].includes(type)) { | ||||||
|             throw new Error(t("book_properties.invalid_view_type", { type })); |             throw new Error(t("book_properties.invalid_view_type", { type })); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -122,7 +122,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget { | |||||||
|         const { top } = this.$widget[0].getBoundingClientRect(); |         const { top } = this.$widget[0].getBoundingClientRect(); | ||||||
|  |  | ||||||
|         const height = ($(window).height() ?? 0) - top; |         const height = ($(window).height() ?? 0) - top; | ||||||
|         const width = (this.$widget.width() ?? 0); |         const width = this.$widget.width() ?? 0; | ||||||
|  |  | ||||||
|         this.$widget.find(".note-map-container") |         this.$widget.find(".note-map-container") | ||||||
|             .height(height) |             .height(height) | ||||||
|   | |||||||
| @@ -135,8 +135,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { |     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|         if (loadResults.getBranchRows().find((branch) => branch.noteId === this.noteId) || |         if (loadResults.getBranchRows().find((branch) => branch.noteId === this.noteId) || (this.noteId != null && loadResults.isNoteReloaded(this.noteId))) { | ||||||
|             (this.noteId != null && loadResults.isNoteReloaded(this.noteId))) { |  | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -46,6 +46,9 @@ export default class NotePropertiesWidget extends NoteContextAwareWidget { | |||||||
|     async refreshWithNote(note: FNote) { |     async refreshWithNote(note: FNote) { | ||||||
|         const pageUrl = note.getLabelValue("pageUrl"); |         const pageUrl = note.getLabelValue("pageUrl"); | ||||||
|  |  | ||||||
|         this.$pageUrl.attr("href", pageUrl).attr("title", pageUrl).text(pageUrl ?? ""); |         this.$pageUrl | ||||||
|  |             .attr("href", pageUrl) | ||||||
|  |             .attr("title", pageUrl) | ||||||
|  |             .text(pageUrl ?? ""); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,7 +40,6 @@ interface SimilarNote { | |||||||
|     noteId: string; |     noteId: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| export default class SimilarNotesWidget extends NoteContextAwareWidget { | export default class SimilarNotesWidget extends NoteContextAwareWidget { | ||||||
|  |  | ||||||
|     private $similarNotesWrapper!: JQuery<HTMLElement>; |     private $similarNotesWrapper!: JQuery<HTMLElement>; | ||||||
|   | |||||||
| @@ -185,7 +185,7 @@ export default class SwitchWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|     /** Gets or sets whether the switch is enabled. */ |     /** Gets or sets whether the switch is enabled. */ | ||||||
|     get canToggle() { |     get canToggle() { | ||||||
|         return (!this.$switchButton.hasClass("disabled")); |         return !this.$switchButton.hasClass("disabled"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     set canToggle(isEnabled) { |     set canToggle(isEnabled) { | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ export default class SyncStatusWidget extends BasicWidget { | |||||||
|     settings: { |     settings: { | ||||||
|         // TriliumNextTODO: narrow types and use TitlePlacement Type |         // TriliumNextTODO: narrow types and use TitlePlacement Type | ||||||
|         titlePlacement: string; |         titlePlacement: string; | ||||||
|     } |     }; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| @@ -106,7 +106,6 @@ export default class SyncStatusWidget extends BasicWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`)[0], { |         Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`)[0], { | ||||||
|             html: true, |             html: true, | ||||||
|             placement: this.settings.titlePlacement, |             placement: this.settings.titlePlacement, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ const TAB_CONTAINER_MIN_WIDTH = 24; | |||||||
| const TAB_CONTAINER_MAX_WIDTH = 240; | const TAB_CONTAINER_MAX_WIDTH = 240; | ||||||
| const TAB_CONTAINER_LEFT_PADDING = 5; | const TAB_CONTAINER_LEFT_PADDING = 5; | ||||||
| const NEW_TAB_WIDTH = 32; | const NEW_TAB_WIDTH = 32; | ||||||
| const MIN_FILLER_WIDTH = (isDesktop ? 50 : 15); | const MIN_FILLER_WIDTH = isDesktop ? 50 : 15; | ||||||
| const MARGIN_WIDTH = 5; | const MARGIN_WIDTH = 5; | ||||||
|  |  | ||||||
| const TAB_SIZE_SMALL = 84; | const TAB_SIZE_SMALL = 84; | ||||||
|   | |||||||
| @@ -55,8 +55,8 @@ const TPL = `<div class="toc-widget"> | |||||||
| </div>`; | </div>`; | ||||||
|  |  | ||||||
| interface Toc { | interface Toc { | ||||||
|     $toc: JQuery<HTMLElement>, |     $toc: JQuery<HTMLElement>; | ||||||
|     headingCount: number |     headingCount: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default class TocWidget extends RightPanelWidget { | export default class TocWidget extends RightPanelWidget { | ||||||
| @@ -89,8 +89,8 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const isHelpNote = (this.note.type === "doc" && this.note.noteId.startsWith("_help")); |         const isHelpNote = this.note.type === "doc" && this.note.noteId.startsWith("_help"); | ||||||
|         const isTextNote = (this.note.type === "text"); |         const isTextNote = this.note.type === "text"; | ||||||
|         const isNoteSupported = isTextNote || isHelpNote; |         const isNoteSupported = isTextNote || isHelpNote; | ||||||
|  |  | ||||||
|         return isNoteSupported |         return isNoteSupported | ||||||
| @@ -156,7 +156,7 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|  |  | ||||||
|         const tocLabelValue = this.tocLabelValue; |         const tocLabelValue = this.tocLabelValue; | ||||||
|  |  | ||||||
|         const visible = (tocLabelValue === "" || tocLabelValue === "show") || headingCount >= (options.getInt("minTocHeadings") ?? 0); |         const visible = tocLabelValue === "" || tocLabelValue === "show" || headingCount >= (options.getInt("minTocHeadings") ?? 0); | ||||||
|         this.toggleInt(visible); |         this.toggleInt(visible); | ||||||
|         if (this.noteContext?.viewScope) { |         if (this.noteContext?.viewScope) { | ||||||
|             this.noteContext.viewScope.tocPreviousVisible = visible; |             this.noteContext.viewScope.tocPreviousVisible = visible; | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { | |||||||
|  |  | ||||||
|         this.$linksWrapper.empty().append( |         this.$linksWrapper.empty().append( | ||||||
|             t("attachment_detail.owning_note"), |             t("attachment_detail.owning_note"), | ||||||
|             (await linkService.createLink(this.noteId)), |             await linkService.createLink(this.noteId), | ||||||
|             t("attachment_detail.you_can_also_open"), |             t("attachment_detail.you_can_also_open"), | ||||||
|             await linkService.createLink(this.noteId, { |             await linkService.createLink(this.noteId, { | ||||||
|                 title: t("attachment_detail.list_of_all_attachments"), |                 title: t("attachment_detail.list_of_all_attachments"), | ||||||
| @@ -74,7 +74,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { | |||||||
|             $helpButton |             $helpButton | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         const attachment = (this.attachmentId) ? await froca.getAttachment(this.attachmentId, true) : null; |         const attachment = this.attachmentId ? await froca.getAttachment(this.attachmentId, true) : null; | ||||||
|  |  | ||||||
|         if (!attachment) { |         if (!attachment) { | ||||||
|             this.$wrapper.html("<strong>" + t("attachment_detail.attachment_deleted") + "</strong>"); |             this.$wrapper.html("<strong>" + t("attachment_detail.attachment_deleted") + "</strong>"); | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ export default class AttachmentListTypeWidget extends TypeWidget { | |||||||
|                     .text(t("attachment_list.upload_attachments")) |                     .text(t("attachment_list.upload_attachments")) | ||||||
|                     .on("click", () => { |                     .on("click", () => { | ||||||
|                         if (this.noteId) { |                         if (this.noteId) { | ||||||
|                             this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId }) |                             this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId }); | ||||||
|                         } |                         } | ||||||
|                     }), |                     }), | ||||||
|                 $helpButton |                 $helpButton | ||||||
|   | |||||||
| @@ -36,9 +36,7 @@ export default class BookTypeWidget extends TypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note: FNote) { |     async doRefresh(note: FNote) { | ||||||
|         this.$helpNoChildren.toggle( |         this.$helpNoChildren.toggle(!this.note?.hasChildren() && this.note?.getAttributeValue("label", "viewType") !== "calendar"); | ||||||
|             !this.note?.hasChildren() |  | ||||||
|             && this.note?.getAttributeValue("label", "viewType") !== "calendar"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { |     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|   | |||||||
| @@ -59,9 +59,9 @@ const TPL = ` | |||||||
| `; | `; | ||||||
|  |  | ||||||
| interface CanvasContent { | interface CanvasContent { | ||||||
|     elements: ExcalidrawElement[], |     elements: ExcalidrawElement[]; | ||||||
|     files: BinaryFileData[], |     files: BinaryFileData[]; | ||||||
|     appState: Partial<AppState> |     appState: Partial<AppState>; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface AttachmentMetadata { | interface AttachmentMetadata { | ||||||
| @@ -198,7 +198,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget { | |||||||
|         } |         } | ||||||
|         (window.process.env as any).PREACT = false; |         (window.process.env as any).PREACT = false; | ||||||
|  |  | ||||||
|         const excalidraw = (await import("@excalidraw/excalidraw")); |         const excalidraw = await import("@excalidraw/excalidraw"); | ||||||
|         this.excalidrawLib = excalidraw; |         this.excalidrawLib = excalidraw; | ||||||
|  |  | ||||||
|         const { createRoot } = await import("react-dom/client"); |         const { createRoot } = await import("react-dom/client"); | ||||||
| @@ -476,7 +476,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget { | |||||||
|     createExcalidrawReactApp(react: typeof React, excalidrawComponent: React.MemoExoticComponent<(props: ExcalidrawProps) => JSX.Element>) { |     createExcalidrawReactApp(react: typeof React, excalidrawComponent: React.MemoExoticComponent<(props: ExcalidrawProps) => JSX.Element>) { | ||||||
|         const excalidrawWrapperRef = react.useRef<HTMLElement>(null); |         const excalidrawWrapperRef = react.useRef<HTMLElement>(null); | ||||||
|         this.excalidrawWrapperRef = excalidrawWrapperRef; |         this.excalidrawWrapperRef = excalidrawWrapperRef; | ||||||
|         const [dimensions, setDimensions] = react.useState<{ width?: number, height?: number}>({ |         const [dimensions, setDimensions] = react.useState<{ width?: number; height?: number }>({ | ||||||
|             width: undefined, |             width: undefined, | ||||||
|             height: undefined |             height: undefined | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -7,91 +7,78 @@ export function buildConfig() { | |||||||
|         image: { |         image: { | ||||||
|             styles: { |             styles: { | ||||||
|                 options: [ |                 options: [ | ||||||
|                     'inline', |                     "inline", | ||||||
|                     'alignBlockLeft', |                     "alignBlockLeft", | ||||||
|                     'alignCenter', |                     "alignCenter", | ||||||
|                     'alignBlockRight', |                     "alignBlockRight", | ||||||
|                     'alignLeft', |                     "alignLeft", | ||||||
|                     'alignRight', |                     "alignRight", | ||||||
|                     'full', // full and side are for BC since the old images have been created with these styles |                     "full", // full and side are for BC since the old images have been created with these styles | ||||||
|                     'side' |                     "side" | ||||||
|                 ] |                 ] | ||||||
|             }, |             }, | ||||||
|             resizeOptions: [ |             resizeOptions: [ | ||||||
|                 { |                 { | ||||||
|                     name: 'imageResize:original', |                     name: "imageResize:original", | ||||||
|                     value: null, |                     value: null, | ||||||
|                     icon: 'original' |                     icon: "original" | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     name: 'imageResize:25', |                     name: "imageResize:25", | ||||||
|                     value: '25', |                     value: "25", | ||||||
|                     icon: 'small' |                     icon: "small" | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     name: 'imageResize:50', |                     name: "imageResize:50", | ||||||
|                     value: '50', |                     value: "50", | ||||||
|                     icon: 'medium' |                     icon: "medium" | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     name: 'imageResize:75', |                     name: "imageResize:75", | ||||||
|                     value: '75', |                     value: "75", | ||||||
|                     icon: 'medium' |                     icon: "medium" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             toolbar: [ |             toolbar: [ | ||||||
|                 // Image styles, see https://ckeditor.com/docs/ckeditor5/latest/features/images/images-styles.html#demo. |                 // Image styles, see https://ckeditor.com/docs/ckeditor5/latest/features/images/images-styles.html#demo. | ||||||
|                 'imageStyle:inline', |                 "imageStyle:inline", | ||||||
|                 'imageStyle:alignCenter', |                 "imageStyle:alignCenter", | ||||||
|                 { |                 { | ||||||
|                     name: "imageStyle:wrapText", |                     name: "imageStyle:wrapText", | ||||||
|                     title: "Wrap text", |                     title: "Wrap text", | ||||||
|                     items: [ |                     items: ["imageStyle:alignLeft", "imageStyle:alignRight"], | ||||||
|                         'imageStyle:alignLeft', |                     defaultItem: "imageStyle:alignRight" | ||||||
|                         'imageStyle:alignRight', |  | ||||||
|                     ], |  | ||||||
|                     defaultItem: 'imageStyle:alignRight' |  | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     name: "imageStyle:block", |                     name: "imageStyle:block", | ||||||
|                     title: "Block align", |                     title: "Block align", | ||||||
|                     items: [ |                     items: ["imageStyle:alignBlockLeft", "imageStyle:alignBlockRight"], | ||||||
|                         'imageStyle:alignBlockLeft', |                     defaultItem: "imageStyle:alignBlockLeft" | ||||||
|                         'imageStyle:alignBlockRight' |  | ||||||
|                     ], |  | ||||||
|                     defaultItem: "imageStyle:alignBlockLeft", |  | ||||||
|                 }, |                 }, | ||||||
|                 '|', |                 "|", | ||||||
|                 'imageResize:25', |                 "imageResize:25", | ||||||
|                 'imageResize:50', |                 "imageResize:50", | ||||||
|                 'imageResize:original', |                 "imageResize:original", | ||||||
|                 '|', |                 "|", | ||||||
|                 'toggleImageCaption' |                 "toggleImageCaption" | ||||||
|             ], |             ], | ||||||
|             upload: { |             upload: { | ||||||
|                 types: [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff', 'svg', 'svg+xml', 'avif' ] |                 types: ["jpeg", "png", "gif", "bmp", "webp", "tiff", "svg", "svg+xml", "avif"] | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         heading: { |         heading: { | ||||||
|             options: [ |             options: [ | ||||||
|                 { model: 'paragraph' as const, title: 'Paragraph', class: 'ck-heading_paragraph' }, |                 { model: "paragraph" as const, title: "Paragraph", class: "ck-heading_paragraph" }, | ||||||
|                 // // heading1 is not used since that should be a note's title |                 // // heading1 is not used since that should be a note's title | ||||||
|                 { model: 'heading2' as const, view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }, |                 { model: "heading2" as const, view: "h2", title: "Heading 2", class: "ck-heading_heading2" }, | ||||||
|                 { model: 'heading3' as const, view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }, |                 { model: "heading3" as const, view: "h3", title: "Heading 3", class: "ck-heading_heading3" }, | ||||||
|                 { model: 'heading4' as const, view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }, |                 { model: "heading4" as const, view: "h4", title: "Heading 4", class: "ck-heading_heading4" }, | ||||||
|                 { model: 'heading5' as const, view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' }, |                 { model: "heading5" as const, view: "h5", title: "Heading 5", class: "ck-heading_heading5" }, | ||||||
|                 { model: 'heading6' as const, view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' } |                 { model: "heading6" as const, view: "h6", title: "Heading 6", class: "ck-heading_heading6" } | ||||||
|             ] |             ] | ||||||
|         }, |         }, | ||||||
|         table: { |         table: { | ||||||
|             contentToolbar: [ |             contentToolbar: ["tableColumn", "tableRow", "mergeTableCells", "tableProperties", "tableCellProperties", "toggleTableCaption"] | ||||||
|                 'tableColumn', |  | ||||||
|                 'tableRow', |  | ||||||
|                 'mergeTableCells', |  | ||||||
|                 'tableProperties', |  | ||||||
|                 'tableCellProperties', |  | ||||||
|                 'toggleTableCaption' |  | ||||||
|             ] |  | ||||||
|         }, |         }, | ||||||
|         list: { |         list: { | ||||||
|             properties: { |             properties: { | ||||||
| @@ -101,17 +88,17 @@ export function buildConfig() { | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         link: { |         link: { | ||||||
|             defaultProtocol: 'https://', |             defaultProtocol: "https://", | ||||||
|             allowedProtocols: ALLOWED_PROTOCOLS |             allowedProtocols: ALLOWED_PROTOCOLS | ||||||
|         }, |         }, | ||||||
|         // This value must be kept in sync with the language defined in webpack.config.js. |         // This value must be kept in sync with the language defined in webpack.config.js. | ||||||
|         language: 'en' |         language: "en" | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function buildToolbarConfig(isClassicToolbar: boolean) { | export function buildToolbarConfig(isClassicToolbar: boolean) { | ||||||
|     if (isClassicToolbar) { |     if (isClassicToolbar) { | ||||||
|         const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true" |         const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true"; | ||||||
|         return buildClassicToolbar(multilineToolbar); |         return buildClassicToolbar(multilineToolbar); | ||||||
|     } else { |     } else { | ||||||
|         return buildFloatingToolbar(); |         return buildFloatingToolbar(); | ||||||
| @@ -123,101 +110,92 @@ function buildClassicToolbar(multilineToolbar: boolean) { | |||||||
|     return { |     return { | ||||||
|         toolbar: { |         toolbar: { | ||||||
|             items: [ |             items: [ | ||||||
|                 'heading', 'fontSize', |                 "heading", | ||||||
|                 '|', |                 "fontSize", | ||||||
|                 'bold', 'italic', |                 "|", | ||||||
|  |                 "bold", | ||||||
|  |                 "italic", | ||||||
|                 { |                 { | ||||||
|                     label: "Text formatting", |                     label: "Text formatting", | ||||||
|                     icon: "text", |                     icon: "text", | ||||||
|                     items: [ |                     items: ["underline", "strikethrough", "superscript", "subscript", "code"] | ||||||
|                         'underline', |  | ||||||
|                         'strikethrough', |  | ||||||
|                         'superscript', |  | ||||||
|                         'subscript', |  | ||||||
|                         'code', |  | ||||||
|                     ], |  | ||||||
|                 }, |                 }, | ||||||
|                 '|', |                 "|", | ||||||
|                 'fontColor', 'fontBackgroundColor', 'removeFormat', |                 "fontColor", | ||||||
|                 '|', |                 "fontBackgroundColor", | ||||||
|                 'bulletedList', 'numberedList', 'todoList', |                 "removeFormat", | ||||||
|                 '|', |                 "|", | ||||||
|                 'blockQuote', 'insertTable', 'codeBlock', 'footnote', |                 "bulletedList", | ||||||
|  |                 "numberedList", | ||||||
|  |                 "todoList", | ||||||
|  |                 "|", | ||||||
|  |                 "blockQuote", | ||||||
|  |                 "insertTable", | ||||||
|  |                 "codeBlock", | ||||||
|  |                 "footnote", | ||||||
|                 { |                 { | ||||||
|                     label: "Insert", |                     label: "Insert", | ||||||
|                     icon: "plus", |                     icon: "plus", | ||||||
|                     items: [ |                     items: ["imageUpload", "|", "link", "internallink", "includeNote", "|", "specialCharacters", "math", "mermaid", "horizontalLine", "pageBreak"] | ||||||
|                         'imageUpload', |  | ||||||
|                         '|', |  | ||||||
|                         'link', |  | ||||||
|                         'internallink', |  | ||||||
|                         'includeNote', |  | ||||||
|                         '|', |  | ||||||
|                         'specialCharacters', |  | ||||||
|                         'math', |  | ||||||
|                         'mermaid', |  | ||||||
|                         'horizontalLine', |  | ||||||
|                         'pageBreak' |  | ||||||
|                     ] |  | ||||||
|                 }, |                 }, | ||||||
|                 '|', |                 "|", | ||||||
|                 'outdent', 'indent', |                 "outdent", | ||||||
|                 '|', |                 "indent", | ||||||
|                 'markdownImport', 'cuttonote', 'findAndReplace' |                 "|", | ||||||
|  |                 "markdownImport", | ||||||
|  |                 "cuttonote", | ||||||
|  |                 "findAndReplace" | ||||||
|             ], |             ], | ||||||
|             shouldNotGroupWhenFull: multilineToolbar |             shouldNotGroupWhenFull: multilineToolbar | ||||||
|         } |         } | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| function buildFloatingToolbar() { | function buildFloatingToolbar() { | ||||||
|     return { |     return { | ||||||
|         toolbar: { |         toolbar: { | ||||||
|             items: [ |             items: [ | ||||||
| 				'fontSize', |                 "fontSize", | ||||||
| 				'bold', |                 "bold", | ||||||
| 				'italic', |                 "italic", | ||||||
| 				'underline', |                 "underline", | ||||||
| 				'strikethrough', |                 "strikethrough", | ||||||
| 				'superscript', |                 "superscript", | ||||||
| 				'subscript', |                 "subscript", | ||||||
| 				'fontColor', |                 "fontColor", | ||||||
| 				'fontBackgroundColor', |                 "fontBackgroundColor", | ||||||
| 				'code', |                 "code", | ||||||
| 				'link', |                 "link", | ||||||
| 				'removeFormat', |                 "removeFormat", | ||||||
| 				'internallink', |                 "internallink", | ||||||
| 				'cuttonote' |                 "cuttonote" | ||||||
|             ] |             ] | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         blockToolbar: [ |         blockToolbar: [ | ||||||
| 			'heading', |             "heading", | ||||||
| 			'|', |             "|", | ||||||
| 			'bulletedList', 'numberedList', 'todoList', |             "bulletedList", | ||||||
| 			'|', |             "numberedList", | ||||||
| 			'blockQuote', 'codeBlock', 'insertTable', |             "todoList", | ||||||
| 			'footnote', |             "|", | ||||||
|  |             "blockQuote", | ||||||
|  |             "codeBlock", | ||||||
|  |             "insertTable", | ||||||
|  |             "footnote", | ||||||
|             { |             { | ||||||
|                 label: "Insert", |                 label: "Insert", | ||||||
|                 icon: "plus", |                 icon: "plus", | ||||||
| 				items: [ |                 items: ["internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak"] | ||||||
| 					'internallink', |  | ||||||
| 					'includeNote', |  | ||||||
| 					'|', |  | ||||||
| 					'math', |  | ||||||
| 					'mermaid', |  | ||||||
| 					'horizontalLine', |  | ||||||
| 					'pageBreak' |  | ||||||
| 				] |  | ||||||
|             }, |             }, | ||||||
| 			'|', |             "|", | ||||||
| 			'outdent', 'indent', |             "outdent", | ||||||
| 			'|', |             "indent", | ||||||
| 			'imageUpload', |             "|", | ||||||
| 			'markdownImport', |             "imageUpload", | ||||||
| 			'specialCharacters', |             "markdownImport", | ||||||
| 			'findAndReplace' |             "specialCharacters", | ||||||
|  |             "findAndReplace" | ||||||
|         ] |         ] | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js"; | |||||||
| import ImageOptions from "./options/images/images.js"; | import ImageOptions from "./options/images/images.js"; | ||||||
| import SpellcheckOptions from "./options/spellcheck.js"; | import SpellcheckOptions from "./options/spellcheck.js"; | ||||||
| import PasswordOptions from "./options/password/password.js"; | import PasswordOptions from "./options/password/password.js"; | ||||||
| import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js" | import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js"; | ||||||
| import EtapiOptions from "./options/etapi.js"; | import EtapiOptions from "./options/etapi.js"; | ||||||
| import BackupOptions from "./options/backup.js"; | import BackupOptions from "./options/backup.js"; | ||||||
| import SyncOptions from "./options/sync.js"; | import SyncOptions from "./options/sync.js"; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet"; | import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js"; | import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js"; | ||||||
| import TypeWidget from "./type_widget.js" | import TypeWidget from "./type_widget.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import toastService from "../../services/toast.js"; | import toastService from "../../services/toast.js"; | ||||||
| import dialogService from "../../services/dialog.js"; | import dialogService from "../../services/dialog.js"; | ||||||
| @@ -75,21 +75,21 @@ const TPL = `\ | |||||||
|  |  | ||||||
| const LOCATION_ATTRIBUTE = "geolocation"; | const LOCATION_ATTRIBUTE = "geolocation"; | ||||||
| const CHILD_NOTE_ICON = "bx bx-pin"; | const CHILD_NOTE_ICON = "bx bx-pin"; | ||||||
| const DEFAULT_COORDINATES: [ number, number ] = [ 3.878638227135724, 446.6630455551659 ]; | const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; | ||||||
| const DEFAULT_ZOOM = 2; | const DEFAULT_ZOOM = 2; | ||||||
|  |  | ||||||
| interface MapData { | interface MapData { | ||||||
|     view?: { |     view?: { | ||||||
|         center?: LatLng | [ number, number ]; |         center?: LatLng | [number, number]; | ||||||
|         zoom?: number; |         zoom?: number; | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: Deduplicate | // TODO: Deduplicate | ||||||
| interface CreateChildResponse { | interface CreateChildResponse { | ||||||
|     note: { |     note: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum State { | enum State { | ||||||
| @@ -220,7 +220,7 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); |         const [lat, lng] = latLng.split(",", 2).map((el) => parseFloat(el)); | ||||||
|         const L = this.L; |         const L = this.L; | ||||||
|         const icon = this.#buildIcon(note.getIcon(), note.getColorClass(), note.title); |         const icon = this.#buildIcon(note.getIcon(), note.getColorClass(), note.title); | ||||||
|  |  | ||||||
| @@ -228,10 +228,10 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|             icon, |             icon, | ||||||
|             draggable: true, |             draggable: true, | ||||||
|             autoPan: true, |             autoPan: true, | ||||||
|             autoPanSpeed: 5, |             autoPanSpeed: 5 | ||||||
|         }) |         }) | ||||||
|             .addTo(map) |             .addTo(map) | ||||||
|             .on("moveend", e => { |             .on("moveend", (e) => { | ||||||
|                 this.moveMarker(note.noteId, (e.target as Marker).getLatLng()); |                 this.moveMarker(note.noteId, (e.target as Marker).getLatLng()); | ||||||
|             }); |             }); | ||||||
|         marker.on("mousedown", ({ originalEvent }) => { |         marker.on("mousedown", ({ originalEvent }) => { | ||||||
| @@ -264,9 +264,9 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|                 <img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" /> |                 <img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" /> | ||||||
|                 <span class="bx ${bxIconClass} ${colorClass}"></span> |                 <span class="bx ${bxIconClass} ${colorClass}"></span> | ||||||
|                 <span class="title-label">${title}</span>`, |                 <span class="title-label">${title}</span>`, | ||||||
|             iconSize: [ 25, 41 ], |             iconSize: [25, 41], | ||||||
|             iconAnchor: [ 12, 41 ] |             iconAnchor: [12, 41] | ||||||
|         }) |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #changeState(newState: State) { |     #changeState(newState: State) { | ||||||
| @@ -296,7 +296,7 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async moveMarker(noteId: string, latLng: LatLng | null) { |     async moveMarker(noteId: string, latLng: LatLng | null) { | ||||||
|         const value = (latLng ? [latLng.lat, latLng.lng].join(",") : ""); |         const value = latLng ? [latLng.lat, latLng.lng].join(",") : ""; | ||||||
|         await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value); |         await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -361,7 +361,7 @@ export default class GeoMapTypeWidget extends TypeWidget { | |||||||
|         // If any of note has its location attribute changed. |         // If any of note has its location attribute changed. | ||||||
|         // TODO: Should probably filter by parent here as well. |         // TODO: Should probably filter by parent here as well. | ||||||
|         const attributeRows = loadResults.getAttributeRows(); |         const attributeRows = loadResults.getAttributeRows(); | ||||||
|         if (attributeRows.find((at) => [ LOCATION_ATTRIBUTE, "color" ].includes(at.name ?? ""))) { |         if (attributeRows.find((at) => [LOCATION_ATTRIBUTE, "color"].includes(at.name ?? ""))) { | ||||||
|             this.#reloadMarkers(); |             this.#reloadMarkers(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -259,7 +259,7 @@ export default class MindMapWidget extends TypeWidget { | |||||||
|         return await this.mind.exportSvg().text(); |         return await this.mind.exportSvg().text(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded"> ) { |     async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||||
|         if (this.noteId && loadResults.isNoteReloaded(this.noteId)) { |         if (this.noteId && loadResults.isNoteReloaded(this.noteId)) { | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ interface Theme { | |||||||
|     val: string; |     val: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| type Response = Record<string, Theme[]> | type Response = Record<string, Theme[]>; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter. |  * Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter. | ||||||
|   | |||||||
| @@ -94,10 +94,12 @@ export default class ThemeOptions extends OptionsWidget { | |||||||
|         this.$themeSelect.empty(); |         this.$themeSelect.empty(); | ||||||
|  |  | ||||||
|         for (const theme of themes) { |         for (const theme of themes) { | ||||||
|             this.$themeSelect.append($("<option>") |             this.$themeSelect.append( | ||||||
|  |                 $("<option>") | ||||||
|                     .attr("value", theme.val) |                     .attr("value", theme.val) | ||||||
|                     .attr("data-note-id", theme.noteId || "") |                     .attr("data-note-id", theme.noteId || "") | ||||||
|                 .text(theme.title)); |                     .text(theme.title) | ||||||
|  |             ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.$themeSelect.val(options.theme); |         this.$themeSelect.val(options.theme); | ||||||
|   | |||||||
| @@ -97,9 +97,8 @@ export default class CodeMimeTypesOptions extends OptionsWidget { | |||||||
|         const checkbox = $(`<label class="tn-checkbox">`) |         const checkbox = $(`<label class="tn-checkbox">`) | ||||||
|             .append($('<input type="checkbox" class="form-check-input">').attr("id", id).attr("data-mime-type", mimeType.mime).prop("checked", mimeType.enabled)) |             .append($('<input type="checkbox" class="form-check-input">').attr("id", id).attr("data-mime-type", mimeType.mime).prop("checked", mimeType.enabled)) | ||||||
|             .on("change", () => this.save()) |             .on("change", () => this.save()) | ||||||
|             .append(mimeType.title) |             .append(mimeType.title); | ||||||
|  |  | ||||||
|         return $("<li>") |         return $("<li>").append(checkbox); | ||||||
|             .append(checkbox); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ export default class NoteErasureTimeoutOptions extends TimeSelector { | |||||||
|         const $timeSelector = this.$widget; |         const $timeSelector = this.$widget; | ||||||
|         // inject TimeSelector widget template |         // inject TimeSelector widget template | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) |         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector); | ||||||
|  |  | ||||||
|         this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button"); |         this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,6 +26,6 @@ export default class RevisionsSnapshotIntervalOptions extends TimeSelector { | |||||||
|         const $timeSelector = this.$widget; |         const $timeSelector = this.$widget; | ||||||
|         // inject TimeSelector widget template |         // inject TimeSelector widget template | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) |         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ export default class ShareSettingsOptions extends OptionsWidget { | |||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.contentSized(); |         this.contentSized(); | ||||||
|  |  | ||||||
|         this.$shareRootCheck = this.$widget.find('.share-root-check'); |         this.$shareRootCheck = this.$widget.find(".share-root-check"); | ||||||
|         this.$shareRootStatus = this.$widget.find('.share-root-status'); |         this.$shareRootStatus = this.$widget.find(".share-root-status"); | ||||||
|  |  | ||||||
|         // Add change handlers for both checkboxes |         // Add change handlers for both checkboxes | ||||||
|         this.$widget.find('input[type="checkbox"]').on("change", (e: JQuery.ChangeEvent) => { |         this.$widget.find('input[type="checkbox"]').on("change", (e: JQuery.ChangeEvent) => { | ||||||
| @@ -38,7 +38,7 @@ export default class ShareSettingsOptions extends OptionsWidget { | |||||||
|  |  | ||||||
|             // Show/hide share root status section based on redirectBareDomain checkbox |             // Show/hide share root status section based on redirectBareDomain checkbox | ||||||
|             const target = e.target as HTMLInputElement; |             const target = e.target as HTMLInputElement; | ||||||
|             if (target.name === 'redirectBareDomain') { |             if (target.name === "redirectBareDomain") { | ||||||
|                 this.$shareRootCheck.toggle(target.checked); |                 this.$shareRootCheck.toggle(target.checked); | ||||||
|                 if (target.checked) { |                 if (target.checked) { | ||||||
|                     this.checkShareRoot(); |                     this.checkShareRoot(); | ||||||
| @@ -47,7 +47,7 @@ export default class ShareSettingsOptions extends OptionsWidget { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Add click handler for check share root button |         // Add click handler for check share root button | ||||||
|         this.$widget.find('.check-share-root').on("click", () => this.checkShareRoot()); |         this.$widget.find(".check-share-root").on("click", () => this.checkShareRoot()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async optionsLoaded(options: OptionMap) { |     async optionsLoaded(options: OptionMap) { | ||||||
| @@ -62,28 +62,26 @@ export default class ShareSettingsOptions extends OptionsWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async checkShareRoot() { |     async checkShareRoot() { | ||||||
|         const $button = this.$widget.find('.check-share-root'); |         const $button = this.$widget.find(".check-share-root"); | ||||||
|         $button.prop('disabled', true); |         $button.prop("disabled", true); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             const shareRootNotes = await searchService.searchForNotes("#shareRoot"); |             const shareRootNotes = await searchService.searchForNotes("#shareRoot"); | ||||||
|             const sharedShareRootNote = shareRootNotes.find(note => note.isShared()); |             const sharedShareRootNote = shareRootNotes.find((note) => note.isShared()); | ||||||
|  |  | ||||||
|             if (sharedShareRootNote) { |             if (sharedShareRootNote) { | ||||||
|                 this.$shareRootStatus |                 this.$shareRootStatus | ||||||
|                     .removeClass('text-danger') |                     .removeClass("text-danger") | ||||||
|                     .addClass('text-success') |                     .addClass("text-success") | ||||||
|                     .text(t("share.share_root_found", {noteTitle: sharedShareRootNote.title})); |                     .text(t("share.share_root_found", { noteTitle: sharedShareRootNote.title })); | ||||||
|             } else { |             } else { | ||||||
|                 this.$shareRootStatus |                 this.$shareRootStatus | ||||||
|                     .removeClass('text-success') |                     .removeClass("text-success") | ||||||
|                     .addClass('text-danger') |                     .addClass("text-danger") | ||||||
|                     .text(shareRootNotes.length > 0 |                     .text(shareRootNotes.length > 0 ? t("share.share_root_not_shared", { noteTitle: shareRootNotes[0].title }) : t("share.share_root_not_found")); | ||||||
|                         ? t("share.share_root_not_shared", {noteTitle: shareRootNotes[0].title}) |  | ||||||
|                         : t("share.share_root_not_found")); |  | ||||||
|             } |             } | ||||||
|         } finally { |         } finally { | ||||||
|             $button.prop('disabled', false); |             $button.prop("disabled", false); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,6 @@ export default class ProtectedSessionTimeoutOptions extends TimeSelector { | |||||||
|         const $timeSelector = this.$widget; |         const $timeSelector = this.$widget; | ||||||
|         // inject TimeSelector widget template |         // inject TimeSelector widget template | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) |         this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ export default class TimeSelector extends OptionsWidget { | |||||||
|         this.optionValueId = options.optionValueId; |         this.optionValueId = options.optionValueId; | ||||||
|         this.optionTimeScaleId = options.optionTimeScaleId; |         this.optionTimeScaleId = options.optionTimeScaleId; | ||||||
|         this.includedTimeScales = options.includedTimeScales || new Set(["seconds", "minutes", "hours", "days"]); |         this.includedTimeScales = options.includedTimeScales || new Set(["seconds", "minutes", "hours", "days"]); | ||||||
|         this.minimumSeconds = options.minimumSeconds || 0 |         this.minimumSeconds = options.minimumSeconds || 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     doRender() { |     doRender() { | ||||||
| @@ -131,10 +131,10 @@ export default class TimeSelector extends OptionsWidget { | |||||||
|  |  | ||||||
|     private setInternalTimeInSeconds(time: number) { |     private setInternalTimeInSeconds(time: number) { | ||||||
|         if (time < this.minimumSeconds) { |         if (time < this.minimumSeconds) { | ||||||
|             toastService.showError(t("time_selector.minimum_input", {minimumSeconds: this.minimumSeconds})); |             toastService.showError(t("time_selector.minimum_input", { minimumSeconds: this.minimumSeconds })); | ||||||
|             return this.internalTimeInSeconds = this.minimumSeconds; |             return (this.internalTimeInSeconds = this.minimumSeconds); | ||||||
|         } |         } | ||||||
|         return this.internalTimeInSeconds = time; |         return (this.internalTimeInSeconds = time); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ const TPL = ` | |||||||
| `; | `; | ||||||
|  |  | ||||||
| function buildTasks(tasks: FTask[]) { | function buildTasks(tasks: FTask[]) { | ||||||
|     let html = ''; |     let html = ""; | ||||||
|  |  | ||||||
|     const now = dayjs(); |     const now = dayjs(); | ||||||
|     const dateFormat = "DD-MM-YYYY"; |     const dateFormat = "DD-MM-YYYY"; | ||||||
| @@ -137,7 +137,9 @@ export default class TaskListWidget extends TypeWidget { | |||||||
|     private $taskContainer!: JQuery<HTMLElement>; |     private $taskContainer!: JQuery<HTMLElement>; | ||||||
|     private $addNewTask!: JQuery<HTMLElement>; |     private $addNewTask!: JQuery<HTMLElement>; | ||||||
|  |  | ||||||
|     static getType() { return "taskList" } |     static getType() { | ||||||
|  |         return "taskList"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
| @@ -231,8 +233,7 @@ export default class TaskListWidget extends TypeWidget { | |||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return (await froca.getTasks(this.noteId)) |         return (await froca.getTasks(this.noteId)).toSorted((a, b) => { | ||||||
|             .toSorted((a, b) => { |  | ||||||
|             // Sort by due date, closest date first. |             // Sort by due date, closest date first. | ||||||
|             if (!a.dueDate) { |             if (!a.dueDate) { | ||||||
|                 return 1; |                 return 1; | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ const TPL = ` | |||||||
| interface CreateChildResponse { | interface CreateChildResponse { | ||||||
|     note: { |     note: { | ||||||
|         noteId: string; |         noteId: string; | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default class CalendarView extends ViewMode { | export default class CalendarView extends ViewMode { | ||||||
| @@ -126,7 +126,7 @@ export default class CalendarView extends ViewMode { | |||||||
|             weekNumbers: this.parentNote.hasAttribute("label", "calendar:weekNumbers"), |             weekNumbers: this.parentNote.hasAttribute("label", "calendar:weekNumbers"), | ||||||
|             locale: await CalendarView.#getLocale(), |             locale: await CalendarView.#getLocale(), | ||||||
|             height: "100%", |             height: "100%", | ||||||
|             eventContent: (e => { |             eventContent: (e) => { | ||||||
|                 let html = ""; |                 let html = ""; | ||||||
|                 const { iconClass, promotedAttributes } = e.event.extendedProps; |                 const { iconClass, promotedAttributes } = e.event.extendedProps; | ||||||
|  |  | ||||||
| @@ -138,7 +138,7 @@ export default class CalendarView extends ViewMode { | |||||||
|  |  | ||||||
|                 // Promoted attributes |                 // Promoted attributes | ||||||
|                 if (promotedAttributes) { |                 if (promotedAttributes) { | ||||||
|                     for (const [ name, value ] of Object.entries(promotedAttributes)) { |                     for (const [name, value] of Object.entries(promotedAttributes)) { | ||||||
|                         html += `\ |                         html += `\ | ||||||
|                         <div class="promoted-attribute"> |                         <div class="promoted-attribute"> | ||||||
|                             <span class="promoted-attribute-name">${name}</span>: <span class="promoted-attribute-value">${value}</span> |                             <span class="promoted-attribute-name">${name}</span>: <span class="promoted-attribute-value">${value}</span> | ||||||
| @@ -147,7 +147,7 @@ export default class CalendarView extends ViewMode { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return { html }; |                 return { html }; | ||||||
|             }), |             }, | ||||||
|             dateClick: async (e) => { |             dateClick: async (e) => { | ||||||
|                 if (!this.isCalendarRoot) { |                 if (!this.isCalendarRoot) { | ||||||
|                     return; |                     return; | ||||||
| @@ -260,7 +260,7 @@ export default class CalendarView extends ViewMode { | |||||||
|             // TODO: Deduplicate get type. |             // TODO: Deduplicate get type. | ||||||
|             const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${this.parentNote.noteId}`); |             const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${this.parentNote.noteId}`); | ||||||
|             const dateNoteIds = Object.values(dateNotesForMonth); |             const dateNoteIds = Object.values(dateNotesForMonth); | ||||||
|             allDateNoteIds = [ ...allDateNoteIds, ...dateNoteIds ]; |             allDateNoteIds = [...allDateNoteIds, ...dateNoteIds]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Request all the date notes. |         // Request all the date notes. | ||||||
| @@ -379,7 +379,7 @@ export default class CalendarView extends ViewMode { | |||||||
|         const result: Record<string, string> = {}; |         const result: Record<string, string> = {}; | ||||||
|  |  | ||||||
|         for (const promotedAttribute of filteredPromotedAttributes) { |         for (const promotedAttribute of filteredPromotedAttributes) { | ||||||
|             const [ type, name ] = promotedAttribute.name.split(":", 2); |             const [type, name] = promotedAttribute.name.split(":", 2); | ||||||
|             const definition = promotedAttribute.getDefinition(); |             const definition = promotedAttribute.getDefinition(); | ||||||
|  |  | ||||||
|             if (definition.multiplicity !== "single") { |             if (definition.multiplicity !== "single") { | ||||||
| @@ -411,7 +411,7 @@ export default class CalendarView extends ViewMode { | |||||||
|             if (customTitleValue.startsWith("#")) { |             if (customTitleValue.startsWith("#")) { | ||||||
|                 const labelValue = note.getAttributeValue("label", attributeName); |                 const labelValue = note.getAttributeValue("label", attributeName); | ||||||
|                 if (labelValue) { |                 if (labelValue) { | ||||||
|                     return [ labelValue ]; |                     return [labelValue]; | ||||||
|                 } |                 } | ||||||
|             } else if (allowRelations && customTitleValue.startsWith("~")) { |             } else if (allowRelations && customTitleValue.startsWith("~")) { | ||||||
|                 const relations = note.getRelations(attributeName); |                 const relations = note.getRelations(attributeName); | ||||||
| @@ -432,7 +432,7 @@ export default class CalendarView extends ViewMode { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return [ note.title ]; |         return [note.title]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static async #setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) { |     static async #setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) { | ||||||
| @@ -456,7 +456,7 @@ export default class CalendarView extends ViewMode { | |||||||
|  |  | ||||||
|         const offset = date.getTimezoneOffset(); |         const offset = date.getTimezoneOffset(); | ||||||
|         const localDate = new Date(date.getTime() - offset * 60 * 1000); |         const localDate = new Date(date.getTime() - offset * 60 * 1000); | ||||||
|         return localDate.toISOString().split('T')[0]; |         return localDate.toISOString().split("T")[0]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static #offsetDate(date: Date | string | null | undefined, offset: number) { |     static #offsetDate(date: Date | string | null | undefined, offset: number) { | ||||||
|   | |||||||
| @@ -75,7 +75,6 @@ | |||||||
|     flex-wrap: wrap; |     flex-wrap: wrap; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| .calendar-dropdown-widget .calendar-week span { | .calendar-dropdown-widget .calendar-week span { | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     flex: 0 0 14.28%; |     flex: 0 0 14.28%; | ||||||
|   | |||||||
| @@ -202,9 +202,8 @@ span[style] { | |||||||
|  |  | ||||||
| @supports selector(.todo-list__label__description:has(*)) and (height: 1lh) { | @supports selector(.todo-list__label__description:has(*)) and (height: 1lh) { | ||||||
|     .note-detail-printable .todo-list__label__description { |     .note-detail-printable .todo-list__label__description { | ||||||
|  |  | ||||||
|         /* The percentage of the line height that the check box occupies */ |         /* The percentage of the line height that the check box occupies */ | ||||||
|         --box-ratio: .75; |         --box-ratio: 0.75; | ||||||
|         /* The size of the gap between the check box and the caption */ |         /* The size of the gap between the check box and the caption */ | ||||||
|         --box-text-gap: 0.25em; |         --box-text-gap: 0.25em; | ||||||
|  |  | ||||||
| @@ -303,7 +302,12 @@ pre > code { | |||||||
|     orphans: 6; |     orphans: 6; | ||||||
| } | } | ||||||
|  |  | ||||||
| h1, h2, h3, h4, h5, h6 { | h1, | ||||||
|  | h2, | ||||||
|  | h3, | ||||||
|  | h4, | ||||||
|  | h5, | ||||||
|  | h6 { | ||||||
|     page-break-after: avoid; |     page-break-after: avoid; | ||||||
|     break-after: avoid; |     break-after: avoid; | ||||||
| } | } | ||||||
| @@ -32,7 +32,7 @@ | |||||||
|     --cmd-button-icon-color: white; |     --cmd-button-icon-color: white; | ||||||
|     --cmd-button-keyboard-shortcut-background: #0000004d; |     --cmd-button-keyboard-shortcut-background: #0000004d; | ||||||
|     --cmd-button-keyboard-shortcut-color: white; |     --cmd-button-keyboard-shortcut-color: white; | ||||||
|     --cmd-button-disabled-opacity: .5; |     --cmd-button-disabled-opacity: 0.5; | ||||||
|  |  | ||||||
|     --icon-button-color: currentColor; |     --icon-button-color: currentColor; | ||||||
|     --icon-button-hover-background: var(--hover-item-background-color); |     --icon-button-hover-background: var(--hover-item-background-color); | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ | |||||||
|     --cmd-button-icon-color: black; |     --cmd-button-icon-color: black; | ||||||
|     --cmd-button-keyboard-shortcut-background: #00000017; |     --cmd-button-keyboard-shortcut-background: #00000017; | ||||||
|     --cmd-button-keyboard-shortcut-color: black; |     --cmd-button-keyboard-shortcut-color: black; | ||||||
|     --cmd-button-disabled-opacity: .5; |     --cmd-button-disabled-opacity: 0.5; | ||||||
|  |  | ||||||
|     --icon-button-color: currentColor; |     --icon-button-color: currentColor; | ||||||
|     --icon-button-hover-background: var(--hover-item-background-color); |     --icon-button-hover-background: var(--hover-item-background-color); | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ | |||||||
|     --help-backdrop-blur: 10px; |     --help-backdrop-blur: 10px; | ||||||
|  |  | ||||||
|     --icon-button-size: 32px; |     --icon-button-size: 32px; | ||||||
|     --icon-button-icon-ratio: .65; |     --icon-button-icon-ratio: 0.65; | ||||||
|  |  | ||||||
|     /* Theme capabilities */ |     /* Theme capabilities */ | ||||||
|     --tab-note-icons: true; |     --tab-note-icons: true; | ||||||
|   | |||||||
| @@ -30,11 +30,11 @@ button.btn.btn-primary:active, | |||||||
| button.btn.btn-secondary:active, | button.btn.btn-secondary:active, | ||||||
| button.btn.btn-sm:not(.select-button):active, | button.btn.btn-sm:not(.select-button):active, | ||||||
| button.btn.btn-success:active { | button.btn.btn-success:active { | ||||||
|     opacity: .85; |     opacity: 0.85; | ||||||
|     box-shadow: unset; |     box-shadow: unset; | ||||||
|     background: var(--cmd-button-background-color) !important; |     background: var(--cmd-button-background-color) !important; | ||||||
|     color: var(--cmd-button-text-color) !important; |     color: var(--cmd-button-text-color) !important; | ||||||
|     transform: scale(.95); |     transform: scale(0.95); | ||||||
| } | } | ||||||
|  |  | ||||||
| button.btn.btn-primary:disabled, | button.btn.btn-primary:disabled, | ||||||
| @@ -57,7 +57,7 @@ button.btn.btn-secondary span.bx, | |||||||
| button.btn.btn-sm span.bx, | button.btn.btn-sm span.bx, | ||||||
| button.btn.btn-success span.bx { | button.btn.btn-success span.bx { | ||||||
|     color: var(--cmd-button-icon-color); |     color: var(--cmd-button-icon-color); | ||||||
|     padding-right: .35em; |     padding-right: 0.35em; | ||||||
|     font-size: 1.2em; |     font-size: 1.2em; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -66,12 +66,12 @@ button.btn.btn-primary kbd, | |||||||
| button.btn.btn-secondary kbd, | button.btn.btn-secondary kbd, | ||||||
| button.btn.btn-sm kbd, | button.btn.btn-sm kbd, | ||||||
| button.btn.btn-success kbd { | button.btn.btn-success kbd { | ||||||
|     margin-left: .5em; |     margin-left: 0.5em; | ||||||
|     background: var(--cmd-button-keyboard-shortcut-background); |     background: var(--cmd-button-keyboard-shortcut-background); | ||||||
|     color: var(--cmd-button-keyboard-shortcut-color); |     color: var(--cmd-button-keyboard-shortcut-color); | ||||||
|     font-size: .6em; |     font-size: 0.6em; | ||||||
|     text-transform: uppercase; |     text-transform: uppercase; | ||||||
|     letter-spacing: .5pt; |     letter-spacing: 0.5pt; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -101,7 +101,7 @@ button.btn.btn-success kbd { | |||||||
|     --icon-button-hover-background: var(--tab-close-button-hover-background); |     --icon-button-hover-background: var(--tab-close-button-hover-background); | ||||||
|     --icon-button-hover-color: var(--tab-close-button-hover-color); |     --icon-button-hover-color: var(--tab-close-button-hover-color); | ||||||
|     --icon-button-size: 24px; |     --icon-button-size: 24px; | ||||||
|     --icon-button-icon-ratio: .8; |     --icon-button-icon-ratio: 0.8; | ||||||
|  |  | ||||||
|     border-radius: 50%; |     border-radius: 50%; | ||||||
| } | } | ||||||
| @@ -124,7 +124,7 @@ button.btn.btn-success kbd { | |||||||
|  |  | ||||||
| :root .icon-action:not(.global-menu-button):active::before, | :root .icon-action:not(.global-menu-button):active::before, | ||||||
| :root .tn-tool-button:active::before { | :root .tn-tool-button:active::before { | ||||||
|     transform: scale(.85); |     transform: scale(0.85); | ||||||
| } | } | ||||||
|  |  | ||||||
| :root .icon-action:not(.global-menu-button):focus-visible, | :root .icon-action:not(.global-menu-button):focus-visible, | ||||||
| @@ -137,7 +137,7 @@ button.btn.btn-success kbd { | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| input:disabled { | input:disabled { | ||||||
|     opacity: .33; |     opacity: 0.33; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Text boxes */ | /* Text boxes */ | ||||||
| @@ -190,7 +190,8 @@ textarea:focus, | |||||||
|     outline-offset: 0; |     outline-offset: 0; | ||||||
|     background: var(--input-focus-background); |     background: var(--input-focus-background); | ||||||
|     color: var(--input-focus-color); |     color: var(--input-focus-color); | ||||||
|     transition: outline-color 50ms linear, |     transition: | ||||||
|  |         outline-color 50ms linear, | ||||||
|         outline-offset 200ms ease-out; |         outline-offset 200ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -229,12 +230,12 @@ input::selection, | |||||||
|     outline: 3px solid var(--input-focus-outline-color); |     outline: 3px solid var(--input-focus-outline-color); | ||||||
|     outline-offset: 0; |     outline-offset: 0; | ||||||
|     background: var(--input-focus-background); |     background: var(--input-focus-background); | ||||||
|     transition: outline-color 50ms linear, |     transition: | ||||||
|  |         outline-color 50ms linear, | ||||||
|         outline-offset 200ms ease-out; |         outline-offset 200ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| .input-group input | .input-group input .input-group input:hover, | ||||||
| .input-group input:hover, |  | ||||||
| .input-group input:focus, | .input-group input:focus, | ||||||
| .input-group .form-control, | .input-group .form-control, | ||||||
| .input-group .form-control:hover, | .input-group .form-control:hover, | ||||||
| @@ -277,7 +278,7 @@ input::selection, | |||||||
| } | } | ||||||
|  |  | ||||||
| .input-group a.disabled { | .input-group a.disabled { | ||||||
|     opacity: .5; |     opacity: 0.5; | ||||||
|     /* Workaround to set the "background" property. */ |     /* Workaround to set the "background" property. */ | ||||||
|     --button-disabled-background-color: transparent; |     --button-disabled-background-color: transparent; | ||||||
|     --button-disabled-text-color: var(--input-action-button-color); |     --button-disabled-text-color: var(--input-action-button-color); | ||||||
| @@ -319,8 +320,7 @@ select.form-control, | |||||||
|     outline: 3px solid transparent; |     outline: 3px solid transparent; | ||||||
|     outline-offset: 6px; |     outline-offset: 6px; | ||||||
|     padding-right: calc(15px + 1.5rem); |     padding-right: calc(15px + 1.5rem); | ||||||
|     background: var(--input-background-color) |     background: var(--input-background-color) var(--dropdown-arrow); | ||||||
|                 var(--dropdown-arrow); |  | ||||||
|     color: var(--input-text-color); |     color: var(--input-text-color); | ||||||
|     border: unset; |     border: unset; | ||||||
|     border-radius: 0.375rem; |     border-radius: 0.375rem; | ||||||
| @@ -330,8 +330,7 @@ select:hover, | |||||||
| select.form-select:hover, | select.form-select:hover, | ||||||
| select.form-control:hover, | select.form-control:hover, | ||||||
| .select-button.dropdown-toggle.btn:hover { | .select-button.dropdown-toggle.btn:hover { | ||||||
|     background: var(--input-hover-background) |     background: var(--input-hover-background) var(--dropdown-arrow); | ||||||
|                 var(--dropdown-arrow); |  | ||||||
|     color: var(--input-hover-color); |     color: var(--input-hover-color); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -347,10 +346,10 @@ select.form-control:focus, | |||||||
|     box-shadow: unset; |     box-shadow: unset; | ||||||
|     outline: 3px solid var(--input-focus-outline-color); |     outline: 3px solid var(--input-focus-outline-color); | ||||||
|     outline-offset: 0; |     outline-offset: 0; | ||||||
|     background: var(--select-focus-background) |     background: var(--select-focus-background) var(--dropdown-arrow); | ||||||
|                 var(--dropdown-arrow); |  | ||||||
|     color: var(--select-focus-text-color); |     color: var(--select-focus-text-color); | ||||||
|     transition: outline-color 50ms linear, |     transition: | ||||||
|  |         outline-color 50ms linear, | ||||||
|         outline-offset 200ms ease-out; |         outline-offset 200ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -360,7 +359,7 @@ option { | |||||||
|  |  | ||||||
| optgroup { | optgroup { | ||||||
|     color: var(--select-group-heading-text-color); |     color: var(--select-group-heading-text-color); | ||||||
|     font-size: .75em; |     font-size: 0.75em; | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     font-style: normal; |     font-style: normal; | ||||||
|     line-height: 40px; |     line-height: 40px; | ||||||
| @@ -374,9 +373,9 @@ optgroup { | |||||||
|  * </label> |  * </label> | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  .tn-file-input { | .tn-file-input { | ||||||
|     position: relative; |     position: relative; | ||||||
|     padding: .375rem 2.25rem .375rem .75rem; |     padding: 0.375rem 2.25rem 0.375rem 0.75rem; | ||||||
| } | } | ||||||
|  |  | ||||||
| .tn-file-input input[type="file"] { | .tn-file-input input[type="file"] { | ||||||
| @@ -416,19 +415,17 @@ optgroup { | |||||||
| /* Check boxes and radio buttons */ | /* Check boxes and radio buttons */ | ||||||
|  |  | ||||||
| @supports selector(label:has(*)) { | @supports selector(label:has(*)) { | ||||||
|  |  | ||||||
|     /* Check box & radio button commons */ |     /* Check box & radio button commons */ | ||||||
|  |  | ||||||
|     /* The parent label */ |     /* The parent label */ | ||||||
|     label.tn-radio, |     label.tn-radio, | ||||||
|     label.tn-checkbox { |     label.tn-checkbox { | ||||||
|         --box-size: 1em; |         --box-size: 1em; | ||||||
|         --box-label-gap: .5em; |         --box-label-gap: 0.5em; | ||||||
|  |  | ||||||
|         position: relative; |         position: relative; | ||||||
|         padding-left: calc(var(--box-size) + var(--box-label-gap)) !important; |         padding-left: calc(var(--box-size) + var(--box-label-gap)) !important; | ||||||
|         user-select: none; |         user-select: none; | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* The original input */ |     /* The original input */ | ||||||
| @@ -455,8 +452,8 @@ optgroup { | |||||||
|         height: var(--box-size); |         height: var(--box-size); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label.tn-radio:has(>input[type="radio"]:focus-visible)::before, |     label.tn-radio:has(> input[type="radio"]:focus-visible)::before, | ||||||
|     label.tn-checkbox:has(>input[type="checkbox"]:focus-visible)::before { |     label.tn-checkbox:has(> input[type="checkbox"]:focus-visible)::before { | ||||||
|         outline: 2px solid var(--input-focus-outline-color); |         outline: 2px solid var(--input-focus-outline-color); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -468,14 +465,15 @@ optgroup { | |||||||
|         background: var(--radio-checkbox-background); |         background: var(--radio-checkbox-background); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label.tn-checkbox:has(>input[type="checkbox"]:not(:disabled)):hover:before { |     label.tn-checkbox:has(> input[type="checkbox"]:not(:disabled)):hover:before { | ||||||
|         background: var(--radio-checkbox-hover-background); |         background: var(--radio-checkbox-hover-background); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @keyframes checkbox-checked { |     @keyframes checkbox-checked { | ||||||
|         from { |         from { | ||||||
|             transform: scale(2); |             transform: scale(2); | ||||||
|         } to { |         } | ||||||
|  |         to { | ||||||
|             transform: scale(1); |             transform: scale(1); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -484,15 +482,16 @@ optgroup { | |||||||
|     label.tn-checkbox::after { |     label.tn-checkbox::after { | ||||||
|         mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3ctitle%3echeck-bold%3c/title%3e%3cpath d='M9%2c20.42L2.79%2c14.21L5.62%2c11.38L9%2c14.77L18.88%2c4.88L21.71%2c7.71L9%2c20.42Z' /%3e%3c/svg%3e"); |         mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3ctitle%3echeck-bold%3c/title%3e%3cpath d='M9%2c20.42L2.79%2c14.21L5.62%2c11.38L9%2c14.77L18.88%2c4.88L21.71%2c7.71L9%2c20.42Z' /%3e%3c/svg%3e"); | ||||||
|         mask-position: center center; |         mask-position: center center; | ||||||
|         mask-size: .95em; |         mask-size: 0.95em; | ||||||
|         background-color: var(--radio-checkbox-indicator-color); |         background-color: var(--radio-checkbox-indicator-color); | ||||||
|         transform: scale(0); |         transform: scale(0); | ||||||
|         opacity: 0; |         opacity: 0; | ||||||
|         transition: transform 300ms ease-out, |         transition: | ||||||
|  |             transform 300ms ease-out, | ||||||
|             opacity 300ms linear; |             opacity 300ms linear; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label.tn-checkbox:has(>input[type="checkbox"]:checked)::after { |     label.tn-checkbox:has(> input[type="checkbox"]:checked)::after { | ||||||
|         opacity: 1; |         opacity: 1; | ||||||
|         transform: scale(1); |         transform: scale(1); | ||||||
|         transition: opacity 100ms ease-in-out; |         transition: opacity 100ms ease-in-out; | ||||||
| @@ -511,15 +510,16 @@ optgroup { | |||||||
|         background: var(--radio-checkbox-background); |         background: var(--radio-checkbox-background); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label.tn-radio:has(>input[type="radio"]:not(:disabled)):hover::before { |     label.tn-radio:has(> input[type="radio"]:not(:disabled)):hover::before { | ||||||
|         background: var(--radio-checkbox-hover-background); |         background: var(--radio-checkbox-hover-background); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @keyframes radio-checked { |     @keyframes radio-checked { | ||||||
|         from { |         from { | ||||||
|             transform: scale(1.5); |             transform: scale(1.5); | ||||||
|         } to { |         } | ||||||
|             transform: scale(.5); |         to { | ||||||
|  |             transform: scale(0.5); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -528,12 +528,13 @@ optgroup { | |||||||
|         background: var(--radio-checkbox-indicator-color); |         background: var(--radio-checkbox-indicator-color); | ||||||
|         transform: scale(0); |         transform: scale(0); | ||||||
|         opacity: 0; |         opacity: 0; | ||||||
|         transition: opacity 300ms linear, |         transition: | ||||||
|  |             opacity 300ms linear, | ||||||
|             transform 300ms ease-in; |             transform 300ms ease-in; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     label.tn-radio:has(>input[type="radio"]:checked)::after { |     label.tn-radio:has(> input[type="radio"]:checked)::after { | ||||||
|         transform: scale(.5); |         transform: scale(0.5); | ||||||
|         opacity: 1; |         opacity: 1; | ||||||
|         transition: opacity 150ms linear; |         transition: opacity 150ms linear; | ||||||
|         animation: radio-checked 200ms ease-out; |         animation: radio-checked 200ms ease-out; | ||||||
| @@ -545,9 +546,8 @@ optgroup { | |||||||
|     label.tn-radio:has(> input[type="radio"]:disabled)::after, |     label.tn-radio:has(> input[type="radio"]:disabled)::after, | ||||||
|     label.tn-checkbox:has(> input[type="checkbox"]:disabled)::before, |     label.tn-checkbox:has(> input[type="checkbox"]:disabled)::before, | ||||||
|     label.tn-checkbox:has(> input[type="checkbox"]:disabled)::after { |     label.tn-checkbox:has(> input[type="checkbox"]:disabled)::after { | ||||||
|         opacity: .5; |         opacity: 0.5; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Switches */ | /* Switches */ | ||||||
| @@ -579,7 +579,8 @@ body a.tn-link:visited, | |||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     text-decoration: underline; |     text-decoration: underline; | ||||||
|  |  | ||||||
|     transition: background-color 200ms ease-out, |     transition: | ||||||
|  |         background-color 200ms ease-out, | ||||||
|         box-shadow 200ms ease-out, |         box-shadow 200ms ease-out, | ||||||
|         color 300ms ease-out; |         color 300ms ease-out; | ||||||
| } | } | ||||||
| @@ -596,7 +597,8 @@ body a.tn-link:hover, | |||||||
|     --background: var(--link-hover-background); |     --background: var(--link-hover-background); | ||||||
|     color: var(--link-hover-color); |     color: var(--link-hover-color); | ||||||
|  |  | ||||||
|     transition: background-color 100ms ease-in, |     transition: | ||||||
|  |         background-color 100ms ease-in, | ||||||
|         box-shadow 100ms ease-in, |         box-shadow 100ms ease-in, | ||||||
|         color 150ms ease-in; |         color 150ms ease-in; | ||||||
| } | } | ||||||
| @@ -606,16 +608,18 @@ a.tn-link[href^="http://"]:not(.no-arrow)::after, | |||||||
| a.tn-link[href^="https://"]:not(.no-arrow)::after, | a.tn-link[href^="https://"]:not(.no-arrow)::after, | ||||||
| .use-tn-links a.external:not(.no-arrow)::after, | .use-tn-links a.external:not(.no-arrow)::after, | ||||||
| .use-tn-links a[href^="http://"]:not(.no-arrow)::after, | .use-tn-links a[href^="http://"]:not(.no-arrow)::after, | ||||||
| .use-tn-links a[href^="https://"]:not(.no-arrow)::after { | .use-tn-links a[href^="https://"]:not(.no-arrow)::after | ||||||
|  | { | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
|     opacity: .5; |     opacity: 0.5; | ||||||
| } | } | ||||||
|  |  | ||||||
| @keyframes link-arrow-blink { | @keyframes link-arrow-blink { | ||||||
|     from { |     from { | ||||||
|         opacity: 1; |         opacity: 1; | ||||||
|     } to { |     } | ||||||
|         opacity: .5; |     to { | ||||||
|  |         opacity: 0.5; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -624,7 +628,8 @@ a.tn-link:hover[href^="http://"]:not(.no-arrow)::after, | |||||||
| a.tn-link:hover[href^="https://"]:not(.no-arrow)::after, | a.tn-link:hover[href^="https://"]:not(.no-arrow)::after, | ||||||
| .use-tn-links a:hover.external:not(.no-arrow)::after, | .use-tn-links a:hover.external:not(.no-arrow)::after, | ||||||
| .use-tn-links a:hover[href^="http://"]:not(.no-arrow)::after, | .use-tn-links a:hover[href^="http://"]:not(.no-arrow)::after, | ||||||
| .use-tn-links a:hover[href^="https://"]:not(.no-arrow)::after { | .use-tn-links a:hover[href^="https://"]:not(.no-arrow)::after | ||||||
|  | { | ||||||
|     animation: link-arrow-blink 500ms linear alternate infinite; |     animation: link-arrow-blink 500ms linear alternate infinite; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -152,7 +152,7 @@ div.find-replace-widget div.find-widget-found-wrapper { | |||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
|  /* The up / down buttons of the "Find in text" input */ | /* The up / down buttons of the "Find in text" input */ | ||||||
| .find-replace-widget .input-group button { | .find-replace-widget .input-group button { | ||||||
|     font-size: 1.3em; |     font-size: 1.3em; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ div.note-type-dropdown .check { | |||||||
|     margin-right: 6px; |     margin-right: 6px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  /* Editability dropdown */ | /* Editability dropdown */ | ||||||
|  |  | ||||||
| div.editability-dropdown a.dropdown-item { | div.editability-dropdown a.dropdown-item { | ||||||
|     padding: 4px 16px 4px 0; |     padding: 4px 16px 4px 0; | ||||||
| @@ -29,8 +29,8 @@ div.editability-dropdown a.dropdown-item { | |||||||
| } | } | ||||||
|  |  | ||||||
| .editability-dropdown .dropdown-item .description { | .editability-dropdown .dropdown-item .description { | ||||||
|     opacity: .75; |     opacity: 0.75; | ||||||
|     font-size: .85em; |     font-size: 0.85em; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -39,7 +39,7 @@ div.editability-dropdown a.dropdown-item { | |||||||
|  |  | ||||||
| .attribute-list .add-new-attribute-button, | .attribute-list .add-new-attribute-button, | ||||||
| .attribute-list .save-attributes-button { | .attribute-list .save-attributes-button { | ||||||
|     bottom: .3em; |     bottom: 0.3em; | ||||||
| } | } | ||||||
|  |  | ||||||
| .attribute-list .save-attributes-button { | .attribute-list .save-attributes-button { | ||||||
| @@ -70,12 +70,12 @@ div.editability-dropdown a.dropdown-item { | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| .note-info-widget-table th { | .note-info-widget-table th { | ||||||
|     opacity: .65; |     opacity: 0.65; | ||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
| } | } | ||||||
|  |  | ||||||
| :root .note-info-widget-table button.calculate-button { | :root .note-info-widget-table button.calculate-button { | ||||||
|     min-width: 0; |     min-width: 0; | ||||||
|     padding: 4px 10px !important; |     padding: 4px 10px !important; | ||||||
|     font-size: .8em; |     font-size: 0.8em; | ||||||
| } | } | ||||||
| @@ -266,7 +266,9 @@ div.quick-search { | |||||||
|     padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left); |     padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left); | ||||||
| } | } | ||||||
|  |  | ||||||
| div.quick-search, div.quick-search:hover, div.quick-search:focus-within { | div.quick-search, | ||||||
|  | div.quick-search:hover, | ||||||
|  | div.quick-search:focus-within { | ||||||
|     /* Prevent changes to background and outline when the state of the input group changes */ |     /* Prevent changes to background and outline when the state of the input group changes */ | ||||||
|     background: transparent; |     background: transparent; | ||||||
|     outline: none; |     outline: none; | ||||||
| @@ -1086,7 +1088,7 @@ html body .dropdown-item[disabled] { | |||||||
|     background: transparent; |     background: transparent; | ||||||
|     padding: 1em 8px 14px 8px; |     padding: 1em 8px 14px 8px; | ||||||
|     text-transform: uppercase; |     text-transform: uppercase; | ||||||
|     font-size: .8em; |     font-size: 0.8em; | ||||||
|     letter-spacing: 1pt; |     letter-spacing: 1pt; | ||||||
|     color: var(--menu-item-group-header-color) !important; |     color: var(--menu-item-group-header-color) !important; | ||||||
| } | } | ||||||
| @@ -1826,13 +1828,13 @@ body.background-effects.zen #root-widget { | |||||||
|     --root-background: transparent; |     --root-background: transparent; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /* Alert bar */ | /* Alert bar */ | ||||||
|  |  | ||||||
| @keyframes alert-show { | @keyframes alert-show { | ||||||
|     from { |     from { | ||||||
|         opacity: 0; |         opacity: 0; | ||||||
|     } to { |     } | ||||||
|  |     to { | ||||||
|         opacity: 1; |         opacity: 1; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1844,7 +1846,7 @@ body.background-effects.zen #root-widget { | |||||||
|     border-radius: 0; |     border-radius: 0; | ||||||
|     padding: 8px 16px; |     padding: 8px 16px; | ||||||
|     background: var(--alert-bar-background) !important; |     background: var(--alert-bar-background) !important; | ||||||
|     font-size: .9em; |     font-size: 0.9em; | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     animation: alert-show 300ms ease-in; |     animation: alert-show 300ms ease-in; | ||||||
|     border-bottom: 2px solid #0000001c !important; |     border-bottom: 2px solid #0000001c !important; | ||||||
| @@ -1866,7 +1868,7 @@ div.promoted-attributes-container { | |||||||
|  |  | ||||||
| div.promoted-attributes-container, | div.promoted-attributes-container, | ||||||
| div.promoted-attributes-container input { | div.promoted-attributes-container input { | ||||||
|     font-size: .9rem; |     font-size: 0.9rem; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* A promoted attribute card */ | /* A promoted attribute card */ | ||||||
| @@ -1905,7 +1907,7 @@ div.promoted-attribute-cell > * { | |||||||
| div.promoted-attribute-cell > label { | div.promoted-attribute-cell > label { | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     white-space: nowrap; |     white-space: nowrap; | ||||||
|     opacity: .75; |     opacity: 0.75; | ||||||
| } | } | ||||||
|  |  | ||||||
| div.promoted-attribute-cell:not(:has(input[type="checkbox"])) > label::after { | div.promoted-attribute-cell:not(:has(input[type="checkbox"])) > label::after { | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ | |||||||
|   }, |   }, | ||||||
|   "clone_to": { |   "clone_to": { | ||||||
|     "clone_notes_to": "Notizen klonen nach...", |     "clone_notes_to": "Notizen klonen nach...", | ||||||
|     "close":"Schließen", |     "close": "Schließen", | ||||||
|     "help_on_links": "Hilfe zu Links", |     "help_on_links": "Hilfe zu Links", | ||||||
|     "notes_to_clone": "Notizen zum Klonen", |     "notes_to_clone": "Notizen zum Klonen", | ||||||
|     "target_parent_note": "Ziel-Übergeordnetenotiz", |     "target_parent_note": "Ziel-Übergeordnetenotiz", | ||||||
| @@ -74,7 +74,7 @@ | |||||||
|   }, |   }, | ||||||
|   "confirm": { |   "confirm": { | ||||||
|     "confirmation": "Bestätigung", |     "confirmation": "Bestätigung", | ||||||
|     "close":"Schließen", |     "close": "Schließen", | ||||||
|     "cancel": "Abbrechen", |     "cancel": "Abbrechen", | ||||||
|     "ok": "OK", |     "ok": "OK", | ||||||
|     "are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ", |     "are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ", | ||||||
| @@ -93,7 +93,6 @@ | |||||||
|     "cancel": "Abbrechen", |     "cancel": "Abbrechen", | ||||||
|     "ok": "OK", |     "ok": "OK", | ||||||
|     "deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert." |     "deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert." | ||||||
|  |  | ||||||
|   }, |   }, | ||||||
|   "export": { |   "export": { | ||||||
|     "export_note_title": "Notiz exportieren", |     "export_note_title": "Notiz exportieren", | ||||||
|   | |||||||
| @@ -43,9 +43,9 @@ function getDayNotesForMonth(req: Request) { | |||||||
|             AND attr.value LIKE '${month}%'`; |             AND attr.value LIKE '${month}%'`; | ||||||
|  |  | ||||||
|     if (calendarRoot) { |     if (calendarRoot) { | ||||||
|         const rows = sql.getRows<{ date: string, noteId: string }>(query); |         const rows = sql.getRows<{ date: string; noteId: string }>(query); | ||||||
|         const result: Record<string, string> = {}; |         const result: Record<string, string> = {}; | ||||||
|         for (const {date, noteId} of rows) { |         for (const { date, noteId } of rows) { | ||||||
|             const note = becca.getNote(noteId); |             const note = becca.getNote(noteId); | ||||||
|             if (note?.hasAncestor(String(calendarRoot))) { |             if (note?.hasAncestor(String(calendarRoot))) { | ||||||
|                 result[date] = noteId; |                 result[date] = noteId; | ||||||
|   | |||||||
| @@ -7,9 +7,7 @@ import yaml from "js-yaml"; | |||||||
| import type { JsonObject } from "swagger-ui-express"; | import type { JsonObject } from "swagger-ui-express"; | ||||||
|  |  | ||||||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | const __dirname = dirname(fileURLToPath(import.meta.url)); | ||||||
| const etapiDocument = yaml.load( | const etapiDocument = yaml.load(await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8")) as JsonObject; | ||||||
|     await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8") |  | ||||||
| ) as JsonObject; |  | ||||||
| const apiDocument = JSON.parse(await readFile(join(__dirname, "api", "openapi.json"), "utf-8")); | const apiDocument = JSON.parse(await readFile(join(__dirname, "api", "openapi.json"), "utf-8")); | ||||||
|  |  | ||||||
| function register(app: Application) { | function register(app: Application) { | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ export interface TriliumConfig { | |||||||
|     Session: { |     Session: { | ||||||
|         cookiePath: string; |         cookiePath: string; | ||||||
|         cookieMaxAge: number; |         cookieMaxAge: number; | ||||||
|     } |     }; | ||||||
|     Sync: { |     Sync: { | ||||||
|         syncServerHost: string; |         syncServerHost: string; | ||||||
|         syncServerTimeout: string; |         syncServerTimeout: string; | ||||||
|   | |||||||
| @@ -408,8 +408,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree | |||||||
|                 value: attr.value, |                 value: attr.value, | ||||||
|                 isInheritable: false |                 isInheritable: false | ||||||
|             }).save(); |             }).save(); | ||||||
|         } else if (attr.name === "docName" |         } else if (attr.name === "docName" || (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) { | ||||||
|                 || (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) { |  | ||||||
|             if (existingAttribute.value !== attr.value) { |             if (existingAttribute.value !== attr.value) { | ||||||
|                 existingAttribute.value = attr.value ?? ""; |                 existingAttribute.value = attr.value ?? ""; | ||||||
|                 console.log("Updating attribute ", attrId); |                 console.log("Updating attribute ", attrId); | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ export default function buildLaunchBarConfig() { | |||||||
|         { id: "_lbProtectedSession", title: t("hidden-subtree.protected-session-title"), type: "launcher", builtinWidget: "protectedSession", icon: "bx bx bx-shield-quarter" }, |         { id: "_lbProtectedSession", title: t("hidden-subtree.protected-session-title"), type: "launcher", builtinWidget: "protectedSession", icon: "bx bx bx-shield-quarter" }, | ||||||
|         { id: "_lbSyncStatus", title: t("hidden-subtree.sync-status-title"), type: "launcher", builtinWidget: "syncStatus", icon: "bx bx-wifi" }, |         { id: "_lbSyncStatus", title: t("hidden-subtree.sync-status-title"), type: "launcher", builtinWidget: "syncStatus", icon: "bx bx-wifi" }, | ||||||
|         { id: "_lbSettings", title: t("hidden-subtree.settings-title"), type: "launcher", command: "showOptions", icon: "bx bx-cog" } |         { id: "_lbSettings", title: t("hidden-subtree.settings-title"), type: "launcher", command: "showOptions", icon: "bx bx-cog" } | ||||||
|     ] |     ]; | ||||||
|  |  | ||||||
|     const mobileAvailableLaunchers: HiddenSubtreeItem[] = [ |     const mobileAvailableLaunchers: HiddenSubtreeItem[] = [ | ||||||
|         { id: "_lbMobileNewNote", ...sharedLaunchers.newNote }, |         { id: "_lbMobileNewNote", ...sharedLaunchers.newNote }, | ||||||
| @@ -98,5 +98,5 @@ export default function buildLaunchBarConfig() { | |||||||
|         desktopVisibleLaunchers, |         desktopVisibleLaunchers, | ||||||
|         mobileAvailableLaunchers, |         mobileAvailableLaunchers, | ||||||
|         mobileVisibleLaunchers |         mobileVisibleLaunchers | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -48,6 +48,6 @@ describe("sanitize", () => { | |||||||
|                     </tbody> |                     </tbody> | ||||||
|                 </table> |                 </table> | ||||||
|             </figure>`; |             </figure>`; | ||||||
|         expect(html_sanitizer.sanitize(dirty)) .toBe(clean); |         expect(html_sanitizer.sanitize(dirty)).toBe(clean); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -153,20 +153,22 @@ function sanitize(dirtyHtml: string) { | |||||||
|         }, |         }, | ||||||
|         allowedStyles: { |         allowedStyles: { | ||||||
|             "*": { |             "*": { | ||||||
|                 "color": colorRegex, |                 color: colorRegex, | ||||||
|                 "background-color": colorRegex |                 "background-color": colorRegex | ||||||
|             }, |             }, | ||||||
|             "figure": { |             figure: { | ||||||
|                 "float": [ /^\s*(left|right|none)\s*$/ ], |                 float: [/^\s*(left|right|none)\s*$/], | ||||||
|                 "width": sizeRegex, |                 width: sizeRegex, | ||||||
|                 "height": sizeRegex |                 height: sizeRegex | ||||||
|             }, |             }, | ||||||
|             "table": { |             table: { | ||||||
|                 "border-color": colorRegex, |                 "border-color": colorRegex, | ||||||
|                 "border-style": [ /^\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*$/ ] |                 "border-style": [/^\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*$/] | ||||||
|             }, |             }, | ||||||
|             "td": { |             td: { | ||||||
|                 "border": [ /^\s*\d+(?:px|em|%)\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*(#(0x)?[0-9a-fA-F]+|rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)|hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\))\s*$/ ] |                 border: [ | ||||||
|  |                     /^\s*\d+(?:px|em|%)\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*(#(0x)?[0-9a-fA-F]+|rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)|hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\))\s*$/ | ||||||
|  |                 ] | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         allowedSchemes: ALLOWED_PROTOCOLS, |         allowedSchemes: ALLOWED_PROTOCOLS, | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| import { parse, Renderer, type Tokens } from "marked"; | import { parse, Renderer, type Tokens } from "marked"; | ||||||
|  |  | ||||||
| const renderer = new Renderer({ async: false }); | const renderer = new Renderer({ async: false }); | ||||||
| renderer.code = ({text, lang, escaped}: Tokens.Code) => { | renderer.code = ({ text, lang, escaped }: Tokens.Code) => { | ||||||
|     if (!text) { |     if (!text) { | ||||||
|         return ""; |         return ""; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,18 +20,22 @@ async function testImport(fileName: string, mimetype: string) { | |||||||
|         codeImportedAsCode: true |         codeImportedAsCode: true | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     return new Promise<{ buffer: Buffer, importedNote: BNote }>((resolve, reject) => { |     return new Promise<{ buffer: Buffer; importedNote: BNote }>((resolve, reject) => { | ||||||
|         cls.init(async () => { |         cls.init(async () => { | ||||||
|             const rootNote = becca.getNote("root"); |             const rootNote = becca.getNote("root"); | ||||||
|             if (!rootNote) { |             if (!rootNote) { | ||||||
|                 reject("Missing root note."); |                 reject("Missing root note."); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const importedNote = single.importSingleFile(taskContext, { |             const importedNote = single.importSingleFile( | ||||||
|  |                 taskContext, | ||||||
|  |                 { | ||||||
|                     originalname: fileName, |                     originalname: fileName, | ||||||
|                     mimetype, |                     mimetype, | ||||||
|                     buffer: buffer |                     buffer: buffer | ||||||
|             }, rootNote as BNote); |                 }, | ||||||
|  |                 rootNote as BNote | ||||||
|  |             ); | ||||||
|             resolve({ |             resolve({ | ||||||
|                 buffer, |                 buffer, | ||||||
|                 importedNote |                 importedNote | ||||||
| @@ -85,4 +89,4 @@ describe("processNoteContent", () => { | |||||||
|         expect(importedNote.mime).toBe("text/html"); |         expect(importedNote.mime).toBe("text/html"); | ||||||
|         expect(importedNote.getContent().toString()).toBe("<h2>Hello world</h2>\n<p>Plain text goes here.</p>\n"); |         expect(importedNote.getContent().toString()).toBe("<h2>Hello world</h2>\n<p>Plain text goes here.</p>\n"); | ||||||
|     }); |     }); | ||||||
| }) | }); | ||||||
|   | |||||||
| @@ -61,4 +61,4 @@ describe("processNoteContent", () => { | |||||||
|         const htmlNote = rootNote.children.find((ch) => ch.title === "IREN Reports Q2 FY25 Results"); |         const htmlNote = rootNote.children.find((ch) => ch.title === "IREN Reports Q2 FY25 Results"); | ||||||
|         expect(htmlNote?.getContent().toString().substring(0, 4)).toEqual("<div"); |         expect(htmlNote?.getContent().toString().substring(0, 4)).toEqual("<div"); | ||||||
|     }); |     }); | ||||||
| }) | }); | ||||||
|   | |||||||
| @@ -3,39 +3,34 @@ import { parseNoteMeta } from "./in_app_help.js"; | |||||||
| import type NoteMeta from "./meta/note_meta.js"; | import type NoteMeta from "./meta/note_meta.js"; | ||||||
|  |  | ||||||
| describe("In-app help", () => { | describe("In-app help", () => { | ||||||
|  |  | ||||||
|     it("preserves custom folder icon", () => { |     it("preserves custom folder icon", () => { | ||||||
|         const meta: NoteMeta = { |         const meta: NoteMeta = { | ||||||
|             "isClone": false, |             isClone: false, | ||||||
|             "noteId": "yoAe4jV2yzbd", |             noteId: "yoAe4jV2yzbd", | ||||||
|             "notePath": [ |             notePath: ["OkOZllzB3fqN", "yoAe4jV2yzbd"], | ||||||
|                 "OkOZllzB3fqN", |             title: "Features", | ||||||
|                 "yoAe4jV2yzbd" |             notePosition: 40, | ||||||
|             ], |             prefix: null, | ||||||
|             "title": "Features", |             isExpanded: false, | ||||||
|             "notePosition": 40, |             type: "text", | ||||||
|             "prefix": null, |             mime: "text/html", | ||||||
|             "isExpanded": false, |             attributes: [ | ||||||
|             "type": "text", |  | ||||||
|             "mime": "text/html", |  | ||||||
|             "attributes": [ |  | ||||||
|                 { |                 { | ||||||
|                     "type": "label", |                     type: "label", | ||||||
|                     "name": "iconClass", |                     name: "iconClass", | ||||||
|                     "value": "bx bx-star", |                     value: "bx bx-star", | ||||||
|                     "isInheritable": false, |                     isInheritable: false, | ||||||
|                     "position": 10 |                     position: 10 | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "format": "html", |             format: "html", | ||||||
|             "attachments": [], |             attachments: [], | ||||||
|             "dirFileName": "Features", |             dirFileName: "Features", | ||||||
|             "children": [] |             children: [] | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         const item = parseNoteMeta(meta, "/"); |         const item = parseNoteMeta(meta, "/"); | ||||||
|         const icon = item.attributes?.find((a) => a.name === "iconClass"); |         const icon = item.attributes?.find((a) => a.name === "iconClass"); | ||||||
|         expect(icon?.value).toBe("bx bx-star"); |         expect(icon?.value).toBe("bx bx-star"); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -66,8 +66,7 @@ export function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSu | |||||||
|  |  | ||||||
|     // Handle text notes |     // Handle text notes | ||||||
|     if (noteMeta.type === "text" && noteMeta.dataFileName) { |     if (noteMeta.type === "text" && noteMeta.dataFileName) { | ||||||
|         const docPath = `${docNameRoot}/${path.basename(noteMeta.dataFileName, ".html")}` |         const docPath = `${docNameRoot}/${path.basename(noteMeta.dataFileName, ".html")}`.substring(1); | ||||||
|             .substring(1); |  | ||||||
|         item.attributes?.push({ |         item.attributes?.push({ | ||||||
|             type: "label", |             type: "label", | ||||||
|             name: "docName", |             name: "docName", | ||||||
| @@ -84,7 +83,7 @@ export function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSu | |||||||
|     if (noteMeta.children) { |     if (noteMeta.children) { | ||||||
|         const children: HiddenSubtreeItem[] = []; |         const children: HiddenSubtreeItem[] = []; | ||||||
|         for (const childMeta of noteMeta.children) { |         for (const childMeta of noteMeta.children) { | ||||||
|             let newDocNameRoot = (noteMeta.dirFileName ? `${docNameRoot}/${noteMeta.dirFileName}` : docNameRoot); |             let newDocNameRoot = noteMeta.dirFileName ? `${docNameRoot}/${noteMeta.dirFileName}` : docNameRoot; | ||||||
|             children.push(parseNoteMeta(childMeta, newDocNameRoot)); |             children.push(parseNoteMeta(childMeta, newDocNameRoot)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -100,7 +100,6 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     // Share settings |     // Share settings | ||||||
|     redirectBareDomain: boolean; |     redirectBareDomain: boolean; | ||||||
|     showLoginInShareTheme: boolean; |     showLoginInShareTheme: boolean; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export type OptionNames = keyof OptionDefinitions; | export type OptionNames = keyof OptionDefinitions; | ||||||
|   | |||||||
| @@ -14,11 +14,14 @@ import { default as parseInternal, type ParseOpts } from "./parse.js"; | |||||||
|  |  | ||||||
| describe("Parser", () => { | describe("Parser", () => { | ||||||
|     it("fulltext parser without content", () => { |     it("fulltext parser without content", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: tokens(["hello", "hi"]), |                 fulltextTokens: tokens(["hello", "hi"]), | ||||||
|                 expressionTokens: [], |                 expressionTokens: [], | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         expectExpression(rootExp.subExpressions[0], PropertyComparisonExp); |         expectExpression(rootExp.subExpressions[0], PropertyComparisonExp); | ||||||
|         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); |         const orExp = expectExpression(rootExp.subExpressions[2], OrExp); | ||||||
| @@ -27,11 +30,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("fulltext parser with content", () => { |     it("fulltext parser with content", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: tokens(["hello", "hi"]), |                 fulltextTokens: tokens(["hello", "hi"]), | ||||||
|                 expressionTokens: [], |                 expressionTokens: [], | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -45,11 +51,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label comparison", () => { |     it("simple label comparison", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#mylabel", "=", "text"]), |                 expressionTokens: tokens(["#mylabel", "=", "text"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp); |         const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp); | ||||||
| @@ -59,11 +68,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple attribute negation", () => { |     it("simple attribute negation", () => { | ||||||
|         let rootExp = parse({ |         let rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#!mylabel"]), |                 expressionTokens: tokens(["#!mylabel"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         let notExp = expectExpression(rootExp.subExpressions[2], NotExp); |         let notExp = expectExpression(rootExp.subExpressions[2], NotExp); | ||||||
| @@ -71,11 +83,14 @@ describe("Parser", () => { | |||||||
|         expect(attributeExistsExp.attributeType).toEqual("label"); |         expect(attributeExistsExp.attributeType).toEqual("label"); | ||||||
|         expect(attributeExistsExp.attributeName).toEqual("mylabel"); |         expect(attributeExistsExp.attributeName).toEqual("mylabel"); | ||||||
|  |  | ||||||
|         rootExp = parse({ |         rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["~!myrelation"]), |                 expressionTokens: tokens(["~!myrelation"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         notExp = expectExpression(rootExp.subExpressions[2], NotExp); |         notExp = expectExpression(rootExp.subExpressions[2], NotExp); | ||||||
| @@ -85,11 +100,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label AND", () => { |     it("simple label AND", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]), |                 expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -101,11 +119,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label AND without explicit AND", () => { |     it("simple label AND without explicit AND", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]), |                 expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -117,11 +138,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label OR", () => { |     it("simple label OR", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]), |                 expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -132,11 +156,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("fulltext and simple label", () => { |     it("fulltext and simple label", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: tokens(["hello"]), |                 fulltextTokens: tokens(["hello"]), | ||||||
|                 expressionTokens: tokens(["#mylabel", "=", "text"]), |                 expressionTokens: tokens(["#mylabel", "=", "text"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp); |         const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp); | ||||||
|  |  | ||||||
| @@ -149,11 +176,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("label sub-expression", () => { |     it("label sub-expression", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]), |                 expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -168,11 +198,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("label sub-expression without explicit operator", () => { |     it("label sub-expression without explicit operator", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]), |                 expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]), | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -189,11 +222,14 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("parses limit without order by", () => { |     it("parses limit without order by", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: tokens(["hello", "hi"]), |                 fulltextTokens: tokens(["hello", "hi"]), | ||||||
|                 expressionTokens: [], |                 expressionTokens: [], | ||||||
|                 searchContext: new SearchContext({ limit: 2 }) |                 searchContext: new SearchContext({ limit: 2 }) | ||||||
|         }, OrderByAndLimitExp); |             }, | ||||||
|  |             OrderByAndLimitExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         expect(rootExp.limit).toBe(2); |         expect(rootExp.limit).toBe(2); | ||||||
|         expect(rootExp.subExpression).toBeInstanceOf(AndExp); |         expect(rootExp.subExpression).toBeInstanceOf(AndExp); | ||||||
| @@ -236,7 +272,8 @@ describe("Invalid expressions", () => { | |||||||
|  |  | ||||||
|         expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`); |         expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`); | ||||||
|  |  | ||||||
|         const rootExp = parse({ |         const rootExp = parse( | ||||||
|  |             { | ||||||
|                 fulltextTokens: [], |                 fulltextTokens: [], | ||||||
|                 expressionTokens: [ |                 expressionTokens: [ | ||||||
|                     { token: "#first", inQuotes: false }, |                     { token: "#first", inQuotes: false }, | ||||||
| @@ -244,7 +281,9 @@ describe("Invalid expressions", () => { | |||||||
|                     { token: "#second", inQuotes: true } |                     { token: "#second", inQuotes: true } | ||||||
|                 ], |                 ], | ||||||
|                 searchContext: new SearchContext() |                 searchContext: new SearchContext() | ||||||
|         }, AndExp); |             }, | ||||||
|  |             AndExp | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         assertIsArchived(rootExp.subExpressions[0]); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
| @@ -327,16 +366,13 @@ function expectExpression<T extends Expression>(exp: Expression, type: ClassType | |||||||
|  * @param fourthType the type of the fourth subexpression. |  * @param fourthType the type of the fourth subexpression. | ||||||
|  * @returns an array of all the subexpressions (in order) typecasted to their expected type. |  * @returns an array of all the subexpressions (in order) typecasted to their expected type. | ||||||
|  */ |  */ | ||||||
| function expectSubexpressions<FirstT extends Expression, | function expectSubexpressions<FirstT extends Expression, SecondT extends Expression, ThirdT extends Expression, FourthT extends Expression>( | ||||||
|                               SecondT extends Expression, |  | ||||||
|                               ThirdT extends Expression, |  | ||||||
|                               FourthT extends Expression>( |  | ||||||
|     exp: AndExp, |     exp: AndExp, | ||||||
|     firstType: ClassType<FirstT>, |     firstType: ClassType<FirstT>, | ||||||
|     secondType?: ClassType<SecondT>, |     secondType?: ClassType<SecondT>, | ||||||
|     thirdType?: ClassType<ThirdT>, |     thirdType?: ClassType<ThirdT>, | ||||||
|                                 fourthType?: ClassType<FourthT>): [ FirstT, SecondT, ThirdT, FourthT ] |     fourthType?: ClassType<FourthT> | ||||||
| { | ): [FirstT, SecondT, ThirdT, FourthT] { | ||||||
|     expectExpression(exp.subExpressions[0], firstType); |     expectExpression(exp.subExpressions[0], firstType); | ||||||
|     if (secondType) { |     if (secondType) { | ||||||
|         expectExpression(exp.subExpressions[1], secondType); |         expectExpression(exp.subExpressions[1], secondType); | ||||||
| @@ -347,10 +383,5 @@ function expectSubexpressions<FirstT extends Expression, | |||||||
|     if (fourthType) { |     if (fourthType) { | ||||||
|         expectExpression(exp.subExpressions[3], fourthType); |         expectExpression(exp.subExpressions[3], fourthType); | ||||||
|     } |     } | ||||||
|     return [ |     return [exp.subExpressions[0] as FirstT, exp.subExpressions[1] as SecondT, exp.subExpressions[2] as ThirdT, exp.subExpressions[3] as FourthT]; | ||||||
|         exp.subExpressions[0] as FirstT, |  | ||||||
|         exp.subExpressions[1] as SecondT, |  | ||||||
|         exp.subExpressions[2] as ThirdT, |  | ||||||
|         exp.subExpressions[3] as FourthT |  | ||||||
|     ] |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -427,7 +427,7 @@ export interface ParseOpts { | |||||||
|     fulltextTokens: TokenData[]; |     fulltextTokens: TokenData[]; | ||||||
|     expressionTokens: TokenStructure; |     expressionTokens: TokenStructure; | ||||||
|     searchContext: SearchContext; |     searchContext: SearchContext; | ||||||
|     originalQuery?: string |     originalQuery?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| function parse({ fulltextTokens, expressionTokens, searchContext }: ParseOpts) { | function parse({ fulltextTokens, expressionTokens, searchContext }: ParseOpts) { | ||||||
| @@ -450,7 +450,7 @@ function parse({ fulltextTokens, expressionTokens, searchContext }: ParseOpts) { | |||||||
|  |  | ||||||
|     if (searchContext.limit && !searchContext.orderBy) { |     if (searchContext.limit && !searchContext.orderBy) { | ||||||
|         const filterExp = exp; |         const filterExp = exp; | ||||||
|         exp = new OrderByAndLimitExp([], searchContext.limit || undefined ); |         exp = new OrderByAndLimitExp([], searchContext.limit || undefined); | ||||||
|         (exp as any).subExpression = filterExp; |         (exp as any).subExpression = filterExp; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,8 +3,7 @@ import BTask from "../becca/entities/btask.js"; | |||||||
| import type { TaskRow } from "../becca/entities/rows.js"; | import type { TaskRow } from "../becca/entities/rows.js"; | ||||||
|  |  | ||||||
| export function getTasks(parentNoteId: string) { | export function getTasks(parentNoteId: string) { | ||||||
|     return becca.getTasks() |     return becca.getTasks().filter((task) => task.parentNoteId === parentNoteId && !task.isDone); | ||||||
|         .filter((task) => task.parentNoteId === parentNoteId && !task.isDone); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| interface CreateTaskParams { | interface CreateTaskParams { | ||||||
| @@ -19,7 +18,7 @@ export function createNewTask(params: CreateTaskParams) { | |||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         task |         task | ||||||
|     } |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function toggleTaskDone(taskId: string) { | export function toggleTaskDone(taskId: string) { | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ function getTrayIconPath() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getIconPath(name: string) { | function getIconPath(name: string) { | ||||||
|     const suffix = (!isMac && nativeTheme.shouldUseDarkColors ? "-inverted" : ""); |     const suffix = !isMac && nativeTheme.shouldUseDarkColors ? "-inverted" : ""; | ||||||
|     return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}Template${suffix}.png`); |     return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}Template${suffix}.png`); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -110,7 +110,6 @@ function updateTrayMenu() { | |||||||
|                     click: () => openInSameTab(bookmarkNote) |                     click: () => openInSameTab(bookmarkNote) | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return menuItems; |         return menuItems; | ||||||
| @@ -139,7 +138,7 @@ function updateTrayMenu() { | |||||||
|                 type: "normal", |                 type: "normal", | ||||||
|                 sublabel: formatter.format(date), |                 sublabel: formatter.format(date), | ||||||
|                 click: () => openInSameTab(recentNote) |                 click: () => openInSameTab(recentNote) | ||||||
|             }) |             }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return menuItems; |         return menuItems; | ||||||
|   | |||||||
| @@ -12,11 +12,13 @@ describe("Tree", () => { | |||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
|         becca.reset(); |         becca.reset(); | ||||||
|  |  | ||||||
|         rootNote = new NoteBuilder(new BNote({ |         rootNote = new NoteBuilder( | ||||||
|  |             new BNote({ | ||||||
|                 noteId: "root", |                 noteId: "root", | ||||||
|                 title: "root", |                 title: "root", | ||||||
|                 type: "text" |                 type: "text" | ||||||
|         })) |             }) | ||||||
|  |         ); | ||||||
|         new BBranch({ |         new BBranch({ | ||||||
|             branchId: "none_root", |             branchId: "none_root", | ||||||
|             noteId: "root", |             noteId: "root", | ||||||
| @@ -30,17 +32,19 @@ describe("Tree", () => { | |||||||
|                     transactional: (cb: Function) => { |                     transactional: (cb: Function) => { | ||||||
|                         cb(); |                         cb(); | ||||||
|                     }, |                     }, | ||||||
|                     execute: () => { }, |                     execute: () => {}, | ||||||
|                     replace: () => { }, |                     replace: () => {}, | ||||||
|                     getMap: () => { } |                     getMap: () => {} | ||||||
|                 } |  | ||||||
|                 } |                 } | ||||||
|  |             }; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         vi.mock("./sql_init.js", () => { |         vi.mock("./sql_init.js", () => { | ||||||
|             return { |             return { | ||||||
|                 dbReady: () => { console.log("Hello world") } |                 dbReady: () => { | ||||||
|  |                     console.log("Hello world"); | ||||||
|                 } |                 } | ||||||
|  |             }; | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -48,19 +52,19 @@ describe("Tree", () => { | |||||||
|         rootNote.label("sorted", "order"); |         rootNote.label("sorted", "order"); | ||||||
|  |  | ||||||
|         // Add values which have a defined order. |         // Add values which have a defined order. | ||||||
|         for (let i=0; i<=5; i++) { |         for (let i = 0; i <= 5; i++) { | ||||||
|             rootNote.child(note(String(i)).label("order", String(i))); |             rootNote.child(note(String(i)).label("order", String(i))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Add a few values which have no defined order. |         // Add a few values which have no defined order. | ||||||
|         for (let i=6; i<10; i++) { |         for (let i = 6; i < 10; i++) { | ||||||
|             rootNote.child(note(String(i))); |             rootNote.child(note(String(i))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const expectedOrder = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; |         const expectedOrder = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; | ||||||
|  |  | ||||||
|         // Sort a few times to ensure that the resulting order is the same. |         // Sort a few times to ensure that the resulting order is the same. | ||||||
|         for (let i=0; i<5; i++) { |         for (let i = 0; i < 5; i++) { | ||||||
|             cls.init(() => { |             cls.init(() => { | ||||||
|                 tree.sortNotesIfNeeded(rootNote.note.noteId); |                 tree.sortNotesIfNeeded(rootNote.note.noteId); | ||||||
|             }); |             }); | ||||||
|   | |||||||
| @@ -4,24 +4,20 @@ import utils from "./utils.js"; | |||||||
| type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>]; | type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>]; | ||||||
|  |  | ||||||
| describe("#newEntityId", () => { | describe("#newEntityId", () => { | ||||||
|  |  | ||||||
|     it("should return a string with a length of 12", () => { |     it("should return a string with a length of 12", () => { | ||||||
|         const result = utils.newEntityId(); |         const result = utils.newEntityId(); | ||||||
|         expect(result).toBeTypeOf("string"); |         expect(result).toBeTypeOf("string"); | ||||||
|         expect(result).toHaveLength(12); |         expect(result).toHaveLength(12); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#randomString", () => { | describe("#randomString", () => { | ||||||
|  |  | ||||||
|     it("should return a string with a length as per argument", () => { |     it("should return a string with a length as per argument", () => { | ||||||
|         const stringLength = 5; |         const stringLength = 5; | ||||||
|         const result = utils.randomString(stringLength); |         const result = utils.randomString(stringLength); | ||||||
|         expect(result).toBeTypeOf("string"); |         expect(result).toBeTypeOf("string"); | ||||||
|         expect(result).toHaveLength(stringLength); |         expect(result).toHaveLength(stringLength); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // TriliumNextTODO: should use mocks and assert that functions get called | // TriliumNextTODO: should use mocks and assert that functions get called | ||||||
| @@ -64,7 +60,6 @@ describe.todo("#hmac", () => {}); | |||||||
| describe.todo("#hash", () => {}); | describe.todo("#hash", () => {}); | ||||||
|  |  | ||||||
| describe("#isEmptyOrWhitespace", () => { | describe("#isEmptyOrWhitespace", () => { | ||||||
|  |  | ||||||
|     const testCases: TestCase<typeof utils.isEmptyOrWhitespace>[] = [ |     const testCases: TestCase<typeof utils.isEmptyOrWhitespace>[] = [ | ||||||
|         ["w/ 'null' it should return true", [null], true], |         ["w/ 'null' it should return true", [null], true], | ||||||
|         ["w/ 'null' it should return true", [null], true], |         ["w/ 'null' it should return true", [null], true], | ||||||
| @@ -72,21 +67,19 @@ describe("#isEmptyOrWhitespace", () => { | |||||||
|         ["w/ empty string '' it should return true", [""], true], |         ["w/ empty string '' it should return true", [""], true], | ||||||
|         ["w/ single whitespace string ' ' it should return true", [" "], true], |         ["w/ single whitespace string ' ' it should return true", [" "], true], | ||||||
|         ["w/ multiple whitespace string '   ' it should return true", ["  "], true], |         ["w/ multiple whitespace string '   ' it should return true", ["  "], true], | ||||||
|     ["w/ non-empty string ' t  ' it should return false", [" t  "], false], |         ["w/ non-empty string ' t  ' it should return false", [" t  "], false] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|   testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.isEmptyOrWhitespace(...fnParams); |             const result = utils.isEmptyOrWhitespace(...fnParams); | ||||||
|             expect(result).toStrictEqual(expected); |             expect(result).toStrictEqual(expected); | ||||||
|     }) |         }); | ||||||
|   }) |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#sanitizeSqlIdentifier", () => { | describe("#sanitizeSqlIdentifier", () => { | ||||||
|  |  | ||||||
|     const testCases: TestCase<typeof utils.sanitizeSqlIdentifier>[] = [ |     const testCases: TestCase<typeof utils.sanitizeSqlIdentifier>[] = [ | ||||||
|         ["w/ 'test' it should not strip anything", ["test"], "test"], |         ["w/ 'test' it should not strip anything", ["test"], "test"], | ||||||
|         ["w/ 'test123' it should not strip anything", ["test123"], "test123"], |         ["w/ 'test123' it should not strip anything", ["test123"], "test123"], | ||||||
| @@ -95,17 +88,16 @@ describe("#sanitizeSqlIdentifier", () => { | |||||||
|         ["w/ 'test-' it should strip the '-'", ["test-"], "test"], |         ["w/ 'test-' it should strip the '-'", ["test-"], "test"], | ||||||
|         ["w/ 'test-test' it should strip the '-'", ["test-test"], "testtest"], |         ["w/ 'test-test' it should strip the '-'", ["test-test"], "testtest"], | ||||||
|         ["w/ 'test; --test' it should strip the '; --'", ["test; --test"], "testtest"], |         ["w/ 'test; --test' it should strip the '; --'", ["test; --test"], "testtest"], | ||||||
|     ["w/ 'test test' it should strip the ' '", ["test test"], "testtest"], |         ["w/ 'test test' it should strip the ' '", ["test test"], "testtest"] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|   testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.sanitizeSqlIdentifier(...fnParams); |             const result = utils.sanitizeSqlIdentifier(...fnParams); | ||||||
|             expect(result).toStrictEqual(expected); |             expect(result).toStrictEqual(expected); | ||||||
|     }) |  | ||||||
|         }); |         }); | ||||||
|  |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#escapeHtml", () => { | describe("#escapeHtml", () => { | ||||||
| @@ -122,21 +114,23 @@ describe("#unescapeHtml", () => { | |||||||
|  |  | ||||||
| describe("#toObject", () => { | describe("#toObject", () => { | ||||||
|     it("should return an object with keys and value being set from the supplied Function", () => { |     it("should return an object with keys and value being set from the supplied Function", () => { | ||||||
|         type TestListEntry = { testPropA: string, testPropB: string }; |         type TestListEntry = { testPropA: string; testPropB: string }; | ||||||
|         type TestListFn = (testListEntry: TestListEntry) => [string, string]; |         type TestListFn = (testListEntry: TestListEntry) => [string, string]; | ||||||
|         const testList: [TestListEntry, TestListEntry] = [{ testPropA: "keyA", testPropB: "valueA" }, { testPropA: "keyB", testPropB: "valueB" }]; |         const testList: [TestListEntry, TestListEntry] = [ | ||||||
|  |             { testPropA: "keyA", testPropB: "valueA" }, | ||||||
|  |             { testPropA: "keyB", testPropB: "valueB" } | ||||||
|  |         ]; | ||||||
|         const fn: TestListFn = (testListEntry: TestListEntry) => [testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn"]; |         const fn: TestListFn = (testListEntry: TestListEntry) => [testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn"]; | ||||||
|  |  | ||||||
|         const result = utils.toObject(testList, fn); |         const result = utils.toObject(testList, fn); | ||||||
|         expect(result).toStrictEqual({ |         expect(result).toStrictEqual({ | ||||||
|             "keyA_fn": "valueA_fn", |             keyA_fn: "valueA_fn", | ||||||
|             "keyB_fn": "valueB_fn" |             keyB_fn: "valueB_fn" | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#stripTags", () => { | describe("#stripTags", () => { | ||||||
|  |  | ||||||
|     //prettier-ignore |     //prettier-ignore | ||||||
|     const htmlWithNewlines = |     const htmlWithNewlines = | ||||||
| `<p>abc | `<p>abc | ||||||
| @@ -146,15 +140,15 @@ def</p> | |||||||
|     const testCases: TestCase<typeof utils.stripTags>[] = [ |     const testCases: TestCase<typeof utils.stripTags>[] = [ | ||||||
|         ["should strip all tags and only return the content, leaving new lines and spaces in tact", [htmlWithNewlines], "abc\ndef\nghi"], |         ["should strip all tags and only return the content, leaving new lines and spaces in tact", [htmlWithNewlines], "abc\ndef\nghi"], | ||||||
|         //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text? |         //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text? | ||||||
|         ["should strip all tags and only return the content", ["<h1>abc</h1><p>def</p>"], "abcdef"], |         ["should strip all tags and only return the content", ["<h1>abc</h1><p>def</p>"], "abcdef"] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.stripTags(...fnParams); |             const result = utils.stripTags(...fnParams); | ||||||
|             expect(result).toStrictEqual(expected); |             expect(result).toStrictEqual(expected); | ||||||
|         }) |         }); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -233,7 +227,7 @@ describe("#isStringNote", () => { | |||||||
|         ], |         ], | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.isStringNote(...fnParams); |             const result = utils.isStringNote(...fnParams); | ||||||
| @@ -252,17 +246,16 @@ describe("#removeTextFileExtension", () => { | |||||||
|         ["w/ 'test.markdown' it should strip '.markdown'", ["test.markdown"], "test"], |         ["w/ 'test.markdown' it should strip '.markdown'", ["test.markdown"], "test"], | ||||||
|         ["w/ 'test.html' it should strip '.html'", ["test.html"], "test"], |         ["w/ 'test.html' it should strip '.html'", ["test.html"], "test"], | ||||||
|         ["w/ 'test.htm' it should strip '.htm'", ["test.htm"], "test"], |         ["w/ 'test.htm' it should strip '.htm'", ["test.htm"], "test"], | ||||||
|         ["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"], |         ["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.removeTextFileExtension(...fnParams); |             const result = utils.removeTextFileExtension(...fnParams); | ||||||
|             expect(result).toStrictEqual(expected); |             expect(result).toStrictEqual(expected); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#getNoteTitle", () => { | describe("#getNoteTitle", () => { | ||||||
| @@ -330,7 +323,6 @@ describe("#getNoteTitle", () => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#timeLimit", () => { | describe("#timeLimit", () => { | ||||||
|  |  | ||||||
|     it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => { |     it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => { | ||||||
|         const resolvedValue = `resolved: ${new Date().toISOString()}`; |         const resolvedValue = `resolved: ${new Date().toISOString()}`; | ||||||
|         const testPromise = new Promise((res, rej) => { |         const testPromise = new Promise((res, rej) => { | ||||||
| @@ -350,7 +342,7 @@ describe("#timeLimit", () => { | |||||||
|                 rej(rejectedValue); |                 rej(rejectedValue); | ||||||
|             }, 100); |             }, 100); | ||||||
|         }); |         }); | ||||||
|         await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue) |         await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { |     it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { | ||||||
| @@ -360,7 +352,7 @@ describe("#timeLimit", () => { | |||||||
|             }, 500); |             }, 500); | ||||||
|             //rej("rejected!"); |             //rej("rejected!"); | ||||||
|         }); |         }); | ||||||
|         await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`) |         await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { |     it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { | ||||||
| @@ -371,42 +363,40 @@ describe("#timeLimit", () => { | |||||||
|             }, 500); |             }, 500); | ||||||
|             //rej("rejected!"); |             //rej("rejected!"); | ||||||
|         }); |         }); | ||||||
|         await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg) |         await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? |     // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? | ||||||
|     it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => { |     it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => { | ||||||
|         //@ts-expect-error - passing in illegal type 'undefined' |         //@ts-expect-error - passing in illegal type 'undefined' | ||||||
|         expect(utils.timeLimit(undefined, 200)).toBe(undefined) |         expect(utils.timeLimit(undefined, 200)).toBe(undefined); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? |     // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? | ||||||
|     it("when the passed promise is not a promise, it should return the passed value", async () => { |     it("when the passed promise is not a promise, it should return the passed value", async () => { | ||||||
|         //@ts-expect-error - passing in illegal type 'object' |         //@ts-expect-error - passing in illegal type 'object' | ||||||
|         expect(utils.timeLimit({test: 1}, 200)).toStrictEqual({test: 1}) |         expect(utils.timeLimit({ test: 1 }, 200)).toStrictEqual({ test: 1 }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#deferred", () => { | describe("#deferred", () => { | ||||||
|     it("should return a promise", () => { |     it("should return a promise", () => { | ||||||
|         const result = utils.deferred(); |         const result = utils.deferred(); | ||||||
|         expect(result).toBeInstanceOf(Promise) |         expect(result).toBeInstanceOf(Promise); | ||||||
|     }) |     }); | ||||||
|     // TriliumNextTODO: Add further tests! |     // TriliumNextTODO: Add further tests! | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#removeDiacritic", () => { | describe("#removeDiacritic", () => { | ||||||
|  |  | ||||||
|     const testCases: TestCase<typeof utils.removeDiacritic>[] = [ |     const testCases: TestCase<typeof utils.removeDiacritic>[] = [ | ||||||
|         ["w/ 'Äpfel' it should replace the 'Ä'", ["Äpfel"], "Apfel"], |         ["w/ 'Äpfel' it should replace the 'Ä'", ["Äpfel"], "Apfel"], | ||||||
|         ["w/ 'Été' it should replace the 'É' and 'é'", ["Été"], "Ete"], |         ["w/ 'Été' it should replace the 'É' and 'é'", ["Été"], "Ete"], | ||||||
|         ["w/ 'Fête' it should replace the 'ê'", ["Fête"], "Fete"], |         ["w/ 'Fête' it should replace the 'ê'", ["Fête"], "Fete"], | ||||||
|         ["w/ 'Αλφαβήτα' it should replace the 'ή'", ["Αλφαβήτα"], "Αλφαβητα"], |         ["w/ 'Αλφαβήτα' it should replace the 'ή'", ["Αλφαβήτα"], "Αλφαβητα"], | ||||||
|         ["w/ '' (empty string) it should return empty string", [""], ""], |         ["w/ '' (empty string) it should return empty string", [""], ""] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.removeDiacritic(...fnParams); |             const result = utils.removeDiacritic(...fnParams); | ||||||
| @@ -415,25 +405,22 @@ describe("#removeDiacritic", () => { | |||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| describe("#normalize", () => { | describe("#normalize", () => { | ||||||
|  |  | ||||||
|     const testCases: TestCase<typeof utils.normalize>[] = [ |     const testCases: TestCase<typeof utils.normalize>[] = [ | ||||||
|         ["w/ 'Äpfel' it should replace the 'Ä' and return lowercased", ["Äpfel"], "apfel"], |         ["w/ 'Äpfel' it should replace the 'Ä' and return lowercased", ["Äpfel"], "apfel"], | ||||||
|         ["w/ 'Été' it should replace the 'É' and 'é' and return lowercased", ["Été"], "ete"], |         ["w/ 'Été' it should replace the 'É' and 'é' and return lowercased", ["Été"], "ete"], | ||||||
|         ["w/ 'FêTe' it should replace the 'ê' and return lowercased", ["FêTe"], "fete"], |         ["w/ 'FêTe' it should replace the 'ê' and return lowercased", ["FêTe"], "fete"], | ||||||
|         ["w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", ["ΑλΦαβήΤα"], "αλφαβητα"], |         ["w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", ["ΑλΦαβήΤα"], "αλφαβητα"], | ||||||
|         ["w/ '' (empty string) it should return empty string", [""], ""], |         ["w/ '' (empty string) it should return empty string", [""], ""] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.normalize(...fnParams); |             const result = utils.normalize(...fnParams); | ||||||
|             expect(result).toStrictEqual(expected); |             expect(result).toStrictEqual(expected); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#toMap", () => { | describe("#toMap", () => { | ||||||
| @@ -475,10 +462,10 @@ describe("#envToBoolean", () => { | |||||||
|         ["w/ ' ' (white space string) it should return undefined", [" "], undefined], |         ["w/ ' ' (white space string) it should return undefined", [" "], undefined], | ||||||
|         ["w/ undefined it should return undefined", [undefined], undefined], |         ["w/ undefined it should return undefined", [undefined], undefined], | ||||||
|         //@ts-expect-error - pass wrong type as param |         //@ts-expect-error - pass wrong type as param | ||||||
|         ["w/ number 1 it should return undefined", [1], undefined], |         ["w/ number 1 it should return undefined", [1], undefined] | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     testCases.forEach(testCase => { |     testCases.forEach((testCase) => { | ||||||
|         const [desc, fnParams, expected] = testCase; |         const [desc, fnParams, expected] = testCase; | ||||||
|         it(desc, () => { |         it(desc, () => { | ||||||
|             const result = utils.envToBoolean(...fnParams); |             const result = utils.envToBoolean(...fnParams); | ||||||
| @@ -514,7 +501,6 @@ describe("#isDev", () => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| describe("#formatDownloadTitle", () => { | describe("#formatDownloadTitle", () => { | ||||||
|  |  | ||||||
|     //prettier-ignore |     //prettier-ignore | ||||||
|     const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [ |     const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { | |||||||
|         filters: [ |         filters: [ | ||||||
|             { |             { | ||||||
|                 name: t("pdf.export_filter"), |                 name: t("pdf.export_filter"), | ||||||
|                 extensions: [ "pdf" ] |                 extensions: ["pdf"] | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -269,5 +269,4 @@ | |||||||
|     "new-note": "Neue Notiz", |     "new-note": "Neue Notiz", | ||||||
|     "show-windows": "Fenster anzeigen" |     "show-windows": "Fenster anzeigen" | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user