diff --git a/apps/client/package.json b/apps/client/package.json index 174d2d2df0..0418110cf8 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -59,7 +59,6 @@ "jquery.fancytree": "2.38.5", "jsplumb": "2.15.6", "katex": "0.16.38", - "knockout": "3.5.2", "leaflet": "1.9.4", "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", @@ -92,4 +91,4 @@ "script-loader": "0.7.2", "vite-plugin-static-copy": "3.3.0" } -} \ No newline at end of file +} diff --git a/apps/client/src/setup.ts b/apps/client/src/setup.ts index 3b3a40c336..30232b85a3 100644 --- a/apps/client/src/setup.ts +++ b/apps/client/src/setup.ts @@ -1,68 +1,107 @@ import "jquery"; -import ko from "knockout"; - import utils from "./services/utils.js"; -// TriliumNextTODO: properly make use of below types -// type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; -// type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop"; +type SetupStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop" | "sync-from-server"; +type SetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; -class SetupModel { - syncInProgress: boolean; - step: ko.Observable; - setupType: ko.Observable; - setupNewDocument: ko.Observable; - setupSyncFromDesktop: ko.Observable; - setupSyncFromServer: ko.Observable; - syncServerHost: ko.Observable; - syncProxy: ko.Observable; - password: ko.Observable; +class SetupController { + private step: SetupStep; + private setupType: SetupType = ""; + private syncPollIntervalId: number | null = null; + private rootNode: HTMLElement; + private setupTypeForm: HTMLFormElement; + private syncFromServerForm: HTMLFormElement; + private setupTypeNextButton: HTMLButtonElement; + private setupTypeInputs: HTMLInputElement[]; + private syncServerHostInput: HTMLInputElement; + private syncProxyInput: HTMLInputElement; + private passwordInput: HTMLInputElement; + private sections: Record; - constructor(syncInProgress: boolean) { - this.syncInProgress = syncInProgress; - this.step = ko.observable(syncInProgress ? "sync-in-progress" : "setup-type"); - this.setupType = ko.observable(""); - this.setupNewDocument = ko.observable(false); - this.setupSyncFromDesktop = ko.observable(false); - this.setupSyncFromServer = ko.observable(false); - this.syncServerHost = ko.observable(); - this.syncProxy = ko.observable(); - this.password = ko.observable(); + constructor(rootNode: HTMLElement, syncInProgress: boolean) { + this.rootNode = rootNode; + this.step = syncInProgress ? "sync-in-progress" : "setup-type"; + this.setupTypeForm = mustGetElement("setup-type-form", HTMLFormElement); + this.syncFromServerForm = mustGetElement("sync-from-server-form", HTMLFormElement); + this.setupTypeNextButton = mustGetElement("setup-type-next", HTMLButtonElement); + this.setupTypeInputs = Array.from(document.querySelectorAll("input[name='setup-type']")); + this.syncServerHostInput = mustGetElement("sync-server-host", HTMLInputElement); + this.syncProxyInput = mustGetElement("sync-proxy", HTMLInputElement); + this.passwordInput = mustGetElement("password", HTMLInputElement); + this.sections = { + "setup-type": mustGetElement("setup-type-section", HTMLElement), + "new-document-in-progress": mustGetElement("new-document-in-progress-section", HTMLElement), + "sync-from-desktop": mustGetElement("sync-from-desktop-section", HTMLElement), + "sync-from-server": mustGetElement("sync-from-server-section", HTMLElement), + "sync-in-progress": mustGetElement("sync-in-progress-section", HTMLElement) + }; + } - if (this.syncInProgress) { - setInterval(checkOutstandingSyncs, 1000); + init() { + this.setupTypeForm.addEventListener("submit", (event) => { + event.preventDefault(); + void this.selectSetupType(); + }); + + this.syncFromServerForm.addEventListener("submit", (event) => { + event.preventDefault(); + void this.finish(); + }); + + for (const input of this.setupTypeInputs) { + input.addEventListener("change", () => { + this.setupType = input.value as SetupType; + this.render(); + }); } + + for (const backButton of document.querySelectorAll("[data-action='back']")) { + backButton.addEventListener("click", () => { + this.back(); + }); + } + const serverAddress = `${location.protocol}//${location.host}`; $("#current-host").html(serverAddress); + + if (this.step === "sync-in-progress") { + this.startSyncPolling(); + } + + this.render(); + this.rootNode.style.display = ""; } - // this is called in setup.ejs - setupTypeSelected() { - return !!this.setupType(); - } + private async selectSetupType() { + if (this.setupType === "new-document") { + this.setStep("new-document-in-progress"); - selectSetupType() { - if (this.setupType() === "new-document") { - this.step("new-document-in-progress"); + await $.post("api/setup/new-document"); + window.location.replace("./setup"); + return; + } - $.post("api/setup/new-document").then(() => { - window.location.replace("./setup"); - }); - } else { - this.step(this.setupType()); + if (this.setupType) { + this.setStep(this.setupType); } } - back() { - this.step("setup-type"); - this.setupType(""); + private back() { + this.setStep("setup-type"); + this.setupType = ""; + + for (const input of this.setupTypeInputs) { + input.checked = false; + } + + this.render(); } - async finish() { - const syncServerHost = this.syncServerHost(); - const syncProxy = this.syncProxy(); - const password = this.password(); + private async finish() { + const syncServerHost = this.syncServerHostInput.value.trim(); + const syncProxy = this.syncProxyInput.value.trim(); + const password = this.passwordInput.value; if (!syncServerHost) { showAlert("Trilium server address can't be empty"); @@ -82,15 +121,38 @@ class SetupModel { }); if (resp.result === "success") { - this.step("sync-in-progress"); - - setInterval(checkOutstandingSyncs, 1000); - hideAlert(); + this.setStep("sync-in-progress"); + this.startSyncPolling(); } else { showAlert(`Sync setup failed: ${resp.error}`); } } + + private setStep(step: SetupStep) { + this.step = step; + this.render(); + } + + private render() { + for (const [step, section] of Object.entries(this.sections) as [SetupStep, HTMLElement][]) { + section.style.display = step === this.step ? "" : "none"; + } + + this.setupTypeNextButton.disabled = !this.setupType; + } + + private getSelectedSetupType(): SetupType { + return (this.setupTypeInputs.find((input) => input.checked)?.value ?? "") as SetupType; + } + + private startSyncPolling() { + if (this.syncPollIntervalId !== null) { + return; + } + + this.syncPollIntervalId = window.setInterval(checkOutstandingSyncs, 1000); + } } async function checkOutstandingSyncs() { @@ -124,9 +186,19 @@ function getSyncInProgress() { return !!parseInt(el.content); } +function mustGetElement(id: string, ctor: T): InstanceType { + const element = document.getElementById(id); + + if (!element || !(element instanceof ctor)) { + throw new Error(`Expected element #${id}`); + } + + return element as InstanceType; +} + addEventListener("DOMContentLoaded", (event) => { const rootNode = document.getElementById("setup-dialog"); - if (!rootNode) return; - ko.applyBindings(new SetupModel(getSyncInProgress()), rootNode); - $("#setup-dialog").show(); + if (!rootNode || !(rootNode instanceof HTMLElement)) return; + + new SetupController(rootNode, getSyncInProgress()).init(); }); diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index e57de8cd67..7bf9423138 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -675,10 +675,11 @@ li.dropdown-item a.dropdown-item-button:focus-visible { div.alert { margin-bottom: 8px; background: var(--alert-bar-background) !important; + color: var(--main-text-color); border-radius: 8px; font-size: .85em; } div.alert p + p { margin-block: 1em 0; -} \ No newline at end of file +} diff --git a/apps/server/package.json b/apps/server/package.json index 0d9cb6843f..e5e6314a6e 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -6,6 +6,7 @@ "main": "./src/main.ts", "scripts": { "dev": "cross-env NODE_ENV=development TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts", + "dev-alt": "cross-env NODE_ENV=development TRILIUM_ENV=dev TRILIUM_DATA_DIR=data2 TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts", "start-no-dir": "cross-env NODE_ENV=development TRILIUM_ENV=dev TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts", "edit-integration-db": "cross-env NODE_ENV=development TRILIUM_PORT=8086 TRILIUM_ENV=dev TRILIUM_DATA_DIR=spec/db TRILIUM_INTEGRATION_TEST=edit TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts", "build": "tsx scripts/build.ts", diff --git a/apps/server/src/assets/views/setup.ejs b/apps/server/src/assets/views/setup.ejs index 68ee58b517..79e41b4672 100644 --- a/apps/server/src/assets/views/setup.ejs +++ b/apps/server/src/assets/views/setup.ejs @@ -58,35 +58,35 @@ -
-
+
+
- +
-
+

<%= t("setup.init-in-progress") %>

@@ -103,7 +103,7 @@
-
+

<%= t("setup_sync-from-desktop.heading") %>

<%= t("setup_sync-from-desktop.description") %>

@@ -117,11 +117,11 @@
  • <%- t("setup_sync-from-desktop.step6", { link: `${t("setup_sync-from-desktop.step6-here")}` }) %>
  • - +
    -
    -
    +
    +

    <%= t("setup_sync-from-server.heading") %>

    @@ -129,27 +129,27 @@
    - "> + ">
    - "> + ">

    <%= t("setup_sync-from-server.note") %> <%= t("setup_sync-from-server.proxy-instruction") %>

    - "> + ">
    - +
    -
    +

    <%= t("setup_sync-in-progress.heading") %>

    <%= t("setup_sync-in-progress.successful") %>
    diff --git a/package.json b/package.json index cea6e87513..82ec1ded48 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "server:build": "pnpm run --filter server build", "server:coverage": "pnpm run --filter server test --coverage", "server:start": "pnpm run --filter server dev", + "server:start-alt": "pnpm run --filter server dev-alt", "server:start-prod": "pnpm run --filter server start-prod", "desktop:start": "pnpm run --filter desktop dev", "desktop:build": "pnpm run --filter desktop build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be7af6c403..d2489c0a81 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,9 +314,6 @@ importers: katex: specifier: 0.16.38 version: 0.16.38 - knockout: - specifier: 3.5.2 - version: 3.5.2 leaflet: specifier: 1.9.4 version: 1.9.4 @@ -11059,9 +11056,6 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} - knockout@3.5.2: - resolution: {integrity: sha512-AcJS2PqsYspjtOAlnnVS8hAuBnHMEqRVEwdvmQTeXj/9zfjV//KHurzdYc8MtBd/Pu8bZLMGHc7x0cj8qUvKxQ==} - kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -29025,8 +29019,6 @@ snapshots: klona@2.0.6: {} - knockout@3.5.2: {} - kolorist@1.8.0: {} ky@1.14.2: {}