From 0cf13ca559d6f1a693dcadc182e459174017aac2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jan 2025 02:27:09 +0000 Subject: [PATCH 01/47] fix(deps): update dependency mind-elixir to v4.3.6 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78f8e3bef..734d7a998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,7 @@ "marked": "15.0.6", "mermaid": "11.4.1", "mime-types": "2.1.35", - "mind-elixir": "4.3.5", + "mind-elixir": "4.3.6", "multer": "1.4.5-lts.1", "normalize-strings": "1.1.1", "normalize.css": "8.0.1", @@ -12866,9 +12866,9 @@ } }, "node_modules/mind-elixir": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.3.5.tgz", - "integrity": "sha512-I1Mxc/jCwHEDMecDjQVpc+WShmzrEnIv6+MnWPauJ0LAiOXMBQB/wpKqlF4bTp+kCqzOHMYryAvIWm0jvloZ8Q==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.3.6.tgz", + "integrity": "sha512-6E9DT5vOYJ7DMDFXJlAnKU3Q6ekwBkR48Tjo6PchEcxJjPURJsiIASxtIeZCfvp8V39N4WyIa3Yt7Q/SFQkVfw==", "license": "MIT" }, "node_modules/minimalistic-assert": { diff --git a/package.json b/package.json index 2f056ed91..d078620b9 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "marked": "15.0.6", "mermaid": "11.4.1", "mime-types": "2.1.35", - "mind-elixir": "4.3.5", + "mind-elixir": "4.3.6", "multer": "1.4.5-lts.1", "normalize-strings": "1.1.1", "normalize.css": "8.0.1", From e3b8de8843f7551ad93d328eede0d25d4b64a377 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jan 2025 02:27:20 +0000 Subject: [PATCH 02/47] fix(deps): update dependency ts-loader to v9.5.2 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78f8e3bef..7f480f86b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "stream-throttle": "0.1.3", "striptags": "3.2.0", "tmp": "0.2.3", - "ts-loader": "9.5.1", + "ts-loader": "9.5.2", "turndown": "7.2.0", "unescape": "1.0.1", "vanilla-js-wheel-zoom": "9.0.4", @@ -17057,9 +17057,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", "license": "MIT", "dependencies": { "chalk": "^4.1.0", diff --git a/package.json b/package.json index 2f056ed91..2e1c8f5bf 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "stream-throttle": "0.1.3", "striptags": "3.2.0", "tmp": "0.2.3", - "ts-loader": "9.5.1", + "ts-loader": "9.5.2", "turndown": "7.2.0", "unescape": "1.0.1", "vanilla-js-wheel-zoom": "9.0.4", From 1abc8abee63da8c9414d0c55d7900afa19e418f2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 17:17:07 +0200 Subject: [PATCH 03/47] fix(e2e): missing method --- e2e/layout/tab_bar.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/layout/tab_bar.spec.ts b/e2e/layout/tab_bar.spec.ts index d5c2b7f15..4d214b8dc 100644 --- a/e2e/layout/tab_bar.spec.ts +++ b/e2e/layout/tab_bar.spec.ts @@ -49,7 +49,7 @@ test("Can drag tab to new window", async ({ page }) => { await page.mouse.move(x, y + tabPos.height + 100, { steps: 5 }); await page.mouse.up(); } else { - fail("Unable to determine tab position"); + test.fail(true, "Unable to determine tab position"); } // Wait for the popup to show From 0db3bfd8ae23e500f6231a9d7b61eeb5b01fc6eb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 17:28:41 +0200 Subject: [PATCH 04/47] feat(e2e): add test for settings translations --- e2e/i18n.spec.ts | 13 +++++++++++++ e2e/support/app.ts | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 e2e/i18n.spec.ts diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts new file mode 100644 index 000000000..87019c201 --- /dev/null +++ b/e2e/i18n.spec.ts @@ -0,0 +1,13 @@ +import { test, expect, Page } from "@playwright/test"; +import App from "./support/app"; + +test("Displays translations in Settings", async ({ page }) => { + const app = new App(page); + await app.goto(); + await app.closeAllTabs(); + await app.goToSettings(); + await app.noteTree.getByText("Appearance").click(); + + expect(app.currentNoteSplit).toContainText("Localization"); + expect(app.currentNoteSplit).toContainText("Language"); +}); diff --git a/e2e/support/app.ts b/e2e/support/app.ts index 4f36dfb7b..40546acae 100644 --- a/e2e/support/app.ts +++ b/e2e/support/app.ts @@ -31,6 +31,10 @@ export default class App { await autocomplete.press("Enter"); } + async goToSettings() { + await this.page.locator(".launcher-button.bx-cog").click(); + } + getTab(tabIndex: number) { return this.tabBar.locator(".note-tab-wrapper").nth(tabIndex); } @@ -43,6 +47,7 @@ export default class App { await this.getTab(0).click({ button: "right" }); await this.page.waitForTimeout(500); // TODO: context menu won't dismiss otherwise await this.page.getByText("Close all tabs").click({ force: true }); + await this.page.waitForTimeout(500); // TODO: context menu won't dismiss otherwise } async addNewTab() { From dc83ba51dbb0b89b2511aadd038157ff743abeb3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 17:36:10 +0200 Subject: [PATCH 05/47] chore(e2e): port old test for changing language --- e2e/i18n.spec.ts | 21 +++++++++++++++++++++ integration-tests/i18n.spec.ts | 25 ------------------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index 87019c201..7dc485079 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -11,3 +11,24 @@ test("Displays translations in Settings", async ({ page }) => { expect(app.currentNoteSplit).toContainText("Localization"); expect(app.currentNoteSplit).toContainText("Language"); }); + +test("User can change language from settings", async ({ page }) => { + const app = new App(page); + await app.goto(); + + await app.closeAllTabs(); + await app.goToSettings(); + await app.noteTree.getByText("Appearance").click(); + + // Check that the default value (English) is set. + await expect(app.currentNoteSplit).toContainText("Theme"); + const languageCombobox = await app.currentNoteSplit.getByRole("combobox").first(); + await expect(languageCombobox).toHaveValue("en"); + + // Select Chinese and ensure the translation is set. + languageCombobox.selectOption("cn"); + await expect(app.currentNoteSplit).toContainText("主题"); + + // Select English again. + languageCombobox.selectOption("en"); +}); diff --git a/integration-tests/i18n.spec.ts b/integration-tests/i18n.spec.ts index 1c3fc1d36..16a49fdbc 100644 --- a/integration-tests/i18n.spec.ts +++ b/integration-tests/i18n.spec.ts @@ -1,30 +1,5 @@ import test, { expect } from "@playwright/test"; -test("User can change language from settings", async ({ page }) => { - await page.goto("http://localhost:8082"); - - // Clear all tabs - await page.locator(".note-tab:first-of-type").locator("div").nth(1).click({ button: "right" }); - await page.getByText("Close all tabs").click(); - - // Go to options -> Appearance - await page.locator("#launcher-pane div").filter({ hasText: "Options Open New Window" }).getByRole("button").click(); - await page.locator("#launcher-pane").getByText("Options").click(); - await page.locator("#center-pane").getByText("Appearance").click(); - - // Check that the default value (English) is set. - await expect(page.locator("#center-pane")).toContainText("Theme"); - const languageCombobox = await page.getByRole("combobox").first(); - await expect(languageCombobox).toHaveValue("en"); - - // Select Chinese and ensure the translation is set. - languageCombobox.selectOption("cn"); - await expect(page.locator("#center-pane")).toContainText("主题"); - - // Select English again. - languageCombobox.selectOption("en"); -}); - test("Restores language on start-up on desktop", async ({ page, context }) => { await page.goto("http://localhost:8082"); await expect(page.locator("#launcher-pane").first()).toContainText("Open New Window"); From c641ce26d1137c234d1b6629857c6d1d125aefb8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 17:43:16 +0200 Subject: [PATCH 06/47] chore(e2e): port old test for checking desktop language --- e2e/i18n.spec.ts | 12 ++++++++++-- integration-tests/i18n.spec.ts | 5 ----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index 7dc485079..1ca65edf8 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -1,6 +1,14 @@ import { test, expect, Page } from "@playwright/test"; import App from "./support/app"; +test("Displays translation on desktop", async ({ page }) => { + const app = new App(page); + await app.goto(); + + await expect(page.locator("#left-pane .quick-search input")) + .toHaveAttribute("placeholder", "Quick search"); +}); + test("Displays translations in Settings", async ({ page }) => { const app = new App(page); await app.goto(); @@ -8,8 +16,8 @@ test("Displays translations in Settings", async ({ page }) => { await app.goToSettings(); await app.noteTree.getByText("Appearance").click(); - expect(app.currentNoteSplit).toContainText("Localization"); - expect(app.currentNoteSplit).toContainText("Language"); + await expect(app.currentNoteSplit).toContainText("Localization"); + await expect(app.currentNoteSplit).toContainText("Language"); }); test("User can change language from settings", async ({ page }) => { diff --git a/integration-tests/i18n.spec.ts b/integration-tests/i18n.spec.ts index 16a49fdbc..4cf7e31c2 100644 --- a/integration-tests/i18n.spec.ts +++ b/integration-tests/i18n.spec.ts @@ -1,10 +1,5 @@ import test, { expect } from "@playwright/test"; -test("Restores language on start-up on desktop", async ({ page, context }) => { - await page.goto("http://localhost:8082"); - await expect(page.locator("#launcher-pane").first()).toContainText("Open New Window"); -}); - test("Restores language on start-up on mobile", async ({ page, context }) => { await context.addCookies([ { From 54c5ce9257813dc46976570721ee154408198350 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 18:05:24 +0200 Subject: [PATCH 07/47] chore(e2e): port old test for checking mobile language --- e2e/i18n.spec.ts | 20 ++++++++++++++------ e2e/layout/tab_bar.spec.ts | 10 +++++----- e2e/note_types/code.spec.ts | 8 ++++---- e2e/note_types/text.spec.ts | 8 ++++---- e2e/support/app.ts | 23 ++++++++++++++++++++--- integration-tests/i18n.spec.ts | 13 ------------- 6 files changed, 47 insertions(+), 35 deletions(-) delete mode 100644 integration-tests/i18n.spec.ts diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index 1ca65edf8..b4c9a0842 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -1,16 +1,24 @@ import { test, expect, Page } from "@playwright/test"; import App from "./support/app"; -test("Displays translation on desktop", async ({ page }) => { - const app = new App(page); +test("Displays translation on desktop", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await expect(page.locator("#left-pane .quick-search input")) .toHaveAttribute("placeholder", "Quick search"); }); -test("Displays translations in Settings", async ({ page }) => { - const app = new App(page); +test("Displays translation on mobile", async ({ page, context }) => { + const app = new App(page, context); + await app.goto({ isMobile: true }); + + await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")) + .toHaveAttribute("placeholder", "Quick search"); +}); + +test("Displays translations in Settings", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); await app.goToSettings(); @@ -20,8 +28,8 @@ test("Displays translations in Settings", async ({ page }) => { await expect(app.currentNoteSplit).toContainText("Language"); }); -test("User can change language from settings", async ({ page }) => { - const app = new App(page); +test("User can change language from settings", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); diff --git a/e2e/layout/tab_bar.spec.ts b/e2e/layout/tab_bar.spec.ts index 4d214b8dc..99391c115 100644 --- a/e2e/layout/tab_bar.spec.ts +++ b/e2e/layout/tab_bar.spec.ts @@ -3,8 +3,8 @@ import App from "../support/app"; const NOTE_TITLE = "Trilium Integration Test DB"; -test("Can drag tabs around", async ({ page }) => { - const app = new App(page); +test("Can drag tabs around", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); // [1]: Trilium Integration Test DB note @@ -29,8 +29,8 @@ test("Can drag tabs around", async ({ page }) => { await expect(app.getTab(0)).toContainText(NOTE_TITLE); }); -test("Can drag tab to new window", async ({ page }) => { - const app = new App(page); +test("Can drag tab to new window", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); @@ -54,6 +54,6 @@ test("Can drag tab to new window", async ({ page }) => { // Wait for the popup to show const popup = await popupPromise; - const popupApp = new App(popup); + const popupApp = new App(popup, context); await expect(popupApp.getActiveTab()).toHaveText(NOTE_TITLE); }); diff --git a/e2e/note_types/code.spec.ts b/e2e/note_types/code.spec.ts index 4f63588ee..6473b6849 100644 --- a/e2e/note_types/code.spec.ts +++ b/e2e/note_types/code.spec.ts @@ -1,8 +1,8 @@ import { test, expect, Page } from "@playwright/test"; import App from "../support/app"; -test("Displays lint warnings for backend script", async ({ page }) => { - const app = new App(page); +test("Displays lint warnings for backend script", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); await app.goToNoteInNewTab("Backend script with lint warnings"); @@ -21,8 +21,8 @@ test("Displays lint warnings for backend script", async ({ page }) => { await expectTooltip(page, "'world' is defined but never used."); }); -test("Displays lint errors for backend script", async ({ page }) => { - const app = new App(page); +test("Displays lint errors for backend script", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); await app.goToNoteInNewTab("Backend script with lint errors"); diff --git a/e2e/note_types/text.spec.ts b/e2e/note_types/text.spec.ts index 2a8880f18..d7b878724 100644 --- a/e2e/note_types/text.spec.ts +++ b/e2e/note_types/text.spec.ts @@ -1,8 +1,8 @@ import { test, expect, Page } from "@playwright/test"; import App from "../support/app"; -test("Table of contents is displayed", async ({ page }) => { - const app = new App(page); +test("Table of contents is displayed", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); await app.goToNoteInNewTab("Table of contents"); @@ -36,8 +36,8 @@ test("Table of contents is displayed", async ({ page }) => { await expect(rootList.locator("> ol").nth(1).locator("> ol > ol > ol")).toHaveCount(1); }); -test("Highlights list is displayed", async ({ page }) => { - const app = new App(page); +test("Highlights list is displayed", async ({ page, context }) => { + const app = new App(page, context); await app.goto(); await app.closeAllTabs(); await app.goToNoteInNewTab("Highlights list"); diff --git a/e2e/support/app.ts b/e2e/support/app.ts index 40546acae..08d1ebb77 100644 --- a/e2e/support/app.ts +++ b/e2e/support/app.ts @@ -1,27 +1,44 @@ -import { Locator, Page, expect } from "@playwright/test"; +import { expect, Locator, Page } from "@playwright/test"; +import type { BrowserContext } from "@playwright/test"; + +interface GotoOpts { + isMobile?: boolean; +} export default class App { readonly page: Page; + readonly context: BrowserContext; readonly tabBar: Locator; readonly noteTree: Locator; readonly currentNoteSplit: Locator; readonly sidebar: Locator; - constructor(page: Page) { + constructor(page: Page, context: BrowserContext) { this.page = page; + this.context = context; + this.tabBar = page.locator(".tab-row-widget-container"); this.noteTree = page.locator(".tree-wrapper"); this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)") this.sidebar = page.locator("#right-pane"); } - async goto() { + async goto(opts: GotoOpts = {}) { + await this.context.addCookies([ + { + url: "http://127.0.0.1:8082", + name: "trilium-device", + value: opts.isMobile ? "mobile" : "desktop" + } + ]); + await this.page.goto("/", { waitUntil: "networkidle" }); // Wait for the page to load. await expect(this.page.locator(".tree")) .toContainText("Trilium Integration Test"); + await this.closeAllTabs(); } async goToNoteInNewTab(noteTitle: string) { diff --git a/integration-tests/i18n.spec.ts b/integration-tests/i18n.spec.ts deleted file mode 100644 index 4cf7e31c2..000000000 --- a/integration-tests/i18n.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import test, { expect } from "@playwright/test"; - -test("Restores language on start-up on mobile", async ({ page, context }) => { - await context.addCookies([ - { - url: "http://localhost:8082", - name: "trilium-device", - value: "mobile" - } - ]); - await page.goto("http://localhost:8082"); - await expect(page.locator("#launcher-pane div").first()).toContainText("Open New Window"); -}); From 8dbb5497f16e905971f88a75bd8a9a9a9008135b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 19:34:35 +0200 Subject: [PATCH 08/47] feat(e2e): test mind map display --- e2e/note_types/mermaid.spec.ts | 12 ++++++++++++ integration-tests/db/document.db | Bin 503808 -> 532480 bytes 2 files changed, 12 insertions(+) create mode 100644 e2e/note_types/mermaid.spec.ts diff --git a/e2e/note_types/mermaid.spec.ts b/e2e/note_types/mermaid.spec.ts new file mode 100644 index 000000000..0ead19df8 --- /dev/null +++ b/e2e/note_types/mermaid.spec.ts @@ -0,0 +1,12 @@ +import { test, expect, Page } from "@playwright/test"; +import App from "../support/app"; + +test("Displays simple map", async ({ page, context }) => { + const app = new App(page, context); + await app.goto(); + await app.goToNoteInNewTab("Sample mindmap"); + + expect(app.currentNoteSplit).toContainText("Hello world"); + expect(app.currentNoteSplit).toContainText("1"); + expect(app.currentNoteSplit).toContainText("1a"); +}); diff --git a/integration-tests/db/document.db b/integration-tests/db/document.db index a4a44162f7205dfc4b0173feb9cde3f8554d8e36..578b95cd79cb8cbd64bdb7e79809f3a4d14c1ad7 100644 GIT binary patch delta 25650 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 p{yuNAA=c^gd$sLhgI%wL_!; Date: Sun, 12 Jan 2025 19:43:29 +0200 Subject: [PATCH 09/47] feat(e2e): test note settings --- e2e/note_types/mermaid.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/e2e/note_types/mermaid.spec.ts b/e2e/note_types/mermaid.spec.ts index 0ead19df8..23f43399e 100644 --- a/e2e/note_types/mermaid.spec.ts +++ b/e2e/note_types/mermaid.spec.ts @@ -1,7 +1,7 @@ import { test, expect, Page } from "@playwright/test"; import App from "../support/app"; -test("Displays simple map", async ({ page, context }) => { +test("displays simple map", async ({ page, context }) => { const app = new App(page, context); await app.goto(); await app.goToNoteInNewTab("Sample mindmap"); @@ -10,3 +10,13 @@ test("Displays simple map", async ({ page, context }) => { expect(app.currentNoteSplit).toContainText("1"); expect(app.currentNoteSplit).toContainText("1a"); }); + +test("displays note settings", async ({ page, context }) => { + const app = new App(page, context); + await app.goto(); + await app.goToNoteInNewTab("Sample mindmap"); + + await app.currentNoteSplit.getByText("Hello world").click({ force: true }); + const nodeMenu = app.currentNoteSplit.locator(".node-menu"); + expect(nodeMenu).toBeVisible(); +}); From 9fd8e4d03084e515efdb4ad52d6745a837274d73 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 12 Jan 2025 23:41:27 +0200 Subject: [PATCH 10/47] fix(e2e): language change breaking other tests --- e2e/i18n.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index b4c9a0842..da5be4e19 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -42,9 +42,10 @@ test("User can change language from settings", async ({ page, context }) => { await expect(languageCombobox).toHaveValue("en"); // Select Chinese and ensure the translation is set. - languageCombobox.selectOption("cn"); + await languageCombobox.selectOption("cn"); await expect(app.currentNoteSplit).toContainText("主题"); // Select English again. - languageCombobox.selectOption("en"); + await languageCombobox.selectOption("en"); + await expect(app.currentNoteSplit).toContainText("Language"); }); From e556137a2575889af79137690b35298cd60e4739 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 00:01:23 +0200 Subject: [PATCH 11/47] feat(ci): run playwright for docker builds --- .github/workflows/main-docker.yml | 13 ++++++++++++- playwright.config.ts | 6 +++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 44043f73e..13d57701b 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -68,7 +68,7 @@ jobs: - name: Validate container run output run: | - CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }}) + CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host --name trilium_local ${{ env.TEST_TAG }}) echo "Container ID: $CONTAINER_ID" - name: Wait for the healthchecks to pass @@ -79,6 +79,17 @@ jobs: require-status: running require-healthy: true + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: TRILIUM_DOCKER=1 npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + # Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded - name: Print entire log if: always() diff --git a/playwright.config.ts b/playwright.config.ts index 86de95af0..1e0668cb4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: SERVER_URL, + baseURL: (!process.env.TRILIUM_DOCKER ? SERVER_URL : "http://127.0.0.1:8080"), /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -73,9 +73,9 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - webServer: { + webServer: !process.env.TRILIUM_DOCKER ? { command: 'npm run integration-mem-db-dev', url: SERVER_URL, // reuseExistingServer: !process.env.CI, - }, + } : undefined, }); From ffacc1f5f7793988e54af311738b2bddbcd708dc Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 00:08:56 +0200 Subject: [PATCH 12/47] chore(e2e): disable reuse of server on CI --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 1e0668cb4..6feb9f8ab 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -76,6 +76,6 @@ export default defineConfig({ webServer: !process.env.TRILIUM_DOCKER ? { command: 'npm run integration-mem-db-dev', url: SERVER_URL, - // reuseExistingServer: !process.env.CI, + reuseExistingServer: !process.env.CI, } : undefined, }); From fbfee818b25357ab39cbc2c754cd99ac14accfaf Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 09:11:11 +0200 Subject: [PATCH 13/47] fix(ci): directory for e2e tests --- .github/workflows/main-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 13d57701b..d7370b222 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -68,7 +68,7 @@ jobs: - name: Validate container run output run: | - CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host --name trilium_local ${{ env.TEST_TAG }}) + CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) echo "Container ID: $CONTAINER_ID" - name: Wait for the healthchecks to pass From 61a19d5628dcbbad79c7a608a2be8672db5fcb63 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 15:03:42 +0100 Subject: [PATCH 14/47] refactor(data_dir): add FOLDER_PERMISSION const gets rid of previously "magic number" --- src/services/data_dir.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index b87b00d32..97cc6652f 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -32,11 +32,13 @@ function getAppDataDir() { } const DIR_NAME = "trilium-data"; +const FOLDER_PERMISSIONS = 0o700; + function getTriliumDataDir() { if (process.env.TRILIUM_DATA_DIR) { if (!fs.existsSync(process.env.TRILIUM_DATA_DIR)) { - fs.mkdirSync(process.env.TRILIUM_DATA_DIR, 0o700); + fs.mkdirSync(process.env.TRILIUM_DATA_DIR, FOLDER_PERMISSIONS); } return process.env.TRILIUM_DATA_DIR; @@ -51,7 +53,7 @@ function getTriliumDataDir() { const appDataPath = getAppDataDir() + path.sep + DIR_NAME; if (!fs.existsSync(appDataPath)) { - fs.mkdirSync(appDataPath, 0o700); + fs.mkdirSync(appDataPath, FOLDER_PERMISSIONS); } return appDataPath; From 8826021c63b259a0774494564e3e88f0b618e0c4 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 15:06:42 +0100 Subject: [PATCH 15/47] refactor(data_dir): add createDirIfNotExisting function removes some code duplication --- src/services/data_dir.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 97cc6652f..4e7c0315a 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -34,13 +34,15 @@ function getAppDataDir() { const DIR_NAME = "trilium-data"; const FOLDER_PERMISSIONS = 0o700; +function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { + if (!fs.existsSync(path)) { + fs.mkdirSync(path, permissionMode); + } +} function getTriliumDataDir() { if (process.env.TRILIUM_DATA_DIR) { - if (!fs.existsSync(process.env.TRILIUM_DATA_DIR)) { - fs.mkdirSync(process.env.TRILIUM_DATA_DIR, FOLDER_PERMISSIONS); - } - + createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); return process.env.TRILIUM_DATA_DIR; } @@ -52,9 +54,7 @@ function getTriliumDataDir() { const appDataPath = getAppDataDir() + path.sep + DIR_NAME; - if (!fs.existsSync(appDataPath)) { - fs.mkdirSync(appDataPath, FOLDER_PERMISSIONS); - } + createDirIfNotExisting(appDataPath); return appDataPath; } From 3481c8ba84133944bfe1d40017f0e27cb61551c9 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 15:45:41 +0100 Subject: [PATCH 16/47] refactor(data_dir): use path.join for safer joins https://nodejs.org/api/path.html#pathjoinpaths --- src/services/data_dir.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 4e7c0315a..050afd078 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -10,7 +10,7 @@ import os from "os"; import fs from "fs"; -import path from "path"; +import { join as pathJoin} from "path"; function getAppDataDir() { let appDataDir = os.homedir(); // fallback if OS is not recognized @@ -46,27 +46,24 @@ function getTriliumDataDir() { return process.env.TRILIUM_DATA_DIR; } - const homePath = os.homedir() + path.sep + DIR_NAME; - + const homePath = pathJoin(os.homedir(), DIR_NAME); if (fs.existsSync(homePath)) { return homePath; } - const appDataPath = getAppDataDir() + path.sep + DIR_NAME; - + const appDataPath = pathJoin(getAppDataDir(), DIR_NAME); createDirIfNotExisting(appDataPath); return appDataPath; } const TRILIUM_DATA_DIR = getTriliumDataDir(); -const DIR_SEP = TRILIUM_DATA_DIR + path.sep; -const DOCUMENT_PATH = process.env.TRILIUM_DOCUMENT_PATH || `${DIR_SEP}document.db`; -const BACKUP_DIR = process.env.TRILIUM_BACKUP_DIR || `${DIR_SEP}backup`; -const LOG_DIR = process.env.TRILIUM_LOG_DIR || `${DIR_SEP}log`; -const ANONYMIZED_DB_DIR = process.env.TRILIUM_ANONYMIZED_DB_DIR || `${DIR_SEP}anonymized-db`; -const CONFIG_INI_PATH = process.env.TRILIUM_CONFIG_INI_PATH || `${DIR_SEP}config.ini`; +const DOCUMENT_PATH = process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"); +const BACKUP_DIR = process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"); +const LOG_DIR = process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"); +const ANONYMIZED_DB_DIR = process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"); +const CONFIG_INI_PATH = process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini"); export default { TRILIUM_DATA_DIR, From 7a1e8714af49a143012eb65344c4a2329547339e Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 20:38:37 +0100 Subject: [PATCH 17/47] refactor(data_dir): logically order/split cases in getTriliumDataDir - the blocks now clearly follow the intended logic described in the comments - I renamed the `getAppDataDir` to more specific `getPlatformAppDataDir` --- src/services/data_dir.ts | 61 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 050afd078..5b963dae7 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -2,38 +2,38 @@ /* * This file resolves trilium data path in this order of priority: - * - if TRILIUM_DATA_DIR environment variable exists, then its value is used as the path - * - if "trilium-data" dir exists directly in the home dir, then it is used - * - based on OS convention, if the "app data directory" exists, we'll use or create "trilium-data" directory there - * - as a fallback if the previous step fails, we'll use home dir + * - case A) if TRILIUM_DATA_DIR environment variable exists, then its value is used as the path + * - case B) if "trilium-data" dir exists directly in the home dir, then it is used + * - case C) based on OS convention, if the "app data directory" exists, we'll use or create "trilium-data" directory there + * - case D) as a fallback if the previous step fails, we'll use home dir */ import os from "os"; import fs from "fs"; import { join as pathJoin} from "path"; -function getAppDataDir() { - let appDataDir = os.homedir(); // fallback if OS is not recognized - - if (os.platform() === "win32" && process.env.APPDATA) { - appDataDir = process.env.APPDATA; - } else if (os.platform() === "linux") { - appDataDir = `${os.homedir()}/.local/share`; - } else if (os.platform() === "darwin") { - appDataDir = `${os.homedir()}/Library/Application Support`; - } - - if (!fs.existsSync(appDataDir)) { - // expected app data path doesn't exist, let's use fallback - appDataDir = os.homedir(); - } - - return appDataDir; -} - const DIR_NAME = "trilium-data"; const FOLDER_PERMISSIONS = 0o700; +function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { + + switch(true) { + case platform === "win32" && !!ENV_APPDATA_DIR: + return ENV_APPDATA_DIR; + + case platform === "linux": + return `${os.homedir()}/.local/share`; + + case platform === "darwin": + return `${os.homedir()}/Library/Application Support`; + + default: + // if OS is not recognized + return null; + } + +} + function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { if (!fs.existsSync(path)) { fs.mkdirSync(path, permissionMode); @@ -41,20 +41,29 @@ function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOL } function getTriliumDataDir() { + // case A if (process.env.TRILIUM_DATA_DIR) { createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); return process.env.TRILIUM_DATA_DIR; } + // case B const homePath = pathJoin(os.homedir(), DIR_NAME); if (fs.existsSync(homePath)) { return homePath; } - const appDataPath = pathJoin(getAppDataDir(), DIR_NAME); - createDirIfNotExisting(appDataPath); + // case C + const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); + if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { + const appDataDirPath = pathJoin(platformAppDataDir, DIR_NAME); + createDirIfNotExisting(appDataDirPath); + return appDataDirPath; + } - return appDataPath; + // case D + createDirIfNotExisting(homePath); + return homePath; } const TRILIUM_DATA_DIR = getTriliumDataDir(); From 759d24855b42f16238d3b8d2fa3b89228f7f6769 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 20:42:03 +0100 Subject: [PATCH 18/47] style(data_dir): fix indentation --- src/services/data_dir.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 5b963dae7..090c334b7 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -35,9 +35,9 @@ function getPlatformAppDataDir(platform: ReturnType, ENV_APP } function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { - if (!fs.existsSync(path)) { - fs.mkdirSync(path, permissionMode); - } + if (!fs.existsSync(path)) { + fs.mkdirSync(path, permissionMode); + } } function getTriliumDataDir() { From 8b1071c4593b51cc656fed25dff8f0b256ab562c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 21:17:44 +0100 Subject: [PATCH 19/47] refactor(data_dir): export dirs as frozen readonly object previously exported object allowed the values to be changed accidentally at runtime and buildtime --- src/services/data_dir.ts | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 090c334b7..6b7806628 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -66,19 +66,28 @@ function getTriliumDataDir() { return homePath; } +function getDataDirs(TRILIUM_DATA_DIR: string) { + const dataDirs = { + "TRILIUM_DATA_DIR": + TRILIUM_DATA_DIR, + "DOCUMENT_PATH": + process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), + "BACKUP_DIR": + process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), + "LOG_DIR": + process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), + "ANONYMIZED_DB_DIR": + process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), + "CONFIG_INI_PATH": + process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") + } as const + + Object.freeze(dataDirs); + return dataDirs; + +} + const TRILIUM_DATA_DIR = getTriliumDataDir(); +const dataDirs = getDataDirs(TRILIUM_DATA_DIR); -const DOCUMENT_PATH = process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"); -const BACKUP_DIR = process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"); -const LOG_DIR = process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"); -const ANONYMIZED_DB_DIR = process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"); -const CONFIG_INI_PATH = process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini"); - -export default { - TRILIUM_DATA_DIR, - DOCUMENT_PATH, - BACKUP_DIR, - LOG_DIR, - ANONYMIZED_DB_DIR, - CONFIG_INI_PATH -}; +export default dataDirs; \ No newline at end of file From 94b8bcf8c930f04ea98aed209d3970d73e09151c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 22:08:17 +0100 Subject: [PATCH 20/47] refactor(data_dir): export functions to allow for testing --- src/services/data_dir.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 6b7806628..c896e959e 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -15,7 +15,7 @@ import { join as pathJoin} from "path"; const DIR_NAME = "trilium-data"; const FOLDER_PERMISSIONS = 0o700; -function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { +export function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { switch(true) { case platform === "win32" && !!ENV_APPDATA_DIR: @@ -40,7 +40,7 @@ function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOL } } -function getTriliumDataDir() { +export function getTriliumDataDir() { // case A if (process.env.TRILIUM_DATA_DIR) { createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); @@ -66,7 +66,7 @@ function getTriliumDataDir() { return homePath; } -function getDataDirs(TRILIUM_DATA_DIR: string) { +export function getDataDirs(TRILIUM_DATA_DIR: string) { const dataDirs = { "TRILIUM_DATA_DIR": TRILIUM_DATA_DIR, From 63079c093988c2c6ff683dd2adb32e065fc2ad2f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 3 Jan 2025 22:08:56 +0100 Subject: [PATCH 21/47] test(data_dir): add tests for getPlatformAppDataDir --- spec-es6/data_dir.spec.ts | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 spec-es6/data_dir.spec.ts diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts new file mode 100644 index 000000000..7d80a2811 --- /dev/null +++ b/spec-es6/data_dir.spec.ts @@ -0,0 +1,78 @@ +import { describe, it, execute, expect } from "./mini_test.ts"; + +import { getPlatformAppDataDir } from "../src/services/data_dir.ts" + + + +describe("data_dir.ts unit tests", () => { + + describe("#getPlatformAppDataDir", () => { + + type TestCaseGetPlatformAppDataDir = [ + description: string, + fnValue: Parameters, + expectedValueFn: (val: ReturnType) => boolean + ] + const testCases: TestCaseGetPlatformAppDataDir[] = [ + + [ + "w/ unsupported OS it should return 'null'", + ["aix", undefined], + (val) => val === null + ], + + [ + "w/ win32 and no APPDATA set it should return 'null'", + ["win32", undefined], + (val) => val === null + ], + + [ + "w/ win32 and set APPDATA it should return set 'APPDATA'", + ["win32", "AppData"], + (val) => val === "AppData" + ], + + [ + "w/ linux it should return '/.local/share'", + ["linux", undefined], + (val) => val !== null && val.endsWith("/.local/share") + ], + + [ + "w/ linux and wrongly set APPDATA it should ignore APPDATA and return /.local/share", + ["linux", "FakeAppData"], + (val) => val !== null && val.endsWith("/.local/share") + ], + + [ + "w/ darwin it should return /Library/Application Support", + ["darwin", undefined], + (val) => val !== null && val.endsWith("/Library/Application Support") + ], + ]; + + testCases.forEach(testCase => { + const [testDescription, value, isExpected] = testCase; + return it(testDescription, () => { + const actual = getPlatformAppDataDir(...value); + const result = isExpected(actual); + expect(result).toBeTruthy() + + }) + }) + + + }) + + describe("#getTriliumDataDir", () => { + // TODO + }) + + describe("#getDataDirs", () => { + // TODO + }) + +}); + +execute() \ No newline at end of file From e021c0cd0e83096b4357f4e441d7aefc9eae493f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 4 Jan 2025 00:16:26 +0100 Subject: [PATCH 22/47] test(data_dir): add tests for getDataDirs --- spec-es6/data_dir.spec.ts | 68 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts index 7d80a2811..70a329ff5 100644 --- a/spec-es6/data_dir.spec.ts +++ b/spec-es6/data_dir.spec.ts @@ -1,12 +1,12 @@ import { describe, it, execute, expect } from "./mini_test.ts"; -import { getPlatformAppDataDir } from "../src/services/data_dir.ts" +import { getPlatformAppDataDir, getDataDirs} from "../src/services/data_dir.ts" describe("data_dir.ts unit tests", () => { - describe("#getPlatformAppDataDir", () => { + describe("#getPlatformAppDataDir()", () => { type TestCaseGetPlatformAppDataDir = [ description: string, @@ -69,8 +69,68 @@ describe("data_dir.ts unit tests", () => { // TODO }) - describe("#getDataDirs", () => { - // TODO + describe("#getDataDirs()", () => { + + const envKeys: Omit, "TRILIUM_DATA_DIR">[] = [ + "DOCUMENT_PATH", + "BACKUP_DIR", + "LOG_DIR", + "ANONYMIZED_DB_DIR", + "CONFIG_INI_PATH", + ]; + + const setMockedEnv = (prefix: string | null) => { + envKeys.forEach(key => { + if (prefix) { + process.env[`TRILIUM_${key}`] = `${prefix}_${key}` + } else { + delete process.env[`TRILIUM_${key}`] + } + }) + }; + + it("w/ process.env values present, it should return an object using values from process.env", () => { + + // set mocked values + const mockValuePrefix = "MOCK"; + setMockedEnv(mockValuePrefix); + + // get result + const result = getDataDirs(`${mockValuePrefix}_TRILIUM_DATA_DIR`); + + for (const key in result) { + expect(result[key]).toEqual(`${mockValuePrefix}_${key}`) + } + }) + + it("w/ NO process.env values present, it should return an object using supplied TRILIUM_DATA_DIR as base", () => { + + // make sure values are undefined + setMockedEnv(null); + + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR" + const result = getDataDirs(mockDataDir); + + for (const key in result) { + expect(result[key].startsWith(mockDataDir)).toBeTruthy() + } + }) + + it("should ignore attempts to change a property on the returned object", () => { + + // make sure values are undefined + setMockedEnv(null); + + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR" + const result = getDataDirs(mockDataDir); + + //@ts-expect-error - attempt to change value of readonly property + result.BACKUP_DIR = "attempt to change"; + + for (const key in result) { + expect(result[key].startsWith(mockDataDir)).toBeTruthy() + } + }) }) }); From c47522eb50175f1b63b72876032cb19b54a31e66 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 6 Jan 2025 09:37:25 +0100 Subject: [PATCH 23/47] refactor(data_dir): pass DIR_NAME as argument to getTriliumDir makes it a bit cleaner and easier to test in the future, as it is one thing less that'd need mocking :-) --- src/services/data_dir.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index c896e959e..3e3ec396d 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -40,7 +40,7 @@ function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOL } } -export function getTriliumDataDir() { +export function getTriliumDataDir(dataDirName: string) { // case A if (process.env.TRILIUM_DATA_DIR) { createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); @@ -48,7 +48,7 @@ export function getTriliumDataDir() { } // case B - const homePath = pathJoin(os.homedir(), DIR_NAME); + const homePath = pathJoin(os.homedir(), dataDirName); if (fs.existsSync(homePath)) { return homePath; } @@ -56,7 +56,7 @@ export function getTriliumDataDir() { // case C const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { - const appDataDirPath = pathJoin(platformAppDataDir, DIR_NAME); + const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); createDirIfNotExisting(appDataDirPath); return appDataDirPath; } @@ -87,7 +87,7 @@ export function getDataDirs(TRILIUM_DATA_DIR: string) { } -const TRILIUM_DATA_DIR = getTriliumDataDir(); +const TRILIUM_DATA_DIR = getTriliumDataDir(DIR_NAME); const dataDirs = getDataDirs(TRILIUM_DATA_DIR); export default dataDirs; \ No newline at end of file From 6818b2d54caaeb4d61f5813407725c4199d89907 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 6 Jan 2025 09:42:35 +0100 Subject: [PATCH 24/47] style: move "important" funcs to top of file --- src/services/data_dir.ts | 91 ++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 3e3ec396d..9b8c70ec6 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -15,6 +15,52 @@ import { join as pathJoin} from "path"; const DIR_NAME = "trilium-data"; const FOLDER_PERMISSIONS = 0o700; +export function getTriliumDataDir(dataDirName: string) { + // case A + if (process.env.TRILIUM_DATA_DIR) { + createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); + return process.env.TRILIUM_DATA_DIR; + } + + // case B + const homePath = pathJoin(os.homedir(), dataDirName); + if (fs.existsSync(homePath)) { + return homePath; + } + + // case C + const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); + if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { + const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); + createDirIfNotExisting(appDataDirPath); + return appDataDirPath; + } + + // case D + createDirIfNotExisting(homePath); + return homePath; +} + +export function getDataDirs(TRILIUM_DATA_DIR: string) { + const dataDirs = { + "TRILIUM_DATA_DIR": + TRILIUM_DATA_DIR, + "DOCUMENT_PATH": + process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), + "BACKUP_DIR": + process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), + "LOG_DIR": + process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), + "ANONYMIZED_DB_DIR": + process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), + "CONFIG_INI_PATH": + process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") + } as const + + Object.freeze(dataDirs); + return dataDirs; +} + export function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { switch(true) { @@ -40,52 +86,7 @@ function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOL } } -export function getTriliumDataDir(dataDirName: string) { - // case A - if (process.env.TRILIUM_DATA_DIR) { - createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); - return process.env.TRILIUM_DATA_DIR; - } - // case B - const homePath = pathJoin(os.homedir(), dataDirName); - if (fs.existsSync(homePath)) { - return homePath; - } - - // case C - const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); - if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { - const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); - createDirIfNotExisting(appDataDirPath); - return appDataDirPath; - } - - // case D - createDirIfNotExisting(homePath); - return homePath; -} - -export function getDataDirs(TRILIUM_DATA_DIR: string) { - const dataDirs = { - "TRILIUM_DATA_DIR": - TRILIUM_DATA_DIR, - "DOCUMENT_PATH": - process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), - "BACKUP_DIR": - process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), - "LOG_DIR": - process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), - "ANONYMIZED_DB_DIR": - process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), - "CONFIG_INI_PATH": - process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") - } as const - - Object.freeze(dataDirs); - return dataDirs; - -} const TRILIUM_DATA_DIR = getTriliumDataDir(DIR_NAME); const dataDirs = getDataDirs(TRILIUM_DATA_DIR); From 5373ef509bdae43923df7242a72b2db785041e73 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 13 Jan 2025 08:28:12 +0100 Subject: [PATCH 25/47] chore(prettier): fix code style --- src/services/data_dir.ts | 76 +++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/src/services/data_dir.ts b/src/services/data_dir.ts index 9b8c70ec6..2e0c2f522 100644 --- a/src/services/data_dir.ts +++ b/src/services/data_dir.ts @@ -10,60 +10,53 @@ import os from "os"; import fs from "fs"; -import { join as pathJoin} from "path"; +import { join as pathJoin } from "path"; const DIR_NAME = "trilium-data"; const FOLDER_PERMISSIONS = 0o700; export function getTriliumDataDir(dataDirName: string) { - // case A - if (process.env.TRILIUM_DATA_DIR) { - createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); - return process.env.TRILIUM_DATA_DIR; - } + // case A + if (process.env.TRILIUM_DATA_DIR) { + createDirIfNotExisting(process.env.TRILIUM_DATA_DIR); + return process.env.TRILIUM_DATA_DIR; + } - // case B - const homePath = pathJoin(os.homedir(), dataDirName); - if (fs.existsSync(homePath)) { - return homePath; - } + // case B + const homePath = pathJoin(os.homedir(), dataDirName); + if (fs.existsSync(homePath)) { + return homePath; + } - // case C - const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); - if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { - const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); - createDirIfNotExisting(appDataDirPath); - return appDataDirPath; - } + // case C + const platformAppDataDir = getPlatformAppDataDir(os.platform(), process.env.APPDATA); + if (platformAppDataDir && fs.existsSync(platformAppDataDir)) { + const appDataDirPath = pathJoin(platformAppDataDir, dataDirName); + createDirIfNotExisting(appDataDirPath); + return appDataDirPath; + } - // case D - createDirIfNotExisting(homePath); - return homePath; + // case D + createDirIfNotExisting(homePath); + return homePath; } export function getDataDirs(TRILIUM_DATA_DIR: string) { - const dataDirs = { - "TRILIUM_DATA_DIR": - TRILIUM_DATA_DIR, - "DOCUMENT_PATH": - process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), - "BACKUP_DIR": - process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), - "LOG_DIR": - process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), - "ANONYMIZED_DB_DIR": - process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), - "CONFIG_INI_PATH": - process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") - } as const + const dataDirs = { + TRILIUM_DATA_DIR: TRILIUM_DATA_DIR, + DOCUMENT_PATH: process.env.TRILIUM_DOCUMENT_PATH || pathJoin(TRILIUM_DATA_DIR, "document.db"), + BACKUP_DIR: process.env.TRILIUM_BACKUP_DIR || pathJoin(TRILIUM_DATA_DIR, "backup"), + LOG_DIR: process.env.TRILIUM_LOG_DIR || pathJoin(TRILIUM_DATA_DIR, "log"), + ANONYMIZED_DB_DIR: process.env.TRILIUM_ANONYMIZED_DB_DIR || pathJoin(TRILIUM_DATA_DIR, "anonymized-db"), + CONFIG_INI_PATH: process.env.TRILIUM_CONFIG_INI_PATH || pathJoin(TRILIUM_DATA_DIR, "config.ini") + } as const; - Object.freeze(dataDirs); - return dataDirs; + Object.freeze(dataDirs); + return dataDirs; } export function getPlatformAppDataDir(platform: ReturnType, ENV_APPDATA_DIR: string | undefined = process.env.APPDATA) { - - switch(true) { + switch (true) { case platform === "win32" && !!ENV_APPDATA_DIR: return ENV_APPDATA_DIR; @@ -77,7 +70,6 @@ export function getPlatformAppDataDir(platform: ReturnType, // if OS is not recognized return null; } - } function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) { @@ -86,9 +78,7 @@ function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOL } } - - const TRILIUM_DATA_DIR = getTriliumDataDir(DIR_NAME); const dataDirs = getDataDirs(TRILIUM_DATA_DIR); -export default dataDirs; \ No newline at end of file +export default dataDirs; From b30164ef66e90fc2b19d7d96e6027d803a685673 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 10:00:13 +0200 Subject: [PATCH 26/47] chore(e2e): add missing await to expect --- e2e/note_types/code.spec.ts | 2 +- e2e/note_types/mermaid.spec.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/note_types/code.spec.ts b/e2e/note_types/code.spec.ts index 6473b6849..f0f204fcc 100644 --- a/e2e/note_types/code.spec.ts +++ b/e2e/note_types/code.spec.ts @@ -10,7 +10,7 @@ test("Displays lint warnings for backend script", async ({ page, context }) => { const codeEditor = app.currentNoteSplit.locator(".CodeMirror"); // Expect two warning signs in the gutter. - expect(codeEditor.locator(".CodeMirror-gutter-wrapper .CodeMirror-lint-marker-warning")).toHaveCount(2); + await expect(codeEditor.locator(".CodeMirror-gutter-wrapper .CodeMirror-lint-marker-warning")).toHaveCount(2); // Hover over hello await codeEditor.getByText("hello").first().hover(); diff --git a/e2e/note_types/mermaid.spec.ts b/e2e/note_types/mermaid.spec.ts index 23f43399e..b06076cc3 100644 --- a/e2e/note_types/mermaid.spec.ts +++ b/e2e/note_types/mermaid.spec.ts @@ -6,9 +6,9 @@ test("displays simple map", async ({ page, context }) => { await app.goto(); await app.goToNoteInNewTab("Sample mindmap"); - expect(app.currentNoteSplit).toContainText("Hello world"); - expect(app.currentNoteSplit).toContainText("1"); - expect(app.currentNoteSplit).toContainText("1a"); + await expect(app.currentNoteSplit).toContainText("Hello world"); + await expect(app.currentNoteSplit).toContainText("1"); + await expect(app.currentNoteSplit).toContainText("1a"); }); test("displays note settings", async ({ page, context }) => { @@ -18,5 +18,5 @@ test("displays note settings", async ({ page, context }) => { await app.currentNoteSplit.getByText("Hello world").click({ force: true }); const nodeMenu = app.currentNoteSplit.locator(".node-menu"); - expect(nodeMenu).toBeVisible(); + await expect(nodeMenu).toBeVisible(); }); From b2e83caf4aee7d35b3c8512c95c5973b59f23a5e Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 13 Jan 2025 08:38:47 +0100 Subject: [PATCH 27/47] chore(scripts): add `test-playwright` script to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2f056ed91..cea4f3aac 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "build-frontend-docs": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", "webpack": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts", + "test-playwright": "playwright test", "test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js", "test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts", "test": "npm run test-jasmine && npm run test-es6", From 89d700d5edec51b450693adbf4ff238ce46069b0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 17:21:50 +0200 Subject: [PATCH 28/47] chore(e2e): use different mechanism for closing all tabs --- e2e/support/app.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/e2e/support/app.ts b/e2e/support/app.ts index 08d1ebb77..d0d443b27 100644 --- a/e2e/support/app.ts +++ b/e2e/support/app.ts @@ -60,19 +60,36 @@ export default class App { return this.tabBar.locator(".note-tab[active]"); } + /** + * Closes all the tabs in the client by issuing a command. + */ async closeAllTabs() { - await this.getTab(0).click({ button: "right" }); - await this.page.waitForTimeout(500); // TODO: context menu won't dismiss otherwise - await this.page.getByText("Close all tabs").click({ force: true }); - await this.page.waitForTimeout(500); // TODO: context menu won't dismiss otherwise + await this.triggerCommand("closeAllTabs"); } + /** + * Adds a new tab by cliking on the + button near the tab bar. + */ async addNewTab() { await this.page.locator('[data-trigger-command="openNewTab"]').click(); } + /** + * Looks for a given title in the note tree and clicks on it. Useful for selecting option pages in settings in a similar fashion as the user. + * @param title the title of the note to click, as displayed in the note tree. + */ async clickNoteOnNoteTreeByTitle(title: string) { - this.noteTree.getByText(title).click(); + await this.noteTree.getByText(title).click(); + } + + /** + * Executes any Trilium command on the client. + * @param command the command to send. + */ + async triggerCommand(command: string) { + await this.page.evaluate(async (command: string) => { + await (window as any).glob.appContext.triggerCommand(command); + }, command); } } From b69cad229848502f1f5bb5191e43b83a20d1c812 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 17:42:21 +0200 Subject: [PATCH 29/47] fix(e2e): leaks if language fails --- e2e/i18n.spec.ts | 6 ++++++ e2e/support/app.ts | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index da5be4e19..d075a61ba 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -1,6 +1,12 @@ import { test, expect, Page } from "@playwright/test"; import App from "./support/app"; +test.afterEach(async ({ page, context }) => { + const app = new App(page, context); + // Ensure English is set after each locale change to avoid any leaks to other tests. + await app.setOption("locale", "en"); +}); + test("Displays translation on desktop", async ({ page, context }) => { const app = new App(page, context); await app.goto(); diff --git a/e2e/support/app.ts b/e2e/support/app.ts index d0d443b27..290d9c420 100644 --- a/e2e/support/app.ts +++ b/e2e/support/app.ts @@ -5,6 +5,8 @@ interface GotoOpts { isMobile?: boolean; } +const BASE_URL = "http://127.0.0.1:8082"; + export default class App { readonly page: Page; readonly context: BrowserContext; @@ -27,7 +29,7 @@ export default class App { async goto(opts: GotoOpts = {}) { await this.context.addCookies([ { - url: "http://127.0.0.1:8082", + url: BASE_URL, name: "trilium-device", value: opts.isMobile ? "mobile" : "desktop" } @@ -92,4 +94,17 @@ export default class App { }, command); } + async setOption(key: string, value: string) { + const csrfToken = await this.page.evaluate(() => { + return (window as any).glob.csrfToken; + }); + + expect(csrfToken).toBeTruthy(); + await expect(await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, { + headers: { + "x-csrf-token": csrfToken + } + })).toBeOK(); + } + } From 093f9d60f038b9dd830dca74b5f2f60523a54c8d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 17:46:57 +0200 Subject: [PATCH 30/47] fix(e2e): flaky test due to timeout --- e2e/i18n.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/i18n.spec.ts b/e2e/i18n.spec.ts index d075a61ba..70713f097 100644 --- a/e2e/i18n.spec.ts +++ b/e2e/i18n.spec.ts @@ -49,9 +49,9 @@ test("User can change language from settings", async ({ page, context }) => { // Select Chinese and ensure the translation is set. await languageCombobox.selectOption("cn"); - await expect(app.currentNoteSplit).toContainText("主题"); + await expect(app.currentNoteSplit).toContainText("主题", { timeout: 15000 }); // Select English again. await languageCombobox.selectOption("en"); - await expect(app.currentNoteSplit).toContainText("Language"); + await expect(app.currentNoteSplit).toContainText("Language", { timeout: 15000 }); }); From 6c886fe3b918db6649f7ea91ffcd16fcc76f0b08 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 21:21:18 +0200 Subject: [PATCH 31/47] chore(e2e): order around docker test --- .github/workflows/main-docker.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index d7370b222..06a1d1d4c 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -48,7 +48,11 @@ jobs: node-version: 20 cache: "npm" - - run: npm ci + - name: Install npm dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps - name: Run the TypeScript build run: npx tsc @@ -78,9 +82,7 @@ jobs: wait-time: 50 require-status: running require-healthy: true - - - name: Install Playwright Browsers - run: npx playwright install --with-deps + - name: Run Playwright tests run: TRILIUM_DOCKER=1 npx playwright test - uses: actions/upload-artifact@v4 From 2fa5955bd57f3602cbe2e5082a298260f914dede Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 21:41:32 +0200 Subject: [PATCH 32/47] fix(e2e): port for docker --- .github/workflows/main-docker.yml | 2 +- playwright.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 06a1d1d4c..eb1d5fcfb 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -72,7 +72,7 @@ jobs: - name: Validate container run output run: | - CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) + CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) echo "Container ID: $CONTAINER_ID" - name: Wait for the healthchecks to pass diff --git a/playwright.config.ts b/playwright.config.ts index 6feb9f8ab..86bf074ce 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: (!process.env.TRILIUM_DOCKER ? SERVER_URL : "http://127.0.0.1:8080"), + baseURL: SERVER_URL, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', From 1807b2b031870b0ef818b99beb14f59c604d694b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Jan 2025 23:18:10 +0200 Subject: [PATCH 33/47] chore(types): missing import type for JS imports --- spec/search/becca_mocking.ts | 2 +- spec/search/parser.spec.ts | 2 +- spec/support/etapi.ts | 2 +- src/becca/becca-interface.ts | 12 ++++++------ src/becca/becca_loader.ts | 2 +- src/becca/entities/abstract_becca_entity.ts | 2 +- src/becca/entities/battachment.ts | 4 ++-- src/becca/entities/bnote.ts | 2 +- src/becca/entity_constructor.ts | 2 +- src/becca/similarity.ts | 2 +- src/etapi/app_info.ts | 2 +- src/etapi/attachments.ts | 2 +- src/etapi/attributes.ts | 2 +- src/etapi/backup.ts | 2 +- src/etapi/branches.ts | 2 +- src/etapi/mappers.ts | 8 ++++---- src/etapi/notes.ts | 2 +- src/etapi/spec.ts | 2 +- src/etapi/special_notes.ts | 2 +- src/public/app/components/app_context.ts | 8 ++++---- src/public/app/components/entrypoints.ts | 2 +- src/public/app/components/note_context.ts | 2 +- src/public/app/entities/fnote.ts | 4 ++-- src/public/app/layouts/mobile_layout.ts | 2 +- src/public/app/menus/launcher_context_menu.ts | 4 ++-- src/public/app/menus/tree_context_menu.ts | 6 +++--- src/public/app/services/attribute_renderer.ts | 4 ++-- src/public/app/services/attributes.ts | 2 +- src/public/app/services/bulk_action.ts | 2 +- src/public/app/services/froca-interface.ts | 10 +++++----- src/public/app/services/froca_updater.ts | 2 +- src/public/app/services/frontend_script_api.ts | 8 ++++---- src/public/app/services/hoisted_note.ts | 2 +- src/public/app/services/keyboard_actions.ts | 2 +- .../app/services/note_attribute_cache.ts | 2 +- src/public/app/services/note_create.ts | 4 ++-- src/public/app/services/note_list_renderer.ts | 2 +- src/public/app/services/note_tooltip.ts | 2 +- .../app/services/protected_session_holder.ts | 2 +- src/public/app/services/render.ts | 2 +- .../attribute_widgets/attribute_editor.ts | 10 +++++----- .../bulk_actions/abstract_bulk_action.ts | 2 +- src/public/app/widgets/containers/container.ts | 2 +- .../app/widgets/containers/flex_container.ts | 2 +- src/public/app/widgets/containers/launcher.ts | 2 +- .../widgets/containers/right_pane_container.ts | 2 +- src/public/app/widgets/highlights_list.ts | 2 +- .../mobile_widgets/sidebar_container.ts | 2 +- .../app/widgets/note_context_aware_widget.ts | 4 ++-- src/public/app/widgets/right_panel_widget.ts | 4 ++-- .../type_widgets/options/options_widget.ts | 2 +- src/routes/api/clipper.ts | 2 +- src/routes/api/files.ts | 4 ++-- src/routes/api/image.ts | 4 ++-- src/routes/api/import.ts | 2 +- src/routes/api/note_map.ts | 4 ++-- src/routes/api/notes.ts | 2 +- src/routes/api/revisions.ts | 6 +++--- src/routes/api/search.ts | 2 +- src/routes/api/tree.ts | 2 +- src/routes/assets.ts | 2 +- src/routes/index.ts | 2 +- src/services/attributes.ts | 2 +- src/services/backend_script_api.ts | 18 +++++++++--------- src/services/backend_script_api_interface.ts | 4 ++-- src/services/branches.ts | 2 +- src/services/bulk_actions.ts | 2 +- src/services/date_notes.ts | 2 +- src/services/export/opml.ts | 4 ++-- src/services/export/single.ts | 4 ++-- src/services/export/zip.ts | 2 +- src/services/handlers.ts | 4 ++-- src/services/import/enex.ts | 4 ++-- src/services/import/opml.ts | 4 ++-- src/services/import/single.ts | 4 ++-- src/services/import/zip.ts | 6 +++--- src/services/notes.ts | 2 +- src/services/revisions.ts | 2 +- src/services/scheduler.ts | 2 +- src/services/script.ts | 2 +- src/services/script_context.ts | 2 +- src/services/search/expressions/ancestor.ts | 2 +- src/services/search/expressions/and.ts | 4 ++-- .../search/expressions/attribute_exists.ts | 2 +- src/services/search/expressions/child_of.ts | 2 +- .../search/expressions/descendant_of.ts | 2 +- src/services/search/expressions/is_hidden.ts | 2 +- .../search/expressions/label_comparison.ts | 2 +- src/services/search/expressions/not.ts | 4 ++-- .../expressions/note_content_fulltext.ts | 2 +- .../search/expressions/note_flat_text.ts | 4 ++-- src/services/search/expressions/or.ts | 2 +- .../search/expressions/order_by_and_limit.ts | 4 ++-- src/services/search/expressions/parent_of.ts | 2 +- .../search/expressions/relation_where.ts | 2 +- src/services/search/expressions/true.ts | 4 ++-- src/services/search/note_set.ts | 2 +- src/services/search/services/parse.ts | 4 ++-- src/services/search/services/search.ts | 6 +++--- src/services/search/value_extractor.ts | 2 +- src/services/tree.ts | 2 +- src/services/ws.ts | 2 +- src/share/content_renderer.ts | 2 +- src/share/routes.ts | 6 +++--- .../shaca/entities/abstract_shaca_entity.ts | 2 +- src/share/shaca/entities/sattachment.ts | 2 +- src/share/shaca/entities/sattribute.ts | 2 +- src/share/shaca/entities/sbranch.ts | 2 +- src/share/shaca/entities/snote.ts | 6 +++--- src/share/shaca/shaca-interface.ts | 8 ++++---- 110 files changed, 178 insertions(+), 178 deletions(-) diff --git a/spec/search/becca_mocking.ts b/spec/search/becca_mocking.ts index 76ee955f6..0c65f16b6 100644 --- a/spec/search/becca_mocking.ts +++ b/spec/search/becca_mocking.ts @@ -3,7 +3,7 @@ import BBranch from "../../src/becca/entities/bbranch.js"; import BAttribute from "../../src/becca/entities/battribute.js"; import becca from "../../src/becca/becca.js"; import randtoken from "rand-token"; -import SearchResult from "../../src/services/search/search_result.js"; +import type SearchResult from "../../src/services/search/search_result.js"; import type { NoteType } from "../../src/becca/entities/rows.js"; randtoken.generator({ source: "crypto" }); diff --git a/spec/search/parser.spec.ts b/spec/search/parser.spec.ts index 09c7ce06b..76e557067 100644 --- a/spec/search/parser.spec.ts +++ b/spec/search/parser.spec.ts @@ -1,6 +1,6 @@ import AndExp from "../../src/services/search/expressions/and.js"; import AttributeExistsExp from "../../src/services/search/expressions/attribute_exists.js"; -import Expression from "../../src/services/search/expressions/expression.js"; +import type Expression from "../../src/services/search/expressions/expression.js"; import LabelComparisonExp from "../../src/services/search/expressions/label_comparison.js"; import NotExp from "../../src/services/search/expressions/not.js"; import NoteContentFulltextExp from "../../src/services/search/expressions/note_content_fulltext.js"; diff --git a/spec/support/etapi.ts b/spec/support/etapi.ts index 3e6e1ac98..d653707d5 100644 --- a/spec/support/etapi.ts +++ b/spec/support/etapi.ts @@ -1,4 +1,4 @@ -import child_process from "child_process"; +import type child_process from "child_process"; let etapiAuthToken: string | undefined; diff --git a/src/becca/becca-interface.ts b/src/becca/becca-interface.ts index 6497dad7d..cdee5d1cd 100644 --- a/src/becca/becca-interface.ts +++ b/src/becca/becca-interface.ts @@ -1,17 +1,17 @@ import sql from "../services/sql.js"; import NoteSet from "../services/search/note_set.js"; import NotFoundError from "../errors/not_found_error.js"; -import BOption from "./entities/boption.js"; -import BNote from "./entities/bnote.js"; -import BEtapiToken from "./entities/betapi_token.js"; -import BAttribute from "./entities/battribute.js"; -import BBranch from "./entities/bbranch.js"; +import type BOption from "./entities/boption.js"; +import type BNote from "./entities/bnote.js"; +import type BEtapiToken from "./entities/betapi_token.js"; +import type BAttribute from "./entities/battribute.js"; +import type BBranch from "./entities/bbranch.js"; import BRevision from "./entities/brevision.js"; import BAttachment from "./entities/battachment.js"; import type { AttachmentRow, BlobRow, RevisionRow } from "./entities/rows.js"; import BBlob from "./entities/bblob.js"; import BRecentNote from "./entities/brecent_note.js"; -import AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; +import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; interface AttachmentOpts { includeContentLength?: boolean; diff --git a/src/becca/becca_loader.ts b/src/becca/becca_loader.ts index a461828ac..8397b2dec 100644 --- a/src/becca/becca_loader.ts +++ b/src/becca/becca_loader.ts @@ -12,7 +12,7 @@ import BEtapiToken from "./entities/betapi_token.js"; import cls from "../services/cls.js"; import entityConstructor from "../becca/entity_constructor.js"; import type { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from "./entities/rows.js"; -import AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; +import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; import ws from "../services/ws.js"; const beccaLoaded = new Promise(async (res, rej) => { diff --git a/src/becca/entities/abstract_becca_entity.ts b/src/becca/entities/abstract_becca_entity.ts index b964a2d43..a36132f3a 100644 --- a/src/becca/entities/abstract_becca_entity.ts +++ b/src/becca/entities/abstract_becca_entity.ts @@ -9,7 +9,7 @@ import cls from "../../services/cls.js"; import log from "../../services/log.js"; import protectedSessionService from "../../services/protected_session.js"; import blobService from "../../services/blob.js"; -import Becca, { type ConstructorData } from "../becca-interface.js"; +import type { default as Becca, ConstructorData } from "../becca-interface.js"; import becca from "../becca.js"; interface ContentOpts { diff --git a/src/becca/entities/battachment.ts b/src/becca/entities/battachment.ts index 1e47fc9bf..9461647af 100644 --- a/src/becca/entities/battachment.ts +++ b/src/becca/entities/battachment.ts @@ -7,8 +7,8 @@ import sql from "../../services/sql.js"; import protectedSessionService from "../../services/protected_session.js"; import log from "../../services/log.js"; import type { AttachmentRow } from "./rows.js"; -import BNote from "./bnote.js"; -import BBranch from "./bbranch.js"; +import type BNote from "./bnote.js"; +import type BBranch from "./bbranch.js"; import noteService from "../../services/notes.js"; const attachmentRoleToNoteTypeMapping = { diff --git a/src/becca/entities/bnote.ts b/src/becca/entities/bnote.ts index d83a263ff..acc432127 100644 --- a/src/becca/entities/bnote.ts +++ b/src/becca/entities/bnote.ts @@ -15,7 +15,7 @@ import dayjs from "dayjs"; import utc from "dayjs/plugin/utc.js"; import eventService from "../../services/events.js"; import type { AttachmentRow, AttributeType, NoteRow, NoteType, RevisionRow } from "./rows.js"; -import BBranch from "./bbranch.js"; +import type BBranch from "./bbranch.js"; import BAttribute from "./battribute.js"; import type { NotePojo } from "../becca-interface.js"; import searchService from "../../services/search/services/search.js"; diff --git a/src/becca/entity_constructor.ts b/src/becca/entity_constructor.ts index 83c5bd62c..18f7a14c7 100644 --- a/src/becca/entity_constructor.ts +++ b/src/becca/entity_constructor.ts @@ -1,5 +1,5 @@ import type { ConstructorData } from "./becca-interface.js"; -import AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; +import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; import BAttachment from "./entities/battachment.js"; import BAttribute from "./entities/battribute.js"; import BBlob from "./entities/bblob.js"; diff --git a/src/becca/similarity.ts b/src/becca/similarity.ts index 5313419d6..66af0ac61 100644 --- a/src/becca/similarity.ts +++ b/src/becca/similarity.ts @@ -3,7 +3,7 @@ import log from "../services/log.js"; import beccaService from "./becca_service.js"; import dateUtils from "../services/date_utils.js"; import { JSDOM } from "jsdom"; -import BNote from "./entities/bnote.js"; +import type BNote from "./entities/bnote.js"; const DEBUG = false; diff --git a/src/etapi/app_info.ts b/src/etapi/app_info.ts index ec5b781c2..ab6deb9dd 100644 --- a/src/etapi/app_info.ts +++ b/src/etapi/app_info.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import type { Router } from "express"; import appInfo from "../services/app_info.js"; import eu from "./etapi_utils.js"; diff --git a/src/etapi/attachments.ts b/src/etapi/attachments.ts index 5d3ce57e4..0404e11a1 100644 --- a/src/etapi/attachments.ts +++ b/src/etapi/attachments.ts @@ -3,7 +3,7 @@ import eu from "./etapi_utils.js"; import mappers from "./mappers.js"; import v from "./validators.js"; import utils from "../services/utils.js"; -import { Router } from "express"; +import type { Router } from "express"; import type { AttachmentRow } from "../becca/entities/rows.js"; import type { ValidatorMap } from "./etapi-interface.js"; diff --git a/src/etapi/attributes.ts b/src/etapi/attributes.ts index 3b9cf3451..91a441117 100644 --- a/src/etapi/attributes.ts +++ b/src/etapi/attributes.ts @@ -3,7 +3,7 @@ import eu from "./etapi_utils.js"; import mappers from "./mappers.js"; import attributeService from "../services/attributes.js"; import v from "./validators.js"; -import { Router } from "express"; +import type { Router } from "express"; import type { AttributeRow } from "../becca/entities/rows.js"; import type { ValidatorMap } from "./etapi-interface.js"; diff --git a/src/etapi/backup.ts b/src/etapi/backup.ts index 3b9f3f874..d73bf1a33 100644 --- a/src/etapi/backup.ts +++ b/src/etapi/backup.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import type { Router } from "express"; import eu from "./etapi_utils.js"; import backupService from "../services/backup.js"; diff --git a/src/etapi/branches.ts b/src/etapi/branches.ts index 59f87c3c8..7263b3161 100644 --- a/src/etapi/branches.ts +++ b/src/etapi/branches.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import type { Router } from "express"; import becca from "../becca/becca.js"; import eu from "./etapi_utils.js"; diff --git a/src/etapi/mappers.ts b/src/etapi/mappers.ts index 33cd2c29f..735e767c2 100644 --- a/src/etapi/mappers.ts +++ b/src/etapi/mappers.ts @@ -1,7 +1,7 @@ -import BAttachment from "../becca/entities/battachment.js"; -import BAttribute from "../becca/entities/battribute.js"; -import BBranch from "../becca/entities/bbranch.js"; -import BNote from "../becca/entities/bnote.js"; +import type BAttachment from "../becca/entities/battachment.js"; +import type BAttribute from "../becca/entities/battribute.js"; +import type BBranch from "../becca/entities/bbranch.js"; +import type BNote from "../becca/entities/bnote.js"; function mapNoteToPojo(note: BNote) { return { diff --git a/src/etapi/notes.ts b/src/etapi/notes.ts index 05b120195..5ab1727b9 100644 --- a/src/etapi/notes.ts +++ b/src/etapi/notes.ts @@ -9,7 +9,7 @@ import searchService from "../services/search/services/search.js"; import SearchContext from "../services/search/search_context.js"; import zipExportService from "../services/export/zip.js"; import zipImportService from "../services/import/zip.js"; -import { type Request, Router } from "express"; +import type { Request, Router } from "express"; import type { ParsedQs } from "qs"; import type { NoteParams } from "../services/note-interface.js"; import type { SearchParams } from "../services/search/services/types.js"; diff --git a/src/etapi/spec.ts b/src/etapi/spec.ts index 5e27be949..925ff51a6 100644 --- a/src/etapi/spec.ts +++ b/src/etapi/spec.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import type { Router } from "express"; import fs from "fs"; import path from "path"; diff --git a/src/etapi/special_notes.ts b/src/etapi/special_notes.ts index 14626710d..23411c987 100644 --- a/src/etapi/special_notes.ts +++ b/src/etapi/special_notes.ts @@ -2,7 +2,7 @@ import specialNotesService from "../services/special_notes.js"; import dateNotesService from "../services/date_notes.js"; import eu from "./etapi_utils.js"; import mappers from "./mappers.js"; -import { Router } from "express"; +import type { Router } from "express"; const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`); const getMonthInvalidError = (month: string) => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`); diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index ea11d2ea4..15e9b8910 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -14,15 +14,15 @@ import MainTreeExecutors from "./main_tree_executors.js"; import toast from "../services/toast.js"; import ShortcutComponent from "./shortcut_component.js"; import { t, initLocale } from "../services/i18n.js"; -import NoteDetailWidget from "../widgets/note_detail.js"; +import type NoteDetailWidget from "../widgets/note_detail.js"; import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js"; import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js"; import type { Node } from "../services/tree.js"; -import LoadResults from "../services/load_results.js"; +import type LoadResults from "../services/load_results.js"; import type { Attribute } from "../services/attribute_parser.js"; -import NoteTreeWidget from "../widgets/note_tree.js"; -import NoteContext, { type GetTextEditorCallback } from "./note_context.js"; +import type NoteTreeWidget from "../widgets/note_tree.js"; +import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootWidget; diff --git a/src/public/app/components/entrypoints.ts b/src/public/app/components/entrypoints.ts index b62cfdeb3..5ba9e3bd8 100644 --- a/src/public/app/components/entrypoints.ts +++ b/src/public/app/components/entrypoints.ts @@ -10,7 +10,7 @@ import bundleService from "../services/bundle.js"; import froca from "../services/froca.js"; import linkService from "../services/link.js"; import { t } from "../services/i18n.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; // TODO: Move somewhere else nicer. export type SqlExecuteResults = unknown[]; diff --git a/src/public/app/components/note_context.ts b/src/public/app/components/note_context.ts index 2bd1981cc..e5b649e56 100644 --- a/src/public/app/components/note_context.ts +++ b/src/public/app/components/note_context.ts @@ -8,7 +8,7 @@ import froca from "../services/froca.js"; import hoistedNoteService from "../services/hoisted_note.js"; import options from "../services/options.js"; import type { ViewScope } from "../services/link.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; interface SetNoteOpts { triggerSwitchEvent?: unknown; diff --git a/src/public/app/entities/fnote.ts b/src/public/app/entities/fnote.ts index ed42257e1..9759f8b07 100644 --- a/src/public/app/entities/fnote.ts +++ b/src/public/app/entities/fnote.ts @@ -5,8 +5,8 @@ import froca from "../services/froca.js"; import protectedSessionHolder from "../services/protected_session_holder.js"; import cssClassManager from "../services/css_class_manager.js"; import type { Froca } from "../services/froca-interface.js"; -import FAttachment from "./fattachment.js"; -import FAttribute, { type AttributeType } from "./fattribute.js"; +import type FAttachment from "./fattachment.js"; +import type { default as FAttribute, AttributeType } from "./fattribute.js"; import utils from "../services/utils.js"; const LABEL = "label"; diff --git a/src/public/app/layouts/mobile_layout.ts b/src/public/app/layouts/mobile_layout.ts index 0f31199fd..64e698998 100644 --- a/src/public/app/layouts/mobile_layout.ts +++ b/src/public/app/layouts/mobile_layout.ts @@ -27,7 +27,7 @@ import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolb import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; -import AppContext from "../components/app_context.js"; +import type AppContext from "../components/app_context.js"; import TabRowWidget from "../widgets/tab_row.js"; import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js"; diff --git a/src/public/app/menus/launcher_context_menu.ts b/src/public/app/menus/launcher_context_menu.ts index d61e0e372..8c2b157cd 100644 --- a/src/public/app/menus/launcher_context_menu.ts +++ b/src/public/app/menus/launcher_context_menu.ts @@ -5,7 +5,7 @@ import dialogService from "../services/dialog.js"; import server from "../services/server.js"; import { t } from "../services/i18n.js"; import type { SelectMenuItemEventListener } from "../components/events.js"; -import NoteTreeWidget from "../widgets/note_tree.js"; +import type NoteTreeWidget from "../widgets/note_tree.js"; import type { FilteredCommandNames, ContextMenuCommandData } from "../components/app_context.js"; type LauncherCommandNames = FilteredCommandNames; @@ -58,7 +58,7 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener< { title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-reset destructive-action-icon", enabled: canBeReset } ]; - return items.filter((row) => row !== null); + return items.filter((row) => row !== null) as MenuItem[]; } async selectMenuItemHandler({ command }: MenuCommandItem) { diff --git a/src/public/app/menus/tree_context_menu.ts b/src/public/app/menus/tree_context_menu.ts index be7983faa..650432828 100644 --- a/src/public/app/menus/tree_context_menu.ts +++ b/src/public/app/menus/tree_context_menu.ts @@ -9,8 +9,8 @@ import server from "../services/server.js"; import toastService from "../services/toast.js"; import dialogService from "../services/dialog.js"; import { t } from "../services/i18n.js"; -import NoteTreeWidget from "../widgets/note_tree.js"; -import FAttachment from "../entities/fattachment.js"; +import type NoteTreeWidget from "../widgets/note_tree.js"; +import type FAttachment from "../entities/fattachment.js"; import type { SelectMenuItemEventListener } from "../components/events.js"; // TODO: Deduplicate once client/server is well split. @@ -196,7 +196,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener row !== null); + return items.filter((row) => row !== null) as MenuItem[]; } async selectMenuItemHandler({ command, type, templateNoteId }: MenuCommandItem) { diff --git a/src/public/app/services/attribute_renderer.ts b/src/public/app/services/attribute_renderer.ts index a686b7098..6c7b9412d 100644 --- a/src/public/app/services/attribute_renderer.ts +++ b/src/public/app/services/attribute_renderer.ts @@ -1,7 +1,7 @@ import ws from "./ws.js"; import froca from "./froca.js"; -import FAttribute from "../entities/fattribute.js"; -import FNote from "../entities/fnote.js"; +import type FAttribute from "../entities/fattribute.js"; +import type FNote from "../entities/fnote.js"; async function renderAttribute(attribute: FAttribute, renderIsInheritable: boolean) { const isInheritable = renderIsInheritable && attribute.isInheritable ? `(inheritable)` : ""; diff --git a/src/public/app/services/attributes.ts b/src/public/app/services/attributes.ts index b594630c5..767d8c3af 100644 --- a/src/public/app/services/attributes.ts +++ b/src/public/app/services/attributes.ts @@ -1,6 +1,6 @@ import server from "./server.js"; import froca from "./froca.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; import type { AttributeRow } from "./load_results.js"; async function addLabel(noteId: string, name: string, value: string = "") { diff --git a/src/public/app/services/bulk_action.ts b/src/public/app/services/bulk_action.ts index 615e1c6eb..66922ef62 100644 --- a/src/public/app/services/bulk_action.ts +++ b/src/public/app/services/bulk_action.ts @@ -14,7 +14,7 @@ import AddLabelBulkAction from "../widgets/bulk_actions/label/add_label.js"; import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation.js"; import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js"; import { t } from "./i18n.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; const ACTION_GROUPS = [ { diff --git a/src/public/app/services/froca-interface.ts b/src/public/app/services/froca-interface.ts index c678e220b..8d0077989 100644 --- a/src/public/app/services/froca-interface.ts +++ b/src/public/app/services/froca-interface.ts @@ -1,8 +1,8 @@ -import FAttachment from "../entities/fattachment.js"; -import FAttribute from "../entities/fattribute.js"; -import FBlob from "../entities/fblob.js"; -import FBranch from "../entities/fbranch.js"; -import FNote from "../entities/fnote.js"; +import type FAttachment from "../entities/fattachment.js"; +import type FAttribute from "../entities/fattribute.js"; +import type FBlob from "../entities/fblob.js"; +import type FBranch from "../entities/fbranch.js"; +import type FNote from "../entities/fnote.js"; export interface Froca { notes: Record; diff --git a/src/public/app/services/froca_updater.ts b/src/public/app/services/froca_updater.ts index 89de8c5b7..f10720de4 100644 --- a/src/public/app/services/froca_updater.ts +++ b/src/public/app/services/froca_updater.ts @@ -6,7 +6,7 @@ import noteAttributeCache from "./note_attribute_cache.js"; import FBranch, { type FBranchRow } from "../entities/fbranch.js"; import FAttribute, { type FAttributeRow } from "../entities/fattribute.js"; import FAttachment, { type FAttachmentRow } from "../entities/fattachment.js"; -import FNote, { type FNoteRow } from "../entities/fnote.js"; +import type { default as FNote, FNoteRow } from "../entities/fnote.js"; import type { EntityChange } from "../server_types.js"; async function processEntityChanges(entityChanges: EntityChange[]) { diff --git a/src/public/app/services/frontend_script_api.ts b/src/public/app/services/frontend_script_api.ts index acdff7444..a8160bbff 100644 --- a/src/public/app/services/frontend_script_api.ts +++ b/src/public/app/services/frontend_script_api.ts @@ -15,11 +15,11 @@ import BasicWidget from "../widgets/basic_widget.js"; import SpacedUpdate from "./spaced_update.js"; import shortcutService from "./shortcuts.js"; import dialogService from "./dialog.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; import { t } from "./i18n.js"; -import NoteContext from "../components/note_context.js"; -import NoteDetailWidget from "../widgets/note_detail.js"; -import Component from "../components/component.js"; +import type NoteContext from "../components/note_context.js"; +import type NoteDetailWidget from "../widgets/note_detail.js"; +import type Component from "../components/component.js"; /** * A whole number diff --git a/src/public/app/services/hoisted_note.ts b/src/public/app/services/hoisted_note.ts index 53d62c081..597de9467 100644 --- a/src/public/app/services/hoisted_note.ts +++ b/src/public/app/services/hoisted_note.ts @@ -2,7 +2,7 @@ import appContext from "../components/app_context.js"; import treeService, { type Node } from "./tree.js"; import dialogService from "./dialog.js"; import froca from "./froca.js"; -import NoteContext from "../components/note_context.js"; +import type NoteContext from "../components/note_context.js"; import { t } from "./i18n.js"; function getHoistedNoteId() { diff --git a/src/public/app/services/keyboard_actions.ts b/src/public/app/services/keyboard_actions.ts index bdd13e28b..dfa888620 100644 --- a/src/public/app/services/keyboard_actions.ts +++ b/src/public/app/services/keyboard_actions.ts @@ -1,7 +1,7 @@ import server from "./server.js"; import appContext, { type CommandNames } from "../components/app_context.js"; import shortcutService from "./shortcuts.js"; -import Component from "../components/component.js"; +import type Component from "../components/component.js"; const keyboardActionRepo: Record = {}; diff --git a/src/public/app/services/note_attribute_cache.ts b/src/public/app/services/note_attribute_cache.ts index ae3993cfb..18980036f 100644 --- a/src/public/app/services/note_attribute_cache.ts +++ b/src/public/app/services/note_attribute_cache.ts @@ -1,4 +1,4 @@ -import FAttribute from "../entities/fattribute.js"; +import type FAttribute from "../entities/fattribute.js"; /** * The purpose of this class is to cache the list of attributes for notes. diff --git a/src/public/app/services/note_create.ts b/src/public/app/services/note_create.ts index a7fd5ec76..b85c8277b 100644 --- a/src/public/app/services/note_create.ts +++ b/src/public/app/services/note_create.ts @@ -6,8 +6,8 @@ import froca from "./froca.js"; import treeService from "./tree.js"; import toastService from "./toast.js"; import { t } from "./i18n.js"; -import FNote from "../entities/fnote.js"; -import FBranch from "../entities/fbranch.js"; +import type FNote from "../entities/fnote.js"; +import type FBranch from "../entities/fbranch.js"; import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js"; interface CreateNoteOpts { diff --git a/src/public/app/services/note_list_renderer.ts b/src/public/app/services/note_list_renderer.ts index e6e305689..2e6652b0f 100644 --- a/src/public/app/services/note_list_renderer.ts +++ b/src/public/app/services/note_list_renderer.ts @@ -5,7 +5,7 @@ import attributeRenderer from "./attribute_renderer.js"; import libraryLoader from "./library_loader.js"; import treeService from "./tree.js"; import utils from "./utils.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; const TPL = `
diff --git a/src/public/app/services/note_tooltip.ts b/src/public/app/services/note_tooltip.ts index f456d7a63..32f79c73d 100644 --- a/src/public/app/services/note_tooltip.ts +++ b/src/public/app/services/note_tooltip.ts @@ -5,7 +5,7 @@ import utils from "./utils.js"; import attributeRenderer from "./attribute_renderer.js"; import contentRenderer from "./content_renderer.js"; import appContext from "../components/app_context.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; import { t } from "./i18n.js"; function setupGlobalTooltip() { diff --git a/src/public/app/services/protected_session_holder.ts b/src/public/app/services/protected_session_holder.ts index e4171f389..647c2f51d 100644 --- a/src/public/app/services/protected_session_holder.ts +++ b/src/public/app/services/protected_session_holder.ts @@ -1,4 +1,4 @@ -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; import server from "./server.js"; function enableProtectedSession() { diff --git a/src/public/app/services/render.ts b/src/public/app/services/render.ts index 0eb02c7a7..bec5cb514 100644 --- a/src/public/app/services/render.ts +++ b/src/public/app/services/render.ts @@ -1,6 +1,6 @@ import server from "./server.js"; import bundleService, { type Bundle } from "./bundle.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; async function render(note: FNote, $el: JQuery) { const relations = note.getRelations("renderNote"); diff --git a/src/public/app/widgets/attribute_widgets/attribute_editor.ts b/src/public/app/widgets/attribute_widgets/attribute_editor.ts index 0119bcd3b..622da6ca9 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_editor.ts +++ b/src/public/app/widgets/attribute_widgets/attribute_editor.ts @@ -3,17 +3,17 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; import noteAutocompleteService from "../../services/note_autocomplete.js"; import server from "../../services/server.js"; import contextMenuService from "../../menus/context_menu.js"; -import attributeParser from "../../services/attribute_parser.js"; +import attributeParser, { type Attribute } from "../../services/attribute_parser.js"; import libraryLoader from "../../services/library_loader.js"; import froca from "../../services/froca.js"; import attributeRenderer from "../../services/attribute_renderer.js"; import noteCreateService from "../../services/note_create.js"; import attributeService from "../../services/attributes.js"; import linkService from "../../services/link.js"; -import AttributeDetailWidget from "./attribute_detail.js"; +import type AttributeDetailWidget from "./attribute_detail.js"; import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js"; -import FAttribute, { type AttributeType } from "../../entities/fattribute.js"; -import FNote from "../../entities/fnote.js"; +import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js"; +import type FNote from "../../entities/fnote.js"; const HELP_TEXT = `

${t("attribute_editor.help_text_body1")}

@@ -417,7 +417,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem return null; } - let matchedAttr = null; + let matchedAttr: Attribute | null = null; for (const attr of parsedAttrs) { if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) { diff --git a/src/public/app/widgets/bulk_actions/abstract_bulk_action.ts b/src/public/app/widgets/bulk_actions/abstract_bulk_action.ts index 436f8fbd8..eb24bbd4d 100644 --- a/src/public/app/widgets/bulk_actions/abstract_bulk_action.ts +++ b/src/public/app/widgets/bulk_actions/abstract_bulk_action.ts @@ -2,7 +2,7 @@ import { t } from "../../services/i18n.js"; import server from "../../services/server.js"; import ws from "../../services/ws.js"; import utils from "../../services/utils.js"; -import FAttribute from "../../entities/fattribute.js"; +import type FAttribute from "../../entities/fattribute.js"; interface ActionDefinition { script: string; diff --git a/src/public/app/widgets/containers/container.ts b/src/public/app/widgets/containers/container.ts index 56fe050a0..679fa11a8 100644 --- a/src/public/app/widgets/containers/container.ts +++ b/src/public/app/widgets/containers/container.ts @@ -1,4 +1,4 @@ -import Component, { TypedComponent } from "../../components/component.js"; +import type { default as Component, TypedComponent } from "../../components/component.js"; import BasicWidget, { TypedBasicWidget } from "../basic_widget.js"; export default class Container> extends TypedBasicWidget { diff --git a/src/public/app/widgets/containers/flex_container.ts b/src/public/app/widgets/containers/flex_container.ts index 62660a4ee..274284775 100644 --- a/src/public/app/widgets/containers/flex_container.ts +++ b/src/public/app/widgets/containers/flex_container.ts @@ -1,4 +1,4 @@ -import { TypedComponent } from "../../components/component.js"; +import type { TypedComponent } from "../../components/component.js"; import Container from "./container.js"; export type FlexDirection = "row" | "column"; diff --git a/src/public/app/widgets/containers/launcher.ts b/src/public/app/widgets/containers/launcher.ts index 41024073a..86fbabb96 100644 --- a/src/public/app/widgets/containers/launcher.ts +++ b/src/public/app/widgets/containers/launcher.ts @@ -11,7 +11,7 @@ import utils from "../../services/utils.js"; import TodayLauncher from "../buttons/launcher/today_launcher.js"; import HistoryNavigationButton from "../buttons/history_navigation.js"; import QuickSearchLauncherWidget from "../quick_search_launcher.js"; -import FNote from "../../entities/fnote.js"; +import type FNote from "../../entities/fnote.js"; import type { CommandNames } from "../../components/app_context.js"; interface InnerWidget extends BasicWidget { diff --git a/src/public/app/widgets/containers/right_pane_container.ts b/src/public/app/widgets/containers/right_pane_container.ts index 4325935fe..c7632769b 100644 --- a/src/public/app/widgets/containers/right_pane_container.ts +++ b/src/public/app/widgets/containers/right_pane_container.ts @@ -1,6 +1,6 @@ import FlexContainer from "./flex_container.js"; import splitService from "../../services/resizer.js"; -import RightPanelWidget from "../right_panel_widget.js"; +import type RightPanelWidget from "../right_panel_widget.js"; export default class RightPaneContainer extends FlexContainer { private rightPaneHidden: boolean; diff --git a/src/public/app/widgets/highlights_list.ts b/src/public/app/widgets/highlights_list.ts index 8b8fb06de..a9be2ddfa 100644 --- a/src/public/app/widgets/highlights_list.ts +++ b/src/public/app/widgets/highlights_list.ts @@ -12,7 +12,7 @@ import options from "../services/options.js"; import OnClickButtonWidget from "./buttons/onclick_button.js"; import appContext, { type EventData } from "../components/app_context.js"; import libraryLoader from "../services/library_loader.js"; -import FNote from "../entities/fnote.js"; +import type FNote from "../entities/fnote.js"; const TPL = `