mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 01:36:24 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/develop' into feature/rtl
; Conflicts: ; src/public/app/widgets/floating_buttons/help_button.ts
This commit is contained in:
		| @@ -3,3 +3,4 @@ | |||||||
| *.yml | *.yml | ||||||
| libraries/* | libraries/* | ||||||
| docs/* | docs/* | ||||||
|  | src/public/app/doc_notes/**/* | ||||||
| @@ -66,8 +66,6 @@ chmod 755 $PKG_DIR/trilium.sh | |||||||
| cp bin/tpl/anonymize-database.sql $PKG_DIR/ | cp bin/tpl/anonymize-database.sql $PKG_DIR/ | ||||||
|  |  | ||||||
| cp -r translations $PKG_DIR/ | cp -r translations $PKG_DIR/ | ||||||
| cp -r dump-db $PKG_DIR/ |  | ||||||
| rm -rf $PKG_DIR/dump-db/node_modules |  | ||||||
|  |  | ||||||
| VERSION=`jq -r ".version" package.json` | VERSION=`jq -r ".version" package.json` | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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"; | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -11,28 +11,30 @@ 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: | ||||||
|       contact: { |                 "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).", | ||||||
|         name: "TriliumNext issue tracker", |             contact: { | ||||||
|         url: "https://github.com/TriliumNext/Notes/issues", |                 name: "TriliumNext issue tracker", | ||||||
|       }, |                 url: "https://github.com/TriliumNext/Notes/issues" | ||||||
|       license: { |             }, | ||||||
|         name: "GNU Free Documentation License 1.3 (or later)", |             license: { | ||||||
|         url: "https://www.gnu.org/licenses/fdl-1.3", |                 name: "GNU Free Documentation License 1.3 (or later)", | ||||||
|       }, |                 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", | ||||||
|     './src/routes/api/*.ts', './bin/generate-openapi.js' |         "./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( | ||||||
|             headers: { |             await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, { | ||||||
|                 "x-csrf-token": csrfToken |                 headers: { | ||||||
|             } |                     "x-csrf-token": csrfToken | ||||||
|         })).toBeOK(); |                 } | ||||||
|  |             }) | ||||||
|  |         ).toBeOK(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | import eslint from "@eslint/js"; | ||||||
|  | import tseslint from "typescript-eslint"; | ||||||
|  |  | ||||||
|  | export default tseslint.config( | ||||||
|  |     eslint.configs.recommended, | ||||||
|  |     tseslint.configs.recommended, | ||||||
|  |     // consider using rules below, once we have a full TS codebase and can be more strict | ||||||
|  |     // tseslint.configs.strictTypeChecked, | ||||||
|  |     // tseslint.configs.stylisticTypeChecked, | ||||||
|  |     // tseslint.configs.recommendedTypeChecked, | ||||||
|  |     { | ||||||
|  |         languageOptions: { | ||||||
|  |             parserOptions: { | ||||||
|  |                 projectService: true, | ||||||
|  |                 tsconfigRootDir: import.meta.dirname | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         rules: { | ||||||
|  |             // add rule overrides here | ||||||
|  |             "no-undef": "off", | ||||||
|  |             "no-unused-vars": "off", | ||||||
|  |             "@typescript-eslint/no-unused-vars": [ | ||||||
|  |                 "error", | ||||||
|  |                 { | ||||||
|  |                     "argsIgnorePattern": "^_", | ||||||
|  |                     "varsIgnorePattern": "^_", | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         ignores: [ | ||||||
|  |             "build/*", | ||||||
|  |             "dist/*", | ||||||
|  |             "docs/*", | ||||||
|  |             "libraries/*", | ||||||
|  |             "src/public/app-dist/*", | ||||||
|  |             "src/public/app/doc_notes/*" | ||||||
|  |         ] | ||||||
|  |     } | ||||||
|  | ); | ||||||
							
								
								
									
										901
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										901
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							| @@ -57,6 +57,8 @@ | |||||||
|     "dev:watch-dist": "tsx ./bin/watch-dist.ts", |     "dev:watch-dist": "tsx ./bin/watch-dist.ts", | ||||||
|     "dev:prettier-check": "prettier . --check", |     "dev:prettier-check": "prettier . --check", | ||||||
|     "dev:prettier-fix": "prettier . --write", |     "dev:prettier-fix": "prettier . --write", | ||||||
|  |     "dev:linter-check": "eslint .", | ||||||
|  |     "dev:linter-fix": "eslint . --fix", | ||||||
|     "chore:update-build-info": "tsx bin/update-build-info.ts", |     "chore:update-build-info": "tsx bin/update-build-info.ts", | ||||||
|     "chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", |     "chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", | ||||||
|     "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", |     "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", | ||||||
| @@ -103,7 +105,6 @@ | |||||||
|     "express-session": "1.18.1", |     "express-session": "1.18.1", | ||||||
|     "force-graph": "1.49.2", |     "force-graph": "1.49.2", | ||||||
|     "fs-extra": "11.3.0", |     "fs-extra": "11.3.0", | ||||||
|     "happy-dom": "17.1.8", |  | ||||||
|     "helmet": "8.0.0", |     "helmet": "8.0.0", | ||||||
|     "html": "1.0.0", |     "html": "1.0.0", | ||||||
|     "html2plaintext": "2.1.4", |     "html2plaintext": "2.1.4", | ||||||
| @@ -123,7 +124,6 @@ | |||||||
|     "jsdom": "26.0.0", |     "jsdom": "26.0.0", | ||||||
|     "jsplumb": "2.15.6", |     "jsplumb": "2.15.6", | ||||||
|     "katex": "0.16.21", |     "katex": "0.16.21", | ||||||
|     "knockout": "3.5.1", |  | ||||||
|     "leaflet": "1.9.4", |     "leaflet": "1.9.4", | ||||||
|     "leaflet-gpx": "2.1.2", |     "leaflet-gpx": "2.1.2", | ||||||
|     "mark.js": "8.11.1", |     "mark.js": "8.11.1", | ||||||
| @@ -150,7 +150,6 @@ | |||||||
|     "striptags": "3.2.0", |     "striptags": "3.2.0", | ||||||
|     "swagger-ui-express": "5.0.1", |     "swagger-ui-express": "5.0.1", | ||||||
|     "tmp": "0.2.3", |     "tmp": "0.2.3", | ||||||
|     "ts-loader": "9.5.2", |  | ||||||
|     "turndown": "7.2.0", |     "turndown": "7.2.0", | ||||||
|     "unescape": "1.0.1", |     "unescape": "1.0.1", | ||||||
|     "vanilla-js-wheel-zoom": "9.0.4", |     "vanilla-js-wheel-zoom": "9.0.4", | ||||||
| @@ -168,6 +167,7 @@ | |||||||
|     "@electron-forge/maker-zip": "7.7.0", |     "@electron-forge/maker-zip": "7.7.0", | ||||||
|     "@electron-forge/plugin-auto-unpack-natives": "7.7.0", |     "@electron-forge/plugin-auto-unpack-natives": "7.7.0", | ||||||
|     "@electron/rebuild": "3.7.1", |     "@electron/rebuild": "3.7.1", | ||||||
|  |     "@eslint/js": "9.21.0", | ||||||
|     "@playwright/test": "1.50.1", |     "@playwright/test": "1.50.1", | ||||||
|     "@popperjs/core": "2.11.8", |     "@popperjs/core": "2.11.8", | ||||||
|     "@types/archiver": "6.0.3", |     "@types/archiver": "6.0.3", | ||||||
| @@ -216,14 +216,17 @@ | |||||||
|     "cross-env": "7.0.3", |     "cross-env": "7.0.3", | ||||||
|     "css-loader": "7.1.2", |     "css-loader": "7.1.2", | ||||||
|     "electron": "34.3.0", |     "electron": "34.3.0", | ||||||
|  |     "eslint": "9.21.0", | ||||||
|     "esm": "3.2.25", |     "esm": "3.2.25", | ||||||
|  |     "happy-dom": "17.2.2", | ||||||
|     "i18next-http-backend": "3.0.2", |     "i18next-http-backend": "3.0.2", | ||||||
|     "jsdoc": "4.0.4", |     "jsdoc": "4.0.4", | ||||||
|  |     "knockout": "3.5.1", | ||||||
|     "lorem-ipsum": "2.0.8", |     "lorem-ipsum": "2.0.8", | ||||||
|     "mini-css-extract-plugin": "2.9.2", |     "mini-css-extract-plugin": "2.9.2", | ||||||
|     "nodemon": "3.1.9", |     "nodemon": "3.1.9", | ||||||
|     "postcss-loader": "8.1.1", |     "postcss-loader": "8.1.1", | ||||||
|     "prettier": "3.5.2", |     "prettier": "3.5.3", | ||||||
|     "rcedit": "4.0.1", |     "rcedit": "4.0.1", | ||||||
|     "rimraf": "6.0.1", |     "rimraf": "6.0.1", | ||||||
|     "sass": "1.85.1", |     "sass": "1.85.1", | ||||||
| @@ -231,10 +234,12 @@ | |||||||
|     "split.js": "1.6.5", |     "split.js": "1.6.5", | ||||||
|     "supertest": "7.0.0", |     "supertest": "7.0.0", | ||||||
|     "swagger-jsdoc": "6.2.8", |     "swagger-jsdoc": "6.2.8", | ||||||
|  |     "ts-loader": "9.5.2", | ||||||
|     "tslib": "2.8.1", |     "tslib": "2.8.1", | ||||||
|     "tsx": "4.19.3", |     "tsx": "4.19.3", | ||||||
|     "typedoc": "0.27.9", |     "typedoc": "0.27.9", | ||||||
|     "typescript": "5.8.2", |     "typescript": "5.8.2", | ||||||
|  |     "typescript-eslint": "8.26.0", | ||||||
|     "vitest": "3.0.7", |     "vitest": "3.0.7", | ||||||
|     "webpack": "5.98.0", |     "webpack": "5.98.0", | ||||||
|     "webpack-cli": "6.0.1", |     "webpack-cli": "6.0.1", | ||||||
|   | |||||||
| @@ -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,68 +14,70 @@ 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. */ | ||||||
|   forbidOnly: !!process.env.CI, |     forbidOnly: !!process.env.CI, | ||||||
|   /* Retry on CI only */ |     /* Retry on CI only */ | ||||||
|   retries: process.env.CI ? 2 : 0, |     retries: process.env.CI ? 2 : 0, | ||||||
|   /* 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 */ |  | ||||||
|   projects: [ |  | ||||||
|     { |  | ||||||
|       name: 'chromium', |  | ||||||
|       use: { ...devices['Desktop Chrome'] }, |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // { |     /* Configure projects for major browsers */ | ||||||
|     //   name: 'firefox', |     projects: [ | ||||||
|     //   use: { ...devices['Desktop Firefox'] }, |         { | ||||||
|     // }, |             name: "chromium", | ||||||
|  |             use: { ...devices["Desktop Chrome"] } | ||||||
|  |         } | ||||||
|  |  | ||||||
|     // { |         // { | ||||||
|     //   name: 'webkit', |         //   name: 'firefox', | ||||||
|     //   use: { ...devices['Desktop Safari'] }, |         //   use: { ...devices['Desktop Firefox'] }, | ||||||
|     // }, |         // }, | ||||||
|  |  | ||||||
|     /* Test against mobile viewports. */ |         // { | ||||||
|     // { |         //   name: 'webkit', | ||||||
|     //   name: 'Mobile Chrome', |         //   use: { ...devices['Desktop Safari'] }, | ||||||
|     //   use: { ...devices['Pixel 5'] }, |         // }, | ||||||
|     // }, |  | ||||||
|     // { |  | ||||||
|     //   name: 'Mobile Safari', |  | ||||||
|     //   use: { ...devices['iPhone 12'] }, |  | ||||||
|     // }, |  | ||||||
|  |  | ||||||
|     /* Test against branded browsers. */ |         /* Test against mobile viewports. */ | ||||||
|     // { |         // { | ||||||
|     //   name: 'Microsoft Edge', |         //   name: 'Mobile Chrome', | ||||||
|     //   use: { ...devices['Desktop Edge'], channel: 'msedge' }, |         //   use: { ...devices['Pixel 5'] }, | ||||||
|     // }, |         // }, | ||||||
|     // { |         // { | ||||||
|     //   name: 'Google Chrome', |         //   name: 'Mobile Safari', | ||||||
|     //   use: { ...devices['Desktop Chrome'], channel: 'chrome' }, |         //   use: { ...devices['iPhone 12'] }, | ||||||
|     // }, |         // }, | ||||||
|   ], |  | ||||||
|  |  | ||||||
|   /* Run your local dev server before starting the tests */ |         /* Test against branded browsers. */ | ||||||
|   webServer: !process.env.TRILIUM_DOCKER ? { |         // { | ||||||
|     command: 'npm run test:integration-mem-db-dev', |         //   name: 'Microsoft Edge', | ||||||
|     url: SERVER_URL, |         //   use: { ...devices['Desktop Edge'], channel: 'msedge' }, | ||||||
|     reuseExistingServer: !process.env.CI, |         // }, | ||||||
|   } : undefined, |         // { | ||||||
|  |         //   name: 'Google Chrome', | ||||||
|  |         //   use: { ...devices['Desktop Chrome'], channel: 'chrome' }, | ||||||
|  |         // }, | ||||||
|  |     ], | ||||||
|  |  | ||||||
|  |     /* Run your local dev server before starting the tests */ | ||||||
|  |     webServer: !process.env.TRILIUM_DOCKER | ||||||
|  |         ? { | ||||||
|  |               command: "npm run test:integration-mem-db-dev", | ||||||
|  |               url: SERVER_URL, | ||||||
|  |               reuseExistingServer: !process.env.CI | ||||||
|  |           } | ||||||
|  |         : 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; | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ import type NoteTreeWidget from "../widgets/note_tree.js"; | |||||||
| import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; | ||||||
| import type { ContextMenuEvent } from "../menus/context_menu.js"; | import type { ContextMenuEvent } from "../menus/context_menu.js"; | ||||||
| import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | import type TypeWidget from "../widgets/type_widgets/type_widget.js"; | ||||||
|  | import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js"; | ||||||
|  |  | ||||||
| interface Layout { | interface Layout { | ||||||
|     getRootWidget: (appContext: AppContext) => RootWidget; |     getRootWidget: (appContext: AppContext) => RootWidget; | ||||||
| @@ -62,7 +63,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 +71,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 +109,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 +132,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 +224,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 +237,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 +351,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; | ||||||
| @@ -359,6 +362,12 @@ type EventMappings = { | |||||||
|     relationMapResetPanZoom: { ntxId: string | null | undefined }; |     relationMapResetPanZoom: { ntxId: string | null | undefined }; | ||||||
|     relationMapResetZoomIn: { ntxId: string | null | undefined }; |     relationMapResetZoomIn: { ntxId: string | null | undefined }; | ||||||
|     relationMapResetZoomOut: { ntxId: string | null | undefined }; |     relationMapResetZoomOut: { ntxId: string | null | undefined }; | ||||||
|  |     activeNoteChangedEvent: {}; | ||||||
|  |     showAddLinkDialog: { | ||||||
|  |         textTypeWidget: EditableTextTypeWidget; | ||||||
|  |         text: string; | ||||||
|  |     }; | ||||||
|  |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export type EventListener<T extends EventNames> = { | export type EventListener<T extends EventNames> = { | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> { | |||||||
|     children: ChildT[]; |     children: ChildT[]; | ||||||
|     initialized: Promise<void> | null; |     initialized: Promise<void> | null; | ||||||
|     parent?: TypedComponent<any>; |     parent?: TypedComponent<any>; | ||||||
|     position!: number; |     _position!: number; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; |         this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; | ||||||
| @@ -31,6 +31,14 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> { | |||||||
|         return this.constructor.name.replace(/[^A-Z0-9]/gi, "_"); |         return this.constructor.name.replace(/[^A-Z0-9]/gi, "_"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get position() { | ||||||
|  |         return this._position; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     set position(newPosition: number) { | ||||||
|  |         this._position = newPosition; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     setParent(parent: TypedComponent<any>) { |     setParent(parent: TypedComponent<any>) { | ||||||
|         this.parent = parent; |         this.parent = parent; | ||||||
|         return this; |         return this; | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ export default class ShortcutComponent extends Component implements EventListene | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     bindNoteShortcutHandler(labelOrRow: AttributeRow) { |     bindNoteShortcutHandler(labelOrRow: AttributeRow) { | ||||||
|         const handler = () => appContext.tabManager.getActiveContext().setNote(labelOrRow.noteId); |         const handler = () => appContext.tabManager.getActiveContext()?.setNote(labelOrRow.noteId); | ||||||
|         const namespace = labelOrRow.attributeId; |         const namespace = labelOrRow.attributeId; | ||||||
|  |  | ||||||
|         if (labelOrRow.isDeleted) { |         if (labelOrRow.isDeleted) { | ||||||
|   | |||||||
| @@ -248,7 +248,7 @@ export default class TabManager extends Component { | |||||||
|         await noteContext.setEmpty(); |         await noteContext.setEmpty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId = null) { |     async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId) { | ||||||
|         const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId); |         const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId); | ||||||
|  |  | ||||||
|         const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId); |         const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId); | ||||||
|   | |||||||
| @@ -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); | ||||||
|  |  | ||||||
| @@ -12,21 +12,21 @@ function setCookie(name: string, value?: string) { | |||||||
|     const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000); |     const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000); | ||||||
|     const expires = "; expires=" + date.toUTCString(); |     const expires = "; expires=" + date.toUTCString(); | ||||||
|  |  | ||||||
|     document.cookie = name + "=" + (value || "")  + expires + "; path=/"; |     document.cookie = name + "=" + (value || "") + expires + "; path=/"; | ||||||
| } | } | ||||||
|  |  | ||||||
| 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(); | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|         const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null; |         const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null; | ||||||
|         const branch = froca.getBranch(this.node.data.branchId); |         const branch = froca.getBranch(this.node.data.branchId); | ||||||
|         const isNotRoot = note?.noteId !== "root"; |         const isNotRoot = note?.noteId !== "root"; | ||||||
|         const isHoisted = note?.noteId === appContext.tabManager.getActiveContext().hoistedNoteId; |         const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|         const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null; |         const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null; | ||||||
|  |  | ||||||
|         // some actions don't support multi-note, so they are disabled when notes are selected, |         // some actions don't support multi-note, so they are disabled when notes are selected, | ||||||
| @@ -226,8 +226,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree | |||||||
|                 templateNoteId: templateNoteId |                 templateNoteId: templateNoteId | ||||||
|             }); |             }); | ||||||
|         } else if (command === "openNoteInSplit") { |         } else if (command === "openNoteInSplit") { | ||||||
|             const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |             const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|             const { ntxId } = subContexts[subContexts.length - 1]; |             const { ntxId } = subContexts?.[subContexts.length - 1] ?? {}; | ||||||
|  |  | ||||||
|             this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath }); |             this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath }); | ||||||
|         } else if (command === "convertNoteToAttachment") { |         } else if (command === "convertNoteToAttachment") { | ||||||
|   | |||||||
| @@ -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"]); | ||||||
| @@ -41,7 +40,7 @@ describe("Lexing", () => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| describe.todo("Parser", () => { | describe.todo("Parser", () => { | ||||||
|   /* #TODO |     /* #TODO | ||||||
|     it("simple label", () => { |     it("simple label", () => { | ||||||
|  |  | ||||||
|         const attrs = attributeParser.parse(["#token"].map((t: any) => ({ text: t }))); |         const attrs = attributeParser.parse(["#token"].map((t: any) => ({ text: t }))); | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -152,10 +152,10 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f | |||||||
| async function activateParentNotePath() { | async function activateParentNotePath() { | ||||||
|     // this is not perfect, maybe we should find the next/previous sibling, but that's more complex |     // this is not perfect, maybe we should find the next/previous sibling, but that's more complex | ||||||
|     const activeContext = appContext.tabManager.getActiveContext(); |     const activeContext = appContext.tabManager.getActiveContext(); | ||||||
|     const parentNotePathArr = activeContext.notePathArray.slice(0, -1); |     const parentNotePathArr = activeContext?.notePathArray.slice(0, -1); | ||||||
|  |  | ||||||
|     if (parentNotePathArr.length > 0) { |     if (parentNotePathArr && parentNotePathArr.length > 0) { | ||||||
|         activeContext.setNote(parentNotePathArr.join("/")); |         activeContext?.setNote(parentNotePathArr.join("/")); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -457,13 +457,13 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|     this.BasicWidget = BasicWidget; |     this.BasicWidget = BasicWidget; | ||||||
|  |  | ||||||
|     this.activateNote = async (notePath) => { |     this.activateNote = async (notePath) => { | ||||||
|         await appContext.tabManager.getActiveContext().setNote(notePath); |         await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.activateNewNote = async (notePath) => { |     this.activateNewNote = async (notePath) => { | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         await appContext.tabManager.getActiveContext().setNote(notePath); |         await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|         await appContext.triggerEvent("focusAndSelectTitle", {}); |         await appContext.triggerEvent("focusAndSelectTitle", {}); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -480,8 +480,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|     this.openSplitWithNote = async (notePath, activate) => { |     this.openSplitWithNote = async (notePath, activate) => { | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         const subContexts = appContext.tabManager.getActiveContext().getSubContexts(); |         const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts(); | ||||||
|         const { ntxId } = subContexts[subContexts.length - 1]; |         const { ntxId } = subContexts?.[subContexts.length - 1] ?? {}; | ||||||
|  |  | ||||||
|         await appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath }); |         await appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath }); | ||||||
|  |  | ||||||
| @@ -591,15 +591,48 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
|  |  | ||||||
|     this.addTextToActiveContextEditor = (text) => appContext.triggerCommand("addTextToActiveEditor", { text }); |     this.addTextToActiveContextEditor = (text) => appContext.triggerCommand("addTextToActiveEditor", { text }); | ||||||
|  |  | ||||||
|     this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); |     this.getActiveContextNote = (): FNote => { | ||||||
|     this.getActiveContext = () => appContext.tabManager.getActiveContext(); |         const note = appContext.tabManager.getActiveContextNote(); | ||||||
|     this.getActiveMainContext = () => appContext.tabManager.getActiveMainContext(); |         if (!note) { | ||||||
|  |             throw new Error("No active context note found"); | ||||||
|  |         } | ||||||
|  |         return note; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveContext = (): NoteContext => { | ||||||
|  |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveMainContext = (): NoteContext => { | ||||||
|  |         const context = appContext.tabManager.getActiveMainContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active main context found"); | ||||||
|  |         } | ||||||
|  |         return context; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); |     this.getNoteContexts = () => appContext.tabManager.getNoteContexts(); | ||||||
|     this.getMainNoteContexts = () => appContext.tabManager.getMainNoteContexts(); |     this.getMainNoteContexts = () => appContext.tabManager.getMainNoteContexts(); | ||||||
|  |  | ||||||
|     this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor(); |     this.getActiveContextTextEditor = () => { | ||||||
|     this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor(); |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context.getTextEditor(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     this.getActiveContextCodeEditor = () => { | ||||||
|  |         const context = appContext.tabManager.getActiveContext(); | ||||||
|  |         if (!context) { | ||||||
|  |             throw new Error("No active context found"); | ||||||
|  |         } | ||||||
|  |         return context.getCodeEditor(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     this.getActiveNoteDetailWidget = () => new Promise((resolve) => appContext.triggerCommand("executeInActiveNoteDetailWidget", { callback: resolve })); |     this.getActiveNoteDetailWidget = () => new Promise((resolve) => appContext.triggerCommand("executeInActiveNoteDetailWidget", { callback: resolve })); | ||||||
|     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); |     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); | ||||||
| @@ -665,5 +698,5 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig | |||||||
| } | } | ||||||
|  |  | ||||||
| export default FrontendScriptApi as any as { | export default FrontendScriptApi as any as { | ||||||
|     new (startNote: FNote, currentNote: FNote, originEntity: Entity | null, $container: JQuery<HTMLElement> | null): Api; |     new(startNote: FNote, currentNote: FNote, originEntity: Entity | null, $container: JQuery<HTMLElement> | null): Api; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ ws.subscribeToMessages(async (message) => { | |||||||
|         toastService.showPersistent(toast); |         toastService.showPersistent(toast); | ||||||
|  |  | ||||||
|         if (message.result.importedNoteId) { |         if (message.result.importedNoteId) { | ||||||
|             await appContext.tabManager.getActiveContext().setNote(message.result.importedNoteId); |             await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| @@ -102,7 +102,7 @@ ws.subscribeToMessages(async (message) => { | |||||||
|         toastService.showPersistent(toast); |         toastService.showPersistent(toast); | ||||||
|  |  | ||||||
|         if (message.result.parentNoteId) { |         if (message.result.parentNoteId) { | ||||||
|             await appContext.tabManager.getActiveContext().setNote(message.result.importedNoteId, { |             await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId, { | ||||||
|                 viewScope: { |                 viewScope: { | ||||||
|                     viewMode: "attachments" |                     viewMode: "attachments" | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -322,9 +322,7 @@ function init() { | |||||||
|  |  | ||||||
|     $.fn.setSelectedNotePath = function (notePath) { |     $.fn.setSelectedNotePath = function (notePath) { | ||||||
|         notePath = notePath || ""; |         notePath = notePath || ""; | ||||||
|  |  | ||||||
|         $(this).attr(SELECTED_NOTE_PATH_KEY, notePath); |         $(this).attr(SELECTED_NOTE_PATH_KEY, notePath); | ||||||
|  |  | ||||||
|         $(this).closest(".input-group").find(".go-to-selected-note-button").toggleClass("disabled", !notePath.trim()).attr("href", `#${notePath}`); // we also set href here so tooltip can be displayed |         $(this).closest(".input-group").find(".go-to-selected-note-button").toggleClass("disabled", !notePath.trim()).attr("href", `#${notePath}`); // we also set href here so tooltip can be displayed | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -336,11 +334,9 @@ function init() { | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     $.fn.setSelectedExternalLink = function (externalLink) { |     $.fn.setSelectedExternalLink = function (externalLink: string | null) { | ||||||
|         if (externalLink) { |         $(this).attr(SELECTED_EXTERNAL_LINK_KEY, externalLink); | ||||||
|             // TODO: This doesn't seem to do anything with the external link, is it normal? |         $(this).closest(".input-group").find(".go-to-selected-note-button").toggleClass("disabled", true); | ||||||
|             $(this).closest(".input-group").find(".go-to-selected-note-button").toggleClass("disabled", true); |  | ||||||
|         } |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     $.fn.setNote = async function (noteId) { |     $.fn.setNote = async function (noteId) { | ||||||
|   | |||||||
| @@ -86,8 +86,8 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot | |||||||
|  |  | ||||||
|     await ws.waitForMaxKnownEntityChangeId(); |     await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|     if (options.activate) { |     const activeNoteContext = appContext.tabManager.getActiveContext(); | ||||||
|         const activeNoteContext = appContext.tabManager.getActiveContext(); |     if (activeNoteContext && options.activate) { | ||||||
|         await activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); |         await activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); | ||||||
|  |  | ||||||
|         if (options.focus === "title") { |         if (options.focus === "title") { | ||||||
| @@ -152,8 +152,7 @@ async function duplicateSubtree(noteId: string, parentNotePath: string) { | |||||||
|  |  | ||||||
|     await ws.waitForMaxKnownEntityChangeId(); |     await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|     const activeNoteContext = appContext.tabManager.getActiveContext(); |     appContext.tabManager.getActiveContext()?.setNote(`${parentNotePath}/${note.noteId}`); | ||||||
|     activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`); |  | ||||||
|  |  | ||||||
|     const origNote = await froca.getNote(noteId); |     const origNote = await froca.getNote(noteId); | ||||||
|     toastService.showMessage(t("note_create.duplicated", { title: origNote?.title })); |     toastService.showMessage(t("note_create.duplicated", { title: origNote?.title })); | ||||||
|   | |||||||
| @@ -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"); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -36,20 +36,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; | ||||||
| @@ -164,7 +164,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; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/public/app/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -85,7 +85,7 @@ declare global { | |||||||
|         getSelectedNotePath(): string | undefined; |         getSelectedNotePath(): string | undefined; | ||||||
|         getSelectedNoteId(): string | null; |         getSelectedNoteId(): string | null; | ||||||
|         setSelectedNotePath(notePath: string | null | undefined); |         setSelectedNotePath(notePath: string | null | undefined); | ||||||
|         getSelectedExternalLink(this: HTMLElement): string | undefined; |         getSelectedExternalLink(): string | undefined; | ||||||
|         setSelectedExternalLink(externalLink: string | null | undefined); |         setSelectedExternalLink(externalLink: string | null | undefined); | ||||||
|         setNote(noteId: string); |         setNote(noteId: string); | ||||||
|         markRegExp(regex: RegExp, opts: { |         markRegExp(regex: RegExp, opts: { | ||||||
|   | |||||||
| @@ -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,8 +105,7 @@ 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) { | ||||||
|                 const result = await server.upload(`attachments/${this.attachmentId}/file`, fileToUpload); |                 const result = await server.upload(`attachments/${this.attachmentId}/file`, 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,11 +168,10 @@ 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(); | ||||||
|         await appContext.tabManager.getActiveContext().setNote(newNote.noteId); |         await appContext.tabManager.getActiveContext()?.setNote(newNote.noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renameAttachmentCommand() { |     async renameAttachmentCommand() { | ||||||
|   | |||||||
| @@ -43,8 +43,8 @@ const DROPDOWN_TPL = ` | |||||||
|                 data-calendar-input="month"></button> |                 data-calendar-input="month"></button> | ||||||
|             <ul class="dropdown-menu" data-calendar-input="month-list"> |             <ul class="dropdown-menu" data-calendar-input="month-list"> | ||||||
|                 ${Object.entries(MONTHS) |                 ${Object.entries(MONTHS) | ||||||
|                     .map(([i, month]) => `<li><button class="dropdown-item" data-value=${i}>${month}</button></li>`) |         .map(([i, month]) => `<li><button class="dropdown-item" data-value=${i}>${month}</button></li>`) | ||||||
|                     .join("")} |         .join("")} | ||||||
|             </ul> |             </ul> | ||||||
|  |  | ||||||
|             <button class="calendar-btn tn-tool-button bx bx-chevron-right" data-calendar-toggle="next"></button> |             <button class="calendar-btn tn-tool-button bx bx-chevron-right" data-calendar-toggle="next"></button> | ||||||
| @@ -149,7 +149,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget { | |||||||
|                 const note = await dateNoteService.getDayNote(date); |                 const note = await dateNoteService.getDayNote(date); | ||||||
|  |  | ||||||
|                 if (note) { |                 if (note) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote(note.noteId); |                     appContext.tabManager.getActiveContext()?.setNote(note.noteId); | ||||||
|                     this.dropdown?.hide(); |                     this.dropdown?.hide(); | ||||||
|                 } else { |                 } else { | ||||||
|                     toastService.showError(t("calendar.cannot_find_day_note")); |                     toastService.showError(t("calendar.cannot_find_day_note")); | ||||||
| @@ -189,10 +189,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget { | |||||||
|  |  | ||||||
|     async dropdownShown() { |     async dropdownShown() { | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CALENDAR_WIDGET); |         await libraryLoader.requireLibrary(libraryLoader.CALENDAR_WIDGET); | ||||||
|  |         this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null); | ||||||
|         const activeNote = appContext.tabManager.getActiveContextNote(); |  | ||||||
|  |  | ||||||
|         this.init(activeNote?.getOwnedLabelValue("dateNote")); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     init(activeDate: string | null) { |     init(activeDate: string | null) { | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ export default class NoteLauncher extends AbstractLauncher { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getHoistedNoteId() { |     getHoistedNoteId() { | ||||||
|         return this.launcherNote.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext().hoistedNoteId; |         return this.launcherNote.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getTitle() { |     getTitle() { | ||||||
|   | |||||||
| @@ -10,6 +10,6 @@ export default class TodayLauncher extends NoteLauncher { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getHoistedNoteId() { |     getHoistedNoteId() { | ||||||
|         return appContext.tabManager.getActiveContext().hoistedNoteId; |         return appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -228,7 +228,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title })); |         toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title })); | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|         await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, { |         await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, { | ||||||
|             viewScope: { |             viewScope: { | ||||||
|                 viewMode: "attachments", |                 viewMode: "attachments", | ||||||
|                 attachmentId: newAttachment.attachmentId |                 attachmentId: newAttachment.attachmentId | ||||||
|   | |||||||
| @@ -24,8 +24,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> { | |||||||
|             if (visible) { |             if (visible) { | ||||||
|                 this.triggerEvent("focusTree", {}); |                 this.triggerEvent("focusTree", {}); | ||||||
|             } else { |             } else { | ||||||
|                 const activeNoteContext = appContext.tabManager.getActiveContext(); |                 this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); | ||||||
|                 this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId }); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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)); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,29 @@ | |||||||
| import FlexContainer from "./flex_container.js"; | import FlexContainer from "./flex_container.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
|  | import NoteContext from "../../components/note_context.js"; | ||||||
|  | import type { CommandMappings, EventNames, EventData } from "../../components/app_context.js"; | ||||||
|  | import type BasicWidget from "../basic_widget.js"; | ||||||
| 
 | 
 | ||||||
| export default class SplitNoteContainer extends FlexContainer { | interface NoteContextEvent { | ||||||
|     constructor(widgetFactory) { |     noteContext: NoteContext; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface SplitNoteWidget extends BasicWidget { | ||||||
|  |     hasBeenAlreadyShown?: boolean; | ||||||
|  |     ntxId?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type WidgetFactory = () => SplitNoteWidget; | ||||||
|  | 
 | ||||||
|  | interface Widgets { | ||||||
|  |     [key: string]: SplitNoteWidget; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> { | ||||||
|  |     private widgetFactory: WidgetFactory; | ||||||
|  |     private widgets: Widgets; | ||||||
|  | 
 | ||||||
|  |     constructor(widgetFactory: WidgetFactory) { | ||||||
|         super("row"); |         super("row"); | ||||||
| 
 | 
 | ||||||
|         this.widgetFactory = widgetFactory; |         this.widgetFactory = widgetFactory; | ||||||
| @@ -13,7 +34,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         this.collapsible(); |         this.collapsible(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async newNoteContextCreatedEvent({ noteContext }) { |     async newNoteContextCreatedEvent({ noteContext }: NoteContextEvent) { | ||||||
|         const widget = this.widgetFactory(); |         const widget = this.widgetFactory(); | ||||||
| 
 | 
 | ||||||
|         const $renderedWidget = widget.render(); |         const $renderedWidget = widget.render(); | ||||||
| @@ -23,20 +44,32 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
| 
 | 
 | ||||||
|         this.$widget.append($renderedWidget); |         this.$widget.append($renderedWidget); | ||||||
| 
 | 
 | ||||||
|         widget.handleEvent("initialRenderComplete"); |         widget.handleEvent("initialRenderComplete", {}); | ||||||
| 
 | 
 | ||||||
|         widget.toggleExt(false); |         widget.toggleExt(false); | ||||||
| 
 | 
 | ||||||
|         this.widgets[noteContext.ntxId] = widget; |         if (noteContext.ntxId) { | ||||||
|  |             this.widgets[noteContext.ntxId] = widget; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         await widget.handleEvent("setNoteContext", { noteContext }); |         await widget.handleEvent("setNoteContext", { noteContext }); | ||||||
| 
 | 
 | ||||||
|         this.child(widget); |         this.child(widget); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }) { |     async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: { | ||||||
|  |         ntxId: string; | ||||||
|  |         notePath?: string; | ||||||
|  |         hoistedNoteId?: string; | ||||||
|  |         viewScope?: any; | ||||||
|  |     }) { | ||||||
|         const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId; |         const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId; | ||||||
| 
 | 
 | ||||||
|  |         if (!mainNtxId) { | ||||||
|  |             logError("empty mainNtxId!"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (!ntxId) { |         if (!ntxId) { | ||||||
|             logError("empty ntxId!"); |             logError("empty ntxId!"); | ||||||
| 
 | 
 | ||||||
| @@ -53,7 +86,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         // insert the note context after the originating note context
 |         // insert the note context after the originating note context
 | ||||||
|         ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId); |         ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId); | ||||||
| 
 | 
 | ||||||
|         this.triggerCommand("noteContextReorder", { ntxIdsInOrder: ntxIds }); |         this.triggerCommand("noteContextReorder" as keyof CommandMappings, { ntxIdsInOrder: ntxIds }); | ||||||
| 
 | 
 | ||||||
|         // move the note context rendered widget after the originating widget
 |         // move the note context rendered widget after the originating widget
 | ||||||
|         this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${ntxId}"]`)); |         this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${ntxId}"]`)); | ||||||
| @@ -67,11 +100,11 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     closeThisNoteSplitCommand({ ntxId }) { |     closeThisNoteSplitCommand({ ntxId }: { ntxId: string }): void { | ||||||
|         appContext.tabManager.removeNoteContext(ntxId); |         appContext.tabManager.removeNoteContext(ntxId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async moveThisNoteSplitCommand({ ntxId, isMovingLeft }) { |     async moveThisNoteSplitCommand({ ntxId, isMovingLeft }: { ntxId: string; isMovingLeft: boolean }): Promise<void> { | ||||||
|         if (!ntxId) { |         if (!ntxId) { | ||||||
|             logError("empty ntxId!"); |             logError("empty ntxId!"); | ||||||
|             return; |             return; | ||||||
| @@ -96,7 +129,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         const newNtxIds = [...ntxIds.slice(0, leftIndex), ntxIds[leftIndex + 1], ntxIds[leftIndex], ...ntxIds.slice(leftIndex + 2)]; |         const newNtxIds = [...ntxIds.slice(0, leftIndex), ntxIds[leftIndex + 1], ntxIds[leftIndex], ...ntxIds.slice(leftIndex + 2)]; | ||||||
|         const isChangingMainContext = !contexts[leftIndex].mainNtxId; |         const isChangingMainContext = !contexts[leftIndex].mainNtxId; | ||||||
| 
 | 
 | ||||||
|         this.triggerCommand("noteContextReorder", { |         this.triggerCommand("noteContextReorder" as keyof CommandMappings, { | ||||||
|             ntxIdsInOrder: newNtxIds, |             ntxIdsInOrder: newNtxIds, | ||||||
|             oldMainNtxId: isChangingMainContext ? ntxIds[leftIndex] : null, |             oldMainNtxId: isChangingMainContext ? ntxIds[leftIndex] : null, | ||||||
|             newMainNtxId: isChangingMainContext ? ntxIds[leftIndex + 1] : null |             newMainNtxId: isChangingMainContext ? ntxIds[leftIndex + 1] : null | ||||||
| @@ -109,16 +142,16 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); |         await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     activeContextChangedEvent() { |     activeContextChangedEvent(): void { | ||||||
|         this.refresh(); |         this.refresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteSwitchedAndActivatedEvent() { |     noteSwitchedAndActivatedEvent(): void { | ||||||
|         this.refresh(); |         this.refresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteContextRemovedEvent({ ntxIds }) { |     noteContextRemovedEvent({ ntxIds }: { ntxIds: string[] }): void { | ||||||
|         this.children = this.children.filter((c) => !ntxIds.includes(c.ntxId)); |         this.children = this.children.filter((c) => c.ntxId && !ntxIds.includes(c.ntxId)); | ||||||
| 
 | 
 | ||||||
|         for (const ntxId of ntxIds) { |         for (const ntxId of ntxIds) { | ||||||
|             this.$widget.find(`[data-ntx-id="${ntxId}"]`).remove(); |             this.$widget.find(`[data-ntx-id="${ntxId}"]`).remove(); | ||||||
| @@ -127,7 +160,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     contextsReopenedEvent({ ntxId, afterNtxId }) { |     contextsReopenedEvent({ ntxId, afterNtxId }: { ntxId?: string; afterNtxId?: string }): void { | ||||||
|         if (ntxId === undefined || afterNtxId === undefined) { |         if (ntxId === undefined || afterNtxId === undefined) { | ||||||
|             // no single split reopened
 |             // no single split reopened
 | ||||||
|             return; |             return; | ||||||
| @@ -135,13 +168,11 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|         this.$widget.find(`[data-ntx-id="${ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${afterNtxId}"]`)); |         this.$widget.find(`[data-ntx-id="${ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${afterNtxId}"]`)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refresh() { |     async refresh(): Promise<void> { | ||||||
|         this.toggleExt(true); |         this.toggleExt(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     toggleInt(show) {} // not needed
 |     toggleExt(show: boolean): void { | ||||||
| 
 |  | ||||||
|     toggleExt(show) { |  | ||||||
|         const activeMainContext = appContext.tabManager.getActiveMainContext(); |         const activeMainContext = appContext.tabManager.getActiveMainContext(); | ||||||
|         const activeNtxId = activeMainContext ? activeMainContext.ntxId : null; |         const activeNtxId = activeMainContext ? activeMainContext.ntxId : null; | ||||||
| 
 | 
 | ||||||
| @@ -149,7 +180,7 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|             const noteContext = appContext.tabManager.getNoteContextById(ntxId); |             const noteContext = appContext.tabManager.getNoteContextById(ntxId); | ||||||
| 
 | 
 | ||||||
|             const widget = this.widgets[ntxId]; |             const widget = this.widgets[ntxId]; | ||||||
|             widget.toggleExt(show && activeNtxId && [noteContext.ntxId, noteContext.mainNtxId].includes(activeNtxId)); |             widget.toggleExt(show && activeNtxId !== null && [noteContext.ntxId, noteContext.mainNtxId].includes(activeNtxId)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -158,41 +189,50 @@ export default class SplitNoteContainer extends FlexContainer { | |||||||
|      * are not executed, we're waiting for the first tab activation, and then we update the tab. After this initial |      * are not executed, we're waiting for the first tab activation, and then we update the tab. After this initial | ||||||
|      * activation, further note switches are always propagated to the tabs. |      * activation, further note switches are always propagated to the tabs. | ||||||
|      */ |      */ | ||||||
|     handleEventInChildren(name, data) { |     handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<any> | null { | ||||||
|         if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) { |         if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) { | ||||||
|             // this event is propagated only to the widgets of a particular tab
 |             // this event is propagated only to the widgets of a particular tab
 | ||||||
|             const widget = this.widgets[data.noteContext.ntxId]; |             const noteContext = (data as NoteContextEvent).noteContext; | ||||||
|  |             const widget = noteContext.ntxId ? this.widgets[noteContext.ntxId] : undefined; | ||||||
| 
 | 
 | ||||||
|             if (!widget) { |             if (!widget) { | ||||||
|                 return Promise.resolve(); |                 return Promise.resolve(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivated" || appContext.tabManager.getActiveMainContext() === data.noteContext.getMainContext()) { |             if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivatedEvent" || appContext.tabManager.getActiveMainContext() === noteContext.getMainContext()) { | ||||||
|                 widget.hasBeenAlreadyShown = true; |                 widget.hasBeenAlreadyShown = true; | ||||||
| 
 | 
 | ||||||
|                 return [widget.handleEvent("noteSwitched", data), this.refreshNotShown(data)]; |                 return Promise.all([ | ||||||
|  |                     widget.handleEvent("noteSwitched", { noteContext, notePath: noteContext.notePath }), | ||||||
|  |                     this.refreshNotShown({ noteContext }) | ||||||
|  |                 ]); | ||||||
|             } else { |             } else { | ||||||
|                 return Promise.resolve(); |                 return Promise.resolve(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (name === "activeContextChanged") { |         if (name === "activeContextChanged") { | ||||||
|             return this.refreshNotShown(data); |             return this.refreshNotShown(data as NoteContextEvent); | ||||||
|         } else { |         } else { | ||||||
|             return super.handleEventInChildren(name, data); |             return super.handleEventInChildren(name, data); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     refreshNotShown(data) { |     private refreshNotShown(data: NoteContextEvent): Promise<any> { | ||||||
|         const promises = []; |         const promises: Promise<any>[] = []; | ||||||
| 
 | 
 | ||||||
|         for (const subContext of data.noteContext.getMainContext().getSubContexts()) { |         for (const subContext of data.noteContext.getMainContext().getSubContexts()) { | ||||||
|  |             if (!subContext.ntxId) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             const widget = this.widgets[subContext.ntxId]; |             const widget = this.widgets[subContext.ntxId]; | ||||||
| 
 | 
 | ||||||
|             if (!widget.hasBeenAlreadyShown) { |             if (!widget.hasBeenAlreadyShown) { | ||||||
|                 widget.hasBeenAlreadyShown = true; |                 widget.hasBeenAlreadyShown = true; | ||||||
| 
 | 
 | ||||||
|                 promises.push(widget.handleEvent("activeContextChanged", { noteContext: subContext })); |                 const eventPromise = widget.handleEvent("activeContextChanged", { noteContext: subContext }); | ||||||
|  |                 promises.push(eventPromise || Promise.resolve()); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -5,6 +5,15 @@ import openService from "../../services/open.js"; | |||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| 
 | 
 | ||||||
|  | interface AppInfo { | ||||||
|  |     appVersion: string; | ||||||
|  |     dbVersion: number; | ||||||
|  |     syncVersion: number; | ||||||
|  |     buildDate: string; | ||||||
|  |     buildRevision: string; | ||||||
|  |     dataDirectory: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog"> | <div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog"> | ||||||
|     <div class="modal-dialog modal-lg" role="document"> |     <div class="modal-dialog modal-lg" role="document"> | ||||||
| @@ -35,12 +44,10 @@ const TPL = ` | |||||||
|                         <th>${t("about.build_date")}</th> |                         <th>${t("about.build_date")}</th> | ||||||
|                         <td class="build-date"></td> |                         <td class="build-date"></td> | ||||||
|                     </tr> |                     </tr> | ||||||
| 
 |  | ||||||
|                     <tr> |                     <tr> | ||||||
|                         <th>${t("about.build_revision")}</th> |                         <th>${t("about.build_revision")}</th> | ||||||
|                         <td><a class="tn-link" href="" class="build-revision external" target="_blank"></a></td> |                         <td><a class="tn-link build-revision external" href="" target="_blank"></a></td> | ||||||
|                     </tr> |                     </tr> | ||||||
| 
 |  | ||||||
|                     <tr> |                     <tr> | ||||||
|                         <th>${t("about.data_directory")}</th> |                         <th>${t("about.data_directory")}</th> | ||||||
|                         <td class="data-directory"></td> |                         <td class="data-directory"></td> | ||||||
| @@ -59,7 +66,14 @@ const TPL = ` | |||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| export default class AboutDialog extends BasicWidget { | export default class AboutDialog extends BasicWidget { | ||||||
|     doRender() { |     private $appVersion!: JQuery<HTMLElement>; | ||||||
|  |     private $dbVersion!: JQuery<HTMLElement>; | ||||||
|  |     private $syncVersion!: JQuery<HTMLElement>; | ||||||
|  |     private $buildDate!: JQuery<HTMLElement>; | ||||||
|  |     private $buildRevision!: JQuery<HTMLElement>; | ||||||
|  |     private $dataDirectory!: JQuery<HTMLElement>; | ||||||
|  | 
 | ||||||
|  |     doRender(): void { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$appVersion = this.$widget.find(".app-version"); |         this.$appVersion = this.$widget.find(".app-version"); | ||||||
|         this.$dbVersion = this.$widget.find(".db-version"); |         this.$dbVersion = this.$widget.find(".db-version"); | ||||||
| @@ -70,11 +84,11 @@ export default class AboutDialog extends BasicWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refresh() { |     async refresh() { | ||||||
|         const appInfo = await server.get("app-info"); |         const appInfo = await server.get<AppInfo>("app-info"); | ||||||
| 
 | 
 | ||||||
|         this.$appVersion.text(appInfo.appVersion); |         this.$appVersion.text(appInfo.appVersion); | ||||||
|         this.$dbVersion.text(appInfo.dbVersion); |         this.$dbVersion.text(appInfo.dbVersion.toString()); | ||||||
|         this.$syncVersion.text(appInfo.syncVersion); |         this.$syncVersion.text(appInfo.syncVersion.toString()); | ||||||
|         this.$buildDate.text(formatDateTime(appInfo.buildDate)); |         this.$buildDate.text(formatDateTime(appInfo.buildDate)); | ||||||
|         this.$buildRevision.text(appInfo.buildRevision); |         this.$buildRevision.text(appInfo.buildRevision); | ||||||
|         this.$buildRevision.attr("href", `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`); |         this.$buildRevision.attr("href", `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`); | ||||||
| @@ -84,9 +98,9 @@ export default class AboutDialog extends BasicWidget { | |||||||
|                     href: "#", |                     href: "#", | ||||||
|                     class: "tn-link", |                     class: "tn-link", | ||||||
|                     text: appInfo.dataDirectory |                     text: appInfo.dataDirectory | ||||||
|                 }) |                 }).prop("outerHTML") | ||||||
|             ); |             ); | ||||||
|             this.$dataDirectory.find("a").on("click", (event) => { |             this.$dataDirectory.find("a").on("click", (event: JQuery.ClickEvent) => { | ||||||
|                 event.preventDefault(); |                 event.preventDefault(); | ||||||
|                 openService.openDirectory(appInfo.dataDirectory); |                 openService.openDirectory(appInfo.dataDirectory); | ||||||
|             }); |             }); | ||||||
| @@ -97,7 +111,6 @@ export default class AboutDialog extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|     async openAboutDialogEvent() { |     async openAboutDialogEvent() { | ||||||
|         await this.refresh(); |         await this.refresh(); | ||||||
| 
 |  | ||||||
|         utils.openDialog(this.$widget); |         utils.openDialog(this.$widget); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -3,6 +3,9 @@ import treeService from "../../services/tree.js"; | |||||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import BasicWidget from "../basic_widget.js"; | import BasicWidget from "../basic_widget.js"; | ||||||
|  | import type { Suggestion } from "../../services/note_autocomplete.js"; | ||||||
|  | import type { default as TextTypeWidget } from "../type_widgets/editable_text.js"; | ||||||
|  | import type { EventData } from "../../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="add-link-dialog modal mx-auto" tabindex="-1" role="dialog"> | <div class="add-link-dialog modal mx-auto" tabindex="-1" role="dialog"> | ||||||
| @@ -56,6 +59,14 @@ const TPL = ` | |||||||
| </div>`;
 | </div>`;
 | ||||||
| 
 | 
 | ||||||
| export default class AddLinkDialog extends BasicWidget { | export default class AddLinkDialog extends BasicWidget { | ||||||
|  |     private $form!: JQuery<HTMLElement>; | ||||||
|  |     private $autoComplete!: JQuery<HTMLElement>; | ||||||
|  |     private $linkTitle!: JQuery<HTMLElement>; | ||||||
|  |     private $addLinkTitleSettings!: JQuery<HTMLElement>; | ||||||
|  |     private $addLinkTitleRadios!: JQuery<HTMLElement>; | ||||||
|  |     private $addLinkTitleFormGroup!: JQuery<HTMLElement>; | ||||||
|  |     private textTypeWidget: TextTypeWidget | null = null; | ||||||
|  | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$form = this.$widget.find(".add-link-form"); |         this.$form = this.$widget.find(".add-link-form"); | ||||||
| @@ -65,20 +76,17 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|         this.$addLinkTitleRadios = this.$widget.find(".add-link-title-radios"); |         this.$addLinkTitleRadios = this.$widget.find(".add-link-title-radios"); | ||||||
|         this.$addLinkTitleFormGroup = this.$widget.find(".add-link-title-form-group"); |         this.$addLinkTitleFormGroup = this.$widget.find(".add-link-title-form-group"); | ||||||
| 
 | 
 | ||||||
|         /** @var TextTypeWidget */ |  | ||||||
|         this.textTypeWidget = null; |  | ||||||
| 
 |  | ||||||
|         this.$form.on("submit", () => { |         this.$form.on("submit", () => { | ||||||
|             if (this.$autoComplete.getSelectedNotePath()) { |             if (this.$autoComplete.getSelectedNotePath()) { | ||||||
|                 this.$widget.modal("hide"); |                 this.$widget.modal("hide"); | ||||||
| 
 | 
 | ||||||
|                 const linkTitle = this.getLinkType() === "reference-link" ? null : this.$linkTitle.val(); |                 const linkTitle = this.getLinkType() === "reference-link" ? null : this.$linkTitle.val(); | ||||||
| 
 | 
 | ||||||
|                 this.textTypeWidget.addLink(this.$autoComplete.getSelectedNotePath(), linkTitle); |                 this.textTypeWidget?.addLink(this.$autoComplete.getSelectedNotePath()!, linkTitle); | ||||||
|             } else if (this.$autoComplete.getSelectedExternalLink()) { |             } else if (this.$autoComplete.getSelectedExternalLink()) { | ||||||
|                 this.$widget.modal("hide"); |                 this.$widget.modal("hide"); | ||||||
| 
 | 
 | ||||||
|                 this.textTypeWidget.addLink(this.$autoComplete.getSelectedExternalLink(), this.$linkTitle.val()); |                 this.textTypeWidget?.addLink(this.$autoComplete.getSelectedExternalLink()!, this.$linkTitle.val(), true); | ||||||
|             } else { |             } else { | ||||||
|                 logError("No link to add."); |                 logError("No link to add."); | ||||||
|             } |             } | ||||||
| @@ -87,12 +95,12 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async showAddLinkDialogEvent({ textTypeWidget, text = "" }) { |     async showAddLinkDialogEvent({ textTypeWidget, text = "" }: EventData<"showAddLinkDialog">) { | ||||||
|         this.textTypeWidget = textTypeWidget; |         this.textTypeWidget = textTypeWidget; | ||||||
| 
 | 
 | ||||||
|         this.$addLinkTitleSettings.toggle(!this.textTypeWidget.hasSelection()); |         this.$addLinkTitleSettings.toggle(!this.textTypeWidget.hasSelection()); | ||||||
| 
 | 
 | ||||||
|         this.$addLinkTitleSettings.find("input[type=radio]").on("change", (e) => this.updateTitleSettingsVisibility(e)); |         this.$addLinkTitleSettings.find("input[type=radio]").on("change", () => this.updateTitleSettingsVisibility()); | ||||||
| 
 | 
 | ||||||
|         // with selection hyperlink is implied
 |         // with selection hyperlink is implied
 | ||||||
|         if (this.textTypeWidget.hasSelection()) { |         if (this.textTypeWidget.hasSelection()) { | ||||||
| @@ -108,9 +116,8 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|         this.$autoComplete.val(""); |         this.$autoComplete.val(""); | ||||||
|         this.$linkTitle.val(""); |         this.$linkTitle.val(""); | ||||||
| 
 | 
 | ||||||
|         const setDefaultLinkTitle = async (noteId) => { |         const setDefaultLinkTitle = async (noteId: string) => { | ||||||
|             const noteTitle = await treeService.getNoteTitle(noteId); |             const noteTitle = await treeService.getNoteTitle(noteId); | ||||||
| 
 |  | ||||||
|             this.$linkTitle.val(noteTitle); |             this.$linkTitle.val(noteTitle); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
| @@ -119,7 +126,7 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|             allowCreatingNotes: true |             allowCreatingNotes: true | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.$autoComplete.on("autocomplete:noteselected", (event, suggestion, dataset) => { |         this.$autoComplete.on("autocomplete:noteselected", (event: JQuery.Event, suggestion: Suggestion) => { | ||||||
|             if (!suggestion.notePath) { |             if (!suggestion.notePath) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| @@ -133,7 +140,7 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.$autoComplete.on("autocomplete:externallinkselected", (event, suggestion, dataset) => { |         this.$autoComplete.on("autocomplete:externallinkselected", (event: JQuery.Event, suggestion: Suggestion) => { | ||||||
|             if (!suggestion.externalLink) { |             if (!suggestion.externalLink) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| @@ -143,11 +150,11 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|             this.$linkTitle.val(suggestion.externalLink); |             this.$linkTitle.val(suggestion.externalLink); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.$autoComplete.on("autocomplete:cursorchanged", (event, suggestion, dataset) => { |         this.$autoComplete.on("autocomplete:cursorchanged", (event: JQuery.Event, suggestion: Suggestion) => { | ||||||
|             if (suggestion.externalLink) { |             if (suggestion.externalLink) { | ||||||
|                 this.$linkTitle.val(suggestion.externalLink); |                 this.$linkTitle.val(suggestion.externalLink); | ||||||
|             } else { |             } else { | ||||||
|                 const noteId = treeService.getNoteIdFromUrl(suggestion.notePath); |                 const noteId = treeService.getNoteIdFromUrl(suggestion.notePath!); | ||||||
| 
 | 
 | ||||||
|                 if (noteId) { |                 if (noteId) { | ||||||
|                     setDefaultLinkTitle(noteId); |                     setDefaultLinkTitle(noteId); | ||||||
| @@ -164,7 +171,7 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|         this.$autoComplete.trigger("focus").trigger("select"); // to be able to quickly remove entered text
 |         this.$autoComplete.trigger("focus").trigger("select"); // to be able to quickly remove entered text
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getLinkType() { |     private getLinkType() { | ||||||
|         if (this.$autoComplete.getSelectedExternalLink()) { |         if (this.$autoComplete.getSelectedExternalLink()) { | ||||||
|             return "external-link"; |             return "external-link"; | ||||||
|         } |         } | ||||||
| @@ -172,7 +179,7 @@ export default class AddLinkDialog extends BasicWidget { | |||||||
|         return this.$addLinkTitleSettings.find("input[type=radio]:checked").val(); |         return this.$addLinkTitleSettings.find("input[type=radio]:checked").val(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     updateTitleSettingsVisibility() { |     private updateTitleSettingsVisibility() { | ||||||
|         const linkType = this.getLinkType(); |         const linkType = this.getLinkType(); | ||||||
| 
 | 
 | ||||||
|         this.$addLinkTitleFormGroup.toggle(linkType !== "reference-link"); |         this.$addLinkTitleFormGroup.toggle(linkType !== "reference-link"); | ||||||
| @@ -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") { | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             await ws.waitForMaxKnownEntityChangeId(); |             await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|             await appContext.tabManager.getActiveContext().setNote(notePath); |             await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|  |  | ||||||
|             toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) })); |             toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) })); | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -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"> | ||||||
|   | |||||||
| @@ -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,24 +40,23 @@ 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], { | ||||||
|                     worldCopyJump: true |                 worldCopyJump: true | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 this.map = map; |  | ||||||
|                 if (this.initCallback) { |  | ||||||
|                     this.initCallback(L); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { |  | ||||||
|                     attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', |  | ||||||
|                     detectRetina: true |  | ||||||
|                 }).addTo(map); |  | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|  |             this.map = map; | ||||||
|  |             if (this.initCallback) { | ||||||
|  |                 this.initCallback(L); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { | ||||||
|  |                 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', | ||||||
|  |                 detectRetina: true | ||||||
|  |             }).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(); | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ class MobileDetailMenuWidget extends BasicWidget { | |||||||
|                 x: e.pageX, |                 x: e.pageX, | ||||||
|                 y: e.pageY, |                 y: e.pageY, | ||||||
|                 items: [ |                 items: [ | ||||||
|                     { title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note.type !== "search" }, |                     { title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" }, | ||||||
|                     { title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note.noteId !== "root" } |                     { title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" } | ||||||
|                 ], |                 ], | ||||||
|                 selectMenuItemHandler: async ({ command }) => { |                 selectMenuItemHandler: async ({ command }) => { | ||||||
|                     if (command === "insertChildNote") { |                     if (command === "insertChildNote") { | ||||||
|   | |||||||
| @@ -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 { | ||||||
|  |  | ||||||
| @@ -332,7 +341,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(); | ||||||
|             }) |             }) | ||||||
| @@ -324,7 +322,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|             .warmupTicks(30) |             .warmupTicks(30) | ||||||
|             .onNodeClick((node) => { |             .onNodeClick((node) => { | ||||||
|                 if (node.id) { |                 if (node.id) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote((node as Node).id); |                     appContext.tabManager.getActiveContext()?.setNote((node as Node).id); | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|             .onNodeRightClick((node, e) => { |             .onNodeRightClick((node, e) => { | ||||||
| @@ -373,7 +371,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { | |||||||
|         if (mapRootNoteId === "hoisted") { |         if (mapRootNoteId === "hoisted") { | ||||||
|             mapRootNoteId = hoistedNoteService.getHoistedNoteId(); |             mapRootNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|         } else if (!mapRootNoteId) { |         } else if (!mapRootNoteId) { | ||||||
|             mapRootNoteId = appContext.tabManager.getActiveContext().parentNoteId; |             mapRootNoteId = appContext.tabManager.getActiveContext()?.parentNoteId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return mapRootNoteId ?? ""; |         return mapRootNoteId ?? ""; | ||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ export default class NoteTitleWidget extends NoteContextAwareWidget { | |||||||
|             || utils.isLaunchBarConfig(note.noteId) |             || utils.isLaunchBarConfig(note.noteId) | ||||||
|             || this.noteContext?.viewScope?.viewMode !== "default"; |             || 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>; | ||||||
| @@ -425,10 +424,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|                 const activeNoteContext = appContext.tabManager.getActiveContext(); |                 const activeNoteContext = appContext.tabManager.getActiveContext(); | ||||||
|                 const opts: SetNoteOpts = {}; |                 const opts: SetNoteOpts = {}; | ||||||
|                 if (activeNoteContext.viewScope?.viewMode === "contextual-help") { |                 if (activeNoteContext?.viewScope?.viewMode === "contextual-help") { | ||||||
|                     opts.viewScope = activeNoteContext.viewScope; |                     opts.viewScope = activeNoteContext.viewScope; | ||||||
|                 } |                 } | ||||||
|                 await activeNoteContext.setNote(notePath, opts); |                 await activeNoteContext?.setNote(notePath, opts); | ||||||
|             }, |             }, | ||||||
|             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), |             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), | ||||||
|             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), |             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), | ||||||
| @@ -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 ( | ||||||
|                 node: Fancytree.FancytreeNode; |                 event: Event, | ||||||
|                 noteId: string; |                 data: { | ||||||
|             }) { |                     node: Fancytree.FancytreeNode; | ||||||
|  |                     noteId: string; | ||||||
|  |                 } | ||||||
|  |             ) { | ||||||
|                 const node = data.node; |                 const node = data.node; | ||||||
|  |  | ||||||
|                 if (!node.data.noteId) { |                 if (!node.data.noteId) { | ||||||
| @@ -617,10 +619,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|                 // TODO: Deduplicate with server's notes.ts#getAndValidateParent |                 // TODO: Deduplicate with server's notes.ts#getAndValidateParent | ||||||
|                 if (!["search", "launcher"].includes(note.type) |                 if (!["search", "launcher"].includes(note.type) | ||||||
|                         && !note.isOptions() |                     && !note.isOptions() | ||||||
|                         && !note.isLaunchBarConfig() |                     && !note.isLaunchBarConfig() | ||||||
|                         && !note.noteId.startsWith("_help") |                     && !note.noteId.startsWith("_help") | ||||||
|                     ) { |                 ) { | ||||||
|                     const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on( |                     const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on( | ||||||
|                         "click", |                         "click", | ||||||
|                         cancelClickPropagation |                         cancelClickPropagation | ||||||
| @@ -1756,6 +1758,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         appContext.tabManager.getActiveContext().setNote(resp.note.noteId); |         appContext.tabManager.getActiveContext()?.setNote(resp.note.noteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ export default class ProtectedNoteSwitchWidget extends SwitchWidget { | |||||||
|         super.doRender(); |         super.doRender(); | ||||||
|  |  | ||||||
|         this.switchOnName = t("protect_note.toggle-on"); |         this.switchOnName = t("protect_note.toggle-on"); | ||||||
|         this.switchOnTooltip =  t("protect_note.toggle-on-hint"); |         this.switchOnTooltip = t("protect_note.toggle-on-hint"); | ||||||
|  |  | ||||||
|         this.switchOffName = t("protect_note.toggle-off"); |         this.switchOffName = t("protect_note.toggle-off"); | ||||||
|         this.switchOffTooltip = t("protect_note.toggle-off-hint"); |         this.switchOffTooltip = t("protect_note.toggle-off-hint"); | ||||||
|   | |||||||
| @@ -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 ?? ""); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -246,7 +246,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             await ws.waitForMaxKnownEntityChangeId(); |             await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|             await appContext.tabManager.getActiveContext().setNote(notePath); |             await appContext.tabManager.getActiveContext()?.setNote(notePath); | ||||||
|             // Note the {{- notePathTitle}} in json file is not typo, it's unescaping |             // Note the {{- notePathTitle}} in json file is not typo, it's unescaping | ||||||
|             // See https://www.i18next.com/translation-function/interpolation#unescape |             // See https://www.i18next.com/translation-function/interpolation#unescape | ||||||
|             toastService.showMessage(t("search_definition.search_note_saved", { notePathTitle: await treeService.getNotePathTitle(notePath) })); |             toastService.showMessage(t("search_definition.search_note_saved", { notePathTitle: await treeService.getNotePathTitle(notePath) })); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -17,10 +17,10 @@ export default class TemplateSwitchWidget extends SwitchWidget { | |||||||
|         super.doRender(); |         super.doRender(); | ||||||
|  |  | ||||||
|         this.switchOnName = t("template_switch.template"); |         this.switchOnName = t("template_switch.template"); | ||||||
|         this.switchOnTooltip =  t("template_switch.toggle-on-hint"); |         this.switchOnTooltip = t("template_switch.toggle-on-hint"); | ||||||
|  |  | ||||||
|         this.switchOffName = t("template_switch.template"); |         this.switchOffName = t("template_switch.template"); | ||||||
|         this.switchOffTooltip =  t("template_switch.toggle-off-hint"); |         this.switchOffTooltip = t("template_switch.toggle-off-hint"); | ||||||
|  |  | ||||||
|         this.$helpButton.attr("data-help-page", "template.html").show(); |         this.$helpButton.attr("data-help-page", "template.html").show(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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 | ||||||
|         }); |         }); | ||||||
| @@ -541,10 +541,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget { | |||||||
|                     excalidrawAPI: (api: ExcalidrawImperativeAPI) => { |                     excalidrawAPI: (api: ExcalidrawImperativeAPI) => { | ||||||
|                         this.excalidrawApi = api; |                         this.excalidrawApi = api; | ||||||
|                     }, |                     }, | ||||||
|                     onPaste: (data: unknown, event: unknown) => { |  | ||||||
|                         console.log("Verbose: excalidraw internal paste. No trilium action implemented.", data, event); |  | ||||||
|                         return false; |  | ||||||
|                     }, |  | ||||||
|                     onLibraryChange: () => { |                     onLibraryChange: () => { | ||||||
|                         this.libraryChanged = true; |                         this.libraryChanged = true; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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", | ||||||
| 				label: "Insert", |             "codeBlock", | ||||||
| 				icon: "plus", |             "insertTable", | ||||||
| 				items: [ |             "footnote", | ||||||
| 					'internallink', |             { | ||||||
| 					'includeNote', |                 label: "Insert", | ||||||
| 					'|', |                 icon: "plus", | ||||||
| 					'math', |                 items: ["internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak"] | ||||||
| 					'mermaid', |             }, | ||||||
| 					'horizontalLine', |             "|", | ||||||
| 					'pageBreak' |             "outdent", | ||||||
| 				] |             "indent", | ||||||
| 			}, |             "|", | ||||||
| 			'|', |             "imageUpload", | ||||||
| 			'outdent', 'indent', |             "markdownImport", | ||||||
| 			'|', |             "specialCharacters", | ||||||
| 			'imageUpload', |             "findAndReplace" | ||||||
| 			'markdownImport', |         ] | ||||||
| 			'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"; | ||||||
|   | |||||||
| @@ -313,7 +313,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         this.watchdog?.editor.editing.view.focus(); |         this.watchdog?.editor.editing.view.focus(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     show() {} |     show() { } | ||||||
|  |  | ||||||
|     getEditor() { |     getEditor() { | ||||||
|         return this.watchdog?.editor; |         return this.watchdog?.editor; | ||||||
| @@ -360,14 +360,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         this.addTextToEditor(text); |         this.addTextToEditor(text); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async addLink(notePath, linkTitle) { |     async addLink(notePath, linkTitle, externalLink = false) { | ||||||
|         await this.initialized; |         await this.initialized; | ||||||
|  |  | ||||||
|         if (linkTitle) { |         if (linkTitle) { | ||||||
|             if (this.hasSelection()) { |             if (this.hasSelection()) { | ||||||
|                 this.watchdog.editor.execute("link", `#${notePath}`); |                 this.watchdog.editor.execute("link", externalLink ? `${notePath}` : `#${notePath}`); | ||||||
|             } else { |             } else { | ||||||
|                 await this.addLinkToEditor(`#${notePath}`, linkTitle); |                 await this.addLinkToEditor(externalLink ? `${notePath}` : `#${notePath}`, linkTitle); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             this.watchdog.editor.execute("referenceLink", { href: "#" + notePath }); |             this.watchdog.editor.execute("referenceLink", { href: "#" + notePath }); | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ const TPL = ` | |||||||
|         </label> |         </label> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <button class="btn btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button> |     <button class="btn btn-secondary btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button> | ||||||
| </div> | </div> | ||||||
| `; | `; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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( | ||||||
|                 .attr("value", theme.val) |                 $("<option>") | ||||||
|                 .attr("data-note-id", theme.noteId || "") |                     .attr("value", theme.val) | ||||||
|                 .text(theme.title)); |                     .attr("data-note-id", theme.noteId || "") | ||||||
|  |                     .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); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -102,13 +102,20 @@ export const DEFAULT_ALLOWED_TAGS = [ | |||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="options-section"> | <div class="html-import-tags-settings options-section"> | ||||||
|  |     <style> | ||||||
|  |         .html-import-tags-settings .allowed-html-tags { | ||||||
|  |             height: 150px; | ||||||
|  |             margin-bottom: 12px; | ||||||
|  |             font-family: monospace; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|     <h4>${t("import.html_import_tags.title")}</h4> |     <h4>${t("import.html_import_tags.title")}</h4> | ||||||
|  |  | ||||||
|     <p>${t("import.html_import_tags.description")}</p> |     <p>${t("import.html_import_tags.description")}</p> | ||||||
|  |  | ||||||
|     <textarea class="allowed-html-tags form-control" style="height: 150px; font-family: monospace;" |     <textarea class="allowed-html-tags form-control" spellcheck="false" | ||||||
|                 placeholder="${t("import.html_import_tags.placeholder")}"></textarea> |               placeholder="${t("import.html_import_tags.placeholder")}"></textarea> | ||||||
|  |  | ||||||
|     <div> |     <div> | ||||||
|         <button class="btn btn-sm btn-secondary reset-to-default"> |         <button class="btn btn-sm btn-secondary reset-to-default"> | ||||||
|   | |||||||
| @@ -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,19 +233,18 @@ 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; |             } | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (!b.dueDate) { |             if (!b.dueDate) { | ||||||
|                     return -1; |                 return -1; | ||||||
|                 } |             } | ||||||
|  |  | ||||||
|                 return a.dueDate.localeCompare(b.dueDate, "en"); |             return a.dueDate.localeCompare(b.dueDate, "en"); | ||||||
|             }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note: FNote) { |     async doRefresh(note: FNote) { | ||||||
|   | |||||||
| @@ -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; | ||||||
| @@ -155,7 +155,7 @@ export default class CalendarView extends ViewMode { | |||||||
|  |  | ||||||
|                 const note = await date_notes.getDayNote(e.dateStr); |                 const note = await date_notes.getDayNote(e.dateStr); | ||||||
|                 if (note) { |                 if (note) { | ||||||
|                     appContext.tabManager.getActiveContext().setNote(note.noteId); |                     appContext.tabManager.getActiveContext()?.setNote(note.noteId); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @@ -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 #formatDateToLocalISO(date: Date | null | undefined) { |     static #formatDateToLocalISO(date: Date | null | undefined) { | ||||||
| @@ -442,7 +442,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%; | ||||||
|   | |||||||
| @@ -165,6 +165,11 @@ span[style] { | |||||||
|     overflow: unset !important; |     overflow: unset !important; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* TODO: This will break once we translate the language */ | ||||||
|  | .ck-content pre[data-language="Auto-detected"]:after { | ||||||
|  |     display: none !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Code note specific fixes. |  * Code note specific fixes. | ||||||
|  */ |  */ | ||||||
| @@ -202,9 +207,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; | ||||||
|  |  | ||||||
| @@ -301,9 +305,16 @@ blockquote { | |||||||
| pre > code { | pre > code { | ||||||
|     widows: 6; |     widows: 6; | ||||||
|     orphans: 6; |     orphans: 6; | ||||||
|  |     overflow: auto; | ||||||
|  |     white-space: pre-wrap !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| 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); | ||||||
| @@ -41,7 +41,7 @@ | |||||||
|     --muted-text-color: #bbb; |     --muted-text-color: #bbb; | ||||||
|  |  | ||||||
|     --input-background-color: #ffffff12; |     --input-background-color: #ffffff12; | ||||||
|     --input-text-color:  #ffffffc7; |     --input-text-color: #ffffffc7; | ||||||
|     --input-placeholder-color: #b7b7b782; |     --input-placeholder-color: #b7b7b782; | ||||||
|     --input-selection-background: gray; |     --input-selection-background: gray; | ||||||
|     --input-selection-text-color: white; |     --input-selection-text-color: white; | ||||||
|   | |||||||
| @@ -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,8 +190,9 @@ 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-offset 200ms ease-out; |         outline-color 50ms linear, | ||||||
|  |         outline-offset 200ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| input::placeholder, | input::placeholder, | ||||||
| @@ -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-offset 200ms ease-out; |         outline-color 50ms linear, | ||||||
|  |         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,11 +346,11 @@ 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-offset 200ms ease-out; |         outline-color 50ms linear, | ||||||
|  |         outline-offset 200ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| option { | option { | ||||||
| @@ -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: | ||||||
|                     opacity 300ms linear; |             transform 300ms ease-out, | ||||||
|  |             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: | ||||||
|                     transform 300ms ease-in; |             opacity 300ms linear, | ||||||
|  |             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 */ | ||||||
| @@ -580,9 +580,10 @@ body a.tn-link:visited, | |||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     text-decoration: underline; |     text-decoration: underline; | ||||||
|  |  | ||||||
|     transition: background-color 200ms ease-out, |     transition: | ||||||
|                 box-shadow 200ms ease-out, |         background-color 200ms ease-out, | ||||||
|                 color 300ms ease-out; |         box-shadow 200ms ease-out, | ||||||
|  |         color 300ms ease-out; | ||||||
| } | } | ||||||
|  |  | ||||||
| body a.tn-link:focus-visible, | body a.tn-link:focus-visible, | ||||||
| @@ -597,9 +598,10 @@ 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: | ||||||
|                 box-shadow 100ms ease-in, |         background-color 100ms ease-in, | ||||||
|                 color 150ms ease-in; |         box-shadow 100ms ease-in, | ||||||
|  |         color 150ms ease-in; | ||||||
| } | } | ||||||
|  |  | ||||||
| a.tn-link.external:not(.no-arrow)::after, | a.tn-link.external:not(.no-arrow)::after, | ||||||
| @@ -607,16 +609,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; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -625,7 +629,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; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user