diff --git a/.gitignore b/.gitignore
index b2c4e3c46..d10c0ec0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,4 @@ upload
# docs
site/
+apps/*/coverage
\ No newline at end of file
diff --git a/_regroup/bin/create-anonymization-script.ts b/_regroup/bin/create-anonymization-script.ts
deleted file mode 100644
index ff462ec5e..000000000
--- a/_regroup/bin/create-anonymization-script.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env node
-
-import anonymizationService from "../src/services/anonymization.js";
-import fs from "fs";
-import path from "path";
-
-fs.writeFileSync(path.resolve(__dirname, "tpl", "anonymize-database.sql"), anonymizationService.getFullAnonymizationScript());
diff --git a/_regroup/bin/export-schema.sh b/_regroup/bin/export-schema.sh
deleted file mode 100644
index ab5de1a81..000000000
--- a/_regroup/bin/export-schema.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-SCHEMA_FILE_PATH=db/schema.sql
-
-sqlite3 ./data/document.db .schema | grep -v "sqlite_sequence" > "$SCHEMA_FILE_PATH"
-
-echo "DB schema exported to $SCHEMA_FILE_PATH"
\ No newline at end of file
diff --git a/_regroup/bin/push-docker-image.sh b/_regroup/bin/push-docker-image.sh
deleted file mode 100644
index 0372143cf..000000000
--- a/_regroup/bin/push-docker-image.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ $# -eq 0 ]] ; then
- echo "Missing argument of new version"
- exit 1
-fi
-
-VERSION=$1
-SERIES=${VERSION:0:4}-latest
-
-docker push zadam/trilium:$VERSION
-docker push zadam/trilium:$SERIES
-
-if [[ $1 != *"beta"* ]]; then
- docker push zadam/trilium:latest
-fi
diff --git a/_regroup/bin/release-flatpack.sh b/_regroup/bin/release-flatpack.sh
deleted file mode 100644
index 31e42881b..000000000
--- a/_regroup/bin/release-flatpack.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ $# -eq 0 ]] ; then
- echo "Missing argument of new version"
- exit 1
-fi
-
-VERSION=$1
-
-if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
-then
- echo "Version ${VERSION} isn't in format X.Y.Z"
- exit 1
-fi
-
-VERSION_DATE=$(git log -1 --format=%aI "v${VERSION}" | cut -c -10)
-VERSION_COMMIT=$(git rev-list -n 1 "v${VERSION}")
-
-# expecting the directory at a specific path
-cd ~/trilium-flathub || exit
-
-if ! git diff-index --quiet HEAD --; then
- echo "There are uncommitted changes"
- exit 1
-fi
-
-BASE_BRANCH=main
-
-if [[ "$VERSION" == *"beta"* ]]; then
- BASE_BRANCH=beta
-fi
-
-git switch "${BASE_BRANCH}"
-git pull
-
-BRANCH=b${VERSION}
-
-git branch "${BRANCH}"
-git switch "${BRANCH}"
-
-echo "Updating files with version ${VERSION}, date ${VERSION_DATE} and commit ${VERSION_COMMIT}"
-
-flatpak-node-generator npm ../trilium/package-lock.json
-
-xmlstarlet ed --inplace --update "/component/releases/release/@version" --value "${VERSION}" --update "/component/releases/release/@date" --value "${VERSION_DATE}" ./com.github.zadam.trilium.metainfo.xml
-
-yq --inplace "(.modules[0].sources[0].tag = \"v${VERSION}\") | (.modules[0].sources[0].commit = \"${VERSION_COMMIT}\")" ./com.github.zadam.trilium.yml
-
-git add ./generated-sources.json
-git add ./com.github.zadam.trilium.metainfo.xml
-git add ./com.github.zadam.trilium.yml
-
-git commit -m "release $VERSION"
-git push --set-upstream origin "${BRANCH}"
-
-gh pr create --fill -B "${BASE_BRANCH}"
-gh pr merge --auto --merge --delete-branch
diff --git a/_regroup/bin/release.sh b/_regroup/bin/release.sh
deleted file mode 100644
index fe9a65a36..000000000
--- a/_regroup/bin/release.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-if [[ $# -eq 0 ]] ; then
- echo "Missing argument of new version"
- exit 1
-fi
-
-if ! command -v jq &> /dev/null; then
- echo "Missing command: jq"
- exit 1
-fi
-
-VERSION=$1
-
-if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
-then
- echo "Version ${VERSION} isn't in format X.Y.Z"
- exit 1
-fi
-
-if ! git diff-index --quiet HEAD --; then
- echo "There are uncommitted changes"
- exit 1
-fi
-
-echo "Releasing Trilium $VERSION"
-
-jq '.version = "'$VERSION'"' package.json > package.json.tmp
-mv package.json.tmp package.json
-
-git add package.json
-
-npm run chore:update-build-info
-
-git add src/services/build.ts
-
-TAG=v$VERSION
-
-echo "Committing package.json version change"
-
-git commit -m "chore(release): $VERSION"
-git push
-
-echo "Tagging commit with $TAG"
-
-git tag $TAG
-git push origin $TAG
diff --git a/_regroup/entitlements.plist b/_regroup/entitlements.plist
deleted file mode 100644
index 040a4c1cb..000000000
--- a/_regroup/entitlements.plist
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- com.apple.security.cs.allow-jit
-
- com.apple.security.files.user-selected.read-write
-
-
-
\ No newline at end of file
diff --git a/_regroup/eslint.config.js b/_regroup/eslint.config.js
deleted file mode 100644
index 7c906beb2..000000000
--- a/_regroup/eslint.config.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import eslint from "@eslint/js";
-import tseslint from "typescript-eslint";
-import simpleImportSort from "eslint-plugin-simple-import-sort";
-
-export default tseslint.config(
- eslint.configs.recommended,
- tseslint.configs.recommended,
- // consider using rules below, once we have a full TS codebase and can be more strict
- // tseslint.configs.strictTypeChecked,
- // tseslint.configs.stylisticTypeChecked,
- // tseslint.configs.recommendedTypeChecked,
- {
- languageOptions: {
- parserOptions: {
- projectService: true,
- tsconfigRootDir: import.meta.dirname
- }
- }
- },
- {
- plugins: {
- "simple-import-sort": simpleImportSort
- }
- },
- {
- rules: {
- // add rule overrides here
- "no-undef": "off",
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": [
- "error",
- {
- argsIgnorePattern: "^_",
- varsIgnorePattern: "^_"
- }
- ],
- "simple-import-sort/imports": "error",
- "simple-import-sort/exports": "error"
- }
- },
- {
- ignores: [
- "build/*",
- "dist/*",
- "docs/*",
- "demo/*",
- "src/public/app-dist/*",
- "src/public/app/doc_notes/*"
- ]
- }
-);
diff --git a/_regroup/eslint.format.config.js b/_regroup/eslint.format.config.js
deleted file mode 100644
index 9dbfd78b2..000000000
--- a/_regroup/eslint.format.config.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import stylistic from "@stylistic/eslint-plugin";
-import tsParser from "@typescript-eslint/parser";
-
-// eslint config just for formatting rules
-// potentially to be merged with the linting rules into one single config,
-// once we have fixed the majority of lint errors
-
-// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
-export const stylisticRules = {
- "@stylistic/indent": [ "error", 4 ],
- "@stylistic/quotes": [ "error", "double", { avoidEscape: true, allowTemplateLiterals: "always" } ],
- "@stylistic/semi": [ "error", "always" ],
- "@stylistic/quote-props": [ "error", "consistent-as-needed" ],
- "@stylistic/max-len": [ "error", { code: 100 } ],
- "@stylistic/comma-dangle": [ "error", "never" ],
- "@stylistic/linebreak-style": [ "error", "unix" ],
- "@stylistic/array-bracket-spacing": [ "error", "always" ],
- "@stylistic/object-curly-spacing": [ "error", "always" ],
- "@stylistic/padded-blocks": [ "error", { classes: "always" } ]
-};
-
-export default [
- {
- files: [ "**/*.{js,ts,mjs,cjs}" ],
- languageOptions: {
- parser: tsParser
- },
- plugins: {
- "@stylistic": stylistic
- },
- rules: {
- ...stylisticRules
- }
- },
- {
- ignores: [
- "build/*",
- "dist/*",
- "docs/*",
- "demo/*",
- // TriliumNextTODO: check if we want to format packages here as well - for now skipping it
- "packages/*",
- "src/public/app-dist/*",
- "src/public/app/doc_notes/*"
- ]
- }
-];
diff --git a/_regroup/integration-tests/auth.setup.ts b/_regroup/integration-tests/auth.setup.ts
deleted file mode 100644
index 9b31eec49..000000000
--- a/_regroup/integration-tests/auth.setup.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { test as setup, expect } from "@playwright/test";
-
-const authFile = "playwright/.auth/user.json";
-
-const ROOT_URL = "http://localhost:8082";
-const LOGIN_PASSWORD = "demo1234";
-
-// Reference: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests
-
-setup("authenticate", async ({ page }) => {
- await page.goto(ROOT_URL);
- await expect(page).toHaveURL(`${ROOT_URL}/login`);
-
- await page.getByRole("textbox", { name: "Password" }).fill(LOGIN_PASSWORD);
- await page.getByRole("button", { name: "Login" }).click();
- await page.context().storageState({ path: authFile });
-});
diff --git a/_regroup/integration-tests/duplicate.spec.ts b/_regroup/integration-tests/duplicate.spec.ts
deleted file mode 100644
index fe079952b..000000000
--- a/_regroup/integration-tests/duplicate.spec.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { test, expect } from "@playwright/test";
-
-test("Can duplicate note with broken links", async ({ page }) => {
- await page.goto(`http://localhost:8082/#2VammGGdG6Ie`);
- await page.locator(".tree-wrapper .fancytree-active").getByText("Note map").click({ button: "right" });
- await page.getByText("Duplicate subtree").click();
- await expect(page.locator(".toast-body")).toBeHidden();
- await expect(page.locator(".tree-wrapper").getByText("Note map (dup)")).toBeVisible();
-});
diff --git a/_regroup/integration-tests/example.disabled.ts b/_regroup/integration-tests/example.disabled.ts
deleted file mode 100644
index a149fe328..000000000
--- a/_regroup/integration-tests/example.disabled.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { test, expect } from "@playwright/test";
-
-test("has title", async ({ page }) => {
- await page.goto("https://playwright.dev/");
-
- // Expect a title "to contain" a substring.
- await expect(page).toHaveTitle(/Playwright/);
-});
-
-test("get started link", async ({ page }) => {
- await page.goto("https://playwright.dev/");
-
- // Click the get started link.
- await page.getByRole("link", { name: "Get started" }).click();
-
- // Expects page to have a heading with the name of Installation.
- await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible();
-});
diff --git a/_regroup/integration-tests/settings.spec.ts b/_regroup/integration-tests/settings.spec.ts
deleted file mode 100644
index b3fe16fda..000000000
--- a/_regroup/integration-tests/settings.spec.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import test, { expect } from "@playwright/test";
-
-test("Native Title Bar not displayed on web", async ({ page }) => {
- await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsAppearance");
- await expect(page.getByRole("heading", { name: "Theme" })).toBeVisible();
- await expect(page.getByRole("heading", { name: "Native Title Bar (requires" })).toBeHidden();
-});
-
-test("Tray settings not displayed on web", async ({ page }) => {
- await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsOther");
- await expect(page.getByRole("heading", { name: "Note Erasure Timeout" })).toBeVisible();
- await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
-});
-
-test("Spellcheck settings not displayed on web", async ({ page }) => {
- await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsSpellcheck");
- await expect(page.getByRole("heading", { name: "Spell Check" })).toBeVisible();
- await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
- await expect(page.getByText("These options apply only for desktop builds")).toBeVisible();
- await expect(page.getByText("Enable spellcheck")).toBeHidden();
-});
diff --git a/_regroup/integration-tests/tree.spec.ts b/_regroup/integration-tests/tree.spec.ts
deleted file mode 100644
index 257375fa8..000000000
--- a/_regroup/integration-tests/tree.spec.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import test, { expect } from "@playwright/test";
-
-test("Renders on desktop", async ({ page, context }) => {
- await page.goto("http://localhost:8082");
- await expect(page.locator(".tree")).toContainText("Trilium Integration Test");
-});
-
-test("Renders 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(".tree")).toContainText("Trilium Integration Test");
-});
diff --git a/_regroup/integration-tests/update_check.spec.ts b/_regroup/integration-tests/update_check.spec.ts
deleted file mode 100644
index 38e28bf22..000000000
--- a/_regroup/integration-tests/update_check.spec.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { test, expect } from "@playwright/test";
-
-const expectedVersion = "0.90.3";
-
-test("Displays update badge when there is a version available", async ({ page }) => {
- await page.goto("http://localhost:8080");
- await page.getByRole("button", { name: "" }).click();
- await page.getByText(`Version ${expectedVersion} is available,`).click();
-
- const page1 = await page.waitForEvent("popup");
- expect(page1.url()).toBe(`https://github.com/TriliumNext/Trilium/releases/tag/v${expectedVersion}`);
-});
diff --git a/_regroup/package.json b/_regroup/package.json
deleted file mode 100644
index 39d277064..000000000
--- a/_regroup/package.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "main": "./electron-main.js",
- "bin": {
- "trilium": "src/main.js"
- },
- "type": "module",
- "scripts": {
- "server:start-safe": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nodemon src/main.ts",
- "server:start-no-dir": "cross-env TRILIUM_ENV=dev nodemon src/main.ts",
- "server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
- "server:qstart": "npm run server:switch && npm run server:start",
- "server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
- "electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 electron --inspect=5858 .",
- "electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
- "electron:start-nix-no-dir": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
- "electron:start-prod-no-dir": "npm run build:prepare-dist && cross-env TRILIUM_ENV=prod electron --inspect=5858 .",
- "electron:start-prod-nix": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
- "electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
- "electron:qstart": "npm run electron:switch && npm run electron:start",
- "electron:switch": "electron-rebuild",
- "docs:build": "typedoc",
- "test": "npm run client:test && npm run server:test",
- "client:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app",
- "client:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app --coverage",
- "test:playwright": "playwright test --workers 1",
- "test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
- "test:integration-mem-db": "cross-env nodemon src/main.ts",
- "test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
- "dev:watch-dist": "tsx ./bin/watch-dist.ts",
- "dev:format-check": "eslint -c eslint.format.config.js .",
- "dev:format-fix": "eslint -c eslint.format.config.js . --fix",
- "dev:linter-check": "eslint .",
- "dev:linter-fix": "eslint . --fix",
- "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
- "chore:generate-openapi": "tsx bin/generate-openapi.js"
- },
- "devDependencies": {
- "@playwright/test": "1.57.0",
- "@stylistic/eslint-plugin": "5.6.1",
- "@types/express": "5.0.6",
- "@types/node": "24.10.1",
- "@types/yargs": "17.0.35",
- "@vitest/coverage-v8": "4.0.15",
- "eslint": "9.39.1",
- "eslint-plugin-simple-import-sort": "12.1.1",
- "esm": "3.2.25",
- "jsdoc": "4.0.5",
- "lorem-ipsum": "2.0.8",
- "rcedit": "5.0.2",
- "rimraf": "6.1.2",
- "tslib": "2.8.1"
- },
- "optionalDependencies": {
- "appdmg": "0.6.6"
- }
-}
diff --git a/_regroup/spec/etapi/app_info.ts b/_regroup/spec/etapi/app_info.ts
deleted file mode 100644
index 9c510d99b..000000000
--- a/_regroup/spec/etapi/app_info.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import etapi from "../support/etapi.js";
-/* TriliumNextTODO: port to Vitest
-etapi.describeEtapi("app_info", () => {
- it("get", async () => {
- const appInfo = await etapi.getEtapi("app-info");
- expect(appInfo.clipperProtocolVersion).toEqual("1.0");
- });
-});
-*/
diff --git a/_regroup/spec/etapi/backup.ts b/_regroup/spec/etapi/backup.ts
deleted file mode 100644
index 924213f0e..000000000
--- a/_regroup/spec/etapi/backup.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import etapi from "../support/etapi.js";
-
-/* TriliumNextTODO: port to Vitest
-etapi.describeEtapi("backup", () => {
- it("create", async () => {
- const response = await etapi.putEtapiContent("backup/etapi_test");
- expect(response.status).toEqual(204);
- });
-});
-*/
diff --git a/_regroup/spec/etapi/import.ts b/_regroup/spec/etapi/import.ts
deleted file mode 100644
index 36782a26a..000000000
--- a/_regroup/spec/etapi/import.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import etapi from "../support/etapi.js";
-import fs from "fs";
-import path from "path";
-import { fileURLToPath } from "url";
-
-/* TriliumNextTODO: port to Vitest
-etapi.describeEtapi("import", () => {
- // temporarily skip this test since test-export.zip is missing
- xit("import", async () => {
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
-
- const zipFileBuffer = fs.readFileSync(path.resolve(scriptDir, "test-export.zip"));
-
- const response = await etapi.postEtapiContent("notes/root/import", zipFileBuffer);
- expect(response.status).toEqual(201);
-
- const { note, branch } = await response.json();
-
- expect(note.title).toEqual("test-export");
- expect(branch.parentNoteId).toEqual("root");
-
- const content = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
- expect(content).toContain("test export content");
- });
-});
-*/
diff --git a/_regroup/spec/etapi/notes.ts b/_regroup/spec/etapi/notes.ts
deleted file mode 100644
index d5c9b680c..000000000
--- a/_regroup/spec/etapi/notes.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import crypto from "crypto";
-import etapi from "../support/etapi.js";
-
-/* TriliumNextTODO: port to Vitest
-etapi.describeEtapi("notes", () => {
- it("create", async () => {
- const { note, branch } = await etapi.postEtapi("create-note", {
- parentNoteId: "root",
- type: "text",
- title: "Hello World!",
- content: "Content",
- prefix: "Custom prefix"
- });
-
- expect(note.title).toEqual("Hello World!");
- expect(branch.parentNoteId).toEqual("root");
- expect(branch.prefix).toEqual("Custom prefix");
-
- const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
- expect(rNote.title).toEqual("Hello World!");
-
- const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
- expect(rContent).toEqual("Content");
-
- const rBranch = await etapi.getEtapi(`branches/${branch.branchId}`);
- expect(rBranch.parentNoteId).toEqual("root");
- expect(rBranch.prefix).toEqual("Custom prefix");
- });
-
- it("patch", async () => {
- const { note } = await etapi.postEtapi("create-note", {
- parentNoteId: "root",
- type: "text",
- title: "Hello World!",
- content: "Content"
- });
-
- await etapi.patchEtapi(`notes/${note.noteId}`, {
- title: "new title",
- type: "code",
- mime: "text/apl",
- dateCreated: "2000-01-01 12:34:56.999+0200",
- utcDateCreated: "2000-01-01 10:34:56.999Z"
- });
-
- const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
- expect(rNote.title).toEqual("new title");
- expect(rNote.type).toEqual("code");
- expect(rNote.mime).toEqual("text/apl");
- expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200");
- expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z");
- });
-
- it("update content", async () => {
- const { note } = await etapi.postEtapi("create-note", {
- parentNoteId: "root",
- type: "text",
- title: "Hello World!",
- content: "Content"
- });
-
- await etapi.putEtapiContent(`notes/${note.noteId}/content`, "new content");
-
- const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
- expect(rContent).toEqual("new content");
- });
-
- it("create / update binary content", async () => {
- const { note } = await etapi.postEtapi("create-note", {
- parentNoteId: "root",
- type: "file",
- title: "Hello World!",
- content: "ZZZ"
- });
-
- const updatedContent = crypto.randomBytes(16);
-
- await etapi.putEtapiContent(`notes/${note.noteId}/content`, updatedContent);
-
- const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer();
- expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent);
- });
-
- it("delete note", async () => {
- const { note } = await etapi.postEtapi("create-note", {
- parentNoteId: "root",
- type: "text",
- title: "Hello World!",
- content: "Content"
- });
-
- await etapi.deleteEtapi(`notes/${note.noteId}`);
-
- const resp = await etapi.getEtapiResponse(`notes/${note.noteId}`);
- expect(resp.status).toEqual(404);
-
- const error = await resp.json();
- expect(error.status).toEqual(404);
- expect(error.code).toEqual("NOTE_NOT_FOUND");
- expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
- });
-});
-*/
diff --git a/_regroup/spec/support/etapi.ts b/_regroup/spec/support/etapi.ts
deleted file mode 100644
index b32ba38e7..000000000
--- a/_regroup/spec/support/etapi.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { describe, beforeAll, afterAll } from "vitest";
-
-let etapiAuthToken: string | undefined;
-
-const getEtapiAuthorizationHeader = (): string => "Basic " + Buffer.from(`etapi:${etapiAuthToken}`).toString("base64");
-
-const PORT: string = "9999";
-const HOST: string = "http://localhost:" + PORT;
-
-type SpecDefinitionsFunc = () => void;
-
-function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void {
- describe(description, () => {
- beforeAll(async () => {});
-
- afterAll(() => {});
-
- specDefinitions();
- });
-}
-
-async function getEtapiResponse(url: string): Promise {
- return await fetch(`${HOST}/etapi/${url}`, {
- method: "GET",
- headers: {
- Authorization: getEtapiAuthorizationHeader()
- }
- });
-}
-
-async function getEtapi(url: string): Promise {
- const response = await getEtapiResponse(url);
- return await processEtapiResponse(response);
-}
-
-async function getEtapiContent(url: string): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "GET",
- headers: {
- Authorization: getEtapiAuthorizationHeader()
- }
- });
-
- checkStatus(response);
-
- return response;
-}
-
-async function postEtapi(url: string, data: Record = {}): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: getEtapiAuthorizationHeader()
- },
- body: JSON.stringify(data)
- });
- return await processEtapiResponse(response);
-}
-
-async function postEtapiContent(url: string, data: BodyInit): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/octet-stream",
- Authorization: getEtapiAuthorizationHeader()
- },
- body: data
- });
-
- checkStatus(response);
-
- return response;
-}
-
-async function putEtapi(url: string, data: Record = {}): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- Authorization: getEtapiAuthorizationHeader()
- },
- body: JSON.stringify(data)
- });
- return await processEtapiResponse(response);
-}
-
-async function putEtapiContent(url: string, data?: BodyInit): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "PUT",
- headers: {
- "Content-Type": "application/octet-stream",
- Authorization: getEtapiAuthorizationHeader()
- },
- body: data
- });
-
- checkStatus(response);
-
- return response;
-}
-
-async function patchEtapi(url: string, data: Record = {}): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: getEtapiAuthorizationHeader()
- },
- body: JSON.stringify(data)
- });
- return await processEtapiResponse(response);
-}
-
-async function deleteEtapi(url: string): Promise {
- const response = await fetch(`${HOST}/etapi/${url}`, {
- method: "DELETE",
- headers: {
- Authorization: getEtapiAuthorizationHeader()
- }
- });
- return await processEtapiResponse(response);
-}
-
-async function processEtapiResponse(response: Response): Promise {
- const text = await response.text();
-
- if (response.status < 200 || response.status >= 300) {
- throw new Error(`ETAPI error ${response.status}: ${text}`);
- }
-
- return text?.trim() ? JSON.parse(text) : null;
-}
-
-function checkStatus(response: Response): void {
- if (response.status < 200 || response.status >= 300) {
- throw new Error(`ETAPI error ${response.status}`);
- }
-}
-
-export default {
- describeEtapi,
- getEtapi,
- getEtapiResponse,
- getEtapiContent,
- postEtapi,
- postEtapiContent,
- putEtapi,
- putEtapiContent,
- patchEtapi,
- deleteEtapi
-};
diff --git a/_regroup/tsconfig.webpack.json b/_regroup/tsconfig.webpack.json
deleted file mode 100644
index ed622818b..000000000
--- a/_regroup/tsconfig.webpack.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "compilerOptions": {
- "module": "NodeNext",
- "declaration": false,
- "sourceMap": true,
- "outDir": "./build",
- "strict": true,
- "noImplicitAny": true,
- "resolveJsonModule": true,
- "lib": ["ES2023"],
- "downlevelIteration": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowJs": true
- },
- "include": ["./src/public/app/**/*"],
- "files": [
- "./src/public/app/types.d.ts",
- "./src/public/app/types-lib.d.ts",
- "./src/types.d.ts"
- ]
-}
diff --git a/apps/client/eslint.config.mjs b/apps/client/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/client/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/client/package.json b/apps/client/package.json
index 7215a3f35..91eaad1bc 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -12,10 +12,10 @@
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"test": "vitest",
+ "coverage": "vitest --coverage",
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
- "@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
diff --git a/apps/db-compare/eslint.config.mjs b/apps/db-compare/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/db-compare/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/desktop/eslint.config.mjs b/apps/desktop/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/desktop/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/dump-db/eslint.config.mjs b/apps/dump-db/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/dump-db/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/edit-docs/eslint.config.mjs b/apps/edit-docs/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/edit-docs/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/server-e2e/eslint.config.mjs b/apps/server-e2e/eslint.config.mjs
deleted file mode 100644
index 1603594d7..000000000
--- a/apps/server-e2e/eslint.config.mjs
+++ /dev/null
@@ -1,15 +0,0 @@
-import playwright from "eslint-plugin-playwright";
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- playwright.configs["flat/recommended"],
- ...baseConfig,
- {
- files: [
- "**/*.ts",
- "**/*.js"
- ],
- // Override or add rules here
- rules: {}
- }
-];
diff --git a/apps/server-e2e/src/duplicate.spec.ts b/apps/server-e2e/src/duplicate.spec.ts
new file mode 100644
index 000000000..56c170960
--- /dev/null
+++ b/apps/server-e2e/src/duplicate.spec.ts
@@ -0,0 +1,14 @@
+import { test, expect } from "@playwright/test";
+import App from "./support/app";
+
+test("Can duplicate note with broken links", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto({
+ url: "http://localhost:8082/#root/Q5abPvymDH6C/2VammGGdG6Ie"
+ });
+
+ await app.noteTree.getByText("Note map").first().click({ button: "right" });
+ await page.locator("#context-menu-container").getByText("Duplicate").click();
+ await expect(page.locator(".toast-body")).toBeHidden();
+ await expect(app.noteTree.getByText("Note map (dup)")).toBeVisible();
+});
diff --git a/apps/server-e2e/src/settings.spec.ts b/apps/server-e2e/src/settings.spec.ts
new file mode 100644
index 000000000..28210e57c
--- /dev/null
+++ b/apps/server-e2e/src/settings.spec.ts
@@ -0,0 +1,25 @@
+import test, { expect } from "@playwright/test";
+import App from "./support/app";
+
+test("Native Title Bar not displayed on web", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto({ url: "http://localhost:8082/#root/_hidden/_options/_optionsAppearance" });
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Theme" })).toBeVisible();
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Native Title Bar (requires" })).toBeHidden();
+});
+
+test("Tray settings not displayed on web", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto({ url: "http://localhost:8082/#root/_hidden/_options/_optionsOther" });
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Note Erasure Timeout" })).toBeVisible();
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Tray" })).toBeHidden();
+});
+
+test("Spellcheck settings not displayed on web", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto({ url: "http://localhost:8082/#root/_hidden/_options/_optionsSpellcheck" });
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Spell Check" })).toBeVisible();
+ await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Tray" })).toBeHidden();
+ await expect(app.currentNoteSplitContent.getByText("These options apply only for desktop builds")).toBeVisible();
+ await expect(app.currentNoteSplitContent.getByText("Enable spellcheck")).toBeHidden();
+});
diff --git a/apps/server-e2e/src/tree.spec.ts b/apps/server-e2e/src/tree.spec.ts
new file mode 100644
index 000000000..f1ca8f88f
--- /dev/null
+++ b/apps/server-e2e/src/tree.spec.ts
@@ -0,0 +1,14 @@
+import test, { expect } from "@playwright/test";
+import App from "./support/app";
+
+test("Renders on desktop", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto();
+ await expect(app.noteTree).toContainText("Trilium Integration Test");
+});
+
+test("Renders on mobile", async ({ page, context }) => {
+ const app = new App(page, context);
+ await app.goto({ isMobile: true });
+ await expect(app.noteTree).toContainText("Trilium Integration Test");
+});
diff --git a/apps/server/eslint.config.mjs b/apps/server/eslint.config.mjs
deleted file mode 100644
index 724052a2e..000000000
--- a/apps/server/eslint.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import baseConfig from "../../eslint.config.mjs";
-
-export default [
- ...baseConfig
-];
diff --git a/apps/server/package.json b/apps/server/package.json
index 48a9cdea8..20f6e1fe9 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -11,6 +11,7 @@
"build": "tsx scripts/build.ts",
"package": "pnpm build && bash scripts/build-server.sh",
"test": "vitest",
+ "coverage": "vitest --coverage",
"test-build": "vitest --config vitest.build.config.mts",
"start-prod": "cross-env TRILIUM_DATA_DIR=data pnpm start-prod-no-dir",
"start-prod-no-dir": "pnpm build && cross-env TRILIUM_ENV=production TRILIUM_PORT=8082 node dist/main.cjs",
@@ -22,7 +23,8 @@
"docker-start-debian": "pnpm docker-build-debian && docker run -p 8081:8080 triliumnext-debian",
"docker-start-alpine": "pnpm docker-build-alpine && docker run -p 8081:8080 triliumnext-alpine",
"docker-start-rootless-debian": "pnpm docker-build-rootless-debian && docker run -p 8081:8080 triliumnext-rootless-debian",
- "docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine"
+ "docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine",
+ "generate-document": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx ./scripts/generate_document.ts"
},
"dependencies": {
"better-sqlite3": "12.5.0",
@@ -102,6 +104,7 @@
"is-animated": "2.0.2",
"is-svg": "6.1.0",
"jimp": "1.6.0",
+ "lorem-ipsum": "2.0.8",
"marked": "17.0.1",
"mime-types": "3.0.2",
"multer": "2.0.2",
diff --git a/apps/server/scripts/export-schema.sh b/apps/server/scripts/export-schema.sh
new file mode 100755
index 000000000..c93adee0c
--- /dev/null
+++ b/apps/server/scripts/export-schema.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$(realpath $(dirname $0))
+DB_FILE_PATH="$SCRIPT_DIR/../data/document.db"
+SCHEMA_FILE_PATH="$SCRIPT_DIR/../src/assets/db/schema.sql"
+
+if ! command -v sqlite3 &> /dev/null; then
+ echo "Missing command: sqlite3"
+ exit 1
+fi
+
+sqlite3 "$DB_FILE_PATH" .schema | grep -v "sqlite_sequence" > "$SCHEMA_FILE_PATH"
+
+echo "DB schema exported to $SCHEMA_FILE_PATH"
\ No newline at end of file
diff --git a/_regroup/bin/generate-cert.sh b/apps/server/scripts/generate-cert.sh
similarity index 100%
rename from _regroup/bin/generate-cert.sh
rename to apps/server/scripts/generate-cert.sh
diff --git a/_regroup/bin/generate_document.ts b/apps/server/scripts/generate_document.ts
similarity index 79%
rename from _regroup/bin/generate_document.ts
rename to apps/server/scripts/generate_document.ts
index 793efc105..acfd9d512 100644
--- a/_regroup/bin/generate_document.ts
+++ b/apps/server/scripts/generate_document.ts
@@ -6,10 +6,11 @@
import sqlInit from "../src/services/sql_init.js";
import noteService from "../src/services/notes.js";
import attributeService from "../src/services/attributes.js";
-import cls from "../src/services/cls.js";
import cloningService from "../src/services/cloning.js";
-import loremIpsum from "lorem-ipsum";
+import { loremIpsum } from "lorem-ipsum";
import "../src/becca/entity_constructor.js";
+import { initializeTranslations } from "../src/services/i18n.js";
+import cls from "../src/services/cls.js";
const noteCount = parseInt(process.argv[2]);
@@ -27,8 +28,18 @@ function getRandomNoteId() {
}
async function start() {
+ if (!sqlInit.isDbInitialized()) {
+ console.error("Database not initialized.");
+ process.exit(1);
+ }
+
+ await initializeTranslations();
+
+ sqlInit.initializeDb();
+ await sqlInit.dbReady;
+
for (let i = 0; i < noteCount; i++) {
- const title = loremIpsum.loremIpsum({
+ const title = loremIpsum({
count: 1,
units: "sentences",
sentenceLowerBound: 1,
@@ -36,7 +47,7 @@ async function start() {
});
const paragraphCount = Math.floor(Math.random() * Math.random() * 100);
- const content = loremIpsum.loremIpsum({
+ const content = loremIpsum({
count: paragraphCount,
units: "paragraphs",
sentenceLowerBound: 1,
@@ -60,13 +71,13 @@ async function start() {
const parentNoteId = getRandomNoteId();
const prefix = Math.random() > 0.8 ? "prefix" : "";
- const result = await cloningService.cloneNoteToBranch(noteIdToClone, parentNoteId, prefix);
+ const result = cloningService.cloneNoteToBranch(noteIdToClone, parentNoteId, prefix);
console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
}
// does not have to be for the current note
- await attributeService.createAttribute({
+ attributeService.createAttribute({
noteId: getRandomNoteId(),
type: "label",
name: "label",
@@ -74,7 +85,7 @@ async function start() {
isInheritable: Math.random() > 0.1 // 10% are inheritable
});
- await attributeService.createAttribute({
+ attributeService.createAttribute({
noteId: getRandomNoteId(),
type: "relation",
name: "relation",
@@ -90,6 +101,4 @@ async function start() {
process.exit(0);
}
-// @TriliumNextTODO sqlInit.dbReady never seems to resolve so program hangs
-// see https://github.com/TriliumNext/Trilium/issues/1020
-sqlInit.dbReady.then(cls.wrap(start)).catch((err) => console.error(err));
+cls.init(() => start());
diff --git a/apps/server/src/services/import/single.spec.ts b/apps/server/src/services/import/single.spec.ts
index 0d0af35f7..e8a341a6b 100644
--- a/apps/server/src/services/import/single.spec.ts
+++ b/apps/server/src/services/import/single.spec.ts
@@ -112,4 +112,4 @@ describe("processNoteContent", () => {
title: "New note"
});
});
-});
+}, 60_000);
diff --git a/apps/server/src/services/import/zip.spec.ts b/apps/server/src/services/import/zip.spec.ts
index a74c243c4..ecc070c6a 100644
--- a/apps/server/src/services/import/zip.spec.ts
+++ b/apps/server/src/services/import/zip.spec.ts
@@ -83,7 +83,7 @@ describe("processNoteContent", () => {
const content = attachment.getContent();
expect(content).toStrictEqual(`{"view":{"center":{"lat":49.19598332223546,"lng":-2.1414576506668808},"zoom":12}}`);
});
-});
+}, 60_000);
function getNoteByTitlePath(parentNote: BNote, ...titlePath: string[]) {
let cursor = parentNote;
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 000000000..5ae642a87
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,77 @@
+// @ts-check
+
+import eslint from '@eslint/js';
+import { defineConfig, globalIgnores } from 'eslint/config';
+import tseslint from 'typescript-eslint';
+import simpleImportSort from "eslint-plugin-simple-import-sort";
+import playwright from "eslint-plugin-playwright";
+import tsParser from "@typescript-eslint/parser";
+import preact from "eslint-config-preact";
+
+const mainConfig = [
+ ...preact,
+ eslint.configs.recommended,
+ tseslint.configs.recommended,
+ // consider using rules below, once we have a full TS codebase and can be more strict
+ // tseslint.configs.strictTypeChecked,
+ // tseslint.configs.stylisticTypeChecked,
+ // tseslint.configs.recommendedTypeChecked,
+ {
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname
+ }
+ }
+ },
+ {
+ rules: {
+ "no-undef": "off",
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_"
+ }
+ ]
+ }
+ },
+ {
+ plugins: {
+ "simple-import-sort": simpleImportSort
+ },
+
+ rules: {
+ "simple-import-sort/imports": "error",
+ "simple-import-sort/exports": "error"
+ }
+ }
+];
+
+const playwrightConfig = {
+ files: [
+ "apps/server-e2e/src/**/*.spec.ts",
+ "apps/desktop/e2e/**/*.spec.ts"
+ ],
+ plugins: { playwright },
+ // Override or add rules here
+ rules: { ...playwright.configs["flat/recommended"].rules, },
+ languageOptions: { parser: tsParser },
+};
+
+export default defineConfig(
+ globalIgnores([
+ ".cache",
+ "tmp",
+ "**/dist",
+ "**/out-tsc",
+ "apps/edit-docs/demo/*",
+ "docs/*",
+ "apps/web-clipper/lib/*",
+ // TODO: check if we want to format packages here as well - for now skipping it
+ "packages/*",
+ ]),
+ ...mainConfig,
+ playwrightConfig
+);
diff --git a/eslint.format.config.mjs b/eslint.format.config.mjs
new file mode 100644
index 000000000..b9591c3d3
--- /dev/null
+++ b/eslint.format.config.mjs
@@ -0,0 +1,51 @@
+// @ts-check
+
+import { defineConfig, globalIgnores } from 'eslint/config';
+import tsParser from "@typescript-eslint/parser";
+import stylistic from "@stylistic/eslint-plugin";
+
+// eslint config just for formatting rules
+// potentially to be merged with the linting rules into one single config,
+// once we have fixed the majority of lint errors
+
+// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
+export const stylisticRules = {
+ "@stylistic/indent": ["error", 4],
+ "@stylistic/quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: "always" }],
+ "@stylistic/semi": ["error", "always"],
+ "@stylistic/quote-props": ["error", "consistent-as-needed"],
+ "@stylistic/max-len": ["error", { code: 100 }],
+ "@stylistic/comma-dangle": ["error", "never"],
+ "@stylistic/linebreak-style": ["error", "unix"],
+ "@stylistic/array-bracket-spacing": ["error", "always"],
+ "@stylistic/object-curly-spacing": ["error", "always"],
+ "@stylistic/padded-blocks": ["error", { classes: "always" }]
+};
+
+export default defineConfig(
+ globalIgnores([
+ ".cache",
+ "tmp",
+ "**/dist",
+ "**/out-tsc",
+ "apps/edit-docs/demo/*",
+ "docs/*",
+ "apps/web-clipper/lib/*"
+ ]),
+
+ {
+ files: ["**/*.{js,ts,mjs,cjs}"],
+
+ languageOptions: {
+ parser: tsParser
+ },
+
+ plugins: {
+ "@stylistic": stylistic
+ },
+
+ rules: {
+ ...stylisticRules
+ }
+ }
+);
\ No newline at end of file
diff --git a/package.json b/package.json
index 0384ae8f4..9164e6182 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,10 @@
"test:parallel": "pnpm --filter=!server --filter=!ckeditor5-mermaid --filter=!ckeditor5-math --parallel test",
"test:sequential": "pnpm --filter=server --filter=ckeditor5-mermaid --filter=ckeditor5-math --sequential test",
"typecheck": "tsc --build",
+ "dev:format-check": "eslint -c eslint.format.config.mjs .",
+ "dev:format-fix": "eslint -c eslint.format.config.mjs . --fix",
+ "dev:linter-check": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint .",
+ "dev:linter-fix": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint . --fix",
"postinstall": "tsx scripts/electron-rebuild.mts"
},
"private": true,
@@ -52,9 +56,10 @@
"dpdm": "3.14.0",
"esbuild": "0.27.1",
"eslint": "9.39.1",
+ "eslint-config-preact": "2.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-playwright": "2.4.0",
- "eslint-plugin-react-hooks": "7.0.1",
+ "eslint-plugin-simple-import-sort": "12.1.1",
"happy-dom": "~20.0.0",
"http-server": "14.1.1",
"jiti": "2.6.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c937e8283..79a222b34 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -76,15 +76,18 @@ importers:
eslint:
specifier: 9.39.1
version: 9.39.1(jiti@2.6.1)
+ eslint-config-preact:
+ specifier: 2.0.0
+ version: 2.0.0(eslint@9.39.1(jiti@2.6.1))
eslint-config-prettier:
specifier: 10.1.8
version: 10.1.8(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-playwright:
specifier: 2.4.0
version: 2.4.0(eslint@9.39.1(jiti@2.6.1))
- eslint-plugin-react-hooks:
- specifier: 7.0.1
- version: 7.0.1(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-simple-import-sort:
+ specifier: 12.1.1
+ version: 12.1.1(eslint@9.39.1(jiti@2.6.1))
happy-dom:
specifier: ~20.0.0
version: 20.0.11
@@ -154,9 +157,6 @@ importers:
apps/client:
dependencies:
- '@eslint/js':
- specifier: 9.39.1
- version: 9.39.1
'@excalidraw/excalidraw':
specifier: 0.18.0
version: 0.18.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
@@ -705,6 +705,9 @@ importers:
jimp:
specifier: 1.6.0
version: 1.6.0
+ lorem-ipsum:
+ specifier: 2.0.8
+ version: 2.0.8
marked:
specifier: 17.0.1
version: 17.0.1
@@ -7960,18 +7963,17 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
- eslint-plugin-react-hooks@7.0.1:
- resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
- engines: {node: '>=18'}
- peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
-
eslint-plugin-react@7.37.5:
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
engines: {node: '>=4'}
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+ eslint-plugin-simple-import-sort@12.1.1:
+ resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==}
+ peerDependencies:
+ eslint: '>=5.0.0'
+
eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@@ -8752,12 +8754,6 @@ packages:
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
engines: {node: '>=18.0.0'}
- hermes-estree@0.25.1:
- resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
-
- hermes-parser@0.25.1:
- resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
-
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
@@ -9887,6 +9883,11 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lorem-ipsum@2.0.8:
+ resolution: {integrity: sha512-5RIwHuCb979RASgCJH0VKERn9cQo/+NcAi2BMe9ddj+gp7hujl6BI+qdOG4nVsLDpwWEJwTVYXNKP6BGgbcoGA==}
+ engines: {node: '>= 8.x', npm: '>= 5.x'}
+ hasBin: true
+
lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'}
@@ -14424,12 +14425,6 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
- zod-validation-error@3.5.3:
- resolution: {integrity: sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==}
- engines: {node: '>=18.0.0'}
- peerDependencies:
- zod: ^3.25.0 || ^4.0.0
-
zod@3.24.4:
resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==}
@@ -15197,6 +15192,8 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
+ transitivePeerDependencies:
+ - supports-color
'@ckeditor/ckeditor5-block-quote@47.3.0':
dependencies:
@@ -15207,6 +15204,8 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
+ transitivePeerDependencies:
+ - supports-color
'@ckeditor/ckeditor5-bookmark@47.3.0':
dependencies:
@@ -15336,8 +15335,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-watchdog': 47.3.0
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@@ -15464,8 +15461,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-editor-classic@47.3.0':
dependencies:
@@ -15493,6 +15488,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
+ transitivePeerDependencies:
+ - supports-color
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
dependencies:
@@ -23535,17 +23532,6 @@ snapshots:
dependencies:
eslint: 9.39.1(jiti@2.6.1)
- eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)):
- dependencies:
- '@babel/core': 7.28.0
- '@babel/parser': 7.28.4
- eslint: 9.39.1(jiti@2.6.1)
- hermes-parser: 0.25.1
- zod: 4.1.12
- zod-validation-error: 3.5.3(zod@4.1.12)
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)):
dependencies:
array-includes: 3.1.9
@@ -23568,6 +23554,10 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
+ eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.1(jiti@2.6.1)):
+ dependencies:
+ eslint: 9.39.1(jiti@2.6.1)
+
eslint-scope@5.1.1:
dependencies:
esrecurse: 4.3.0
@@ -24603,12 +24593,6 @@ snapshots:
helmet@8.1.0: {}
- hermes-estree@0.25.1: {}
-
- hermes-parser@0.25.1:
- dependencies:
- hermes-estree: 0.25.1
-
highlight.js@11.11.1: {}
highlightjs-cobol@0.3.3:
@@ -25831,6 +25815,10 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lorem-ipsum@2.0.8:
+ dependencies:
+ commander: 9.5.0
+
lowercase-keys@2.0.0: {}
lru-cache@10.4.3: {}
@@ -31311,13 +31299,10 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.7.0
- zod-validation-error@3.5.3(zod@4.1.12):
- dependencies:
- zod: 4.1.12
-
zod@3.24.4: {}
- zod@4.1.12: {}
+ zod@4.1.12:
+ optional: true
zustand@4.5.6(@types/react@19.1.7)(react@19.2.1):
dependencies:
diff --git a/_regroup/bin/create-icons.sh b/scripts/icons/create-icons.sh
similarity index 100%
rename from _regroup/bin/create-icons.sh
rename to scripts/icons/create-icons.sh
diff --git a/_regroup/bin/tray-icons/bookmarks.svg b/scripts/icons/tray/bookmarks.svg
similarity index 100%
rename from _regroup/bin/tray-icons/bookmarks.svg
rename to scripts/icons/tray/bookmarks.svg
diff --git a/_regroup/bin/tray-icons/build-icons.sh b/scripts/icons/tray/build-icons.sh
similarity index 100%
rename from _regroup/bin/tray-icons/build-icons.sh
rename to scripts/icons/tray/build-icons.sh
diff --git a/_regroup/bin/tray-icons/close.svg b/scripts/icons/tray/close.svg
similarity index 100%
rename from _regroup/bin/tray-icons/close.svg
rename to scripts/icons/tray/close.svg
diff --git a/_regroup/bin/tray-icons/new-note.svg b/scripts/icons/tray/new-note.svg
similarity index 100%
rename from _regroup/bin/tray-icons/new-note.svg
rename to scripts/icons/tray/new-note.svg
diff --git a/_regroup/bin/tray-icons/recents.svg b/scripts/icons/tray/recents.svg
similarity index 100%
rename from _regroup/bin/tray-icons/recents.svg
rename to scripts/icons/tray/recents.svg
diff --git a/_regroup/bin/tray-icons/today.svg b/scripts/icons/tray/today.svg
similarity index 100%
rename from _regroup/bin/tray-icons/today.svg
rename to scripts/icons/tray/today.svg
diff --git a/scripts/repo-migration/README.md b/scripts/repo-migration/README.md
new file mode 100644
index 000000000..3266b69dd
--- /dev/null
+++ b/scripts/repo-migration/README.md
@@ -0,0 +1,13 @@
+# Repo migration scripts
+
+> [!NOTE]
+> These scripts were designed to be single use only, since they took care of migrating between repos. As such, they are not actively maintained and are to be used for reference only.
+
+These scripts were used in the process of migrating from the forked [Notes](https://github.com/TriliumNext/Notes) repo to the original [Trilium](https://github.com/TriliumNext/Trilium) repo.
+Since Git only migrates the code and not the GitHub-specific data, we had to create scripts that handle the migration of:
+
+* Issues, using the "Transfer" function for each issue (via `gh cli`).
+ * The migration logs are available in `migrated-issues.txt`.
+* Discussions, which transferred each discussion from the original repo. This one is a bit more complicated (and potentially flaky) since it works via playwright, by manually selecting the "Transfer" function (no API available at the time).
+ * The migration logs are available in`migrated-discussions.txt`.
+* Releases, by manually downloading the assets from the source repo and creating new releases into the destination repo.
\ No newline at end of file
diff --git a/scripts/migrate-releases.js b/scripts/repo-migration/migrate-releases.js
similarity index 100%
rename from scripts/migrate-releases.js
rename to scripts/repo-migration/migrate-releases.js
diff --git a/port-discussions.log b/scripts/repo-migration/migrated-discussions.txt
similarity index 100%
rename from port-discussions.log
rename to scripts/repo-migration/migrated-discussions.txt
diff --git a/scripts/migrated-issues.txt b/scripts/repo-migration/migrated-issues.txt
similarity index 100%
rename from scripts/migrated-issues.txt
rename to scripts/repo-migration/migrated-issues.txt
diff --git a/scripts/port-discussions.ts b/scripts/repo-migration/port-discussions.ts
similarity index 100%
rename from scripts/port-discussions.ts
rename to scripts/repo-migration/port-discussions.ts
diff --git a/scripts/port-issues.ts b/scripts/repo-migration/port-issues.ts
similarity index 100%
rename from scripts/port-issues.ts
rename to scripts/repo-migration/port-issues.ts
diff --git a/_regroup/bin/translation.sh b/scripts/translation/create-po-file.sh
similarity index 100%
rename from _regroup/bin/translation.sh
rename to scripts/translation/create-po-file.sh
diff --git a/scripts/import-translations-from-weblate-csv.ts b/scripts/translation/import-translations-from-weblate-csv.ts
similarity index 100%
rename from scripts/import-translations-from-weblate-csv.ts
rename to scripts/translation/import-translations-from-weblate-csv.ts