feat(react/settings): port sync options

This commit is contained in:
Elian Doran
2025-08-15 11:21:19 +03:00
parent c368ec3c38
commit f62078d02b
6 changed files with 110 additions and 132 deletions

View File

@@ -374,33 +374,36 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
const inAppHelpPage = $button.attr("data-in-app-help");
if (inAppHelpPage) {
// Dynamic import to avoid import issues in tests.
const appContext = (await import("../components/app_context.js")).default;
const activeContext = appContext.tabManager.getActiveContext();
if (!activeContext) {
return;
}
const subContexts = activeContext.getSubContexts();
const targetNote = `_help_${inAppHelpPage}`;
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
const viewScope: ViewScope = {
viewMode: "contextual-help",
};
if (!helpSubcontext) {
// The help is not already open, open a new split with it.
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {
ntxId,
notePath: targetNote,
hoistedNoteId: "_help",
viewScope
})
} else {
// There is already a help window open, make sure it opens on the right note.
helpSubcontext.setNote(targetNote, { viewScope });
}
openInAppHelpFromUrl(inAppHelpPage);
}
}
export async function openInAppHelpFromUrl(inAppHelpPage: string) {
// Dynamic import to avoid import issues in tests.
const appContext = (await import("../components/app_context.js")).default;
const activeContext = appContext.tabManager.getActiveContext();
if (!activeContext) {
return;
}
const subContexts = activeContext.getSubContexts();
const targetNote = `_help_${inAppHelpPage}`;
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
const viewScope: ViewScope = {
viewMode: "contextual-help",
};
if (!helpSubcontext) {
// The help is not already open, open a new split with it.
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {
ntxId,
notePath: targetNote,
hoistedNoteId: "_help",
viewScope
})
} else {
// There is already a help window open, make sure it opens on the right note.
helpSubcontext.setNote(targetNote, { viewScope });
}
}
function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) {

View File

@@ -1,5 +1,4 @@
import type { InputHTMLAttributes, RefObject } from "preact/compat";
import FormText from "./FormText";
interface FormTextBoxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange" | "value"> {
id?: string;
@@ -11,9 +10,11 @@ interface FormTextBoxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "
export default function FormTextBox({ inputRef, className, type, currentValue, onChange, ...rest}: FormTextBoxProps) {
if (type === "number" && currentValue) {
const { min, max } = rest;
if (min && currentValue < min) {
console.log(currentValue , min, max);
const currentValueNum = parseInt(currentValue, 10);
if (min && currentValueNum < parseInt(String(min), 10)) {
currentValue = String(min);
} else if (max && currentValue > max) {
} else if (max && currentValueNum > parseInt(String(max), 10)) {
currentValue = String(max);
}
}

View File

@@ -6,6 +6,7 @@ import { OptionNames } from "@triliumnext/commons";
import options, { type OptionValue } from "../../services/options";
import utils, { reloadFrontendApp } from "../../services/utils";
import Component from "../../components/component";
import server from "../../services/server";
type TriliumEventHandler<T extends EventNames> = (data: EventData<T>) => void;
const registeredHandlers: Map<Component, Map<EventNames, TriliumEventHandler<any>[]>> = new Map();
@@ -150,6 +151,19 @@ export function useTriliumOptionJson<T>(name: OptionNames): [ T, (newValue: T) =
];
}
export function useTriliumOptions<T extends OptionNames>(...names: T[]) {
const values: Record<string, string> = {};
for (const name of names) {
values[name] = options.get(name);
}
const setValue = (newValues: Record<T, string>) => server.put<void>("options", newValues);
return [
values as Record<T, string>,
setValue
] as const;
}
/**
* Generates a unique name via a random alphanumeric string of a fixed length.
*

View File

@@ -13,7 +13,6 @@ import PasswordOptions from "./options/password/password.js";
import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js";
import EtapiOptions from "./options/etapi.js";
import BackupOptions from "./options/backup.js";
import SyncOptions from "./options/sync.js";
import SearchEngineOptions from "./options/other/search_engine.js";
import TrayOptions from "./options/other/tray.js";
import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js";
@@ -40,6 +39,7 @@ import { renderReactWidget } from "../react/ReactBasicWidget.jsx";
import ImageSettings from "./options/images.jsx";
import AdvancedSettings from "./options/advanced.jsx";
import InternationalizationOptions from "./options/i18n.jsx";
import SyncOptions from "./options/sync.jsx";
const TPL = /*html*/`<div class="note-detail-content-widget note-detail-printable">
<style>
@@ -102,9 +102,7 @@ const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", ((typeof NoteContextA
_optionsBackup: [
BackupOptions
],
_optionsSync: [
SyncOptions
],
_optionsSync: <SyncOptions />,
_optionsAi: [AiSettingsOptions],
_optionsOther: [
SearchEngineOptions,

View File

@@ -1,100 +0,0 @@
import server from "../../../services/server.js";
import toastService from "../../../services/toast.js";
import OptionsWidget from "./options_widget.js";
import { t } from "../../../services/i18n.js";
import type { OptionMap } from "@triliumnext/commons";
const TPL = /*html*/`
<div class="options-section">
<h4 style="margin-top: 0px;">${t("sync_2.config_title")}</h4>
<form class="sync-setup-form">
<div class="form-group">
<label for="sync-server-host" >${t("sync_2.server_address")}</label>
<input id="sync-server-host" class="sync-server-host form-control" placeholder="https://<host>:<port>">
</div>
<div class="form-group">
<label for="sync-proxy form-control" >${t("sync_2.proxy_label")}</label>
<input id="sync-proxy form-control" class="sync-proxy form-control" placeholder="https://<host>:<port>">
<p class="form-text"><strong>${t("sync_2.note")}:</strong> ${t("sync_2.note_description")}</p>
<p class="form-text">${t("sync_2.special_value_description")}</p>
</div>
<div class="form-group">
<label for="sync-server-timeout">${t("sync_2.timeout")}</label>
<label class="input-group tn-number-unit-pair">
<input id="sync-server-timeout" class="sync-server-timeout form-control" min="1" max="10000000" type="number" style="text-align: left;">
<span class="input-group-text">${t("sync_2.timeout_unit")}</span>
</label>
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-primary">${t("sync_2.save")}</button>
<button class="btn btn-secondary" type="button" data-help-page="synchronization.html">${t("sync_2.help")}</button>
</div>
</form>
</div>
<div class="options-section">
<h4>${t("sync_2.test_title")}</h4>
<p>${t("sync_2.test_description")}</p>
<button class="test-sync-button btn btn-secondary">${t("sync_2.test_button")}</button>
</div>`;
// TODO: Deduplicate
interface TestResponse {
success: boolean;
message: string;
}
export default class SyncOptions extends OptionsWidget {
private $form!: JQuery<HTMLElement>;
private $syncServerHost!: JQuery<HTMLElement>;
private $syncServerTimeout!: JQuery<HTMLElement>;
private $syncProxy!: JQuery<HTMLElement>;
private $testSyncButton!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".sync-setup-form");
this.$syncServerHost = this.$widget.find(".sync-server-host");
this.$syncServerTimeout = this.$widget.find(".sync-server-timeout");
this.$syncProxy = this.$widget.find(".sync-proxy");
this.$testSyncButton = this.$widget.find(".test-sync-button");
this.$form.on("submit", () => this.save());
this.$testSyncButton.on("click", async () => {
const result = await server.post<TestResponse>("sync/test");
if (result.success) {
toastService.showMessage(result.message);
} else {
toastService.showError(t("sync_2.handshake_failed", { message: result.message }));
}
});
}
optionsLoaded(options: OptionMap) {
this.$syncServerHost.val(options.syncServerHost);
this.$syncServerTimeout.val(options.syncServerTimeout);
this.$syncProxy.val(options.syncProxy);
}
save() {
this.updateMultipleOptions({
syncServerHost: String(this.$syncServerHost.val()),
syncServerTimeout: String(this.$syncServerTimeout.val()),
syncProxy: String(this.$syncProxy.val())
});
return false;
}
}

View File

@@ -0,0 +1,62 @@
import { useRef } from "preact/hooks";
import { t } from "../../../services/i18n";
import { openInAppHelpFromUrl } from "../../../services/utils";
import Button from "../../react/Button";
import FormGroup from "../../react/FormGroup";
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
import RawHtml from "../../react/RawHtml";
import OptionsSection from "./components/OptionsSection";
import { useTriliumOptions } from "../../react/hooks";
export default function SyncOptions() {
const [ options, setOptions ] = useTriliumOptions("syncServerHost", "syncServerTimeout", "syncProxy");
const syncServerHost = useRef(options.syncServerHost);
const syncServerTimeout = useRef(options.syncServerTimeout);
const syncProxy = useRef(options.syncProxy);
return (
<OptionsSection title={t("sync_2.config_title")}>
<form onSubmit={(e) => {
setOptions({
syncServerHost: syncServerHost.current,
syncServerTimeout: syncServerTimeout.current,
syncProxy: syncProxy.current
});
e.preventDefault();
}}>
<FormGroup label={t("sync_2.server_address")}>
<FormTextBox
name="sync-server-host"
placeholder="https://<host>:<port>"
currentValue={syncServerHost.current} onChange={(newValue) => syncServerHost.current = newValue}
/>
</FormGroup>
<FormGroup label={t("sync_2.proxy_label")} description={<>
<strong>{t("sync_2.note")}:</strong> {t("sync_2.note_description")}<br/>
<RawHtml html={t("sync_2.special_value_description")} /></>}
>
<FormTextBox
name="sync-proxy"
placeholder="https://<host>:<port>"
currentValue={syncProxy.current} onChange={(newValue) => syncProxy.current = newValue}
/>
</FormGroup>
<FormGroup label={t("sync_2.timeout")}>
<FormTextBoxWithUnit
name="sync-server-timeout"
min={1} max={10000000} type="number"
unit={t("sync_2.timeout_unit")}
currentValue={syncServerTimeout.current} onChange={(newValue) => syncServerTimeout.current = newValue}
/>
</FormGroup>
<div style={{ display: "flex", justifyContent: "spaceBetween"}}>
<Button text={t("sync_2.save")} primary />
<Button text={t("sync_2.help")} onClick={() => openInAppHelpFromUrl("cbkrhQjrkKrh")} />
</div>
</form>
</OptionsSection>
)
}